mirror of
https://github.com/tiennm99/export-telegram-group-members.git
synced 2026-10-11 03:13:26 +00:00
110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""Export persistence in Redis.
|
|
|
|
Each group's members are stored per export as one self-contained JSON key:
|
|
|
|
<prefix>:run:<yyyymmddhhmmss>:<group_id>
|
|
-> {group_id, title, time, members:[{id,username,first_name,last_name}]}
|
|
|
|
All groups in a single run share one timestamp. No key references another, so
|
|
deleting any key can never corrupt another's state. Writing a group export is a
|
|
single atomic SET; listing scans the run keys.
|
|
"""
|
|
|
|
import json
|
|
from datetime import datetime
|
|
|
|
from config import key, redis_client
|
|
|
|
|
|
def new_run_time():
|
|
"""Timestamp shared by every group in one run: yyyymmddhhmmss."""
|
|
return datetime.now().strftime('%Y%m%d%H%M%S')
|
|
|
|
|
|
def member_dict(member):
|
|
return {
|
|
'id': member.id,
|
|
'username': member.username,
|
|
'first_name': member.first_name,
|
|
'last_name': member.last_name,
|
|
}
|
|
|
|
|
|
def save_group_export(group_id, title, members, run_time):
|
|
"""Atomically persist one group's members for a run as a single JSON value."""
|
|
record = {
|
|
'group_id': group_id,
|
|
'title': title,
|
|
'time': run_time,
|
|
'members': [member_dict(m) for m in members],
|
|
}
|
|
redis_client.set(
|
|
key('run', run_time, str(group_id)),
|
|
json.dumps(record, ensure_ascii=False),
|
|
)
|
|
|
|
|
|
def list_exports():
|
|
"""Return all group-export records, sorted by (time, group_id).
|
|
|
|
Tolerates keys deleted mid-scan and corrupt/non-JSON values (skips them).
|
|
"""
|
|
records = []
|
|
for export_key in redis_client.scan_iter(match=key('run', '*')):
|
|
raw = redis_client.get(export_key)
|
|
if raw is None: # deleted between scan and get
|
|
continue
|
|
try:
|
|
records.append(json.loads(raw))
|
|
except (ValueError, TypeError): # corrupt / non-JSON value: skip, don't abort
|
|
continue
|
|
records.sort(key=lambda r: (r.get('time', ''), r.get('group_id', 0)))
|
|
return records
|
|
|
|
|
|
def list_group_exports(group_id):
|
|
"""Return export records for one group, sorted by run time."""
|
|
group_id = str(group_id)
|
|
return [
|
|
record for record in list_exports()
|
|
if str(record.get('group_id')) == group_id
|
|
]
|
|
|
|
|
|
def get_group_export(group_id, run_time):
|
|
"""Return a group export at one run time, or None when missing."""
|
|
for record in list_group_exports(group_id):
|
|
if record.get('time') == run_time:
|
|
return record
|
|
return None
|
|
|
|
|
|
def latest_two_group_exports(group_id):
|
|
"""Return the latest two export records for a group."""
|
|
exports = list_group_exports(group_id)
|
|
if len(exports) < 2:
|
|
return None, None
|
|
return exports[-2], exports[-1]
|
|
|
|
|
|
def diff_group_members(before_record, after_record):
|
|
"""Compare two group export records by Telegram member id."""
|
|
before_members = _member_index(before_record)
|
|
after_members = _member_index(after_record)
|
|
|
|
before_ids = set(before_members)
|
|
after_ids = set(after_members)
|
|
|
|
added = [after_members[member_id] for member_id in sorted(after_ids - before_ids)]
|
|
removed = [before_members[member_id] for member_id in sorted(before_ids - after_ids)]
|
|
return added, removed
|
|
|
|
|
|
def _member_index(record):
|
|
members = {}
|
|
for member in record.get('members', []):
|
|
member_id = member.get('id')
|
|
if member_id is not None:
|
|
members[int(member_id)] = member
|
|
return members
|