diff --git a/gateway/scripts/dashboard.py b/gateway/scripts/dashboard.py index 89639a4..b3c57d0 100644 --- a/gateway/scripts/dashboard.py +++ b/gateway/scripts/dashboard.py @@ -1997,6 +1997,28 @@ def api_wechat_status(): 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/health") def api_health(): diff --git a/gateway/scripts/templates/dashboard.html b/gateway/scripts/templates/dashboard.html index eb1de4f..2f2020f 100644 --- a/gateway/scripts/templates/dashboard.html +++ b/gateway/scripts/templates/dashboard.html @@ -323,24 +323,35 @@ async function fI(){ if(s.status==='running'){dc='ok';dt=s.message?' '+esc(s.message)+'':'';} else if(s.status==='logged_out'){dc='ok';ds='background:var(--yellow)';dt=' '+esc(s.message||'\u9700\u626b\u7801')+'';} h+='
'+esc(s.name)+dt+'?\u00a7
'; - if(s.id==='wechat_bridge'&&s.status==='logged_out'&&s.qr_url){ + if(s.id==='wechat_bridge'&&s.status==='logged_out'){ h+='
'; - h+='
'; - h+=''; - h+='
'; - h+='\u4e8c\u7ef4\u7801\u7ea65\u5206\u949f\u6709\u6548
'; - h+='
'; - h+='
'; - h+='

\u83ab\u8377\u5fae\u4fe1\u9700\u8981\u626b\u7801\u767b\u5f55

'; - h+='

'+esc(s.message||'')+'\u3002\u7528\u5fae\u4fe1\u626b\u63cf\u5de6\u4fa7\u4e8c\u7ef4\u7801\u91cd\u65b0\u767b\u5f55\u3002

'; - h+='

\u94fe\u63a5: '+esc(s.qr_url||'')+'

'; - h+='
'; - setTimeout(function(){ - var el=document.getElementById('wechat-qr-age'); - if(el)el.innerHTML='\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f'; - var btn=document.getElementById('wechat-qr-refresh'); - if(btn)btn.textContent='\u5df2\u8fc7\u671f\uff0c\u70b9\u51fb\u5237\u65b0'; - },300000); + if(s.qr_url){ + h+='
'; + h+=''; + h+='
'; + h+='\u4e8c\u7ef4\u7801\u7ea65\u5206\u949f\u6709\u6548
'; + h+='
'; + h+='
'; + h+='

\u83ab\u8377\u5fae\u4fe1\u9700\u8981\u626b\u7801\u767b\u5f55

'; + h+='

'+esc(s.message||'')+'\u3002\u7528\u5fae\u4fe1\u626b\u63cf\u5de6\u4fa7\u4e8c\u7ef4\u7801\u91cd\u65b0\u767b\u5f55\u3002

'; + h+='

\u94fe\u63a5: '+esc(s.qr_url||'')+'

'; + h+='
'; + setTimeout(function(){ + var el=document.getElementById('wechat-qr-age'); + if(el)el.innerHTML='\u4e8c\u7ef4\u7801\u5df2\u8fc7\u671f'; + var btn=document.getElementById('wechat-qr-refresh'); + if(btn)btn.textContent='\u5df2\u8fc7\u671f\uff0c\u70b9\u51fb\u5237\u65b0'; + },300000); + }else{ + h+='
'; + h+=''; + h+='
\u70b9\u51fb\u83b7\u53d6\u65b0\u7684\u767b\u5f55\u4e8c\u7ef4\u7801
'; + h+='
'; + h+='

\u83ab\u8377\u5fae\u4fe1\u5df2\u6389\u7ebf

'; + h+='

'+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

'; + h+='
'; + } + h+=''; } } try{var r2=await fetch('/api/ejabberd'),ej=await r2.json();var ejA=ej.alive||ej.xmpp_bot_connected; @@ -632,4 +643,42 @@ h+='

'+inMd(block)+'

';} return h; function inMd(t){return t.replace(/\*\*([^*]+)\*\*/g,'$1').replace(/\*([^*]+)\*/g,'$1').replace(/`([^`]+)`/g,'$1').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'$1')}} 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='\u6b63\u5728\u83b7\u53d6\u4e8c\u7ef4\u7801...'; + 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='\u5df2\u89e6\u53d1\uff0c\u6b63\u5728\u751f\u6210\u4e8c\u7ef4\u7801...'; + // 触发后轮询 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='\u4e8c\u7ef4\u7801\u672a\u751f\u6210\uff0c\u8bf7\u91cd\u8bd5'; + if(btn)btn.disabled=false; + }else{ + if(st)st.innerHTML='\u83b7\u53d6\u5931\u8d25: '+esc(d.error||'')+''; + if(btn)btn.disabled=false; + } + }catch(e){ + if(st)st.innerHTML='\u83b7\u53d6\u5931\u8d25: '+esc(e.message||'')+''; + if(btn)btn.disabled=false; + } +} +async function refreshWechatQR(){ + try{ + await fetch('/api/wechat/trigger-login',{method:'POST'}); + // 刷新后等几秒重新拉状态 + setTimeout(function(){location.reload();},3000); + }catch(e){} +} \ No newline at end of file