from __future__ import annotations import argparse import json from pathlib import Path TS_HELPER = '''// AIOS Weixin lossless getUpdates JSON begin const WEIXIN_EXACT_ID_FIELDS = new Set(["message_id", "msg_id", "root_id", "parent_id", "server_msg_id", "new_msg_id"]); function parseWeixinGetUpdatesJson(rawText: string): GetUpdatesResp { return JSON.parse(rawText, (key: string, value: unknown, context?: { source?: string }) => { if (!WEIXIN_EXACT_ID_FIELDS.has(key) || value == null || typeof value === "string") return value; if (typeof value !== "number") throw new Error(`invalid ${key} type in getUpdates response`); const source = context?.source; if (!source || !/^(?:0|[1-9]\\d*)$/.test(source)) throw new Error(`unsafe numeric ${key} in getUpdates response`); return source; }) as GetUpdatesResp; } // AIOS Weixin lossless getUpdates JSON end''' JS_HELPER = '''// AIOS Weixin lossless getUpdates JSON begin const WEIXIN_EXACT_ID_FIELDS = new Set(["message_id", "msg_id", "root_id", "parent_id", "server_msg_id", "new_msg_id"]); function parseWeixinGetUpdatesJson(rawText) { return JSON.parse(rawText, (key, value, context) => { if (!WEIXIN_EXACT_ID_FIELDS.has(key) || value == null || typeof value === "string") return value; if (typeof value !== "number") throw new Error(`invalid ${key} type in getUpdates response`); const source = context?.source; if (!source || !/^(?:0|[1-9]\\d*)$/.test(source)) throw new Error(`unsafe numeric ${key} in getUpdates response`); return source; }); } // AIOS Weixin lossless getUpdates JSON end''' def insert_once(text: str, marker: str, addition: str) -> str: if addition in text: return text if marker not in text: raise RuntimeError(f'marker not found: {marker}') return text.replace(marker, marker + '\n' + addition, 1) def patch_api_ts(text: str) -> str: text = insert_once(text, 'import { redactBody, redactUrl } from "../util/redact.js";', '\n' + TS_HELPER) old = 'const resp: GetUpdatesResp = JSON.parse(rawText);' new = 'const resp: GetUpdatesResp = parseWeixinGetUpdatesJson(rawText);' if new not in text: if old not in text: raise RuntimeError('TypeScript getUpdates parser marker not found') text = text.replace(old, new, 1) return text def patch_api_js(text: str) -> str: text = insert_once(text, 'import { redactBody, redactUrl } from "../util/redact.js";', '\n' + JS_HELPER) old = 'const resp = JSON.parse(rawText);' new = 'const resp = parseWeixinGetUpdatesJson(rawText);' if new not in text: if old not in text: raise RuntimeError('JavaScript getUpdates parser marker not found') text = text.replace(old, new, 1) return text def patch_types_ts(text: str) -> str: new = 'message_id?: string | number;' if new in text: return text old = 'message_id?: number;' if old not in text: raise RuntimeError('message_id type marker not found') return text.replace(old, new, 1) def write_if_changed(path: Path, content: str) -> bool: original = path.read_text(encoding='utf-8') if original == content: return False path.write_text(content, encoding='utf-8') return True def apply(plugin_root: Path) -> dict[str, bool]: paths = { 'src/api/api.ts': patch_api_ts, 'dist/src/api/api.js': patch_api_js, 'src/api/types.ts': patch_types_ts, } changed: dict[str, bool] = {} for relative, transform in paths.items(): path = plugin_root / relative if not path.exists(): raise RuntimeError(f'missing plugin file: {path}') original = path.read_text(encoding='utf-8') changed[relative] = write_if_changed(path, transform(original)) return changed def main() -> int: parser = argparse.ArgumentParser() parser.add_argument('plugin_root', type=Path) args = parser.parse_args() changed = apply(args.plugin_root.resolve()) print(json.dumps({'plugin_root': str(args.plugin_root.resolve()), 'changed': changed}, ensure_ascii=False)) return 0 if __name__ == '__main__': raise SystemExit(main())