From 20221c78e1e3e836d1151629b9363c5d4d9dba60 Mon Sep 17 00:00:00 2001 From: vvzvlad Date: Sat, 1 Feb 2025 17:52:23 +0300 Subject: [PATCH] add html support --- api_server.py | 28 ++++++++- config.py | 7 +++ requirements.txt | 4 +- telegram_client.py | 147 +++++++++++++++++++++++++++++++++++++-------- 4 files changed, 159 insertions(+), 27 deletions(-) diff --git a/api_server.py b/api_server.py index b025b5b..2cf611d 100644 --- a/api_server.py +++ b/api_server.py @@ -1,4 +1,5 @@ from fastapi import FastAPI, HTTPException, Response +from fastapi.responses import HTMLResponse from telegram_client import TelegramClient from config import get_settings import logging @@ -17,10 +18,35 @@ async def startup(): async def shutdown(): await client.stop() -@app.get("/post/{channel}/{post_id}") +@app.get("/html/{channel}/{post_id}", response_class=HTMLResponse) +async def get_post_html(channel: str, post_id: int): + try: + post = await client.get_post(channel, post_id) + if post.get("error"): + raise HTTPException( + status_code=404, + detail=post["details"] + ) + return f""" + + + {post['text']} + + + """ + except Exception as e: + logger.error(f"HTML endpoint error: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) + +@app.get("/json/{channel}/{post_id}") async def get_post(channel: str, post_id: int): try: data = await client.get_post(channel, post_id) + if data.get("error"): + raise HTTPException( + status_code=404, + detail=data + ) return Response( content=json.dumps(data), media_type="application/json" diff --git a/config.py b/config.py index 46c3a2c..a3fc832 100644 --- a/config.py +++ b/config.py @@ -8,6 +8,13 @@ API_PORT = int(os.getenv("API_PORT", 8000)) REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", 30)) SESSION_STRING = os.getenv("TG_SESSION_STRING", "") +DEFAULT_CONFIG = { + "session_string": "", + "tg_api_id": 12345, + "tg_api_hash": "xxxxxxxx", + "file_access_flags": ["can_access_files"] +} + def get_settings(): return { "tg_api_id": TG_API_ID, diff --git a/requirements.txt b/requirements.txt index 2671fd3..9bb8c01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ fastapi uvicorn -pyrogram +kurigram tgcrypto -pillow>=10.0.0 \ No newline at end of file +pillow \ No newline at end of file diff --git a/telegram_client.py b/telegram_client.py index 4c1f09b..4fb8629 100644 --- a/telegram_client.py +++ b/telegram_client.py @@ -39,30 +39,40 @@ class TelegramClient: async def _parse_message(self, message: Message) -> dict: # Extract text from different sources - text = message.text or "" + text = message.text.html if message.text else "" # Get HTML formatted text # Try to get caption for media messages - if not text and hasattr(message, "caption"): - text = message.caption or "" + if not text and hasattr(message, "caption") and message.caption: + text = message.caption.html # Try to get text from other attributes if not text and hasattr(message, "action"): - text = str(message.action) # For service messages + text = str(message.action) + elif not text and hasattr(message, "entities"): + # Extract HTML from entities + text = message.text.html if message.text else "" # Parse media and add video markers media = [] if message.media: - media_obj = message.media - if hasattr(message, "video"): - media.append(await self._parse_media(message.video)) + # Handle animations directly from message object + if hasattr(message, "animation") and message.animation: + animation = message.animation + media.append(await self._parse_animation(animation)) + # Handle videos directly from message object + elif hasattr(message, "video") and message.video: + video = message.video + media.append(await self._parse_media(video)) + # Handle photos directly from message object + elif hasattr(message, "photo") and message.photo: + photo = message.photo + media.append(await self._parse_media(photo)) else: - if isinstance(media_obj, list): - for m in media_obj: - media.append(await self._parse_media(m)) - elif hasattr(media_obj, "photo"): - media.append(await self._parse_media(media_obj.photo)) - else: - media.append(await self._parse_media(media_obj)) + media_obj = message.media + parsed_media = await self._parse_media(media_obj) + if parsed_media["type"] not in ["webpage", "nonetype"]: + media.append(parsed_media) + logger.debug(f"Parsed media: {parsed_media}") video_count = sum(1 for m in media if m.get("type") == "video") if video_count > 0: @@ -77,19 +87,35 @@ class TelegramClient: ) text += reactions_text + # Add media previews to text + if media: + previews = [] + for m in media: + if m.get('thumbnail'): + previews.append(f"") + if previews: + text = "
".join(previews) + "

" + text + + # Convert newlines to HTML breaks AFTER adding reactions + text = text.replace('\n', '
') + return { "id": message.id, "date": message.date.isoformat(), "text": text, - "media": media, - "views": message.views or 0, - "reactions": [r.emoji for r in message.reactions.reactions] if getattr(message, "reactions", None) else [] + "views": message.views or 0 } async def _parse_media(self, media_obj) -> dict: # Handle different media types media_type = media_obj.__class__.__name__.lower() - file_id = getattr(media_obj, "file_id", "") + logger.debug(f"Parsing media type: {media_type}") + + # Get file_id directly from media object + file_id = "" + if hasattr(media_obj, "file_id"): + file_id = media_obj.file_id + thumbnail_base64 = None # Special cases @@ -115,12 +141,84 @@ class TelegramClient: "duration": media_obj.duration, "thumbnail": thumbnail_base64 } + elif media_type == "animation": + # Process animation (GIF/Video) + logger.debug(f"Animation details: {media_obj}") + logger.debug(f"Animation file_id: {media_obj.file_id}") + logger.debug(f"Animation thumbs: {media_obj.thumbs}") + if media_obj.thumbs: + try: + thumb_path = await self.client.download_media(media_obj.thumbs[0].file_id) + with open(thumb_path, "rb") as image_file: + thumbnail_base64 = base64.b64encode(image_file.read()).decode("utf-8") + os.remove(thumb_path) + except Exception as e: + logger.error(f"Animation thumbnail error: {e.__class__.__name__} - {str(e)}") + + return { + "type": "animation", + "url": media_obj.file_id, + "size": media_obj.file_size, + "duration": media_obj.duration, + "thumbnail": thumbnail_base64 + } + elif media_type == "messagemediatype": + # Handle Telegram's internal media type + actual_type = str(media_obj).split('.')[-1].lower() + logger.debug(f"Resolved MessageMediaType: {actual_type}") + return {"type": actual_type} + elif media_type == "photo": + # Download and encode full photo + if hasattr(media_obj, "file_id"): + try: + # Download full size photo + photo_path = await self.client.download_media(media_obj.file_id) + with open(photo_path, "rb") as image_file: + thumbnail_base64 = base64.b64encode(image_file.read()).decode("utf-8") + os.remove(photo_path) + except Exception as e: + logger.error(f"Photo download error: {e.__class__.__name__} - {str(e)}") + + return { + "type": "photo", + "url": file_id, + "size": media_obj.file_size, + "thumbnail": thumbnail_base64 + } return { "type": media_type, - "url": str(file_id), + "url": file_id, "size": getattr(media_obj, "file_size", None), - "thumbnail": thumbnail_base64 + "thumbnail": None + } + + async def _parse_animation(self, animation_obj) -> dict: + logger.debug(f"Parsing animation: {animation_obj}") + if not animation_obj or not hasattr(animation_obj, 'thumbs'): + logger.error("Invalid animation object") + return {"type": "animation", "error": "invalid_object"} + + try: + # Check if thumbs exist and have items + if not animation_obj.thumbs or len(animation_obj.thumbs) == 0: + raise ValueError("No thumbnails available") + + thumb_file_id = animation_obj.thumbs[0].file_id + thumb_path = await self.client.download_media(thumb_file_id) + with open(thumb_path, "rb") as f: + thumbnail = base64.b64encode(f.read()).decode() + os.remove(thumb_path) + except Exception as e: + logger.error(f"Animation error: {str(e)}") + thumbnail = None + + return { + "type": "animation", + "url": getattr(animation_obj, "file_id", ""), + "size": animation_obj.file_size, + "duration": animation_obj.duration, + "thumbnail": thumbnail } async def get_post(self, channel: str, post_id: int) -> dict: @@ -129,13 +227,14 @@ class TelegramClient: chat_id=channel, message_ids=post_id ) - if not message: - raise ValueError("Message not found") + if not message or message.empty: + return { + "error": "message_not_found", + "details": f"Message {post_id} in {channel} does not exist" + } # Debug raw message structure print("Raw message object: %s", message) - print("Message attributes: %s", dir(message)) - print("Message media type: %s", type(message.media) if message.media else None) return await self._parse_message(message) except errors.RPCError as e: