feat: WeChat QR get button — trigger container /api/bot/login (DISABLE_AUTO_LOGIN) via POST /api/wechat/trigger-login; show QR block when logged_out even without qr_url

This commit is contained in:
hmo
2026-08-17 19:18:36 +08:00
parent 5a736ddaa8
commit 39a73db9b3
2 changed files with 88 additions and 17 deletions
+22
View File
@@ -1997,6 +1997,28 @@ def api_wechat_status():
return jsonify(result) return jsonify(result)
@app.route("/api/wechat/trigger-login", methods=["POST"])
def api_wechat_trigger_login():
"""触发微信 bot 登录(获取二维码)——容器 DISABLE_AUTO_LOGIN=true 时需主动 POST /api/bot/login。
返回 {ok, login_url} 或 {ok: false, error}。前端拿到 login_url 后轮询 /api/wechat/status 拿 qr_url。
"""
import urllib.request as _ur
try:
req = _ur.Request(
"http://127.0.0.1:3001/api/bot/login",
data=b"",
headers={"Content-Type": "application/json"},
method="POST",
)
with _ur.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode("utf-8", errors="replace"))
if data.get("success"):
return jsonify({"ok": True, "login_url": data.get("loginUrl", "/login")})
return jsonify({"ok": False, "error": data.get("error", "unknown")})
except Exception as e:
return jsonify({"ok": False, "error": str(e)})
@app.route("/api/platform") @app.route("/api/platform")
@app.route("/api/health") @app.route("/api/health")
def api_health(): def api_health():
+66 -17
View File
@@ -323,24 +323,35 @@ async function fI(){
if(s.status==='running'){dc='ok';dt=s.message?' <span style=color:var(--green)>'+esc(s.message)+'</span>':'';} if(s.status==='running'){dc='ok';dt=s.message?' <span style=color:var(--green)>'+esc(s.message)+'</span>':'';}
else if(s.status==='logged_out'){dc='ok';ds='background:var(--yellow)';dt=' <span style=color:var(--yellow)>'+esc(s.message||'\u9700\u626b\u7801')+'</span>';} else if(s.status==='logged_out'){dc='ok';ds='background:var(--yellow)';dt=' <span style=color:var(--yellow)>'+esc(s.message||'\u9700\u626b\u7801')+'</span>';}
h+='<div class="i"><span class="d '+dc+'"'+(ds?' style='+ds:'')+'></span><span class="sn">'+esc(s.name)+dt+'</span><span class="help-btn" onclick="showModuleHelp(\''+s.id+'\',\'human\')" title="\u4f7f\u7528\u8bf4\u660e">?</span><span class="help-btn ai" onclick="showModuleHelp(\''+s.id+'\',\'ai\')" title="AI Spec">\u00a7</span></div>'; h+='<div class="i"><span class="d '+dc+'"'+(ds?' style='+ds:'')+'></span><span class="sn">'+esc(s.name)+dt+'</span><span class="help-btn" onclick="showModuleHelp(\''+s.id+'\',\'human\')" title="\u4f7f\u7528\u8bf4\u660e">?</span><span class="help-btn ai" onclick="showModuleHelp(\''+s.id+'\',\'ai\')" title="AI Spec">\u00a7</span></div>';
if(s.id==='wechat_bridge'&&s.status==='logged_out'&&s.qr_url){ if(s.id==='wechat_bridge'&&s.status==='logged_out'){
h+='<div style="margin:0 0 10px 18px;padding:12px 16px;background:#1a1400;border:1px solid var(--yellow);border-radius:8px;display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap">'; h+='<div style="margin:0 0 10px 18px;padding:12px 16px;background:#1a1400;border:1px solid var(--yellow);border-radius:8px;display:flex;gap:14px;align-items:flex-start;flex-wrap:wrap">';
h+='<div style=text-align:center;flex-shrink:0>'; if(s.qr_url){
h+='<img id=wechat-qr-img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data='+encodeURIComponent(s.qr_url||'')+'" style="border:2px solid var(--yellow);border-radius:6px">'; h+='<div style=text-align:center;flex-shrink:0>';
h+='<div id=wechat-qr-status style="margin-top:6px;font-size:14px;color:var(--dim)">'; h+='<img id=wechat-qr-img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data='+encodeURIComponent(s.qr_url||'')+'" style="border:2px solid var(--yellow);border-radius:6px">';
h+='<span id=wechat-qr-age>\u4e8c\u7ef4\u7801\u7ea65\u5206\u949f\u6709\u6548</span></div>'; h+='<div id=wechat-qr-status style="margin-top:6px;font-size:14px;color:var(--dim)">';
h+='<button id=wechat-qr-refresh class="btn r" style="margin-top:4px;font-size:14px;padding:3px 10px" onclick="refreshWechatQR()">\u5237\u65b0\u4e8c\u7ef4\u7801</button></div>'; h+='<span id=wechat-qr-age>\u4e8c\u7ef4\u7801\u7ea65\u5206\u949f\u6709\u6548</span></div>';
h+='<div style=flex:1;min-width:160px;font-size:14px;color:var(--dim)>'; h+='<button id=wechat-qr-refresh class="btn r" style="margin-top:4px;font-size:14px;padding:3px 10px" onclick="refreshWechatQR()">\u5237\u65b0\u4e8c\u7ef4\u7801</button></div>';
h+='<p style="margin:0 0 4px;color:var(--yellow);font-weight:600">\u83ab\u8377\u5fae\u4fe1\u9700\u8981\u626b\u7801\u767b\u5f55</p>'; h+='<div style=flex:1;min-width:160px;font-size:14px;color:var(--dim)>';
h+='<p style=margin:0;line-height:1.5>'+esc(s.message||'')+'\u3002\u7528\u5fae\u4fe1\u626b\u63cf\u5de6\u4fa7\u4e8c\u7ef4\u7801\u91cd\u65b0\u767b\u5f55\u3002</p>'; h+='<p style="margin:0 0 4px;color:var(--yellow);font-weight:600">\u83ab\u8377\u5fae\u4fe1\u9700\u8981\u626b\u7801\u767b\u5f55</p>';
h+='<p style=margin-top:4px>\u94fe\u63a5: <code style=word-break:break-all;font-size:13px>'+esc(s.qr_url||'')+'</code></p>'; h+='<p style=margin:0;line-height:1.5>'+esc(s.message||'')+'\u3002\u7528\u5fae\u4fe1\u626b\u63cf\u5de6\u4fa7\u4e8c\u7ef4\u7801\u91cd\u65b0\u767b\u5f55\u3002</p>';
h+='</div></div>'; h+='<p style=margin-top:4px>\u94fe\u63a5: <code style=word-break:break-all;font-size:13px>'+esc(s.qr_url||'')+'</code></p>';
setTimeout(function(){ h+='</div>';
var el=document.getElementById('wechat-qr-age'); setTimeout(function(){
if(el)el.innerHTML='<span style=color:var(--red)>\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f</span>'; var el=document.getElementById('wechat-qr-age');
var btn=document.getElementById('wechat-qr-refresh'); if(el)el.innerHTML='<span style=color:var(--red)>\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f</span>';
if(btn)btn.textContent='\u5df2\u8fc7\u671f\uff0c\u70b9\u51fb\u5237\u65b0'; var btn=document.getElementById('wechat-qr-refresh');
},300000); if(btn)btn.textContent='\u5df2\u8fc7\u671f\uff0c\u70b9\u51fb\u5237\u65b0';
},300000);
}else{
h+='<div style=text-align:center;flex-shrink:0>';
h+='<button id=wechat-qr-get class="btn s" style="font-size:14px;padding:8px 18px" onclick="getWechatQR()">\u83b7\u53d6\u767b\u5f55\u4e8c\u7ef4\u7801</button>';
h+='<div id=wechat-qr-get-status style="margin-top:6px;font-size:13px;color:var(--dim)">\u70b9\u51fb\u83b7\u53d6\u65b0\u7684\u767b\u5f55\u4e8c\u7ef4\u7801</div></div>';
h+='<div style=flex:1;min-width:160px;font-size:14px;color:var(--dim)>';
h+='<p style="margin:0 0 4px;color:var(--yellow);font-weight:600">\u83ab\u8377\u5fae\u4fe1\u5df2\u6389\u7ebf</p>';
h+='<p style=margin:0;line-height:1.5>'+esc(s.message||'')+'\u3002\u70b9\u51fb\u5de6\u4fa7\u6309\u94ae\u89e6\u53d1\u767b\u5f55\uff0c\u751f\u6210\u4e8c\u7ef4\u7801\u540e\u7528\u5fae\u4fe1\u626b\u7801\u3002</p>';
h+='</div>';
}
h+='</div>';
} }
} }
try{var r2=await fetch('/api/ejabberd'),ej=await r2.json();var ejA=ej.alive||ej.xmpp_bot_connected; try{var r2=await fetch('/api/ejabberd'),ej=await r2.json();var ejA=ej.alive||ej.xmpp_bot_connected;
@@ -632,4 +643,42 @@ h+='<p style="line-height:1.7;margin:6px 0">'+inMd(block)+'</p>';}
return h; return h;
function inMd(t){return t.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\*([^*]+)\*/g,'<em>$1</em>').replace(/`([^`]+)`/g,'<code style="background:var(--bg);padding:1px 4px;border-radius:3px;font-size:0.9em">$1</code>').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" style="color:var(--accent)" target=_blank>$1</a>')}} function inMd(t){return t.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\*([^*]+)\*/g,'<em>$1</em>').replace(/`([^`]+)`/g,'<code style="background:var(--bg);padding:1px 4px;border-radius:3px;font-size:0.9em">$1</code>').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" style="color:var(--accent)" target=_blank>$1</a>')}}
init();loadAll();setInterval(loadAll,10000);setInterval(fK,15000); init();loadAll();setInterval(loadAll,10000);setInterval(fK,15000);
/* --- WeChat QR: 获取二维码(触发容器 /api/bot/login + 刷新 --- */
async function getWechatQR(){
var st=document.getElementById('wechat-qr-get-status');
var btn=document.getElementById('wechat-qr-get');
if(st)st.innerHTML='<span style=color:var(--yellow)>\u6b63\u5728\u83b7\u53d6\u4e8c\u7ef4\u7801...</span>';
if(btn)btn.disabled=true;
try{
var r=await fetch('/api/wechat/trigger-login',{method:'POST'});
var d=await r.json();
if(d.ok){
if(st)st.innerHTML='<span style=color:var(--green)>\u5df2\u89e6\u53d1\uff0c\u6b63\u5728\u751f\u6210\u4e8c\u7ef4\u7801...</span>';
// 触发后轮询 status 拿 qr_url(容器生成二维码需要几秒)
for(var i=0;i<10;i++){
await new Promise(res=>setTimeout(res,2000));
try{
var rs=await fetch('/api/wechat/status'),sd=await rs.json();
if(sd.qr_url){location.reload();return;}
}catch(e){}
}
if(st)st.innerHTML='<span style=color:var(--red)>\u4e8c\u7ef4\u7801\u672a\u751f\u6210\uff0c\u8bf7\u91cd\u8bd5</span>';
if(btn)btn.disabled=false;
}else{
if(st)st.innerHTML='<span style=color:var(--red)>\u83b7\u53d6\u5931\u8d25: '+esc(d.error||'')+'</span>';
if(btn)btn.disabled=false;
}
}catch(e){
if(st)st.innerHTML='<span style=color:var(--red)>\u83b7\u53d6\u5931\u8d25: '+esc(e.message||'')+'</span>';
if(btn)btn.disabled=false;
}
}
async function refreshWechatQR(){
try{
await fetch('/api/wechat/trigger-login',{method:'POST'});
// 刷新后等几秒重新拉状态
setTimeout(function(){location.reload();},3000);
}catch(e){}
}
</script></body></html> </script></body></html>