Merge pull request #595 from Michaelxwb/main
修复个人微信前端在 headless 容器内无法登录
This commit is contained in:
1 file changed
+37
-12
+37
-12
@@ -63,20 +63,42 @@ class WxBotClient:
|
||||
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':
|
||||
@@ -415,11 +437,14 @@ if __name__ == '__main__':
|
||||
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:
|
||||
|
||||
Reference in new issue
Block a user