Пользователь tgexport, сессия /var/lib/tg-export (600), тексты в /mnt/backup/tg (группа astra), докачка раз в сутки без медиа, итог в backup-metrics. Вход делает владелец. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
2.7 KiB
Python
73 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Выгрузка публичной группы/канала, в которой мы не состоим.
|
|
|
|
`export.py run` ходит по iter_dialogs и потому видит только свои чаты.
|
|
Здесь сущность резолвится по @username и история читается напрямую.
|
|
|
|
uv run fetch_public.py @cyschools --since 2026-03-04
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import datetime as dt
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from telethon import TelegramClient
|
|
|
|
import export as ex
|
|
|
|
ex.load_dotenv(ex.ROOT / ".env")
|
|
|
|
|
|
async def main_async(args) -> None:
|
|
api_id, api_hash = ex.require_credentials()
|
|
client = TelegramClient(args.session, api_id, api_hash)
|
|
client.flood_sleep_threshold = 24 * 60 * 60
|
|
await client.start()
|
|
try:
|
|
entity = await client.get_entity(args.chat)
|
|
dialog_id = int(f"-100{entity.id}")
|
|
folder = Path(args.out) / "chats" / f"{dialog_id}_{ex.slugify(getattr(entity, 'title', args.chat))}"
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
|
|
since = dt.datetime.fromisoformat(args.since).replace(tzinfo=dt.timezone.utc)
|
|
(folder / "meta.json").write_text(json.dumps({
|
|
"id": dialog_id,
|
|
"name": getattr(entity, "title", None),
|
|
"kind": ex.dialog_kind(entity),
|
|
"username": getattr(entity, "username", None),
|
|
"fetched_since": args.since,
|
|
}, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
cache: dict = {}
|
|
n = 0
|
|
with (folder / "messages.jsonl").open("w", encoding="utf-8") as jsonl, \
|
|
(folder / "messages_pretty.jsonl").open("w", encoding="utf-8") as pretty:
|
|
async for msg in client.iter_messages(entity, offset_date=since, reverse=True):
|
|
record = msg.to_dict()
|
|
sender = await ex.sender_label(client, msg, cache)
|
|
jsonl.write(json.dumps(record, ensure_ascii=False, cls=ex.Encoder) + "\n")
|
|
pretty.write(json.dumps(ex.simplify_message(msg, sender, record),
|
|
ensure_ascii=False, cls=ex.Encoder) + "\n")
|
|
n += 1
|
|
if n % 1000 == 0:
|
|
print(f" … {n} ({msg.date:%Y-%m-%d})", flush=True)
|
|
print(f"{folder.name}: {n} сообщений")
|
|
finally:
|
|
await client.disconnect()
|
|
|
|
|
|
def main() -> None:
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("chat")
|
|
p.add_argument("--since", required=True)
|
|
p.add_argument("--out", default=str(ex.ROOT / "out"))
|
|
p.add_argument("--session", default=str(ex.ROOT / "tg_export.session"))
|
|
asyncio.run(main_async(p.parse_args()))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|