"""Search loaded WhatsApp Desktop message objects via read-only process RAM. Does not invoke WhatsApp's native FTS engine or load older history. The query matches Unicode-normalized, case-insensitive literal text in body/caption. """ from .common import normalized as normalize import argparse, ctypes, datetime as dt, json, sys, unicodedata from pathlib import Path from .ram import Memory, discover, scan_markers, recover EVIDENCE='ev_fd70aa06525b6f2259bf6d98bc879adb2472f63f2407a2d0d1186cc9e27b2f21' def search_models(models,query,chat_id=None,redact=False): needle=normalize(query) if not needle.strip():raise ValueError('Query must contain non-whitespace text') matches=[] for model in models: # Reject schema-only candidates without verified collection membership. if not model.get('in_message_collection'):continue if chat_id is not None and model.get('chat_id')!=chat_id:continue fields=[field for field in ('body','caption') if isinstance(model.get(field),str) and needle in normalize(model[field])] if not fields:continue result=dict(model);result['matched_fields']=fields if redact: for field in ('body','caption'): value=result.pop(field,None) result[field+'_length']=len(value) if isinstance(value,str) else None matches.append(result) return matches def main(argv=None): p=argparse.ArgumentParser(description=__doc__) p.add_argument('query') p.add_argument('--chat-id') p.add_argument('--max-mb',type=int,default=512) p.add_argument('--max-models',type=int,default=10000) p.add_argument('--redact-text',action='store_true') p.add_argument('--output',type=Path,default=Path('whatsapp-process-search.json')) args=p.parse_args(argv) if sys.platform!='win32' or ctypes.sizeof(ctypes.c_void_p)!=8:p.error('64-bit Windows Python required') if not args.query.strip():p.error('Query must contain non-whitespace text') if not 16<=args.max_mb<=4096:p.error('--max-mb must be 16..4096') if not 1<=args.max_models<=10000:p.error('--max-models must be 1..10000') if args.output.exists():p.error('Output already exists; choose a new --output path') targets=discover();results=[] for target in targets: pid=target['ProcessId'] try:mem=Memory(pid) except OSError as error: results.append({'pid':pid,'error':str(error),'matches':[]});continue try: regions=list(mem.regions()) markers=scan_markers(mem,regions,args.max_mb) models,diagnostics=recover(mem,regions,markers,args.max_mb,args.max_models) verified=[m for m in models if m.get('in_message_collection')] matches=search_models(verified,args.query,args.chat_id,args.redact_text) results.append({'pid':pid,'loaded_models_examined':len(verified),'matches':matches, 'diagnostics':diagnostics,'bytes_read':mem.read_bytes, 'model_limit_reached':len(models)>=args.max_models, 'debug_flags_present':target['DebugFlagsPresent']}) finally:mem.close() report={'timestamp_utc':dt.datetime.now(dt.timezone.utc).isoformat(), 'mode':'ReadProcessMemory; literal search of collection-verified loaded Msg objects', 'whatsapp_native_search_invoked':False,'rea_search_evidence':EVIDENCE, 'results':results,'limitations':['Loaded message objects only; not the full IndexedDB or native FTS index.', 'No process execution, writes, injection, debugger, UI control, or new history query.', 'Live heap may move; the reader checks object layout and collection membership but is not an atomic snapshot.', 'Search uses NFC/casefold literal matching; it does not reproduce WhatsApp tokenizer, prefix matching or ranking.', 'No match is not evidence of absence from account history.']} args.output.parent.mkdir(parents=True,exist_ok=True) args.output.write_text(json.dumps(report,indent=2,ensure_ascii=False),encoding='utf-8') print(json.dumps({'targets':len(results),'loaded_models_examined':sum(r.get('loaded_models_examined',0) for r in results), 'matches':sum(len(r['matches']) for r in results),'native_search_invoked':False, 'report':str(args.output.resolve())})) return 0 if any(r.get('loaded_models_examined') for r in results) else 2 if __name__=='__main__':raise SystemExit(main())