feat: migrate to wxhelper DLL (3.9.10.19 x64), add SenseNova image gen
This commit is contained in:
+235
-281
@@ -1,20 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
WeChat Agent - wxhook + Hermes API (:8642)
|
WeChat Agent v2 - wxhelper DLL + Hermes API (:8642)
|
||||||
"""
|
"""
|
||||||
import sys, os, json, time, threading, requests, re
|
import os, json, time, threading, requests, re, socketserver, subprocess, urllib.request, urllib.error
|
||||||
|
os.environ["no_proxy"] = "*"
|
||||||
|
os.environ["NO_PROXY"] = "*"
|
||||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
|
||||||
sys.path.insert(0, r"C:\Users\hmo\AppData\Local\Programs\Python\Python310\Lib\site-packages")
|
|
||||||
os.environ["WXHOOK_LOG_LEVEL"] = "ERROR"
|
|
||||||
|
|
||||||
from wxhook import Bot
|
|
||||||
from wxhook.events import TEXT_MESSAGE, IMAGE_MESSAGE, VOICE_MESSAGE, XML_MESSAGE
|
|
||||||
import pymem, pymem.process
|
|
||||||
|
|
||||||
BOT_WXID = "wxid_7onnerpx2s2l22"
|
BOT_WXID = "wxid_7onnerpx2s2l22"
|
||||||
WX_API = ""
|
WX_API = "http://127.0.0.1:19088"
|
||||||
LOG_FILE = r"C:\Users\hmo\Desktop\wechat_agent.log"
|
LOG_FILE = r"C:\Users\hmo\Desktop\wechat_agent.log"
|
||||||
DLL = r"C:\Users\hmo\AppData\Local\Programs\Python\Python310\Lib\site-packages\wxhook\tools\wxhook.dll"
|
TCP_PORT = 19099
|
||||||
last_msg_time = time.time()
|
last_msg_time = time.time()
|
||||||
nickname_cache = {}
|
nickname_cache = {}
|
||||||
|
|
||||||
@@ -23,33 +18,37 @@ HERMES_KEY = "hermes123"
|
|||||||
SENSENOVA_KEY = "sk-aRNj3UwKSLPsDfh15QNTPwbHxahblfaO"
|
SENSENOVA_KEY = "sk-aRNj3UwKSLPsDfh15QNTPwbHxahblfaO"
|
||||||
SENSENOVA_URL = "https://token.sensenova.cn/v1"
|
SENSENOVA_URL = "https://token.sensenova.cn/v1"
|
||||||
|
|
||||||
# SenseNova (商汤) for image gen + vision
|
INJECTOR = r"C:\Users\hmo\Desktop\wxhelper_v11\wxhelper-3.9.5.81-v11\tool\injector\ConsoleApplication.exe"
|
||||||
SENSENOVA_KEY = "sk-aRNj3UwKSLPsDfh15QNTPwbHxahblfaO"
|
WXHELPER_DLL = r"C:\Users\hmo\Desktop\wxhelper_391019.dll"
|
||||||
SENSENOVA_URL = "https://token.sensenova.cn/v1"
|
|
||||||
|
|
||||||
def log(m):
|
def log(m):
|
||||||
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
||||||
f.write(f"{time.strftime('%H:%M:%S')} {m}\n")
|
f.write(f"{time.strftime('%H:%M:%S')} {m}\n")
|
||||||
|
|
||||||
def send_wx(wxid, msg):
|
def wxpost(path, data=None, timeout=10):
|
||||||
try:
|
try:
|
||||||
requests.post(WX_API + "/api/sendTextMsg", json={"wxid": wxid, "msg": msg}, timeout=5)
|
body = json.dumps(data or {}).encode()
|
||||||
log(f"SEND {wxid}: ok")
|
r = urllib.request.urlopen(urllib.request.Request(WX_API + path, data=body, headers={"Content-Type": "application/json"}), timeout=timeout)
|
||||||
|
return json.loads(r.read().decode())
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
return json.loads(e.read().decode()) if e.code else {"code": -1}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"SEND ERR: {e}")
|
log(f"WX ERR: {e}")
|
||||||
|
return {"code": -1}
|
||||||
|
|
||||||
|
def send_wx(wxid, msg):
|
||||||
|
r = wxpost("/api/sendTextMsg", {"wxid": wxid, "msg": msg})
|
||||||
|
log(f"SEND {wxid}: {r.get('msg','')}")
|
||||||
|
|
||||||
def get_nickname(wxid):
|
def get_nickname(wxid):
|
||||||
if wxid in nickname_cache:
|
if wxid in nickname_cache:
|
||||||
return nickname_cache[wxid]
|
return nickname_cache[wxid]
|
||||||
try:
|
r = wxpost("/api/getContactList", timeout=10)
|
||||||
r = requests.post(WX_API + "/api/getContactList", json={}, timeout=5)
|
for c in (r.get("data") or []):
|
||||||
for c in r.json().get("data", []):
|
if c.get("wxid") == wxid:
|
||||||
if c.get("wxid") == wxid:
|
nick = c.get("nickname") or c.get("customAccount") or wxid
|
||||||
nick = c.get("nickname") or c.get("customAccount") or wxid
|
nickname_cache[wxid] = nick
|
||||||
nickname_cache[wxid] = nick
|
return nick
|
||||||
return nick
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
nickname_cache[wxid] = wxid
|
nickname_cache[wxid] = wxid
|
||||||
return wxid
|
return wxid
|
||||||
|
|
||||||
@@ -59,57 +58,225 @@ def call_hermes(wxid, content):
|
|||||||
sys_prompt = "回复简短。"
|
sys_prompt = "回复简短。"
|
||||||
body = {"model": "hermes-agent", "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": content}]}
|
body = {"model": "hermes-agent", "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": content}]}
|
||||||
try:
|
try:
|
||||||
r = requests.post(HERMES_API, json=body, headers=headers, timeout=180, proxies={"http": None, "https": None})
|
r = requests.post(HERMES_API, json=body, headers=headers, timeout=180, proxies={"http": None, "https": None})
|
||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
return r.json()["choices"][0]["message"]["content"]
|
return r.json()["choices"][0]["message"]["content"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_msg = str(e)
|
log(f"API ERR: {e}")
|
||||||
log(f"API ERR: {err_msg[:60]}")
|
|
||||||
# Notify user on errors
|
|
||||||
try:
|
|
||||||
if "timeout" in err_msg.lower() or "timed out" in err_msg.lower():
|
|
||||||
send_wx(fu, "[莫荷处理超时,你再发一遍试试?]")
|
|
||||||
elif "connection" in err_msg.lower() or "refused" in err_msg.lower():
|
|
||||||
send_wx(fu, "[跟莫荷的连接断了,正在自动重连...]")
|
|
||||||
elif "500" in err_msg or "50x" in err_msg:
|
|
||||||
send_wx(fu, "[莫荷那边出错了,等一会儿再试]")
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# ── Inject wxhelper DLL ──
|
||||||
|
def inject_wxhelper():
|
||||||
|
try:
|
||||||
|
r = wxpost("/api/checkLogin", timeout=5)
|
||||||
|
if r.get("code") == 1:
|
||||||
|
log("wxhelper already injected")
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
result = subprocess.run([INJECTOR, "-i", "WeChat.exe", "-p", WXHELPER_DLL], capture_output=True, text=True, timeout=30)
|
||||||
|
log(f"Inject: {result.stdout.strip()[:50]}")
|
||||||
|
time.sleep(3)
|
||||||
|
r = wxpost("/api/checkLogin", timeout=5)
|
||||||
|
if r.get("code") == 1:
|
||||||
|
log("wxhelper injected OK")
|
||||||
|
return True
|
||||||
|
log(f"Inject check: {r}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Inject FAIL: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ── TCP Message Receiver ──
|
||||||
|
class MsgHandler(socketserver.BaseRequestHandler):
|
||||||
|
def handle(self):
|
||||||
|
try:
|
||||||
|
data = b""
|
||||||
|
while True:
|
||||||
|
c = self.request.recv(4096)
|
||||||
|
data += c
|
||||||
|
if not c or c[-1] == 10:
|
||||||
|
break
|
||||||
|
if data.strip():
|
||||||
|
threading.Thread(target=process_msg, args=(data,), daemon=True).start()
|
||||||
|
self.request.sendall(b"200 OK\n")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.request.close()
|
||||||
|
|
||||||
|
def process_msg(raw_data):
|
||||||
|
global last_msg_time
|
||||||
|
last_msg_time = time.time()
|
||||||
|
try:
|
||||||
|
d = json.loads(raw_data)
|
||||||
|
fu = d.get("fromUser", "") or d.get("fromuser", "") or d.get("wxid", "")
|
||||||
|
ct = d.get("content", "") or d.get("msg", "")
|
||||||
|
msg_type = d.get("type", 1)
|
||||||
|
if not fu or not ct or fu == BOT_WXID:
|
||||||
|
return
|
||||||
|
# Route by message type
|
||||||
|
if msg_type == 34: # Voice
|
||||||
|
log(f"<- {fu}: [voice]")
|
||||||
|
reply = call_hermes(fu, "[voice message]")
|
||||||
|
if reply: send_wx(fu, reply)
|
||||||
|
return
|
||||||
|
if msg_type == 3: # Image - wxhelper sends image as separate event
|
||||||
|
return
|
||||||
|
# Text
|
||||||
|
log(f"<- {fu}: {ct[:50]}")
|
||||||
|
reply = call_hermes(fu, ct)
|
||||||
|
if reply:
|
||||||
|
log(f"-> {fu}: {reply[:50]}")
|
||||||
|
process_tags(reply, fu)
|
||||||
|
else:
|
||||||
|
log(f"-> {fu}: no reply")
|
||||||
|
except Exception as e:
|
||||||
|
log(f"MSG ERR: {e}")
|
||||||
|
import traceback
|
||||||
|
log(f"TRACE: {traceback.format_exc()[:200]}")
|
||||||
|
|
||||||
|
def process_tags(reply, fu):
|
||||||
|
if not reply:
|
||||||
|
return
|
||||||
|
clean = reply
|
||||||
|
# [FILE]
|
||||||
|
for tag, pattern, repl in [
|
||||||
|
("FILE", r'\[FILE\](.*?)\[/FILE\]', lambda m: download_and_send_file(m, fu)),
|
||||||
|
("IMG", r'\[IMG\](.*?)\[/IMG\]', lambda m: handle_img(m, fu)),
|
||||||
|
("EMOJI", r'\[EMOJI\](.*?)\[/EMOJI\]', lambda m: download_emoji(m, fu)),
|
||||||
|
]:
|
||||||
|
match = re.search(pattern, clean)
|
||||||
|
if match:
|
||||||
|
clean = re.sub(r'\s*' + pattern.replace('(.*?)', '.*?') + r'\s*', '', clean).strip()
|
||||||
|
try:
|
||||||
|
match = re.search(pattern, reply) # re-match against original
|
||||||
|
if match:
|
||||||
|
threading.Thread(target=repl, args=(match,), daemon=True).start()
|
||||||
|
except: pass
|
||||||
|
# [CONTACT:wxid]
|
||||||
|
cm = re.search(r'\[CONTACT:(\w+)\]', clean)
|
||||||
|
if cm:
|
||||||
|
clean = re.sub(r'\s*\[CONTACT:\w+\]\s*', '', clean).strip()
|
||||||
|
r = wxpost("/api/getContactProfile", {"wxid": cm.group(1)})
|
||||||
|
cd = r.get("data", {})
|
||||||
|
send_wx(fu, f"昵称: {cd.get('nickname','?')} 备注: {cd.get('remark','')}")
|
||||||
|
# [ROOM_MEMBERS:roomid]
|
||||||
|
rm = re.search(r'\[ROOM_MEMBERS:(\S+)\]', clean)
|
||||||
|
if rm:
|
||||||
|
clean = re.sub(r'\s*\[ROOM_MEMBERS:\S+\]\s*', '', clean).strip()
|
||||||
|
r = wxpost("/api/getMemberFromChatRoom", {"chatRoomId": rm.group(1)})
|
||||||
|
members = (r.get("data") or {}).get("members", "")
|
||||||
|
mlist = [m for m in members.split("\u0007") if m]
|
||||||
|
send_wx(fu, f"群成员 ({len(mlist)}): {','.join(mlist[:20])}")
|
||||||
|
# [PAT:roomid:wxid]
|
||||||
|
pm = re.search(r'\[PAT:(\S+):(\S+)\]', clean)
|
||||||
|
if pm:
|
||||||
|
clean = re.sub(r'\s*\[PAT:\S+:\S+\]\s*', '', clean).strip()
|
||||||
|
wxpost("/api/sendPatMsg", {"receiver": pm.group(1), "wxid": pm.group(2)})
|
||||||
|
if clean.strip():
|
||||||
|
send_wx(fu, clean.strip())
|
||||||
|
|
||||||
|
def download_and_send_file(m, fu):
|
||||||
|
url = m.group(1).strip()
|
||||||
|
ir = requests.get(url, timeout=60, proxies={"http": None, "https": None})
|
||||||
|
if ir.status_code == 200:
|
||||||
|
tmp = os.path.join(r"C:\Users\hmo\Desktop", f"send_file_{int(time.time())}.dat")
|
||||||
|
with open(tmp, "wb") as f: f.write(ir.content)
|
||||||
|
wxpost("/api/sendFileMsg", {"wxid": fu, "filePath": tmp})
|
||||||
|
os.remove(tmp)
|
||||||
|
|
||||||
|
def handle_img(m, fu):
|
||||||
|
cmd = m.group(1).strip()
|
||||||
|
if cmd.startswith("generate:") or cmd.startswith("draw:"):
|
||||||
|
parts = cmd.split(":", 1)[1].strip()
|
||||||
|
ratio = "1:1"
|
||||||
|
if "|" in parts:
|
||||||
|
ratio = parts.split("|")[1].strip()
|
||||||
|
prompt = parts.split("|")[0].strip()
|
||||||
|
else:
|
||||||
|
prompt = parts
|
||||||
|
size_map = {"1:1":"2048x2048","16:9":"2752x1536","9:16":"1536x2752","3:2":"2496x1664","2:3":"1664x2496","3:4":"1760x2368","4:3":"2368x1760"}
|
||||||
|
size = size_map.get(ratio, "2048x2048")
|
||||||
|
log(f"GEN SenseNova: {prompt[:30]} [{ratio}]")
|
||||||
|
r = requests.post(SENSENOVA_URL + "/images/generations",
|
||||||
|
json={"model": "sensenova-u1-fast", "prompt": prompt, "size": size, "response_format": "url"},
|
||||||
|
headers={"Authorization": f"Bearer {SENSENOVA_KEY}", "Content-Type": "application/json"}, timeout=180)
|
||||||
|
if r.status_code == 200:
|
||||||
|
img_url = r.json()["data"][0]["url"]
|
||||||
|
ir = requests.get(img_url, timeout=60)
|
||||||
|
if ir.status_code == 200:
|
||||||
|
tmp = os.path.join(r"C:\Users\hmo\Desktop", f"gen_img_{int(time.time())}.png")
|
||||||
|
with open(tmp, "wb") as f: f.write(ir.content)
|
||||||
|
wxpost("/api/sendImagesMsg", {"wxid": fu, "imagePath": tmp})
|
||||||
|
os.remove(tmp)
|
||||||
|
else:
|
||||||
|
ir = requests.get(cmd, timeout=30, proxies={"http": None, "https": None})
|
||||||
|
if ir.status_code == 200:
|
||||||
|
ext = ".jpg"
|
||||||
|
if "png" in ir.headers.get("content-type", ""): ext = ".png"
|
||||||
|
tmp = os.path.join(r"C:\Users\hmo\Desktop", f"send_img_{int(time.time())}{ext}")
|
||||||
|
with open(tmp, "wb") as f: f.write(ir.content)
|
||||||
|
wxpost("/api/sendImagesMsg", {"wxid": fu, "imagePath": tmp})
|
||||||
|
os.remove(tmp)
|
||||||
|
|
||||||
|
def download_emoji(m, fu):
|
||||||
|
url = m.group(1).strip()
|
||||||
|
ir = requests.get(url, timeout=30, proxies={"http": None, "https": None})
|
||||||
|
if ir.status_code == 200:
|
||||||
|
tmp = os.path.join(r"C:\Users\hmo\Desktop", f"emoji_{int(time.time())}.png")
|
||||||
|
with open(tmp, "wb") as f: f.write(ir.content)
|
||||||
|
wxpost("/api/sendCustomEmotion", {"wxid": fu, "filePath": tmp})
|
||||||
|
os.remove(tmp)
|
||||||
|
|
||||||
|
# ── Watchdog ──
|
||||||
def watchdog():
|
def watchdog():
|
||||||
global last_msg_time
|
global last_msg_time
|
||||||
while True:
|
while True:
|
||||||
idle = time.time() - last_msg_time
|
idle = time.time() - last_msg_time
|
||||||
if idle > 120 and WX_API:
|
if idle > 120:
|
||||||
try:
|
try:
|
||||||
r = requests.post(WX_API + "/api/checkLogin", json={}, timeout=5)
|
r = wxpost("/api/checkLogin", timeout=5)
|
||||||
if r.json().get("code") == 1:
|
if r.get("code") == 1:
|
||||||
# API alive, just refresh webhook
|
wxpost("/api/hookSyncMsg", {"ip": "127.0.0.1", "port": TCP_PORT, "enableHttp": 0})
|
||||||
port = WX_API.split(":")[-1]
|
|
||||||
requests.post(WX_API + "/api/hookSyncMsg", json={"ip": "127.0.0.1", "port": int(port), "enableHttp": 1, "url": "", "timeout": 300}, timeout=5)
|
|
||||||
log(f"WATCHDOG: refreshed ({int(idle)}s)")
|
log(f"WATCHDOG: refreshed ({int(idle)}s)")
|
||||||
else:
|
else:
|
||||||
# API dead, find WeChat and inject DLL
|
log("WATCHDOG: re-injecting...")
|
||||||
log("WATCHDOG: re-injecting into running WeChat...")
|
inject_wxhelper()
|
||||||
try:
|
|
||||||
for proc in psutil.process_iter(["pid", "name"]):
|
|
||||||
if proc.info["name"] == "WeChat.exe":
|
|
||||||
pm = pymem.Pymem()
|
|
||||||
pm.open_process_from_id(proc.info["pid"])
|
|
||||||
pymem.process.inject_dll(pm.process_handle, DLL.encode())
|
|
||||||
pm.close()
|
|
||||||
log(f"WATCHDOG: injected into PID {proc.info['pid']}")
|
|
||||||
break
|
|
||||||
except Exception as ej:
|
|
||||||
log(f"WATCHDOG: inject failed: {ej}")
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
last_msg_time = time.time()
|
last_msg_time = time.time()
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
|
|
||||||
|
# ── Start ──
|
||||||
|
print("[Agent] starting...", flush=True)
|
||||||
|
log("=== Agent v2 (wxhelper) ===")
|
||||||
|
|
||||||
|
# Inject wxhelper
|
||||||
|
inject_wxhelper()
|
||||||
|
|
||||||
|
# Check login
|
||||||
|
r = wxpost("/api/checkLogin")
|
||||||
|
if r.get("code") == 1:
|
||||||
|
log(f"Logged in: OK")
|
||||||
|
else:
|
||||||
|
log(f"Login check: {r}")
|
||||||
|
log("Will retry via watchdog")
|
||||||
|
|
||||||
|
# Start watchdog
|
||||||
threading.Thread(target=watchdog, daemon=True).start()
|
threading.Thread(target=watchdog, daemon=True).start()
|
||||||
|
|
||||||
|
# Start TCP server for message receiving
|
||||||
|
tcp_server = socketserver.ThreadingTCPServer(("127.0.0.1", TCP_PORT), MsgHandler)
|
||||||
|
threading.Thread(target=tcp_server.serve_forever, daemon=True).start()
|
||||||
|
log(f"TCP server on :{TCP_PORT}")
|
||||||
|
|
||||||
|
# Hook sync messages (tell DLL to send events to our TCP server)
|
||||||
|
r = wxpost("/api/hookSyncMsg", {"port": TCP_PORT, "ip": "127.0.0.1", "enableHttp": 0})
|
||||||
|
log(f"hookSyncMsg: {r}")
|
||||||
|
|
||||||
|
# Reply server for hermes-msg
|
||||||
class RH(BaseHTTPRequestHandler):
|
class RH(BaseHTTPRequestHandler):
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
global last_msg_time
|
global last_msg_time
|
||||||
@@ -138,226 +305,13 @@ class RH(BaseHTTPRequestHandler):
|
|||||||
threading.Thread(target=lambda: HTTPServer(("0.0.0.0", 5801), RH).serve_forever(), daemon=True).start()
|
threading.Thread(target=lambda: HTTPServer(("0.0.0.0", 5801), RH).serve_forever(), daemon=True).start()
|
||||||
log("HTTP :5801")
|
log("HTTP :5801")
|
||||||
|
|
||||||
log("Creating Bot...")
|
# Notify user
|
||||||
b = Bot()
|
send_wx("filehelper", "[Agent v2] wxhelper online")
|
||||||
WX_API = b.BASE_URL
|
|
||||||
log("Bot ready, API=" + WX_API)
|
|
||||||
|
|
||||||
@b.handle([TEXT_MESSAGE, IMAGE_MESSAGE, VOICE_MESSAGE, XML_MESSAGE])
|
|
||||||
def on_msg(_bot, event):
|
|
||||||
global last_msg_time
|
|
||||||
last_msg_time = time.time()
|
|
||||||
fu = event.fromUser or ""
|
|
||||||
if not fu or fu == BOT_WXID:
|
|
||||||
return
|
|
||||||
if event.type == VOICE_MESSAGE:
|
|
||||||
mid = event.msgId or 0
|
|
||||||
log(f"<- {fu}: [voice] msgId={mid}")
|
|
||||||
# Try various voice download methods with real msgId
|
|
||||||
try:
|
|
||||||
r1 = requests.post(WX_API + "/api/getVoiceByMsgId", json={"msgId": mid, "storeDir": r"C:\Users\hmo\Desktop\wechat_voice"}, timeout=10)
|
|
||||||
log(f"getVoice: {r1.json()}")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"getVoice err: {e}")
|
|
||||||
try:
|
|
||||||
r2 = requests.post(WX_API + "/api/downloadAttach", json={"msgId": mid}, timeout=10)
|
|
||||||
log(f"downloadAttach: {r2.json()}")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"downloadAttach err: {e}")
|
|
||||||
try:
|
|
||||||
r3 = requests.post(WX_API + "/api/forwardMsg", json={"msgId": mid, "wxid": "filehelper"}, timeout=10)
|
|
||||||
log(f"forwardMsg: {r3.json()}")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"forwardMsg err: {e}")
|
|
||||||
reply = call_hermes(fu, "[voice message]")
|
|
||||||
if reply: send_wx(fu, reply)
|
|
||||||
return
|
|
||||||
if event.type == IMAGE_MESSAGE:
|
|
||||||
log(f"<- {fu}: [image]")
|
|
||||||
b64 = event.base64Img or ""
|
|
||||||
ocr_text = ""
|
|
||||||
if b64:
|
|
||||||
try:
|
|
||||||
import base64
|
|
||||||
os.makedirs(r"C:\Users\hmo\Desktop\wechat_images", exist_ok=True)
|
|
||||||
fname = os.path.join(r"C:\Users\hmo\Desktop\wechat_images", str(int(time.time())) + ".jpg")
|
|
||||||
with open(fname, "wb") as f:
|
|
||||||
f.write(base64.b64decode(b64))
|
|
||||||
api_key = os.environ.get("VOLCENGINE_API_KEY", "b0359bed-09f2-49e2-a53c-32ba057412e3")
|
|
||||||
with open(fname, "rb") as f:
|
|
||||||
img_b64 = base64.b64encode(f.read()).decode()
|
|
||||||
r = requests.post("https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions",
|
|
||||||
json={"model": "doubao-seed-code", "messages": [{"role": "user", "content": [{"type": "text", "text": "描述图片"}, {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64," + img_b64}}]}], "max_tokens": 500},
|
|
||||||
headers={"Authorization": "Bearer " + api_key, "Content-Type": "application/json"}, timeout=60)
|
|
||||||
ocr_text = r.json()["choices"][0]["message"]["content"]
|
|
||||||
except Exception as e:
|
|
||||||
log(f"OCR err: {e}")
|
|
||||||
msg = "[image]" + ("\n" + ocr_text if ocr_text else "")
|
|
||||||
reply = call_hermes(fu, msg)
|
|
||||||
if reply: send_wx(fu, reply)
|
|
||||||
return
|
|
||||||
# Handle XML messages (files, cards, etc.)
|
|
||||||
if event.type == XML_MESSAGE:
|
|
||||||
content = str(event.content or "")
|
|
||||||
log(f"<- {fu}: [xml] {content[:80]}")
|
|
||||||
# Try to extract file info from XML
|
|
||||||
import re as _re
|
|
||||||
fname_match = _re.search(r'<filename>(.*?)</filename>', content)
|
|
||||||
if fname_match:
|
|
||||||
reply = call_hermes(fu, f"[sent a file: {fname_match.group(1)}]")
|
|
||||||
else:
|
|
||||||
reply = call_hermes(fu, "[sent a file or card]")
|
|
||||||
if reply: send_wx(fu, reply)
|
|
||||||
return
|
|
||||||
|
|
||||||
content = event.content or ""
|
|
||||||
if not content:
|
|
||||||
return
|
|
||||||
log(f"<- {fu}: {content[:50]}")
|
|
||||||
reply = call_hermes(fu, content)
|
|
||||||
if reply:
|
|
||||||
log(f"-> {fu}: {reply[:50]}")
|
|
||||||
clean = reply
|
|
||||||
# Handle [FILE] tag
|
|
||||||
file_match = re.search(r'\[FILE\](.*?)\[/FILE\]', reply)
|
|
||||||
if file_match:
|
|
||||||
file_url = file_match.group(1).strip()
|
|
||||||
clean = re.sub(r'\s*\[FILE\].*?\[/FILE\]\s*', '', clean).strip()
|
|
||||||
try:
|
|
||||||
fr = requests.get(file_url, timeout=60, proxies={"http": None, "https": None})
|
|
||||||
if fr.status_code == 200:
|
|
||||||
fname = os.path.join(r"C:\Users\hmo\Desktop", f"send_file_{int(time.time())}.dat")
|
|
||||||
with open(fname, "wb") as f:
|
|
||||||
f.write(fr.content)
|
|
||||||
try:
|
|
||||||
_bot.send_file(fu, fname)
|
|
||||||
log(f"FILE sent")
|
|
||||||
except:
|
|
||||||
requests.post(WX_API + "/api/sendFileMsg", json={"wxid": fu, "filePath": fname}, timeout=10)
|
|
||||||
os.remove(fname)
|
|
||||||
except Exception as e:
|
|
||||||
log(f"FILE err: {e}")
|
|
||||||
# Handle [IMG] tag
|
|
||||||
img_match = re.search(r'\[IMG\](.*?)\[/IMG\]', clean)
|
|
||||||
if img_match:
|
|
||||||
img_cmd = img_match.group(1).strip()
|
|
||||||
clean = re.sub(r'\s*\[IMG\].*?\[/IMG\]\s*', '', clean).strip()
|
|
||||||
try:
|
|
||||||
if img_cmd.startswith("generate:") or img_cmd.startswith("draw:"):
|
|
||||||
# Generate image via SenseNova
|
|
||||||
parts = img_cmd.split(":", 1)[1].strip()
|
|
||||||
ratio = "1:1"
|
|
||||||
if "|" in parts:
|
|
||||||
ratio = parts.split("|")[1].strip()
|
|
||||||
prompt = parts.split("|")[0].strip()
|
|
||||||
else:
|
|
||||||
prompt = parts
|
|
||||||
# Map aspect ratio to SenseNova size
|
|
||||||
size_map = {"1:1":"2048x2048", "16:9":"2752x1536", "9:16":"1536x2752",
|
|
||||||
"3:2":"2496x1664", "2:3":"1664x2496", "3:4":"1760x2368", "4:3":"2368x1760"}
|
|
||||||
size = size_map.get(ratio, "2048x2048")
|
|
||||||
log(f"GEN SenseNova: {prompt[:30]} [{ratio}]")
|
|
||||||
gen_r = requests.post(SENSENOVA_URL + "/images/generations",
|
|
||||||
json={"model": "sensenova-u1-fast", "prompt": prompt, "size": size, "response_format": "url"},
|
|
||||||
headers={"Authorization": f"Bearer {SENSENOVA_KEY}", "Content-Type": "application/json"},
|
|
||||||
timeout=180)
|
|
||||||
if gen_r.status_code == 200:
|
|
||||||
img_url = gen_r.json()["data"][0]["url"]
|
|
||||||
ir = requests.get(img_url, timeout=60)
|
|
||||||
if ir.status_code == 200:
|
|
||||||
tmp = os.path.join(r"C:\Users\hmo\Desktop", f"gen_img_{int(time.time())}.png")
|
|
||||||
with open(tmp, "wb") as f:
|
|
||||||
f.write(ir.content)
|
|
||||||
_bot.send_image(fu, tmp)
|
|
||||||
os.remove(tmp)
|
|
||||||
log("GEN sent")
|
|
||||||
else:
|
|
||||||
log(f"GEN err: {gen_r.status_code} {gen_r.text[:100]}")
|
|
||||||
else:
|
|
||||||
ir = requests.get(img_cmd, timeout=30, proxies={"http": None, "https": None})
|
|
||||||
if ir.status_code == 200:
|
|
||||||
ext = ".jpg"
|
|
||||||
if "png" in ir.headers.get("content-type", ""): ext = ".png"
|
|
||||||
tmp = os.path.join(r"C:\Users\hmo\Desktop", f"send_img_{int(time.time())}{ext}")
|
|
||||||
with open(tmp, "wb") as f:
|
|
||||||
f.write(ir.content)
|
|
||||||
try:
|
|
||||||
_bot.send_image(fu, tmp)
|
|
||||||
except:
|
|
||||||
requests.post(WX_API + "/api/sendImagesMsg", json={"wxid": fu, "imagePath": tmp}, timeout=10)
|
|
||||||
os.remove(tmp)
|
|
||||||
except Exception as e:
|
|
||||||
log(f"IMG err: {e}")
|
|
||||||
# Handle [CONTACT:wxid]
|
|
||||||
contact_match = re.search(r'\[CONTACT:(\w+)\]', clean)
|
|
||||||
if contact_match:
|
|
||||||
cwxid = contact_match.group(1)
|
|
||||||
clean = re.sub(r'\s*\[CONTACT:\w+\]\s*', '', clean).strip()
|
|
||||||
try:
|
|
||||||
cr = requests.post(WX_API + "/api/getContactProfile", json={"wxid": cwxid}, timeout=10)
|
|
||||||
cd = cr.json().get("data", {})
|
|
||||||
info = f"昵称: {cd.get('nickname','?')} 备注: {cd.get('remark','')} 账号: {cd.get('account','')}"
|
|
||||||
send_wx(fu, info)
|
|
||||||
log(f"CONTACT info sent")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"CONTACT err: {e}")
|
|
||||||
# Handle [ROOM_MEMBERS:roomid]
|
|
||||||
room_match = re.search(r'\[ROOM_MEMBERS:(\S+)\]', clean)
|
|
||||||
if room_match:
|
|
||||||
rid = room_match.group(1)
|
|
||||||
clean = re.sub(r'\s*\[ROOM_MEMBERS:\S+\]\s*', '', clean).strip()
|
|
||||||
try:
|
|
||||||
rr = requests.post(WX_API + "/api/getMemberFromChatRoom", json={"chatRoomId": rid}, timeout=10)
|
|
||||||
members = rr.json().get("data", {}).get("members", "")
|
|
||||||
send_wx(fu, f"群成员: {members[:100]}")
|
|
||||||
log(f"ROOM members sent")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"ROOM err: {e}")
|
|
||||||
# Handle [EMOJI] tag
|
|
||||||
emoji_match = re.search(r'\[EMOJI\](.*?)\[/EMOJI\]', clean)
|
|
||||||
if emoji_match:
|
|
||||||
eurl = emoji_match.group(1).strip()
|
|
||||||
clean = re.sub(r'\s*\[EMOJI\].*?\[/EMOJI\]\s*', '', clean).strip()
|
|
||||||
try:
|
|
||||||
er = requests.get(eurl, timeout=30, proxies={"http": None, "https": None})
|
|
||||||
if er.status_code == 200:
|
|
||||||
epath = os.path.join(r"C:\Users\hmo\Desktop", f"emoji_{int(time.time())}.png")
|
|
||||||
with open(epath, "wb") as f:
|
|
||||||
f.write(er.content)
|
|
||||||
_bot.send_emotion(fu, epath)
|
|
||||||
os.remove(epath)
|
|
||||||
log(f"EMOJI sent")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"EMOJI err: {e}")
|
|
||||||
# Handle [PAT:roomid:wxid]
|
|
||||||
pat_match = re.search(r'\[PAT:(\S+):(\S+)\]', clean)
|
|
||||||
if pat_match:
|
|
||||||
prid = pat_match.group(1)
|
|
||||||
pwxid = pat_match.group(2)
|
|
||||||
clean = re.sub(r'\s*\[PAT:\S+:\S+\]\s*', '', clean).strip()
|
|
||||||
try:
|
|
||||||
requests.post(WX_API + "/api/sendPatMsg", json={"receiver": prid, "wxid": pwxid}, timeout=10)
|
|
||||||
log(f"PAT sent")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"PAT err: {e}")
|
|
||||||
# Handle [OCR:image_path]
|
|
||||||
ocr_match = re.search(r'\[OCR:(.+?)\]', clean)
|
|
||||||
if ocr_match:
|
|
||||||
opath = ocr_match.group(1).strip()
|
|
||||||
clean = re.sub(r'\s*\[OCR:.+?\]\s*', '', clean).strip()
|
|
||||||
try:
|
|
||||||
or_ = requests.post(WX_API + "/api/ocr", json={"imagePath": opath}, timeout=30)
|
|
||||||
otext = or_.json().get("data", "")
|
|
||||||
send_wx(fu, f"OCR: {otext[:200]}")
|
|
||||||
log(f"OCR sent")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"OCR err: {e}")
|
|
||||||
# Send remaining text
|
|
||||||
if clean.strip():
|
|
||||||
send_wx(fu, clean.strip())
|
|
||||||
else:
|
|
||||||
log(f"-> {fu}: no reply")
|
|
||||||
|
|
||||||
print("[Agent] Hermes API: " + HERMES_API)
|
|
||||||
log("Ready")
|
log("Ready")
|
||||||
b.run()
|
print(f"[Agent v2] wxhelper :19088 | Hermes :8642")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
log("Bye")
|
||||||
|
|||||||
Reference in New Issue
Block a user