全面升级:WeChat 3.9.5.81 + 全尺寸图片 OCR + 启动脚本
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
@echo off
|
||||
title WeChat Agent
|
||||
|
||||
set PROJECT_DIR=D:\F\NewI\opencode\daily-workspace\projects\wechat-hermes-gateway
|
||||
set TOOLS_DIR=%PROJECT_DIR%\tools
|
||||
set PYTHONW=C:\Users\hmo\AppData\Local\Programs\Python\Python310\pythonw.exe
|
||||
set INJECTOR=%TOOLS_DIR%\Injector_x64.exe
|
||||
set DLL=%TOOLS_DIR%\wxhelper_official_39581.dll
|
||||
set LOG=%PROJECT_DIR%\logs\startup.log
|
||||
|
||||
echo [1/4] Waiting for WeChat...
|
||||
:wait_wechat
|
||||
tasklist /fi "imagename eq WeChat.exe" 2>nul | find /i "WeChat.exe" >nul
|
||||
if errorlevel 1 (
|
||||
timeout /t 2 /nobreak >nul
|
||||
goto wait_wechat
|
||||
)
|
||||
echo [2/4] WeChat started, checking wxhelper...
|
||||
|
||||
curl -s -m 3 -X POST http://127.0.0.1:19088/api/checkLogin -H "Content-Type: application/json" -d "{}" 2>nul | find "code" >nul
|
||||
if not errorlevel 1 (
|
||||
echo [3/4] wxhelper OK, skipping inject
|
||||
goto start_agent
|
||||
)
|
||||
|
||||
echo [3/4] Injecting wxhelper...
|
||||
%INJECTOR% -n WeChat.exe -i "%DLL%" >> "%LOG%" 2>&1
|
||||
|
||||
echo [3/4] Waiting for wxhelper HTTP...
|
||||
:wait_wxhelper
|
||||
timeout /t 2 /nobreak >nul
|
||||
curl -s -m 3 -X POST http://127.0.0.1:19088/api/checkLogin -H "Content-Type: application/json" -d "{}" 2>nul | find "code" >nul
|
||||
if errorlevel 1 goto wait_wxhelper
|
||||
|
||||
:start_agent
|
||||
echo [4/4] Clearing cache and starting agent...
|
||||
if exist "%PROJECT_DIR%\scripts\__pycache__" rmdir /s /q "%PROJECT_DIR%\scripts\__pycache__"
|
||||
start "" "%PYTHONW%" "%PROJECT_DIR%\scripts\wechat_agent.py"
|
||||
echo Done.
|
||||
+206
-27
@@ -1,14 +1,14 @@
|
||||
"""
|
||||
"""
|
||||
WeChat Agent v2 - wxhelper DLL + Hermes API (:8642)
|
||||
"""
|
||||
import os, json, time, threading, requests, re, socketserver, subprocess, urllib.request, urllib.error
|
||||
import os, json, time, threading, requests, re, socketserver, subprocess, urllib.request, urllib.error, base64
|
||||
os.environ["no_proxy"] = "*"
|
||||
os.environ["NO_PROXY"] = "*"
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
BOT_WXID = "wxid_7onnerpx2s2l22"
|
||||
BLOCK_WXIDS = {"fmessage", "weixin", "wechat"} # 系统账号/微信团队,不回复
|
||||
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__)))
|
||||
LOG_DIR = os.path.join(PROJECT_ROOT, "logs")
|
||||
@@ -26,8 +26,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\ConsoleApplication.exe"
|
||||
WXHELPER_DLL = r"D:\F\NewI\opencode\daily-workspace\projects\wechat-hermes-gateway\tools\wxhelper_391019.dll"
|
||||
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"
|
||||
|
||||
def log(m):
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
||||
@@ -44,7 +44,7 @@ def wxpost(path, data=None, timeout=10):
|
||||
log(f"WX ERR: {e}")
|
||||
return {"code": -1}
|
||||
|
||||
# ── History Query (via MSG table in MSG*.db databases) ──
|
||||
# ���� History Query (via MSG table in MSG*.db databases) ����
|
||||
def get_db_handle():
|
||||
"""Get handle for database containing MSG table. Cached after first call."""
|
||||
global db_handle_cache
|
||||
@@ -78,7 +78,7 @@ def get_db_handle():
|
||||
return None
|
||||
|
||||
# Message type labels
|
||||
MSG_TYPES = {1: "文字", 3: "图片", 34: "语音", 43: "视频", 47: "表情", 49: "链接", 10000: "系统", 10002: "红包"}
|
||||
MSG_TYPES = {1: "����", 3: "ͼƬ", 34: "����", 43: "��Ƶ", 47: "����", 49: "����", 10000: "ϵͳ", 10002: "���"}
|
||||
|
||||
def query_history(wxid, limit=10):
|
||||
"""Query historical text messages with a contact from MSG table."""
|
||||
@@ -110,7 +110,7 @@ def format_history(wxid, rows):
|
||||
"""Format MSG rows into readable chat history text."""
|
||||
sender_name = get_nickname(wxid)
|
||||
bot_name = get_nickname(BOT_WXID)
|
||||
lines = [f"📜 最近与 {sender_name} 的聊天记录 ({len(rows)}条):"]
|
||||
lines = [f"?? ����� {sender_name} �������¼ ({len(rows)}��):"]
|
||||
for row in rows:
|
||||
ts = int(row.get("CreateTime", 0))
|
||||
time_str = time.strftime("%m/%d %H:%M", time.localtime(ts)) if ts else "?"
|
||||
@@ -121,7 +121,7 @@ def format_history(wxid, rows):
|
||||
who = bot_name if is_sender else sender_name
|
||||
# Format content
|
||||
if msg_type == 49:
|
||||
content = f"[链接] {content[:60]}"
|
||||
content = f"[����] {content[:60]}"
|
||||
else:
|
||||
content = content[:200]
|
||||
lines.append(f"[{time_str}] {who}: {content}")
|
||||
@@ -133,10 +133,10 @@ def handle_history(wxid, count):
|
||||
rows = query_history(wxid, count)
|
||||
if rows:
|
||||
return format_history(wxid, rows)
|
||||
return f"暂无与 {get_nickname(wxid)} 的聊天记录"
|
||||
return f"������ {get_nickname(wxid)} �������¼"
|
||||
except Exception as e:
|
||||
log(f"History ERR: {e}")
|
||||
return "查询历史记录失败"
|
||||
return "��ѯ��ʷ��¼ʧ��"
|
||||
|
||||
def handle_history_json(wxid, count):
|
||||
"""Query history and return JSON-serializable dict for HTTP API."""
|
||||
@@ -176,7 +176,7 @@ def handle_history_json(wxid, count):
|
||||
def send_wx(wxid, msg):
|
||||
# Strip weixin:// URLs that WeChat interprets as commands
|
||||
import re as _re2
|
||||
msg = _re2.sub(r'weixin://[^\s]+', '[链接已过滤]', msg)
|
||||
msg = _re2.sub(r'weixin://[^\s]+', '[�����ѹ���]', msg)
|
||||
r = wxpost("/api/sendTextMsg", {"wxid": wxid, "msg": msg})
|
||||
log(f"SEND {wxid}: {r.get('msg','')}")
|
||||
|
||||
@@ -195,7 +195,7 @@ def get_nickname(wxid):
|
||||
def call_hermes(wxid, content):
|
||||
nickname = get_nickname(wxid)
|
||||
headers = {"Authorization": f"Bearer {HERMES_KEY}", "X-Hermes-Session-Id": "sisyphus", "Content-Type": "application/json"}
|
||||
sys_prompt = "回复简短。"
|
||||
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})
|
||||
@@ -208,7 +208,7 @@ def call_hermes(wxid, content):
|
||||
def inject_to_hermes_session(text):
|
||||
"""Inject chat history / context directly into Hermes's sisyphus session for memory repair."""
|
||||
headers = {"Authorization": f"Bearer {HERMES_KEY}", "X-Hermes-Session-Id": "sisyphus", "Content-Type": "application/json"}
|
||||
sys_prompt = "📥 MEMORY INJECTION: Below is past chat history. Absorb this into your context for memory repair. Do NOT reply to this — just acknowledge with 'Memory synced.'"
|
||||
sys_prompt = "?? MEMORY INJECTION: Below is past chat history. Absorb this into your context for memory repair. Do NOT reply to this �� just acknowledge with 'Memory synced.'"
|
||||
body = {"model": "hermes-agent", "messages": [
|
||||
{"role": "system", "content": sys_prompt},
|
||||
{"role": "user", "content": text}
|
||||
@@ -224,7 +224,7 @@ def inject_to_hermes_session(text):
|
||||
log(f"Inject history ERR: {e}")
|
||||
return False
|
||||
|
||||
# ── Inject wxhelper DLL ──
|
||||
# ���� Inject wxhelper DLL ����
|
||||
def inject_wxhelper():
|
||||
try:
|
||||
r = wxpost("/api/checkLogin", timeout=5)
|
||||
@@ -233,9 +233,37 @@ def inject_wxhelper():
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
# Also check if port 19088 is just listening (wxhelper HTTP server alive)
|
||||
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]}")
|
||||
import socket as _sock
|
||||
s = _sock.create_connection(("127.0.0.1", 19088), timeout=2)
|
||||
s.close()
|
||||
r = wxpost("/api/checkLogin", timeout=5)
|
||||
if r.get("code") == 1:
|
||||
log("wxhelper HTTP server alive, login OK")
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
# Wait a moment in case server is still starting
|
||||
time.sleep(3)
|
||||
try:
|
||||
r = wxpost("/api/checkLogin", timeout=5)
|
||||
if r.get("code") == 1:
|
||||
log("wxhelper responding after wait")
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
# Injector_x64.exe: -n process_name -i dll_path
|
||||
result = subprocess.run([INJECTOR, "-n", "WeChat.exe", "-i", WXHELPER_DLL], capture_output=True, text=True, timeout=30)
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
log(f"Inject: {output[:100]}")
|
||||
# Check if injection succeeded by looking for "success" in output
|
||||
if "success" not in output.lower():
|
||||
log(f"Inject MAY HAVE FAILED (no 'success' in output), retrying...")
|
||||
time.sleep(2)
|
||||
result2 = subprocess.run([INJECTOR, "-n", "WeChat.exe", "-i", WXHELPER_DLL], capture_output=True, text=True, timeout=30)
|
||||
log(f"Inject retry: {(result2.stdout+result2.stderr).strip()[:100]}")
|
||||
time.sleep(3)
|
||||
r = wxpost("/api/checkLogin", timeout=5)
|
||||
if r.get("code") == 1:
|
||||
@@ -247,7 +275,7 @@ def inject_wxhelper():
|
||||
log(f"Inject FAIL: {e}")
|
||||
return False
|
||||
|
||||
# ── TCP Message Receiver ──
|
||||
# ���� TCP Message Receiver ����
|
||||
class MsgHandler(socketserver.BaseRequestHandler):
|
||||
def handle(self):
|
||||
try:
|
||||
@@ -265,6 +293,128 @@ class MsgHandler(socketserver.BaseRequestHandler):
|
||||
finally:
|
||||
self.request.close()
|
||||
|
||||
# ���� Image OCR ����
|
||||
WX_FILES_BASE = os.path.join(os.path.expanduser("~"), "Documents", "WeChat Files")
|
||||
BOT_WX_DIR = os.path.join(WX_FILES_BASE, BOT_WXID, "wxhelper")
|
||||
|
||||
def ocr_image(base64_data):
|
||||
"""OCR from in-memory base64 image data. Returns text or None."""
|
||||
try:
|
||||
headers = {"Authorization": "Bearer b0359bed-09f2-49e2-a53c-32ba057412e3", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"model": "doubao-seed-code",
|
||||
"messages": [{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "请识别这张图片中的所有中文和英文字符,保持原文输出,包括数字、表格、百分比的完整结构。严格逐行逐列输出所有数据,不要省略、不要总结。"},
|
||||
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_data}"}}
|
||||
]
|
||||
}]
|
||||
}
|
||||
r = requests.post(
|
||||
"https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions",
|
||||
json=payload, headers=headers, timeout=60,
|
||||
proxies={"http": None, "https": None}
|
||||
)
|
||||
if r.status_code == 200:
|
||||
text = r.json()["choices"][0]["message"]["content"].strip()
|
||||
log(f"OCR OK ({len(text)} chars)")
|
||||
return text
|
||||
log(f"OCR HTTP {r.status_code}: {r.text[:200]}")
|
||||
except Exception as e:
|
||||
log(f"OCR ERR: {e}")
|
||||
return None
|
||||
|
||||
def ocr_image_file(image_path):
|
||||
"""OCR an image file on disk. Returns text or None."""
|
||||
try:
|
||||
with open(image_path, "rb") as f:
|
||||
b64 = base64.b64encode(f.read()).decode()
|
||||
return ocr_image(b64)
|
||||
except Exception as e:
|
||||
log(f"ocr_image_file ERR: {e}")
|
||||
return None
|
||||
|
||||
# ���� Full Image Download & Decode (wxhelper 3.9.5.81+) ����
|
||||
def download_full_image(msg_id):
|
||||
"""Download full image from CDN via downloadAttach. Returns encrypted .dat path or None.
|
||||
|
||||
Retries both the API call (wxhelper may return -2 transiently)
|
||||
and file existence (async CDN download takes time).
|
||||
"""
|
||||
try:
|
||||
dat_path = os.path.join(BOT_WX_DIR, "image", f"{msg_id}.dat")
|
||||
|
||||
# Phase 1: Retry API call (wxhelper may return -2 if msg not ready)
|
||||
for api_attempt in range(10):
|
||||
r = wxpost("/api/downloadAttach", {"msgId": int(msg_id)}, timeout=30)
|
||||
code = r.get("code", -1)
|
||||
if code >= 0:
|
||||
break
|
||||
log(f"downloadAttach attempt {api_attempt+1}: code={code} {r.get('msg','')}")
|
||||
time.sleep(1)
|
||||
else:
|
||||
log(f"downloadAttach FAILED after 10 attempts, last code={code}")
|
||||
return None
|
||||
|
||||
# Phase 2: Wait for async CDN download
|
||||
log(f"downloadAttach queued, waiting for file...")
|
||||
for wait_attempt in range(20):
|
||||
if os.path.exists(dat_path):
|
||||
log(f"Download OK: {dat_path} ({os.path.getsize(dat_path)} bytes)")
|
||||
return dat_path
|
||||
time.sleep(1)
|
||||
log(f"downloadAttach: .dat not found after 20s for msgId={msg_id}")
|
||||
except Exception as e:
|
||||
log(f"downloadAttach ERR: {e}")
|
||||
return None
|
||||
|
||||
def decode_image_file(dat_path):
|
||||
"""Decrypt encrypted .dat to viewable image. Returns decoded path or None.
|
||||
|
||||
Some .dat files are already valid PNG/JPEG images (not encrypted).
|
||||
Falls back to checking if .dat itself is a valid image.
|
||||
"""
|
||||
try:
|
||||
before_files = set(os.listdir(TEMP_DIR))
|
||||
r = wxpost("/api/decodeImage", {"filePath": dat_path, "storeDir": TEMP_DIR}, timeout=30)
|
||||
if r.get("code", -1) > 0:
|
||||
base = os.path.splitext(os.path.basename(dat_path))[0]
|
||||
for ext in ['.jpg', '.jpeg', '.png', '.bmp']:
|
||||
cand = os.path.join(TEMP_DIR, base + ext)
|
||||
if os.path.exists(cand):
|
||||
log(f"Decoded: {cand}")
|
||||
return cand
|
||||
for f in os.listdir(TEMP_DIR):
|
||||
if f in before_files: continue
|
||||
if f.lower().endswith(('.jpg', '.jpeg', '.png')):
|
||||
cand = os.path.join(TEMP_DIR, f)
|
||||
log(f"Decoded (new): {cand}")
|
||||
return cand
|
||||
log("decodeImage OK but no new image file found")
|
||||
# Fallback: .dat file may already be a valid image (not encrypted)
|
||||
with open(dat_path, "rb") as f:
|
||||
header = f.read(4)
|
||||
ext = None
|
||||
if header[:2] == b'\xff\xd8': # JPEG
|
||||
ext = '.jpg'
|
||||
elif header[:4] == b'\x89PNG': # PNG
|
||||
ext = '.png'
|
||||
elif header[:4] == b'GIF8': # GIF
|
||||
ext = '.gif'
|
||||
elif header[:2] == b'BM': # BMP
|
||||
ext = '.bmp'
|
||||
if ext:
|
||||
out_path = os.path.join(TEMP_DIR, os.path.splitext(os.path.basename(dat_path))[0] + ext)
|
||||
import shutil
|
||||
shutil.copy(dat_path, out_path)
|
||||
log(f".dat is already {ext}, copied to {out_path}")
|
||||
return out_path
|
||||
log(f"decodeImage FAIL: code={r.get('code')} {r.get('msg','')}")
|
||||
except Exception as e:
|
||||
log(f"decodeImage ERR: {e}")
|
||||
return None
|
||||
|
||||
def process_msg(raw_data):
|
||||
global last_msg_time
|
||||
last_msg_time = time.time()
|
||||
@@ -278,24 +428,52 @@ def process_msg(raw_data):
|
||||
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
|
||||
# Route by message type
|
||||
# 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)
|
||||
if reply and reply.strip():
|
||||
send_wx(fu, reply.strip())
|
||||
return
|
||||
if msg_type == 3: # Image - wxhelper sends image as separate event
|
||||
if msg_type == 3: # Image
|
||||
msg_id = d.get("msgId", 0) or d.get("svrid", 0)
|
||||
log(f"IMAGE: msgId={msg_id} b64_len={len(d.get('base64Img',''))}")
|
||||
ocr_text = None
|
||||
# Full-image OCR via wxhelper 3.9.5.81 APIs
|
||||
if msg_id:
|
||||
dat_path = download_full_image(msg_id)
|
||||
if dat_path:
|
||||
decoded = decode_image_file(dat_path)
|
||||
if decoded:
|
||||
log(f"Full image OCR on {decoded}")
|
||||
ocr_text = ocr_image_file(decoded)
|
||||
if ocr_text:
|
||||
log(f"OCR result ({len(ocr_text)} chars): {ocr_text[:200]}")
|
||||
reply = call_hermes(fu, f"[老莫发送了一张图片,OCR识别结果如下]\n{ocr_text}")
|
||||
elif msg_id:
|
||||
# Had msgId but full-image OCR failed - report error, don't use thumbnail
|
||||
log("Full-image OCR failed, skipping thumbnail (useless at 84x210)")
|
||||
reply = call_hermes(fu, "[老莫发送了一张图片,但全尺寸图片下载或OCR识别失败,无法读取内容]")
|
||||
else:
|
||||
# No msgId at all - rare, just report failure
|
||||
log("No msgId available, cannot download full image")
|
||||
reply = call_hermes(fu, "[老莫发送了一张图片,但无法获取图片ID,无法识别]")
|
||||
if reply and reply.strip():
|
||||
log(f"-> {fu}: {reply[:50]}")
|
||||
process_tags(reply, fu)
|
||||
else:
|
||||
log(f"-> {fu}: skip (blank image response)")
|
||||
return
|
||||
# Text - prepend sender wxid+name so Hermes knows who's talking
|
||||
sender_name = get_nickname(fu)
|
||||
msg_with_sender = f"[{fu}|{sender_name}] {ct}"
|
||||
log(f"<- {fu} ({sender_name}): {ct[:50]}")
|
||||
reply = call_hermes(fu, msg_with_sender)
|
||||
if reply:
|
||||
if reply and reply.strip():
|
||||
log(f"-> {fu}: {reply[:50]}")
|
||||
process_tags(reply, fu)
|
||||
else:
|
||||
log(f"-> {fu}: no reply")
|
||||
log(f"-> {fu}: no reply (blank/empty)")
|
||||
except Exception as e:
|
||||
log(f"MSG ERR: {e}")
|
||||
import traceback
|
||||
@@ -325,7 +503,7 @@ def process_tags(reply, fu):
|
||||
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','')}")
|
||||
send_wx(fu, f"�dz�: {cd.get('nickname','?')} ��ע: {cd.get('remark','')}")
|
||||
# [ROOM_MEMBERS:roomid]
|
||||
rm = re.search(r'\[ROOM_MEMBERS:(\S+)\]', clean)
|
||||
if rm:
|
||||
@@ -333,7 +511,7 @@ def process_tags(reply, fu):
|
||||
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])}")
|
||||
send_wx(fu, f"Ⱥ��Ա ({len(mlist)}): {','.join(mlist[:20])}")
|
||||
# [HISTORY:wxid:count] - query chat history from MSG table
|
||||
hm = re.search(r'\[HISTORY:(\S+?):(\d+)\]', clean)
|
||||
if hm:
|
||||
@@ -400,7 +578,7 @@ def download_emoji(m, fu):
|
||||
wxpost("/api/sendCustomEmotion", {"wxid": fu, "filePath": tmp})
|
||||
os.remove(tmp)
|
||||
|
||||
# ── Watchdog ──
|
||||
# ���� Watchdog ����
|
||||
def watchdog():
|
||||
global last_msg_time
|
||||
while True:
|
||||
@@ -419,7 +597,7 @@ def watchdog():
|
||||
last_msg_time = time.time()
|
||||
time.sleep(30)
|
||||
|
||||
# ── Start ──
|
||||
# ���� Start ����
|
||||
print("[Agent] starting...", flush=True)
|
||||
log("=== Agent v2 (wxhelper) ===")
|
||||
|
||||
@@ -519,3 +697,4 @@ try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
log("Bye")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user