feat: unified media download in wechatapp + proxy support for NativeOAISession

This commit is contained in:
Liang Jiaqing
2026-03-23 20:57:39 +08:00
parent 60f40e3c5e
commit 515b0cd985
2 changed files with 40 additions and 11 deletions

View File

@@ -160,6 +160,33 @@ class WxBotClient:
except KeyboardInterrupt: print('[Bot] 退出'); break except KeyboardInterrupt: print('[Bot] 退出'); break
except Exception as e: print(f'[Bot] 异常: {e}5s重试'); time.sleep(5) except Exception as e: print(f'[Bot] 异常: {e}5s重试'); time.sleep(5)
# ── Unified media download (IMAGE/VIDEO/FILE/VOICE) ──
_MEDIA_KEYS = {'image_item': '.jpg', 'video_item': '.mp4', 'file_item': '', 'voice_item': '.silk'}
def _dl_media(items):
"""Download & decrypt all media items → list of local file paths."""
paths = []
for item in items:
for key, ext in _MEDIA_KEYS.items():
sub = item.get(key)
if not sub: continue
eq = (sub.get('media') or {}).get('encrypt_query_param')
if not eq: continue
ak = (sub.get('media') or {}).get('aes_key', '') or sub.get('aeskey', '')
if not ak: continue
try:
aes_key = (bytes.fromhex(base64.b64decode(ak).decode())
if sub.get('media', {}).get('aes_key') else bytes.fromhex(ak))
ct = requests.get(f'{CDN_BASE}/download?encrypted_query_param={quote(eq)}', timeout=60).content
pt = AES.new(aes_key, AES.MODE_ECB).decrypt(ct); pt = pt[:-pt[-1]]
fname = sub.get('file_name') or f'{uuid.uuid4().hex[:8]}{ext or ".bin"}'
p = os.path.join(_TEMP_DIR, fname); open(p, 'wb').write(pt)
paths.append(p); print(f'[WX] media saved: {fname}', file=sys.__stdout__)
except Exception as e:
print(f'[WX] media dl err ({key}): {e}', file=sys.__stdout__)
break # one media per item
return paths
agent = GeneraticAgent() agent = GeneraticAgent()
agent.verbose = False agent.verbose = False
@@ -200,8 +227,11 @@ def on_message(bot, msg):
text = bot.extract_text(msg).strip() text = bot.extract_text(msg).strip()
uid = msg.get('from_user_id', '') uid = msg.get('from_user_id', '')
ctx = msg.get('context_token', '') ctx = msg.get('context_token', '')
if not text: return media_paths = _dl_media(msg.get('item_list', []))
print(f'[WX] 收到: {text[:60]}', file=sys.__stdout__) if not text and not media_paths: return
if media_paths:
text = (text + '\n' if text else '') + '\n'.join(f'[用户发送文件: {p}]' for p in media_paths)
print(f'[WX] 收到: {text[:80]}', file=sys.__stdout__)
# Commands # Commands
if text in ('/stop', '/abort'): if text in ('/stop', '/abort'):
@@ -232,16 +262,15 @@ def on_message(bot, msg):
while True: while True:
item = dq.get(timeout=300) item = dq.get(timeout=300)
if 'done' in item: result = item['done']; break if 'done' in item: result = item['done']; break
except queue.Empty: except queue.Empty: result = '[超时]'
result = '[超时]'
# Extract files BEFORE _clean (which strips underscores via markdown removal)
files = re.findall(r'\[FILE:([^\]]+)\]', result) files = re.findall(r'\[FILE:([^\]]+)\]', result)
files = [f for f in files if (f if os.path.isabs(f) else os.path.join(_TEMP_DIR, f)) not in media_paths]
show = _clean(result) show = _clean(result)
for chunk in _split(show): for chunk in _split(show):
try: bot.send_text(uid, chunk, context_token=ctx) try: bot.send_text(uid, chunk, context_token=ctx)
except Exception as e: print(f'[WX] send err: {e}', file=sys.__stdout__) except Exception as e: print(f'[WX] send err: {e}', file=sys.__stdout__)
time.sleep(0.3) time.sleep(0.3)
for fpath in files: for fpath in set(files):
if not os.path.isabs(fpath): fpath = os.path.join(_TEMP_DIR, fpath) if not os.path.isabs(fpath): fpath = os.path.join(_TEMP_DIR, fpath)
try: try:
if not os.path.exists(fpath): raise FileNotFoundError(f"文件不存在: {fpath}") if not os.path.exists(fpath): raise FileNotFoundError(f"文件不存在: {fpath}")
@@ -252,10 +281,8 @@ def on_message(bot, msg):
threading.Thread(target=_handle, daemon=True).start() threading.Thread(target=_handle, daemon=True).start()
if __name__ == '__main__': if __name__ == '__main__':
try: try: _lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); _lock.bind(('127.0.0.1', 19528))
_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); _lock.bind(('127.0.0.1', 19528)) except OSError: print('[WeChat] Another instance running, exiting.'); sys.exit(1)
except OSError:
print('[WeChat] Another instance running, exiting.'); sys.exit(1)
_logf = open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'temp', 'wechatapp.log'), 'a', encoding='utf-8', buffering=1) _logf = open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'temp', 'wechatapp.log'), 'a', encoding='utf-8', buffering=1)
sys.stdout = sys.stderr = _logf sys.stdout = sys.stderr = _logf
print(f'[NEW] Process starting {time.strftime("%m-%d %H:%M")}') print(f'[NEW] Process starting {time.strftime("%m-%d %H:%M")}')

View File

@@ -403,6 +403,8 @@ class NativeOAISession:
self.api_key = cfg['apikey']; self.api_base = cfg['apibase'].rstrip('/') self.api_key = cfg['apikey']; self.api_base = cfg['apibase'].rstrip('/')
self.default_model = cfg.get('model', 'gpt-4o') self.default_model = cfg.get('model', 'gpt-4o')
self.context_win = cfg.get('context_win', 24000) self.context_win = cfg.get('context_win', 24000)
proxy = cfg.get('proxy')
self.proxies = {"http": proxy, "https": proxy} if proxy else None
self.history = []; self.system = None; self.lock = threading.Lock() self.history = []; self.system = None; self.lock = threading.Lock()
def set_system(self, system_text): self.system = system_text def set_system(self, system_text): self.system = system_text
@@ -414,7 +416,7 @@ class NativeOAISession:
payload = {"model": model, "messages": msgs, "temperature": temperature, "max_tokens": max_tokens, "stream": True} payload = {"model": model, "messages": msgs, "temperature": temperature, "max_tokens": max_tokens, "stream": True}
if tools: payload["tools"] = tools if tools: payload["tools"] = tools
try: try:
resp = requests.post(auto_make_url(self.api_base, "chat/completions"), headers=headers, json=payload, stream=True, timeout=120) resp = requests.post(auto_make_url(self.api_base, "chat/completions"), headers=headers, json=payload, stream=True, timeout=120, proxies=self.proxies)
if resp.status_code != 200: if resp.status_code != 200:
err = f"Error: HTTP {resp.status_code} {resp.text[:500]}"; yield err; return [{"type": "text", "text": err}] err = f"Error: HTTP {resp.status_code} {resp.text[:500]}"; yield err; return [{"type": "text", "text": err}]
except Exception as e: except Exception as e: