rpi: tg_members.py — участники группы с аватарками (CY Fast Track Talks)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
15a9e1d2de
commit
1a525e9073
@@ -63,3 +63,7 @@ sudo systemctl enable --now tg-export.timer
|
||||
сообщения после последней ежечасной докачки уйдут в следующий рекап, без дыр), до 60 тыс. знаков (больше — последние),
|
||||
Codex пишет темы дня и «Полезное» со ссылками → очередь outbox. Сейчас: CY Fast Track Talks (@cyfasttrack).
|
||||
`tg digest run --dry --hours 24` — посмотреть.
|
||||
- **Участники группы с аватарками** — `app/tg_members.py` (`/opt/tg-export/tg_members.py @чат`, от tgexport, ⚠️ не параллельно
|
||||
с `tg-export.service` — одна сессия): `/mnt/backup/tg/members/<id>_<название>/members.{json,csv}` + `avatars/<id>.jpg`
|
||||
(маленькие). Персональные данные участников — в поиск/индекс не идут. 2026-10-18: CY Fast Track Talks — 2774 из 2774,
|
||||
аватарок 1619. Разовая выгрузка, не по таймеру.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Участники группы с аватарками (владелец 2026-10-18): /mnt/backup/tg/members/<id>_<название>/
|
||||
members.json и members.csv (id, username, имя, бот/премиум, статус, есть ли фото), avatars/<id>.jpg (маленькие).
|
||||
|
||||
sudo -u tgexport sh -c '. /etc/tg-export/env; /opt/tg-export/venv/bin/python /opt/tg-export/tg_members.py @cyfasttrack'
|
||||
|
||||
⚠️ Не запускать одновременно с tg-export.service (одна сессия). Если админы скрыли список участников — Telegram
|
||||
отдаст только админов. Это персональные данные участников: в поиск/индекс не идут.
|
||||
"""
|
||||
import asyncio
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from telethon import TelegramClient, errors
|
||||
|
||||
import export as ex
|
||||
|
||||
OUT = Path(os.environ.get("TG_OUT", "/mnt/backup/tg")) / "members"
|
||||
|
||||
|
||||
def status_of(u):
|
||||
s = getattr(u, "status", None)
|
||||
if s is None:
|
||||
return ""
|
||||
name = type(s).__name__.replace("UserStatus", "")
|
||||
when = getattr(s, "was_online", None)
|
||||
return f"{name} {when.isoformat()}" if when else name
|
||||
|
||||
|
||||
async def main(chat):
|
||||
client = TelegramClient("/var/lib/tg-export/tg.session", int(os.environ["TG_API_ID"]), os.environ["TG_API_HASH"])
|
||||
client.flood_sleep_threshold = 3600
|
||||
await client.start()
|
||||
try:
|
||||
ent = await client.get_entity(chat)
|
||||
folder = OUT / f"-100{ent.id}_{ex.slugify(getattr(ent, 'title', chat))}"
|
||||
av = folder / "avatars"
|
||||
av.mkdir(parents=True, exist_ok=True)
|
||||
people = await client.get_participants(ent, aggressive=True)
|
||||
rows = []
|
||||
for i, u in enumerate(people, 1):
|
||||
photo = ""
|
||||
if getattr(u, "photo", None):
|
||||
dest = av / f"{u.id}.jpg"
|
||||
if not dest.exists():
|
||||
try:
|
||||
await client.download_profile_photo(u, file=str(dest), download_big=False)
|
||||
except errors.FloodWaitError as e:
|
||||
await asyncio.sleep(e.seconds + 1)
|
||||
except Exception as e: # фото недоступно — пропускаем
|
||||
print(f" {u.id}: фото не скачалось ({type(e).__name__})", flush=True)
|
||||
photo = dest.name if dest.exists() else ""
|
||||
p = getattr(u, "participant", None)
|
||||
rows.append({"id": u.id, "username": u.username or "", "first_name": u.first_name or "",
|
||||
"last_name": u.last_name or "", "bot": bool(u.bot), "premium": bool(getattr(u, "premium", False)),
|
||||
"deleted": bool(u.deleted), "status": status_of(u),
|
||||
"role": type(p).__name__.replace("ChannelParticipant", "") if p else "",
|
||||
"joined": p.date.isoformat() if getattr(p, "date", None) else "", "avatar": photo})
|
||||
if i % 100 == 0:
|
||||
print(f" {i}/{len(people)}", flush=True)
|
||||
(folder / "members.json").write_text(json.dumps({"chat": getattr(ent, "title", chat), "id": f"-100{ent.id}",
|
||||
"count": len(rows), "members": rows}, ensure_ascii=False, indent=1))
|
||||
with (folder / "members.csv").open("w", newline="", encoding="utf-8") as f:
|
||||
w = csv.DictWriter(f, fieldnames=list(rows[0].keys()) if rows else ["id"])
|
||||
w.writeheader()
|
||||
w.writerows(rows)
|
||||
total = getattr(await client.get_participants(ent, limit=0), "total", None)
|
||||
print(f"{folder.name}: участников получено {len(rows)} (в группе всего {total}), аватарок {sum(1 for r in rows if r['avatar'])}")
|
||||
finally:
|
||||
await client.disconnect()
|
||||
|
||||
|
||||
asyncio.run(main(sys.argv[1]))
|
||||
Reference in New Issue
Block a user