diff --git a/scripts/wechat_agent.py b/scripts/wechat_agent.py index 06e3733..ab844b3 100644 --- a/scripts/wechat_agent.py +++ b/scripts/wechat_agent.py @@ -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 -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" -WX_API = "" +WX_API = "http://127.0.0.1:19088" 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() nickname_cache = {} @@ -23,33 +18,37 @@ HERMES_KEY = "hermes123" SENSENOVA_KEY = "sk-aRNj3UwKSLPsDfh15QNTPwbHxahblfaO" SENSENOVA_URL = "https://token.sensenova.cn/v1" -# SenseNova (商汤) for image gen + vision -SENSENOVA_KEY = "sk-aRNj3UwKSLPsDfh15QNTPwbHxahblfaO" -SENSENOVA_URL = "https://token.sensenova.cn/v1" +INJECTOR = r"C:\Users\hmo\Desktop\wxhelper_v11\wxhelper-3.9.5.81-v11\tool\injector\ConsoleApplication.exe" +WXHELPER_DLL = r"C:\Users\hmo\Desktop\wxhelper_391019.dll" def log(m): with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(f"{time.strftime('%H:%M:%S')} {m}\n") -def send_wx(wxid, msg): +def wxpost(path, data=None, timeout=10): try: - requests.post(WX_API + "/api/sendTextMsg", json={"wxid": wxid, "msg": msg}, timeout=5) - log(f"SEND {wxid}: ok") + body = json.dumps(data or {}).encode() + 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: - 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): if wxid in nickname_cache: return nickname_cache[wxid] - try: - r = requests.post(WX_API + "/api/getContactList", json={}, timeout=5) - for c in r.json().get("data", []): - if c.get("wxid") == wxid: - nick = c.get("nickname") or c.get("customAccount") or wxid - nickname_cache[wxid] = nick - return nick - except: - pass + r = wxpost("/api/getContactList", timeout=10) + for c in (r.get("data") or []): + if c.get("wxid") == wxid: + nick = c.get("nickname") or c.get("customAccount") or wxid + nickname_cache[wxid] = nick + return nick nickname_cache[wxid] = wxid return wxid @@ -59,57 +58,225 @@ def call_hermes(wxid, content): sys_prompt = "回复简短。" body = {"model": "hermes-agent", "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": content}]} 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: return r.json()["choices"][0]["message"]["content"] except Exception as e: - err_msg = str(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 + log(f"API ERR: {e}") 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(): global last_msg_time while True: idle = time.time() - last_msg_time - if idle > 120 and WX_API: + if idle > 120: try: - r = requests.post(WX_API + "/api/checkLogin", json={}, timeout=5) - if r.json().get("code") == 1: - # API alive, just refresh webhook - 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) + r = wxpost("/api/checkLogin", timeout=5) + if r.get("code") == 1: + wxpost("/api/hookSyncMsg", {"ip": "127.0.0.1", "port": TCP_PORT, "enableHttp": 0}) log(f"WATCHDOG: refreshed ({int(idle)}s)") else: - # API dead, find WeChat and inject DLL - log("WATCHDOG: re-injecting into running WeChat...") - 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}") + log("WATCHDOG: re-injecting...") + inject_wxhelper() except: pass last_msg_time = time.time() 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() +# 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): def do_POST(self): 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() log("HTTP :5801") -log("Creating Bot...") -b = Bot() -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'(.*?)', 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) +# Notify user +send_wx("filehelper", "[Agent v2] wxhelper online") log("Ready") -b.run() +print(f"[Agent v2] wxhelper :19088 | Hermes :8642") + +try: + while True: + time.sleep(1) +except KeyboardInterrupt: + log("Bye")