"""Telegram через свою сессию (Telethon): глубина и закрытые каналы. Зачем, если есть веб-превью t.me/s: превью отдаёт ~20 последних постов и ничего не отдаёт для каналов с выключенным превью. Сессия тянет 200+ постов и читает каналы/группы, куда аккаунт уже вступил. Конфиг только через env (секретов в репозитории нет): OMNI_TG_SESSION путь к .session без расширения OMNI_TG_API_ID api_id (my.telegram.org) OMNI_TG_API_HASH api_hash OMNI_TG_LIMIT сколько постов тянуть на канал (по умолчанию 100) Если env не задан — модуль молча выключен, парсер работает на веб-превью. """ import asyncio import os import re from datetime import datetime, timezone SERVICE_RE = re.compile( r"^(channel (created|photo|name|description|username|deleted)|pinned|message pinned|" r"channel photo (updated|removed)|joined|left|invite link)", re.I) def enabled(): return bool(os.environ.get("OMNI_TG_SESSION") and os.environ.get("OMNI_TG_API_ID") and os.environ.get("OMNI_TG_API_HASH")) def _key(handle): return handle.strip().strip("/").split("/")[-1].lstrip("@") async def _pull(handles, limit, session, api_id, api_hash, timeout=40): from telethon import TelegramClient client = TelegramClient(session, api_id, api_hash, connection_retries=1, timeout=timeout / 2) out = [] await asyncio.wait_for(client.connect(), timeout) try: for h in handles: hh = _key(h) rec = {"handle": hh, "posts": []} try: ent = await asyncio.wait_for(client.get_entity(hh), timeout) msgs = await asyncio.wait_for(client.get_messages(ent, limit=limit), timeout) rec["title"] = getattr(ent, "title", "") or hh rec["id"] = getattr(ent, "id", None) for m in msgs: text = (getattr(m, "message", "") or "").strip() if not text or SERVICE_RE.match(text): continue rec["posts"].append({ "id": getattr(m, "id", None), "url": "https://t.me/%s/%s" % (hh, getattr(m, "id", "")), "title": text.splitlines()[0][:300], "text": text[:6000], "views": getattr(m, "views", None), "date": (m.date.astimezone(timezone.utc).isoformat() if isinstance(getattr(m, "date", None), datetime) else ""), "channel": hh, }) except Exception as e: rec["error"] = type(e).__name__ out.append(rec) finally: try: await asyncio.wait_for(client.disconnect(), 10) except Exception: pass return out def fetch(handles, limit=None): """Список словарей {handle, title, id, posts[]} по каналам. Пусто, если нет env.""" if not enabled(): return [] session = os.environ["OMNI_TG_SESSION"] api_id = int(os.environ["OMNI_TG_API_ID"]) api_hash = os.environ["OMNI_TG_API_HASH"] lim = int(limit or os.environ.get("OMNI_TG_LIMIT") or 100) return asyncio.run(_pull(list(handles), lim, session, api_id, api_hash))