mirror of
https://github.com/tiennm99/export-telegram-group-members.git
synced 2026-09-05 08:17:26 +00:00
feat(redis): rename run export keys to group
This commit is contained in:
@@ -68,7 +68,7 @@ All data lives in Redis under the `telegram-export` prefix. No key references an
|
||||
telegram-export:config -> JSON config:
|
||||
{ api_id, api_hash, phone, group_ids }
|
||||
telegram-export:session -> StringSession string (login)
|
||||
telegram-export:run:<group_id>:<yyyymmddhhmmss> -> one group's export as JSON:
|
||||
telegram-export:group:<group_id>:<yyyymmddhhmmss> -> one group's export as JSON:
|
||||
{ group_id, title, time,
|
||||
members: [{ id, username, first_name, last_name }] }
|
||||
```
|
||||
@@ -82,8 +82,8 @@ for rec in list_exports(): # sorted by (time, group_id)
|
||||
print(rec['time'], rec['group_id'], rec['title'], len(rec['members']), 'members')
|
||||
```
|
||||
|
||||
If you have data written with the older `run:<time>:<group_id>` key order,
|
||||
migrate it once:
|
||||
If you have data written under the older `run:<time>:<group_id>` or
|
||||
`run:<group_id>:<time>` keys, migrate it once:
|
||||
|
||||
```bash
|
||||
python migrate_run_keys_to_group_time.py --dry-run
|
||||
@@ -91,7 +91,7 @@ python migrate_run_keys_to_group_time.py
|
||||
```
|
||||
|
||||
After verifying compare/history output, run it again with `--delete-old` to
|
||||
remove the older `run:<time>:<group_id>` keys. Existing new-format keys are kept.
|
||||
remove the older `run:*:*` keys. Existing `group:<group_id>:<time>` keys are kept.
|
||||
|
||||
## Rate limits and visibility notes
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
Each group's members are stored per export as one self-contained JSON key:
|
||||
|
||||
<prefix>:run:<group_id>:<yyyymmddhhmmss>
|
||||
<prefix>:group:<group_id>:<yyyymmddhhmmss>
|
||||
-> {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.
|
||||
single atomic SET; listing scans the group export keys.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -39,7 +39,7 @@ def save_group_export(group_id, title, members, run_time):
|
||||
'members': [member_dict(m) for m in members],
|
||||
}
|
||||
redis_client.set(
|
||||
key('run', str(group_id), run_time),
|
||||
key('group', str(group_id), run_time),
|
||||
json.dumps(record, ensure_ascii=False),
|
||||
)
|
||||
|
||||
@@ -50,8 +50,8 @@ def list_exports():
|
||||
Tolerates keys deleted mid-scan and corrupt/non-JSON values (skips them).
|
||||
"""
|
||||
records = []
|
||||
for export_key in redis_client.scan_iter(match=key('run', '*', '*')):
|
||||
if not _is_group_time_run_key(export_key):
|
||||
for export_key in redis_client.scan_iter(match=key('group', '*', '*')):
|
||||
if not _is_group_export_key(export_key):
|
||||
continue
|
||||
raw = redis_client.get(export_key)
|
||||
if raw is None: # deleted between scan and get
|
||||
@@ -75,7 +75,7 @@ def list_group_exports(group_id):
|
||||
|
||||
def get_group_export(group_id, run_time):
|
||||
"""Return a group export at one run time, or None when missing."""
|
||||
raw = redis_client.get(key('run', str(group_id), run_time))
|
||||
raw = redis_client.get(key('group', str(group_id), run_time))
|
||||
if raw is None:
|
||||
return None
|
||||
try:
|
||||
@@ -118,6 +118,6 @@ def _is_run_time(value):
|
||||
return len(value) == 14 and value.isdigit()
|
||||
|
||||
|
||||
def _is_group_time_run_key(export_key):
|
||||
def _is_group_export_key(export_key):
|
||||
parts = export_key.split(':')
|
||||
return len(parts) == 4 and not _is_run_time(parts[2]) and _is_run_time(parts[3])
|
||||
return len(parts) == 4 and _is_run_time(parts[3])
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
"""Migrate Redis run keys from run:<time>:<group_id> to run:<group_id>:<time>."""
|
||||
"""Migrate Redis export keys from run:*:* to group:<group_id>:<time>.
|
||||
|
||||
Supported source formats:
|
||||
run:<time>:<group_id>
|
||||
run:<group_id>:<time>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
@@ -16,7 +21,7 @@ def redis_key(*parts):
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Migrate Redis export run keys to group-first format.',
|
||||
description='Migrate Redis export run keys to group key format.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
@@ -31,7 +36,7 @@ def parse_args():
|
||||
parser.add_argument(
|
||||
'--delete-old',
|
||||
action='store_true',
|
||||
help='Delete old run:<time>:<group_id> keys after successful copy.',
|
||||
help='Delete old run:*:* keys after successful copy.',
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
@@ -48,16 +53,33 @@ def is_run_time(value):
|
||||
return len(value) == TIME_LENGTH and value.isdigit()
|
||||
|
||||
|
||||
def migrate_run_key(client, old_key, args):
|
||||
parts = old_key.split(':')
|
||||
def export_key_parts(export_key):
|
||||
parts = export_key.split(':')
|
||||
if len(parts) != 4:
|
||||
return None
|
||||
|
||||
_, key_type, first, second = parts
|
||||
if key_type != 'run':
|
||||
return None
|
||||
if is_run_time(first) and not is_run_time(second):
|
||||
return second, first
|
||||
if not is_run_time(first) and is_run_time(second):
|
||||
return first, second
|
||||
return None
|
||||
|
||||
|
||||
def migrate_export_key(client, old_key, args):
|
||||
key_parts = export_key_parts(old_key)
|
||||
if key_parts is None:
|
||||
return 'skipped'
|
||||
|
||||
_, _, run_time, group_id = parts
|
||||
if not is_run_time(run_time):
|
||||
return 'skipped'
|
||||
group_id, run_time = key_parts
|
||||
new_key = redis_key('group', group_id, run_time)
|
||||
|
||||
value = client.get(old_key)
|
||||
if value is None:
|
||||
return 'missing'
|
||||
|
||||
new_key = redis_key('run', group_id, run_time)
|
||||
if client.exists(new_key) and not args.overwrite:
|
||||
if args.delete_old and not args.dry_run:
|
||||
client.delete(old_key)
|
||||
@@ -66,10 +88,6 @@ def migrate_run_key(client, old_key, args):
|
||||
print(f'skip existing: {new_key}')
|
||||
return 'skipped'
|
||||
|
||||
value = client.get(old_key)
|
||||
if value is None:
|
||||
return 'missing'
|
||||
|
||||
print(f'{old_key} -> {new_key}')
|
||||
if args.dry_run:
|
||||
return 'planned'
|
||||
@@ -80,18 +98,23 @@ def migrate_run_key(client, old_key, args):
|
||||
return 'migrated'
|
||||
|
||||
|
||||
def migrate_run_key(client, old_key, args):
|
||||
"""Compatibility wrapper for older imports/tests."""
|
||||
return migrate_export_key(client, old_key, args)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
client = connect_redis()
|
||||
counts = {'planned': 0, 'migrated': 0, 'deleted': 0, 'skipped': 0, 'missing': 0}
|
||||
|
||||
for old_key in client.scan_iter(match=redis_key('run', '*', '*')):
|
||||
result = migrate_run_key(client, old_key, args)
|
||||
result = migrate_export_key(client, old_key, args)
|
||||
counts[result] += 1
|
||||
|
||||
print('')
|
||||
print(
|
||||
'runs: '
|
||||
'exports: '
|
||||
f'{counts["migrated"]} migrated, '
|
||||
f'{counts["deleted"]} deleted, '
|
||||
f'{counts["planned"]} planned, '
|
||||
|
||||
Reference in New Issue
Block a user