diff options
Diffstat (limited to 'tg_backup.py')
| -rw-r--r-- | tg_backup.py | 40 |
1 files changed, 38 insertions, 2 deletions
diff --git a/tg_backup.py b/tg_backup.py index 382a756..779d76e 100644 --- a/tg_backup.py +++ b/tg_backup.py @@ -144,6 +144,15 @@ def parse_target(target): return int(t) return t +def same_target(a, b): + # Only catches targets that are textually the same chat. "@foo" and "foo" + # match; "@foo" and its numeric ID do not, since resolving them needs a + # logged-in client and the check runs before that. + if a is None or b is None: + return True # nothing saved to contradict + norm = lambda t: str(t).strip().lstrip('@').lower() + return norm(a) == norm(b) + def make_client(archive_dir): # One session in CONFIG_DIR, shared by every archive dir: log in once, back # up any number of chats. @@ -189,7 +198,19 @@ async def list_chats(archive_dir): await client.disconnect() print("\nBack up with: --target=<ID> (the = matters for negative IDs)") -async def run_backup(target, archive_dir): +async def run_backup(target, archive_dir, force_target=False): + # last_id is a high-water mark for one specific chat. Applied as min_id to a + # different one it silently skips that chat's history, and the run reports + # success. Refuse before touching the network. + saved = load_state(archive_dir).get('target') + if not force_target and not same_target(saved, target): + sys.exit(f"Archive dir {archive_dir} was last backed up with target " + f"'{saved}', not '{target}'.\n" + "Its saved resume position belongs to that chat and would skip " + "this one's history. Use a separate archive dir per chat, or " + "pass --force-target to overwrite (only correct if the same " + "chat changed handle or ID).") + client = make_client(archive_dir) await start_client(client) @@ -251,6 +272,9 @@ def main(): help="Directory to store media and state.") parser.add_argument("--list-chats", action="store_true", help="List your dialogs with their IDs and exit.") + parser.add_argument("--force-target", action="store_true", + help="Back up --target into an archive dir saved with a " + "different one, keeping its resume position.") args = parser.parse_args() if args.list_chats: @@ -259,7 +283,7 @@ def main(): # An archive dir already backed up once knows its own target. target = args.target or load_state(args.archive_dir).get('target') if target: - asyncio.run(run_backup(target, args.archive_dir)) + asyncio.run(run_backup(target, args.archive_dir, args.force_target)) else: parser.error(f"--target is required the first time ({args.archive_dir}/state.json " "has no saved target), or use --list-chats") @@ -308,6 +332,18 @@ def _self_check(): assert load_state(archive).get('target') == '-1001234567890' assert load_state(Path(tempfile.mkdtemp())).get('target') is None + # A dir keeps its chat. Handle case and the @ prefix are noise, a different + # chat is not. Nothing saved yet must never block a first run. + assert same_target('@examplegroup', 'examplegroup') + assert same_target('@ExampleGroup', ' @examplegroup ') + assert same_target('-1001234567890', '-1001234567890') + assert same_target(-1001234567890, '-1001234567890') # state may hold an int + assert same_target(None, '@examplegroup') # first run, nothing saved + assert same_target('@examplegroup', None) # resumed run, no --target given + assert not same_target('@examplegroup', '@othergroup') + assert not same_target('-1001234567890', '-1009876543210') + assert not same_target('@examplegroup', '-1001234567890') # unresolvable, refuse + # Failures append, one parseable record per line, and survive an exception # object being passed straight in. record_failure(archive, 11, '11_a.jpg', OSError('disk full')) |
