"""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(): try: from . import config config.tg_cfg() except Exception: pass 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 [] try: from . import config config.tg_cfg() except Exception: pass 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)) async def _search(query, limit, session, api_id, api_hash, timeout=60): """Глобальный поиск каналов/групп по запросу (contacts.SearchRequest).""" from telethon import TelegramClient from telethon.tl.functions.contacts import SearchRequest client = TelegramClient(session, api_id, api_hash, connection_retries=1, timeout=timeout / 2) await asyncio.wait_for(client.connect(), timeout) try: res = await asyncio.wait_for(client(SearchRequest(q=str(query), limit=int(limit))), timeout) out = [] for ch in getattr(res, "chats", []) or []: uname = getattr(ch, "username", None) if not uname: continue out.append({"username": uname, "title": getattr(ch, "title", "") or "", "id": getattr(ch, "id", None), "broadcast": bool(getattr(ch, "broadcast", False)), "megagroup": bool(getattr(ch, "megagroup", False)), "participants": getattr(ch, "participants_count", None) or 0, "query": str(query)}) return out finally: try: await asyncio.wait_for(client.disconnect(), 10) except Exception: pass def search(query, limit=40): """Каналы по ключевому слову. Пусто, если сессия не настроена.""" if not enabled(): return [] try: from . import config config.tg_cfg() except Exception: pass session = os.environ["OMNI_TG_SESSION"] api_id = int(os.environ["OMNI_TG_API_ID"]) api_hash = os.environ["OMNI_TG_API_HASH"] return asyncio.run(_search(query, limit, session, api_id, api_hash))