#!/usr/bin/env python3 """ tglib_to_tdata.py — Convert tglib.js JSON payload to a Telegram Desktop tdata zip. Usage: python tglib_to_tdata.py Reads the tglib.js JSON (state + db_sqlite), extracts the master MTProto auth key + DC id, builds a Telethon SQLite session, and uses opentele-ng to write a tdata folder, then zips it. Works fully offline — no Telegram connection is made. """ import json, base64, os, sys, tempfile, shutil, sqlite3, zipfile, asyncio # Standard Telegram production DC endpoints (used to seed the Telethon session). DC_ADDRS = { 1: ("149.154.175.50", 443), 2: ("149.154.167.51", 443), 3: ("149.154.175.100", 443), 4: ("149.154.167.91", 443), 5: ("91.108.56.130", 443), } def extract_keys(payload: dict): """Extract master auth_key, dc_id, user_id from tglib.js JSON payload.""" state_b64 = payload.get("state") if not state_b64: raise ValueError("missing 'state' field") state = json.loads(base64.b64decode(state_b64)) records = state.get("records", []) if not records: raise ValueError("no records in state") backup_b64 = None for attr in records[0].get("attributes", []): if isinstance(attr, dict) and "backupData" in attr: backup_b64 = attr["backupData"]["data"] break if not backup_b64: raise ValueError("no backupData in state records") backup = json.loads(base64.b64decode(backup_b64)) auth_key = base64.b64decode(backup["masterDatacenterKey"]) dc_id = backup["masterDatacenterId"] user_id = backup.get("peerId", 0) if len(auth_key) != 256: raise ValueError(f"auth_key must be 256 bytes, got {len(auth_key)}") return auth_key, dc_id, user_id def make_session(tmpdir: str, auth_key: bytes, dc_id: int) -> str: """Create a Telethon SQLite session file with the given auth key + DC.""" from telethon.sessions import SQLiteSession server, port = DC_ADDRS.get(dc_id, ("149.154.167.91", 443)) path = os.path.join(tmpdir, "tg") sess = SQLiteSession(path) sess._conn.execute("DELETE FROM sessions") sess._conn.execute( "INSERT INTO sessions (dc_id, server_address, port, auth_key) VALUES (?,?,?,?)", (dc_id, server, port, auth_key), ) sess._conn.commit() sess.close() return path + ".session" def convert_to_tdata(session_file: str, out_dir: str): """Use opentele-ng to convert Telethon session → tdata folder (offline).""" from opentele.td import TDesktop from opentele.tl import TelegramClient from opentele.api import UseCurrentSession async def _run(): client = TelegramClient(session_file) try: tdesk = await client.ToTDesktop(flag=UseCurrentSession) if not tdesk.isLoaded(): raise RuntimeError("TDesktop failed to load after conversion") if os.path.exists(out_dir): shutil.rmtree(out_dir) tdesk.SaveTData(out_dir) finally: await client.disconnect() asyncio.run(_run()) def zip_tdata(tdata_dir: str, zip_path: str): """Zip the tdata folder into a zip file.""" with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: for root, dirs, files in os.walk(tdata_dir): for f in files: full = os.path.join(root, f) arc = os.path.relpath(full, os.path.dirname(tdata_dir)) zf.write(full, arc) def main(): if len(sys.argv) != 3: print("usage: tglib_to_tdata.py ", file=sys.stderr) sys.exit(1) input_json, output_zip = sys.argv[1], sys.argv[2] payload = json.load(open(input_json)) auth_key, dc_id, user_id = extract_keys(payload) print(f"auth_key: {len(auth_key)}B, dc_id: {dc_id}, user_id: {user_id}", file=sys.stderr) tmpdir = tempfile.mkdtemp(prefix="tglib_tdata_") try: session_file = make_session(tmpdir, auth_key, dc_id) tdata_dir = os.path.join(tmpdir, "tdata") convert_to_tdata(session_file, tdata_dir) zip_tdata(tdata_dir, output_zip) print(f"wrote {output_zip} ({os.path.getsize(output_zip)} bytes)", file=sys.stderr) finally: shutil.rmtree(tmpdir, ignore_errors=True) if __name__ == "__main__": main()