Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions frontends/wechatapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,42 @@ def _post(self, ep, body, timeout=15):
return r.json()

def login_qr(self, poll_interval=2):
r = requests.get(f'{API}/ilink/bot/get_bot_qrcode', params={'bot_type': 3}, headers={'User-Agent': UA}, timeout=10)
r.raise_for_status()
d = r.json()
# 获取二维码:对限流/缺字段(无 'qrcode')退避重试,避免崩溃→重启→更狠地打接口的死亡螺旋
d = {}
for attempt in range(6):
try:
r = requests.get(f'{API}/ilink/bot/get_bot_qrcode', params={'bot_type': 3}, headers={'User-Agent': UA}, timeout=10)
r.raise_for_status()
d = r.json()
except requests.exceptions.RequestException as e:
print(f'[QR登录] 获取二维码失败({e}),{2 ** attempt}s 后重试...'); time.sleep(2 ** attempt); continue
if d.get('qrcode') and d.get('qrcode_img_content'): # 二维码 ID + 可扫图都就绪才算成功
break
print(f'[QR登录] 二维码未就绪(可能被限流,ret={d.get("ret")}),{2 ** attempt}s 后重试...')
time.sleep(2 ** attempt)
if not (d.get('qrcode') and d.get('qrcode_img_content')):
raise RuntimeError('多次重试仍未获取到可扫二维码(疑似限流),请稍后重试')
qr_id, url = d['qrcode'], d.get('qrcode_img_content', '')
print(f'[QR登录] ID: {qr_id}')
if url:
img = self._tf.parent / 'wx_qr.png'
qrcode.make(url).save(str(img)) # 保存到文件,不弹浏览器
# 先打 ASCII 二维码(纯文本,无需 PIL;容器/无头环境靠它扫码)
qr = qrcode.QRCode(border=1); qr.add_data(url); qr.make(fit=True); qr.print_ascii(invert=True)
# 再尝试存 PNG 兜底——依赖 PIL,缺失/失败不应让登录崩溃
try:
qrcode.make(url).save(str(self._tf.parent / 'wx_qr.png'))
except Exception as e:
print(f'[QR登录] PNG 兜底保存失败({e}),用上方 ASCII 二维码扫码即可')
last = ''
while True:
time.sleep(poll_interval)
try: s = requests.get(f'{API}/ilink/bot/get_qrcode_status', params={'qrcode': qr_id}, headers={'User-Agent': UA}, timeout=60).json()
except requests.exceptions.ReadTimeout: continue
# 轮询状态:对所有网络异常 / 非 JSON(被限流时常返回 HTML)容错重试,
# 不让单次抖动把进程打崩——配合 restart:unless-stopped 否则会死亡螺旋
try:
s = requests.get(f'{API}/ilink/bot/get_qrcode_status', params={'qrcode': qr_id}, headers={'User-Agent': UA}, timeout=60).json()
except requests.exceptions.RequestException:
continue
except ValueError: # 响应非 JSON(限流/网关页)
time.sleep(poll_interval); continue
st = s.get('status', '')
if st != last: print(f' 状态: {st}'); last = st
if st == 'confirmed':
Expand Down Expand Up @@ -415,11 +437,14 @@ def _send(show):
print(f'[NEW] Process starting {time.strftime("%m-%d %H:%M")}')
bot = WxBotClient()
if _do_relogin or not bot.token:
if not sys.stdout.isatty():
print('[Bot] no token and not interactive, exit.'); sys.exit(1)
sys.stdout = sys.stderr = sys.__stdout__ # restore for QR display
bot.login_qr()
sys.stdout = sys.stderr = _logf
# QR 登录在无 TTY 的容器里也可用:把二维码打到真实 stdout(docker logs
# 可见),而不是日志文件——之前在重定向后才判 isatty(),文件句柄恒 false
# 导致容器内必然退出,无法首次登录。PNG 仍存 ~/.wxbot/wx_qr.png 作兜底。
sys.stdout = sys.stderr = sys.__stdout__ # restore for QR display (real stdout / container log)
try:
bot.login_qr()
finally:
sys.stdout = sys.stderr = _logf
threading.Thread(target=agent.run, daemon=True).start()
print(f'WeChat Bot 已启动 (bot_id={bot.bot_id})', file=sys.__stdout__)
try:
Expand Down