Files
kefu/deploy/my-customers-20260918/verify_customers_frozen.py
T
2026-09-21 10:34:06 +08:00

71 lines
4.6 KiB
Python

from pathlib import Path
import hashlib,json,os,sys,tempfile,types
from unittest import mock
from PyInstaller.archive.readers import CArchiveReader
root=Path('C:/wechat_rpa')
folder=Path('C:/kefu/deploy/my-customers-20260918')
app=root/'dist/ZhenAI-WeCom-Assistant-v1.4.26'
exe=app/'ZhenAI-WeCom-Assistant-v1.4.26.exe'
manifest=json.loads((folder/'build-source-manifest.json').read_text(encoding='utf-8'))
assert sys.version_info[:2]==(3,12)
assert all(hashlib.sha256((root/name).read_bytes()).hexdigest()==digest for name,digest in manifest.items())
archive=CArchiveReader(str(exe));pyz=archive.open_embedded_archive('PYZ.pyz')
assert pyz.extract('review_assistant')
assert pyz.extract('review_assistant_contacts')
def code_fingerprint(code):
if isinstance(code, types.CodeType):
return (code.co_code, code.co_names, code.co_varnames, code.co_freevars,
code.co_cellvars, code.co_argcount, code.co_posonlyargcount,
code.co_kwonlyargcount, code.co_flags, code.co_exceptiontable,
tuple(code_fingerprint(item) for item in code.co_consts))
if isinstance(code, tuple): return tuple(code_fingerprint(item) for item in code)
return code
plan=json.loads((folder/'customers-release-plan.json').read_text(encoding='utf-8-sig'))
assert plan.get('finalized') is True and plan.get('version')=='1.4.26'
checked_modules=plan['frozenModules']
assert {'review_assistant_contacts','review_assistant','wechat_gui_qt','app_version'} <= set(checked_modules)
for module_name in checked_modules:
source = root / (module_name + '.py')
compiled = compile(source.read_text(encoding='utf-8-sig'), str(source), 'exec', optimize=1)
assert code_fingerprint(pyz.extract(module_name)) == code_fingerprint(compiled), module_name
sender=pyz.extract('wecom_native_sender')
functions={item.co_name:item for item in sender.co_consts if isinstance(item,types.CodeType)}
needed=['_file_identity','_path_file_identity','_hash_client_file']
assert all(name in functions for name in needed)
assert 'stat' not in functions['_path_file_identity'].co_names
assert 'fstat' in functions['_path_file_identity'].co_names
assert '_path_file_identity' in functions['_hash_client_file'].co_names
env={'Path':Path,'os':os,'hashlib':hashlib,'NativeUnavailable':type('NativeUnavailable',(RuntimeError,),{})}
for name in needed:env[name]=types.FunctionType(functions[name],env)
with tempfile.TemporaryDirectory(prefix='frozen-identity-',dir=folder) as temp:
p=Path(temp)/'fixture.bin';content=b'packaged file identity regression\x00'*4096;p.write_bytes(content)
os.utime(p,ns=(1_600_000_000_000_000_000,1_600_000_000_000_000_000))
with mock.patch.object(Path,'stat',side_effect=AssertionError('Path.stat must not be used for handle identity')):
actual,identity=env['_hash_client_file'](p)
assert actual==hashlib.sha256(content).hexdigest()
assert identity[2]==len(content)
private={'wxwork_keys.json','protocol_sends.sqlite3','pending_replies.protocol.json','pending_replies.json','conversations.json','backend_connection.json','runtime_settings.json','desktop_account.json','app_settings.json','review_assistant.sqlite3'}
private.update({'contacts.json','customers.json','customer_contacts.json','wxwork_contacts.json',
'review_assistant_recipients.json','backend_runtime.json','ai_settings.json',
'wxwork_database_config.json','wxwork_decrypt_state.json'})
private_suffixes=('.db','.db-wal','.db-shm','.db-journal','.sqlite','.sqlite-wal','.sqlite-shm',
'.sqlite-journal','.sqlite3','.sqlite3-wal','.sqlite3-shm','.sqlite3-journal','.key')
excluded=[]
for p in app.rglob('*'):
if p.is_file() and (p.name.lower() in private or p.name.lower().endswith(private_suffixes)):
excluded.append(str(p.relative_to(app)))
assert not excluded, 'Private contact/database/credential files found in package: '+repr(excluded)
for relative in ['assets/ui/console-app.js','assets/ui/zhenyang-ai-console.html','assets/ui/console-review.css','assets/ui/console-responsive.css']:
assert (app/'_internal'/relative).read_bytes()==(root/relative).read_bytes(),relative
assert (app/'_internal/python312.dll').is_file()
report={'version':'1.4.26','sourceManifestMatches':True,'compiledModulesMatchSource':checked_modules,'frozenFileIdentityFixPresent':True,'frozenHashSyntheticRegression':'passed','python312Bundled':True,'privateFilesExcluded':True,'contactDatabasesExcluded':True,'credentialFilesExcluded':True,'uiAssetsMatchSource':True,'runtime':sys.version,'realMessagesSent':0,'installed':False}
(folder/'frozen-release-qa.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps(report,ensure_ascii=False))