import json import os import signal import sys import time from datetime import datetime, timezone import wcferry.client as wcf_client from wcferry import Wcf PORT = int(os.environ.get("WCF_COMMAND_PORT", "10086")) STATE_ROOT = os.environ.get("WCF_STATE_ROOT", r"C:\UniDesk\personal-wechat\wcf-state") DEBUG = os.environ.get("WCF_DEBUG", "false").lower() == "true" STOP = False class WcfProcessExit(Exception): def __init__(self, code): super().__init__(f"wcferry requested process exit: {code}") self.code = code def now_iso(): return datetime.now(timezone.utc).isoformat() def write_json(path, payload): tmp = f"{path}.tmp" with open(tmp, "w", encoding="utf-8") as fp: json.dump(payload, fp, ensure_ascii=False, indent=2, sort_keys=True) os.replace(tmp, path) def log(event, **fields): os.makedirs(STATE_ROOT, exist_ok=True) payload = {"ts": now_iso(), "event": event, **fields} line = json.dumps(payload, ensure_ascii=False, sort_keys=True) print(line, flush=True) with open(os.path.join(STATE_ROOT, "wcf-host.log"), "a", encoding="utf-8") as fp: fp.write(line + "\n") fp.flush() def write_state(**fields): os.makedirs(STATE_ROOT, exist_ok=True) payload = { "ts": now_iso(), "isLogin": None, "qrcodePresent": False, "commandPort": PORT, "messagePort": PORT + 1, **fields, } write_json(os.path.join(STATE_ROOT, "status.json"), payload) def handle_signal(_signum, _frame): global STOP STOP = True def patch_wcferry_exit(): def raise_exit(code=0): raise WcfProcessExit(code) wcf_client.os._exit = raise_exit signal.signal(signal.SIGTERM, handle_signal) signal.signal(signal.SIGINT, handle_signal) def connect_wcf(): patch_wcferry_exit() attempt = 0 while not STOP: attempt += 1 local_init = attempt == 1 or attempt % 15 == 0 mode = "local-init" if local_init else "remote-dial" try: write_state(phase="connecting", connectAttempt=attempt, connectMode=mode) log("wcf-host-connect-attempt", attempt=attempt, mode=mode, port=PORT) if local_init: wcf = Wcf(port=PORT, debug=DEBUG, block=False) else: wcf = Wcf(host="127.0.0.1", port=PORT, debug=DEBUG, block=False) log("wcf-host-connected", attempt=attempt, mode=mode, port=PORT) return wcf except WcfProcessExit as exc: log("wcf-host-connect-retry", attempt=attempt, mode=mode, requestedExit=exc.code) except Exception as exc: log("wcf-host-connect-error", attempt=attempt, mode=mode, error=f"{type(exc).__name__}: {exc}"[:1000]) time.sleep(min(2 + attempt, 10)) return None def main(): os.makedirs(STATE_ROOT, exist_ok=True) with open(os.path.join(STATE_ROOT, "wcf-host.pid"), "w", encoding="ascii") as fp: fp.write(str(os.getpid())) log( "wcf-host-start", port=PORT, debug=DEBUG, pid=os.getpid(), appData=os.environ.get("APPDATA", ""), userProfile=os.environ.get("USERPROFILE", ""), ) wcf = connect_wcf() if wcf is None: log("wcf-host-stop-before-connect") return while not STOP: try: logged_in = bool(wcf.is_login()) qrcode = "" if not logged_in: try: qrcode = wcf.get_qrcode() except Exception as exc: log("wcf-host-qrcode-error", error=f"{type(exc).__name__}: {exc}"[:1000]) qrcode = "" state = { "phase": "ready", "isLogin": logged_in, "qrcodePresent": bool(qrcode), "commandPort": PORT, "messagePort": PORT + 1, } if qrcode: state["qrcode"] = qrcode write_state(**state) log("wcf-host-status", isLogin=logged_in, qrcodePresent=bool(qrcode)) time.sleep(5) except Exception as exc: log("wcf-host-error", error=f"{type(exc).__name__}: {exc}"[:1000]) time.sleep(3) try: wcf.cleanup() except Exception: pass log("wcf-host-stop") if __name__ == "__main__": main()