refactor(xxm): consolidate 4 bot implementations into unified xmpp_agent_core.py

- Merge bot_base.py, gateway/scripts/xmpp_bot.py, bots/*, xmpp_bot_rest.py
  into single xmpp_agent_core.py with --agent flag (xxm|mohe|zhiwei|xiaoguo)
- Add xxm_bot.py wrapper (encoding=utf-8 for Windows exec)
- Fix slixmpp connect() API: use host=/port= keyword args (was tuple)
- Clean up orphans: bots/, scripts/, hermes_state.py, xmpp_bot.py, xmpp_bot_rest.py
- Add docs/CLEANUP_PLAN.md documenting the migration
- Update README.md project structure
- Also: fix WeChat agent path resolution (relative paths)
This commit is contained in:
hmo
2026-06-21 16:13:57 +08:00
parent b9df510f31
commit babbc46801
22 changed files with 1273 additions and 5442 deletions
+78 -3
View File
@@ -21,7 +21,8 @@ if not _lock.ok:
BOT_WXID = "wxid_5bhmquvkbude22"
BLOCK_WXIDS = {"fmessage", "weixin", "wechat"} # ϵͳ?˺?/΢???Ŷӣ----ظ?
WX_API = "http://127.0.0.1:19088"
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
LOG_DIR = os.path.join(PROJECT_ROOT, "logs")
TEMP_DIR = os.path.join(PROJECT_ROOT, "temp")
LOG_FILE = os.path.join(LOG_DIR, "wechat_agent.log")
@@ -155,8 +156,8 @@ HERMES_KEY = "hermes123"
SENSENOVA_KEY = "sk-aRNj3UwKSLPsDfh15QNTPwbHxahblfaO"
SENSENOVA_URL = "https://token.sensenova.cn/v1"
INJECTOR = r"D:\F\NewI\opencode\daily-workspace\projects\wechat-hermes-gateway\tools\Injector_x64.exe"
WXHELPER_DLL = r"D:\F\NewI\opencode\daily-workspace\projects\wechat-hermes-gateway\tools\wxhelper_official_39581.dll"
INJECTOR = os.path.join(SCRIPT_DIR, "..", "tools", "Injector_x64.exe")
WXHELPER_DLL = os.path.join(SCRIPT_DIR, "..", "tools", "wxhelper_official_39581.dll")
def log(m):
with open(LOG_FILE, "a", encoding="utf-8") as f:
@@ -569,8 +570,24 @@ def process_msg(raw_data):
ct = d.get("content", "") or d.get("msg", "") or d.get("text", "")
msg_type = d.get("type", 1)
is_self = d.get("isSelf", 0) or d.get("self", 0)
# DEBUG: capture Type 49 full XML for URL analysis
if msg_type == 49:
try:
with open(LOG_DIR + "/t49_xml.txt", "a", encoding="utf-8") as _f:
_f.write(f"\n=== {time.time()} type=49 from={fu} ===\n{ct[:10000]}\n")
except: pass
if "@chatroom" in fu:
log(f"GROUP RAW DUMP: keys={list(d.keys())} ct_len={len(ct)} ct[:100]={ct[:100]}")
# DEBUG: capture full raw data for quote analysis
try:
with open(LOG_DIR + "/group_raw.jsonl", "a", encoding="utf-8") as _f:
_f.write(json.dumps({k: str(v)[:2000] for k, v in d.items()}, ensure_ascii=False) + "\n")
except: pass
# DEBUG: capture all raw msgs for field analysis
try:
with open(LOG_DIR + "/all_raw.jsonl", "a", encoding="utf-8") as _f:
_f.write(json.dumps({k: str(v)[:500] for k, v in d.items()}, ensure_ascii=False) + "\n")
except: pass
if not fu or not ct or fu == BOT_WXID or fu in BLOCK_WXIDS or fu.startswith("gh_") or is_self:
log(f"SKIP: fu={fu} self={is_self}")
return
@@ -608,6 +625,64 @@ def process_msg(raw_data):
else:
log(f"-> {fu}: skip (blank image response)")
return
# Type 49 (forwarded article) - extract URL and process via article_processor
if msg_type == 49 and ct.strip().startswith("<?xml"):
try:
import re
# Try <url> first, then <shareUrlOriginal>, then <shareUrlOpen>
urls = re.findall(r'<url>(https?://mp\.weixin\.qq\.com[^<]+)</url>', ct)
if not urls:
urls = re.findall(r'<shareUrlOriginal>(https?://mp\.weixin\.qq\.com[^<]+)</shareUrlOriginal>', ct)
if not urls:
urls = re.findall(r'<shareUrlOpen>(https?://mp\.weixin\.qq\.com[^<]+)</shareUrlOpen>', ct)
url = urls[0] if urls else None
# Extract title from XML
titles = re.findall(r'<title>(.*?)</title>', ct)
title = titles[0] if titles else ""
# Extract description
descs = re.findall(r'<des>(.*?)</des>', ct)
desc = descs[0] if descs else ""
if url:
log(f"ARTICLE URL: {url}")
# Call article_processor on localhost
import urllib.request as ur
req = ur.Request("http://127.0.0.1:5810/process",
data=json.dumps({"url": url}).encode("utf-8"),
headers={"Content-Type": "application/json"})
with ur.urlopen(req, timeout=180) as resp:
result = json.loads(resp.read().decode("utf-8"))
if result.get("status") == "ok":
content = result.get("content", "")[:3000]
title = result.get("title", "")
images = result.get("images_ocr", 0)
enriched = f"[老莫转发了一篇文章{(chr(10)+'标题: '+title) if title else ''}{images}张图片已OCR]\n\n{content}"
log(f"ARTICLE processed: {len(content)} chars")
reply = call_hermes(fu, enriched)
if reply and reply.strip():
log(f"-> {fu}: {reply[:50]}")
send_wx(fu, reply.strip())
return
else:
log(f"ARTICLE process failed: {result.get('error','')[:100]}")
# Fallback: send title + description
fallback = f"[老莫转发了一篇文章]{(chr(10)+'标题: '+title) if title else ''}{(chr(10)+'摘要: '+desc[:200]) if desc else ''}\n(全文抓取失败: {result.get('error','')[:60]})"
reply = call_hermes(fu, fallback)
if reply and reply.strip():
send_wx(fu, reply.strip())
return
else:
# No URL found, send title + description
if title:
log(f"ARTICLE: no URL, sending title+desc")
fallback = f"[老莫转发了一篇文章]{(chr(10)+'标题: '+title) if title else ''}{(chr(10)+'摘要: '+desc[:200]) if desc else ''}"
reply = call_hermes(fu, fallback)
if reply and reply.strip():
send_wx(fu, reply.strip())
return
except Exception as e:
log(f"ARTICLE handler error: {e}")
# Fall through to text handler
# Text - prepend sender wxid+name so Hermes knows who's talking
sender_name = get_nickname(fu)
chat_type = "Group" if "@chatroom" in fu else "Private"