""" VoceChat Webhook → Session Bridge. Receives VoceChat webhook events, forwards to opencode serve session, captures AI reply, and sends it back to the VC group. """ import os, sys, json, time, threading, urllib.request from http.server import HTTPServer, BaseHTTPRequestHandler sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from chat_bridge import SessionBridge from session_router import SessionRouter from proc_guard import guard as _proc_guard # ── PID lock — prevent duplicate instances ── _lock = _proc_guard("vc_webhook") if not _lock.ok: print(_lock.message, flush=True) sys.exit(1) # ── Config ──────────────────────────────────────────────── SERVE_URL = "http://127.0.0.1:4096" ATTACH_SESSION = "ses_1d95d15c4ffehQaZ6hrbIbak5k" VC_API = "http://192.168.1.246:3009" VC_BOT_KEY = os.environ.get( "VC_BOT_KEY", "5b2bd4ce2e0395503b4849a69a47a4e2a3f7aa81af242d2666b31e7519589c477b22756964223a362c226e6f6e6365223a2252576a744643384947476f41414141417a4c6a6e355a7a484731723839494b59227d") VC_SELF_UID = 6 SPEAKERS = {1: "老爸", 5: "莫荷", VC_SELF_UID: "莫笑笑"} LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "logs") LOG_FILE = os.path.join(LOG_DIR, "vc_webhook.log") os.makedirs(LOG_DIR, exist_ok=True) # ── Logging ─────────────────────────────────────────────── def log(msg: str): with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(f"{time.strftime('%H:%M:%S')} {msg}\n") # ── Router ──────────────────────────────────────────────── _router = SessionRouter( bridge=SessionBridge(session_id=ATTACH_SESSION, serve_url=SERVE_URL), default_session=ATTACH_SESSION, ) def _speaker(uid: int) -> str: return SPEAKERS.get(uid, f"用户{uid}") def _send_to_vc_group(gid: int, text: str): """Post reply to a VoceChat group via Bot API.""" url = f"{VC_API}/api/bot/send_to_group/{gid}" headers = {"X-API-Key": VC_BOT_KEY, "Content-Type": "text/plain"} urllib.request.urlopen( urllib.request.Request(url, data=text.encode("utf-8"), headers=headers), timeout=10) def _process_message(content: str, sender: int, data: dict): """VC message → router → reply → VC.""" if sender == VC_SELF_UID: return log(f"router.route: sender={sender} content={content[:50]}...") reply = _router.route("vc", str(sender), content) if reply: log(f"reply[:80]={reply[:80]}") gid = data.get("target", {}).get("gid", 0) if gid: try: _send_to_vc_group(gid, reply) log(f"Replied to VC group {gid}") except Exception as e: log(f"VC reply ERR: {e}") else: log("no text reply in time") # ── HTTP Handler ────────────────────────────────────────── class WebhookHandler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.end_headers() self.wfile.write(b"ok") def do_POST(self): body = self.rfile.read(int(self.headers.get("Content-Length", 0))) log(f"RAW: {body.decode('utf-8', errors='replace')[:300]}") try: data = json.loads(body) if data.get("type") in ("new_message", "chat"): detail = data.get("detail", {}) content = detail.get("content", "") or data.get("content", "") sender = data.get("from_uid", 0) log(f"MSG uid={sender}: {str(content)[:80]}") threading.Thread( target=_process_message, args=(str(content), sender, data), daemon=True, ).start() except Exception as e: log(f"ERR: {e}") self.send_response(200) self.end_headers() def log_message(self, *args): pass # ── Main ────────────────────────────────────────────────── if __name__ == "__main__": server = HTTPServer(("0.0.0.0", 8010), WebhookHandler) log("VC webhook listening on :8010") server.serve_forever()