Add RSS feed generation endpoint for Telegram channels

This commit is contained in:
vvzvlad
2025-02-02 01:59:14 +03:00
parent 79f6db9839
commit 9bb0d8a021
3 changed files with 82 additions and 15 deletions
+47 -7
View File
@@ -4,12 +4,13 @@ import re
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Response, BackgroundTasks from fastapi import FastAPI, HTTPException, Response, BackgroundTasks
from fastapi.responses import HTMLResponse, FileResponse from fastapi.responses import HTMLResponse, FileResponse, PlainTextResponse
from telegram_client import TelegramClient from telegram_client import TelegramClient
from config import get_settings from config import get_settings
import mimetypes import mimetypes
import os import os
from pyrogram import errors from pyrogram import errors
from feedgen.feed import FeedGenerator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
client = TelegramClient() client = TelegramClient()
@@ -30,12 +31,7 @@ app = FastAPI(
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run( uvicorn.run( "api_server:app", host=settings["api_host"], port=settings["api_port"], reload=False)
"api_server:app",
host=settings["api_host"],
port=settings["api_port"],
reload=False
)
@app.get("/html/{channel}/{post_id}", response_class=HTMLResponse) @app.get("/html/{channel}/{post_id}", response_class=HTMLResponse)
async def get_post_html(channel: str, post_id: int): async def get_post_html(channel: str, post_id: int):
@@ -124,3 +120,47 @@ async def get_media(
status_code=500, status_code=500,
detail=f"Internal server error: {str(e)}" detail=f"Internal server error: {str(e)}"
) )
@app.get("/rss/{channel}", response_class=PlainTextResponse)
async def get_rss_feed(channel: str):
try:
# Get last 20 posts
posts = await client.get_channel_posts(channel, limit=20)
# Create FeedGenerator
fg = FeedGenerator()
fg.title(channel)
fg.link(href=f"{settings['pyrogram_bridge_url']}/html/{channel}", rel='alternate')
fg.description(f'Telegram channel {channel} RSS feed')
fg.language('ru-ru')
# Add channel metadata
fg.id(f"{settings['pyrogram_bridge_url']}/rss/{channel}")
fg.link(href=f"{settings['pyrogram_bridge_url']}/rss/{channel}", rel='self', type='application/rss+xml')
# Add posts
for post in posts:
fe = fg.add_entry()
fe.id(f"{settings['pyrogram_bridge_url']}/html/{channel}/{post['id']}")
fe.title(post['title'])
fe.link(href=f"{settings['pyrogram_bridge_url']}/html/{channel}/{post['id']}")
fe.pubDate(post['date'].astimezone(tz=None))
fe.description(post['html'])
fe.content(content=post['html'], type='CDATA')
# Add media enclosures
for media in post.get('media', []):
if media.get('url'):
fe.enclosure(
url=f"{settings['pyrogram_bridge_url']}/media/{media['url']}",
type=media.get('type', 'image/jpeg'),
length=str(media.get('size', 0))
)
# Generate RSS
rss = fg.rss_str(pretty=True)
return Response(content=rss, media_type="application/xml")
except Exception as e:
logger.error(f"RSS generation error: {str(e)}")
raise HTTPException(status_code=500, detail="RSS feed generation failed")
+3
View File
@@ -1,5 +1,8 @@
fastapi fastapi
uvicorn uvicorn
python-multipart
kurigram kurigram
tgcrypto tgcrypto
pillow pillow
feedgen
python-dateutil
+29 -5
View File
@@ -181,12 +181,11 @@ class TelegramClient:
return { return {
"id": message.id, "id": message.id,
"date": message.date.isoformat(), "date": message.date,
"html": processed_text, "html": processed_text,
"text": re.sub('<[^<]+?>', '', raw_text).strip(), "raw_text": raw_text,
"views": message.views or 0, "media": media,
"media_group_id": message.media_group_id, "author": self._get_author_info(message)
"author": author_str
} }
async def _parse_media(self, media_obj) -> dict: async def _parse_media(self, media_obj) -> dict:
@@ -497,3 +496,28 @@ class TelegramClient:
except Exception as e: except Exception as e:
logger.error(f"Media download failed {file_id}: {type(e).__name__} - {str(e)}") logger.error(f"Media download failed {file_id}: {type(e).__name__} - {str(e)}")
raise raise
async def get_channel_posts(self, channel: str, limit: int = 20) -> list:
try:
if not self.client.is_connected:
await self.start()
posts = []
async for message in self.client.get_chat_history(
chat_id=channel,
limit=limit
):
parsed = await self._parse_message(message)
if parsed:
posts.append({
"id": message.id,
"date": message.date,
"title": self._generate_title(parsed.get("raw_text", "")),
"html": parsed["html"],
"media": parsed.get("media", [])
})
return posts
except Exception as e:
logger.error(f"Channel posts error {channel}: {type(e).__name__} - {str(e)}")
return []