Files
MapMil/centers/parsing/workers/workers/sources/telegram_client.py
T
gitrusprusandCursor 1492576fd9 Add Telegram listener, scheduler, and extended parsers admin UI.
Introduce background scheduling and migrations for analytics ingest, expand parser management and map toolbar UX, and ignore local data/session files from version control.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 20:47:56 +03:00

123 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Telegram Client API (Telethon) для чтения постов канала."""
import os
import re
from pathlib import Path
from telethon import TelegramClient
from telethon.errors import AuthKeyUnregisteredError, SessionPasswordNeededError
from workers.models import TelegramPost
from workers.sources.telegram_settings import create_client, get_api_credentials
DEFAULT_SESSION_PATH = "/data/telegram.session"
MAX_POSTS = 500
class TelegramConfigError(Exception):
pass
class TelegramAuthError(Exception):
pass
def _get_config() -> tuple[int, str, str]:
api_id, api_hash = get_api_credentials()
session_path = os.environ.get("TELEGRAM_SESSION_PATH", DEFAULT_SESSION_PATH)
return api_id, api_hash, session_path
def normalize_channel(raw: str) -> str:
value = raw.strip()
if not value:
raise ValueError("Укажите канал Telegram")
value = value.replace("https://", "").replace("http://", "")
value = re.sub(r"^web\.telegram\.org/k/#@", "", value)
value = re.sub(r"^t\.me/", "", value)
value = value.lstrip("@").split("/")[0].split("?")[0]
if not value:
raise ValueError("Некорректное имя канала")
return value
def _build_post_url(channel: str, message_id: int) -> str:
username = channel.lstrip("@")
return f"https://t.me/{username}/{message_id}"
def message_to_post(message, channel: str) -> TelegramPost | None:
text = (message.text or message.message or "").strip()
if not text:
return None
username = normalize_channel(channel)
return TelegramPost(
id=message.id,
text=text,
date=message.date,
url=_build_post_url(username, message.id),
channel=username,
)
async def fetch_channel_posts(
channel: str,
limit: int = 100,
*,
client: TelegramClient | None = None,
) -> list[TelegramPost]:
limit = max(1, min(limit, MAX_POSTS))
username = normalize_channel(channel)
own_client = client is None
if own_client:
try:
api_id, api_hash, session_path = _get_config()
except ValueError as exc:
raise TelegramConfigError(str(exc)) from exc
if not Path(session_path).exists():
raise TelegramAuthError(
f"Файл сессии не найден: {session_path}. "
"Выполните: python scripts/telegram_auth.py"
)
client = create_client(session_path, api_id, api_hash)
posts: list[TelegramPost] = []
try:
await client.connect()
if not await client.is_user_authorized():
raise TelegramAuthError(
"Telegram-сессия не авторизована. Выполните: python scripts/telegram_auth.py"
)
entity = await client.get_entity(username)
async for message in client.iter_messages(entity, limit=limit):
post = message_to_post(message, username)
if post:
posts.append(post)
except AuthKeyUnregisteredError as exc:
raise TelegramAuthError(
"Сессия Telegram недействительна. Переавторизуйтесь: python scripts/telegram_auth.py"
) from exc
except SessionPasswordNeededError as exc:
raise TelegramAuthError(
"Для аккаунта включена двухфакторная аутентификация. "
"Авторизуйтесь через scripts/telegram_auth.py с паролем 2FA."
) from exc
finally:
await client.disconnect()
return posts
assert client is not None
posts = []
entity = await client.get_entity(username)
async for message in client.iter_messages(entity, limit=limit):
post = message_to_post(message, username)
if post:
posts.append(post)
return posts