#!/usr/bin/env python3 """WeChat webhook receiver v2 - receives messages from docker-wechatbot-webhook.""" import os, sys, json, logging, threading from http.server import HTTPServer, BaseHTTPRequestHandler HERMES_API = "http://192.168.1.246:8642/v1/chat/completions" HERMES_KEY = "hermes123" PORT = 5804 WECHAT_BOT_TOKEN = os.environ.get("WECHAT_BOT_TOKEN", "mowechat_fixed_token_001") logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler("/home/hmo/projects/AgentsMeeting/gateway/linux/logs/webhook.log"), logging.StreamHandler() ] ) log = logging.getLogger("wc-webhook") class WebhookHandler(BaseHTTPRequestHandler): def do_POST(self): content_type = self.headers.get('Content-Type', '') content_length = int(self.headers.get('Content-Length', 0)) # Read the body body = self.rfile.read(content_length) # Parse multipart/form-data msg_type = 'unknown' content = '' source_raw = '{}' is_system = '0' if 'multipart/form-data' in content_type: # Manual multipart parsing boundary = content_type.split('boundary=')[1].strip() if boundary.startswith('"') and boundary.endswith('"'): boundary = boundary[1:-1] parts = body.split(b'--' + boundary.encode()) for part in parts: if b'Content-Disposition' not in part: continue # Parse headers header_end = part.find(b'\r\n\r\n') if header_end < 0: continue part_headers = part[:header_end].decode('utf-8', errors='replace') part_body = part[header_end + 4:] # Get field name name_start = part_headers.find('name="') if name_start < 0: continue name_start += 6 name_end = part_headers.find('"', name_start) field_name = part_headers[name_start:name_end] # Trim trailing \r\n-- if part_body.endswith(b'\r\n'): part_body = part_body[:-2] if part_body.endswith(b'--'): part_body = part_body[:-2] if part_body.endswith(b'\r\n'): part_body = part_body[:-2] if field_name == 'type': msg_type = part_body.decode('utf-8', errors='replace') elif field_name == 'content': content = part_body.decode('utf-8', errors='replace') elif field_name == 'source': source_raw = part_body.decode('utf-8', errors='replace') elif field_name == 'isSystemEvent': is_system = part_body.decode('utf-8', errors='replace') else: # Try as regular form or JSON try: data = json.loads(body) msg_type = data.get('type', 'unknown') content = data.get('content', '') source_raw = data.get('source', '{}') except: pass # Parse source try: source = json.loads(source_raw) if isinstance(source_raw, str) else source_raw except: source = {} # Extract sender info sender_name = "unknown" sender_id = "unknown" if isinstance(source, dict): from_data = source.get('from', {}) if isinstance(from_data, dict): payload = from_data.get('payload', {}) sender_name = payload.get('name', 'unknown') sender_id = payload.get('id', 'unknown') # Skip system events if is_system == '1': log.info(f"System event: {msg_type}") self._respond(200, {"status": "ok"}) return log.info(f"From: {sender_name} ({sender_id}), Type: {msg_type}") if msg_type == 'text': log.info(f"Text: {content[:300]}") self._forward_to_hermes(sender_name, sender_id, content) elif msg_type == 'urlLink': log.info(f"URL: {content[:200]}") self._forward_to_hermes(sender_name, sender_id, f"[分享链接] {content}") elif msg_type == 'file': log.info(f"File received, length={len(body)}") else: log.info(f"Other: {msg_type}") self._respond(200, {"status": "ok"}) def _forward_to_hermes(self, sender, sender_id, text): """Forward message to Hermes, get response, send back to WeChat.""" payload = json.dumps({ "model": "nova-4", "messages": [ {"role": "user", "content": f"[微信消息] 来自 {sender}({sender_id}): {text}"} ] }).encode() def do_forward(): try: import urllib.request as ureq handler = ureq.ProxyHandler({}) opener = ureq.build_opener(handler) req = ureq.Request(HERMES_API, data=payload, headers={"Content-Type": "application/json", "Authorization": f"Bearer {HERMES_KEY}"}) resp = opener.open(req, timeout=180) resp_data = json.loads(resp.read()) reply = resp_data.get('choices', [{}])[0].get('message', {}).get('content', '') log.info(f"Hermes OK, reply: {reply[:60]}") if reply and sender: self._send_wechat(sender, reply) except Exception as e: log.error(f"Hermes error: {e}") threading.Thread(target=do_forward, daemon=True).start() def _send_wechat(self, to_name, text): """Send message back to WeChat user via bot API.""" import urllib.request as ureq token = WECHAT_BOT_TOKEN api = f"http://localhost:3001/webhook/msg/v2?token={token}" data = json.dumps({"to": to_name, "data": {"content": text}}).encode() try: handler = ureq.ProxyHandler({}) opener = ureq.build_opener(handler) req = ureq.Request(api, data=data, headers={"Content-Type": "application/json"}) resp = opener.open(req, timeout=10) log.info(f"WeChat send OK: {resp.status}") except Exception as e: log.error(f"WeChat send error: {e}") def _respond(self, code, data): self.send_response(code) self.send_header('Content-Type', 'application/json') self.end_headers() self.wfile.write(json.dumps(data).encode()) def log_message(self, format, *args): pass def main(): server = HTTPServer(('0.0.0.0', PORT), WebhookHandler) log.info(f"Webhook receiver on :{PORT}") try: server.serve_forever() except KeyboardInterrupt: server.shutdown() if __name__ == "__main__": main()