rpi: выгрузка Telegram второго аккаунта (tg_export с Mac) — stacks/rpi-tg-export
Пользователь tgexport, сессия /var/lib/tg-export (600), тексты в /mnt/backup/tg (группа astra), докачка раз в сутки без медиа, итог в backup-metrics. Вход делает владелец. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8373a87826
commit
8a06244ef4
@@ -0,0 +1,72 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user