#!/usr/bin/env python3 """Prepare the isolated dashboard database after native identities are verified.""" import argparse import copy import datetime import json import pathlib import sqlite3 import urllib.request import uuid def main(root,live): stage=root/'app-staging' bound=json.loads((root/'classic-roster-bound.json').read_text()) assert len(bound)==50 and len({b['characterGuid'] for b in bound})==50 with urllib.request.urlopen('http://127.0.0.1:8787/api/state',timeout=10) as response:state=json.load(response) # This archive captures memories accumulated since the initial migration export. (root/'wrath-state-before-dashboard-copy.json').write_text(json.dumps(state)) old={b['id']:b for b in state['bots']} now=datetime.datetime.now(datetime.timezone.utc).isoformat() bots=[] for identity in bound: bot=copy.deepcopy(old[identity['id']]) bot.update(name=identity['name'],race=identity['race'],role=identity['role'],level=1,zone='Awaiting Classic observation',status='offline',simulationBand='dormant',decisions=0) for key in ['world','lastDecision','deathMemorySeenId','lastObservation','death']:bot.pop(key,None) bot['autonomy']={'managed':True,'characterGuid':identity['characterGuid'],'accountId':identity['accountId']} for memory in bot.get('memories',[]):memory['text']='[Historical memory from the previous Wrath realm; not current Classic progress] '+memory['text'] bot.setdefault('memories',[]).append({'id':str(uuid.uuid4()),'text':f"I am starting a new life at level 1 as a {identity['race']} {identity['role']} in Classic Azeroth. Older memories describe my previous realm.",'importance':10,'source':'observation','at':now}) bot['memories']=bot['memories'][-100:] bots.append(bot) state['bots']=bots;state['aleRosterAt']=0;state['revision']=state.get('revision',0)+1 state['events']=[];state.pop('operations',None);state['population']={} state['settings']['population']['serverMax']=50 state['settings']['hardcore']['enabled']=False state['community']={'feed':[],'evidence':[],'guilds':[],'partiesEnabled':True,'guildsEnabled':True} ids={b['id'] for b in bots} for home in state.get('homes',[]):home['residentIds']=[id for id in home.get('residentIds',[]) if id in ids] source=list((live/'.wrangler/state/v3/d1').rglob('*.sqlite')) candidates=[] for path in source: with sqlite3.connect(f'file:{path}?mode=ro',uri=True) as db: if db.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='realm_state'").fetchone():candidates.append(path) if len(candidates)!=1:raise RuntimeError('Expected exactly one live realm database') source=candidates[0];target=stage/source.relative_to(live) if target.exists():raise RuntimeError('Staging database already exists; refusing to overwrite') target.parent.mkdir(parents=True,exist_ok=True) with sqlite3.connect(f'file:{source}?mode=ro',uri=True) as src,sqlite3.connect(target) as dst: src.backup(dst) tables={row[0] for row in dst.execute("SELECT name FROM sqlite_master WHERE type='table'")} for table in ['ale_commands','ale_events','community_actions','community_clock','conversation_requests','realm_state_chunks']: if table in tables:dst.execute('DELETE FROM '+table) text=json.dumps(state,ensure_ascii=False,separators=(',',':')) parts=[text[i:i+20000] for i in range(0,len(text),20000)];token=str(uuid.uuid4()) dst.execute('CREATE TABLE IF NOT EXISTS realm_state_chunks (token TEXT,part INTEGER,payload TEXT,PRIMARY KEY(token,part))') dst.executemany('INSERT INTO realm_state_chunks VALUES (?,?,?)',[(token,i,p) for i,p in enumerate(parts)]) dst.execute("UPDATE realm_state SET revision=?,payload=? WHERE id='realm'",(state['revision'],json.dumps({'chunkToken':token,'parts':len(parts)}))) (root/'classic-initial-state.json').write_text(json.dumps(state)) print('Isolated dashboard prepared: 50 preserved personalities, native GUIDs rebound, historical memories labeled, live observations and queues reset') if __name__=='__main__': parser=argparse.ArgumentParser();parser.add_argument('--root',type=pathlib.Path,required=True);parser.add_argument('--live',type=pathlib.Path,required=True) args=parser.parse_args();main(args.root.resolve(),args.live.resolve())