unify xmpp bots: single xmpp_agent_core.py + --agent param wrappers

This commit is contained in:
2026-06-20 01:05:01 +08:00
parent ee86052219
commit 2d0f390657
5 changed files with 451 additions and 368 deletions
+4 -191
View File
@@ -1,192 +1,5 @@
#!/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({}))
# 读取需要加入的MUC群聊房间列表
MUC_ROOMS_FILE = "/home/hmo/.hermes/zhiwei_rooms.txt"
MUC_ROOMS = []
if os.path.exists(MUC_ROOMS_FILE):
with open(MUC_ROOMS_FILE) as f:
MUC_ROOMS = [line.strip() for line in f if line.strip() and not line.strip().startswith('#')]
logging.info(f"📋 知微待加入群聊: {MUC_ROOMS}")
else:
logging.warning(f"⚠️ 群聊配置文件 {MUC_ROOMS_FILE} 不存在")
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 = False
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()
# 加入所有配置的MUC群聊
for room in MUC_ROOMS:
try:
nick = room.split('@')[0]
self.xmpp.plugin['xep_0045'].join_muc(room, nick)
logging.info(f"✅ 知微加入群聊 {room}")
except Exception as e:
logging.warning(f"⚠️ 加入群聊 {room} 失败: {e}")
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']); mtype = msg['type']
if not body:
return
# 群聊处理
if mtype == 'groupchat':
nickname = sender.split('/')[-1] if '/' in sender else ''
if nickname in ('hmo', 'xxm'):
room = sender.split('/')[0]
logging.info(f"📩 知微群消息 [{room}/{nickname}]: {body[:80]}")
self._call_seq += 1
try:
payload = json.dumps({
"model":"hermes-agent",
"messages":[{"role":"user","content":f"[{room} {nickname}] {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=120))
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":
self.xmpp.send_message(mto=room, mbody=reply, mtype='groupchat')
logging.info(f"✅ 知微群回复: {reply[:80]}")
except Exception as e:
logging.error(f"❌ 知微群错误: {e}")
return
# 私聊处理
if mtype != 'chat': return
if 'hmo@yoin.fun' in sender:
self._call_seq += 1
logging.info(f"📩 老爸(#{self._call_seq}): {body}")
# 先秒回确认消息,让老爸知道bot活着
send("zhiwei@yoin.fun", sender, "收到,正在思考...")
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=120))
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.register_plugin('xep_0045') # MUC群聊支持
# XMPP Ping 每 60 秒发一次 keepalive,防服务器超时断线
z.xmpp['xep_0199'].keepalive = True
z.xmpp['xep_0199'].interval = 60
z.xmpp['xep_0199'].timeout = 10
z.xmpp.connect(host='127.0.0.1', port=5222)
await asyncio.wait_for(z.ready.wait(), timeout=30)
logging.info("知微就绪")
retry_delay = 1
# 保持运行,断线时自动跳出重连
while not z._stopped:
await asyncio.sleep(3)
if not z.xmpp.is_connected():
logging.warning("知微连接丢失,重连中...")
break
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__':
import sys, os, signal
# PID 文件防多进程:启动时检查,如果已有实例则杀旧启新
PIDFILE = '/home/hmo/.hermes/zhiwei_bot.pid'
if os.path.exists(PIDFILE):
try:
old_pid = int(open(PIDFILE).read().strip())
os.kill(old_pid, signal.SIGTERM)
print(f"Killed old bot process {old_pid}")
import time
time.sleep(2)
except (ValueError, ProcessLookupError, OSError):
pass
with open(PIDFILE, 'w') as f:
f.write(str(os.getpid()))
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
finally:
if os.path.exists(PIDFILE):
os.remove(PIDFILE)
"""Wrapper for xmpp_agent_core.py --agent zhiwei"""
import sys, os
sys.argv = [sys.argv[0], '--agent', 'zhiwei']
exec(open(os.path.join(os.path.dirname(__file__), 'xmpp_agent_core.py')).read())