#!/usr/bin/env python3 """XMPP Bot zhiwei@yoin.fun - Hermes API 版(修复版:ejabberd 26.4 兼容 + 稳定重连)""" import asyncio, logging, ssl, json, urllib.request, os, subprocess, time from xml.sax.saxutils import escape logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') GATEWAY = "http://localhost:8643/v1/chat/completions" API_KEY = "hermes123" _opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) def send(from_jid, to_jid, body): safe = escape(body) subprocess.run(["docker","exec","ejabberd","ejabberdctl","send_message","chat", from_jid, to_jid, "", safe ], capture_output=True, timeout=10) class ZhiweiBot: def __init__(self): import slixmpp self.xmpp = slixmpp.ClientXMPP('zhiwei@yoin.fun', 'hermes123') self.xmpp.add_event_handler('session_bind', self.on_bind) self.xmpp.add_event_handler('message', self.on_msg) self.xmpp.add_event_handler('disconnected', self.on_disconnect) self.xmpp.add_event_handler('connected', self.on_connected) # 启用 slixmpp 内置自动重连 self.xmpp.auto_reconnect = True ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE self.xmpp.ssl_context = ctx self.ready = asyncio.Event() self._stopped = False self._call_seq = 0 def stop(self): self._stopped = True self.xmpp.abort() async def on_connected(self, event): logging.info("🔗 知微TCP连接已建立") async def on_bind(self, event): self.xmpp.send_presence(); self.xmpp.get_roster(); self.ready.set() logging.info("✅ 知微上线") async def on_disconnect(self, event): self.ready.clear() logging.warning("⚠️ 知微断线") async def on_msg(self, msg): body = msg['body']; sender = str(msg['from']) if not body or msg['type'] != 'chat': return if 'hmo@yoin.fun' in sender: self._call_seq += 1 logging.info(f"📩 老爸(#{self._call_seq}): {body}") try: payload = json.dumps({ "model":"hermes-agent", "messages":[{"role":"user","content":f"[zhiwei] {body}"}] }).encode() req = urllib.request.Request(GATEWAY, data=payload, method="POST") req.add_header("Content-Type","application/json") req.add_header("Authorization",f"Bearer {API_KEY}") req.add_header("X-Hermes-Session-Id","xmpp-zhiwei") loop = asyncio.get_event_loop() result = await loop.run_in_executor(None, lambda: _opener.open(req, timeout=600)) data = json.loads(result.read()) reply = data.get("choices",[{}])[0].get("message",{}).get("content","") finish = data.get("choices",[{}])[0].get("finish_reason","") if reply.strip() and finish != "silent": send("zhiwei@yoin.fun", sender, reply) logging.info(f"✅ 知微回复: {reply[:80]}") except Exception as e: logging.error(f"❌ 知微错误: {e}") async def main(): retry_delay = 1 max_delay = 60 while True: try: z = ZhiweiBot() z.xmpp.register_plugin('xep_0030'); z.xmpp.register_plugin('xep_0199') z.xmpp.connect(host='127.0.0.1', port=5222) await asyncio.wait_for(z.ready.wait(), timeout=30) logging.info("知微就绪") retry_delay = 1 # 保持运行 — slixmpp 内置 auto_reconnect 会自动处理断线重连 while not z._stopped: await asyncio.sleep(5) except asyncio.TimeoutError: logging.error("⏰ 知微连接超时") except Exception as e: logging.error(f"知微main错误: {e}") finally: try: if 'z' in dir() and z: z.stop() except: pass # 指数退避重连 logging.info(f"⏳ 知微等待 {retry_delay} 秒后重连...") await asyncio.sleep(retry_delay) retry_delay = min(retry_delay * 2, max_delay) if __name__ == '__main__': try: asyncio.run(main()) except KeyboardInterrupt: pass