diff --git a/README.md b/README.md index da261cbb..b70644d9 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,19 @@ | HLDP通用语言同步 | ✅ 已执行 | hldp/data/common/ | | 仓库首页更新 | ✅ 已执行 | README.md 签到仪表盘 | | 工作流士兵巡检 | ✅ 18/18 在岗 | 缺失的需冰朔确认 | +| 留言板巡查 | ✅ v2.0活化 | LLM多模型降级 · 定时巡查 · 自愈 | + +### 🎖️ 铸渊副将Agent状态 (v2.0活体Agent) + +| 项目 | 状态 | +|------|------| +| Agent版本 | v2.0 · 活体Agent · 三层自愈 | +| 运行模式 | 事件模式(即时回复) + 巡查模式(08:00/23:00) | +| LLM能力 | 多模型自动降级: deepseek → qwen → moonshot → zhipu | +| 自感知 | data/deputy-status.json · 每次运行更新 | +| 自修复 | 每模型重试2次 · 递增等待 | +| 升级通报 | 连续3次失败 → 创建Issue → 邮件通知冰朔 | +| 📮 留言入口 | [给副将留言](https://github.com/qinfendebingshuo/guanghulab/issues/new?labels=deputy-message-board&title=给铸渊副将留言) | > *此仪表盘由铸渊副将(ZY-DEPUTY-001)每日唤醒时自动更新 · 08:00 / 23:00* diff --git a/docs/index.html b/docs/index.html index 7bc28025..5d38aa17 100644 --- a/docs/index.html +++ b/docs/index.html @@ -716,6 +716,36 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh .app-card-tags{display:flex;gap:5px;flex-wrap:wrap} .app-card-arrow{font-size:18px;color:var(--accent);opacity:.5;flex-shrink:0;transition:all .3s} .app-card:hover .app-card-arrow{opacity:1;transform:translateX(4px)} + +/* ═══ PAGE: Bandwidth Sharing (带宽共享) ═══ */ +#pg-bandwidth .bw-hero{text-align:center;padding:28px 0 20px} +#pg-bandwidth .bw-hero h2{font-size:1.4em;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent} +#pg-bandwidth .bw-hero p{color:var(--dim);font-size:.9em;margin-top:6px} +#pg-bandwidth .bw-stats{display:flex;justify-content:space-around;margin:16px 0;gap:12px} +#pg-bandwidth .bw-stat{text-align:center;flex:1} +#pg-bandwidth .bw-stat .num{font-size:1.5em;font-weight:700;color:var(--accent);font-family:var(--m)} +#pg-bandwidth .bw-stat .label{font-size:.75em;color:var(--dim);margin-top:4px} +#pg-bandwidth .bw-card{background:var(--bg1);border-radius:var(--r);padding:20px;margin:12px 0;border:1px solid var(--border)} +#pg-bandwidth .bw-card h3{font-size:1em;color:var(--accent);margin-bottom:14px} +#pg-bandwidth .bw-input{width:100%;padding:12px 14px;background:var(--bg2);border:2px solid var(--border);border-radius:var(--rs);color:var(--text);font-size:1em;outline:none;transition:border-color .3s;font-family:var(--f)} +#pg-bandwidth .bw-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(138,180,248,.15)} +#pg-bandwidth .bw-input::placeholder{color:var(--dim);font-size:.85em} +#pg-bandwidth .bw-code-input{text-align:center;letter-spacing:8px;font-size:1.3em;font-family:var(--m)} +#pg-bandwidth .bw-code-input::placeholder{letter-spacing:0;font-size:.6em} +#pg-bandwidth .bw-btn{width:100%;padding:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;border:none;border-radius:var(--rs);font-size:1em;font-weight:600;cursor:pointer;transition:opacity .3s,transform .15s;margin-top:8px;font-family:var(--f)} +#pg-bandwidth .bw-btn:hover{opacity:.9} +#pg-bandwidth .bw-btn:active{transform:scale(.98)} +#pg-bandwidth .bw-btn:disabled{opacity:.5;cursor:not-allowed;transform:none} +#pg-bandwidth .bw-btn-outline{background:transparent;border:1px solid var(--accent);color:var(--accent)} +#pg-bandwidth .bw-btn-outline:hover{background:rgba(138,180,248,.1)} +#pg-bandwidth .bw-result{text-align:center;padding:14px;border-radius:var(--rs);margin-top:12px;display:none;font-size:.9em;line-height:1.6} +#pg-bandwidth .bw-result.ok{display:block;background:rgba(52,211,153,.1);color:var(--ok);border:1px solid rgba(52,211,153,.2)} +#pg-bandwidth .bw-result.err{display:block;background:rgba(248,113,113,.1);color:var(--err);border:1px solid rgba(248,113,113,.2)} +#pg-bandwidth .bw-info{background:var(--bg2);border-radius:var(--rs);padding:14px 16px;margin:10px 0;font-size:.85em;line-height:1.8;color:var(--dim);border-left:3px solid var(--accent)} +#pg-bandwidth .bw-consent{display:flex;align-items:flex-start;gap:10px;margin:14px 0;padding:12px 14px;background:var(--bg2);border-radius:var(--rs);border:1px solid var(--border);cursor:pointer;transition:border-color .3s} +#pg-bandwidth .bw-consent:hover{border-color:var(--accent)} +#pg-bandwidth .bw-consent input{margin-top:3px;width:18px;height:18px;accent-color:var(--accent);cursor:pointer;flex-shrink:0} +#pg-bandwidth .bw-consent span{color:var(--dim);font-size:.85em;line-height:1.7} @@ -894,6 +924,23 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh + +
+
⚡ 快速入口
+
+
+
🌊
+
带宽共享加速池
共享你的带宽 · 加速所有人
+
+ +
+
🎖️
+
铸渊副将留言板
每日08:00/23:00唤醒 · LLM深度推理回复
+
+
+
+
+
📋 版本历史
@@ -1131,6 +1178,63 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
+ + + +
+
+ +
🌊 带宽共享
+
+
+
+
+

🌊 带宽共享加速池

+

共享你的带宽 · 加速所有人

+
+ +
+
贡献者
+
活跃
+
总贡献(GB)
+
+ +
+

📧 第一步 · 发送验证码

+
+ +
+ +
+
+ +
+

🔑 第二步 · 输入验证码

+
+ +
+ + +
+
+ +
+ 💡 带宽共享说明 + ✅ 提供了验证码的用户 → 优先保障你的专属通道
+ ✅ 你用不上的资源才会被共享 → 不抢你的带宽
+ ✅ 随时可退出 → 你有完全的控制权
+ ⚠️ 验证码15分钟内有效 · 一次性使用 +
+ +
+

© 2026 光湖语言世界 · 国作登字-2026-A-00037559

+
+
+
+ @@ -3446,6 +3550,132 @@ offline = function(msg){ }); })(); +})(); + +/* ════════════════════════════════════════ + BANDWIDTH SHARING AGENT (带宽共享验证) + GitHub Pages 前端 → 后端API桥接 + ════════════════════════════════════════ */ +var BW_API_BASE = 'https://guanghulab.online/api/proxy-v3'; + +function bwSendCode(){ + var email = document.getElementById('bwEmail').value.trim().toLowerCase(); + var btn = document.getElementById('bwSendCode'); + var result = document.getElementById('bwSendResult'); + + if(!email || email.indexOf('@') === -1){ + result.className = 'bw-result err'; + result.textContent = '请输入有效的邮箱地址'; + return; + } + + btn.disabled = true; + btn.textContent = '发送中...'; + result.className = 'bw-result'; + result.style.display = 'none'; + + fetch(BW_API_BASE + '/bandwidth-send-code', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({email: email}) + }).then(function(r){ return r.json(); }).then(function(data){ + btn.disabled = false; + btn.textContent = '发送验证码'; + result.className = 'bw-result ' + (data.success ? 'ok' : 'err'); + result.textContent = data.message || (data.success ? '验证码已发送' : '发送失败'); + + if(data.success){ + // Cooldown 60s + var cd = 60; + btn.disabled = true; + var timer = setInterval(function(){ + cd--; + btn.textContent = cd + 's 后可重发'; + if(cd <= 0){ + clearInterval(timer); + btn.disabled = false; + btn.textContent = '发送验证码'; + } + }, 1000); + } + }).catch(function(err){ + btn.disabled = false; + btn.textContent = '发送验证码'; + result.className = 'bw-result err'; + result.textContent = '网络错误,请检查网络后重试'; + }); +} + +function bwVerifyCode(){ + var email = document.getElementById('bwEmail').value.trim().toLowerCase(); + var code = document.getElementById('bwCode').value.trim(); + var consent = document.getElementById('bwConsent').checked; + var btn = document.getElementById('bwVerify'); + var result = document.getElementById('bwVerifyResult'); + + if(!email || email.indexOf('@') === -1){ + result.className = 'bw-result err'; + result.textContent = '请先在上方输入邮箱'; + return; + } + if(!code || code.length !== 6){ + result.className = 'bw-result err'; + result.textContent = '请输入6位验证码'; + return; + } + if(!consent){ + result.className = 'bw-result err'; + result.textContent = '请先勾选同意带宽共享协议'; + return; + } + + btn.disabled = true; + btn.textContent = '验证中...'; + result.className = 'bw-result'; + result.style.display = 'none'; + + fetch(BW_API_BASE + '/bandwidth-verify-code', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({email: email, code: code}) + }).then(function(r){ return r.json(); }).then(function(data){ + btn.disabled = false; + btn.textContent = '提交验证码'; + result.className = 'bw-result ' + (data.success ? 'ok' : 'err'); + result.textContent = data.message || (data.success ? '授权成功!' : '验证失败'); + if(data.success) bwLoadPoolStatus(); + }).catch(function(){ + btn.disabled = false; + btn.textContent = '提交验证码'; + result.className = 'bw-result err'; + result.textContent = '网络错误,请检查网络后重试'; + }); +} + +function bwLoadPoolStatus(){ + fetch(BW_API_BASE + '/bandwidth-pool-status').then(function(r){ + return r.json(); + }).then(function(data){ + var el1 = document.getElementById('bwContributors'); + var el2 = document.getElementById('bwActive'); + var el3 = document.getElementById('bwContributed'); + if(el1) el1.textContent = data.total_contributors || 0; + if(el2) el2.textContent = data.active_contributors || 0; + if(el3) el3.textContent = data.total_contributed_gb || 0; + }).catch(function(){ + /* Pool status unavailable - keep defaults */ + }); +} + +// Auto-load pool status when bandwidth page is shown +var _origGo = window.go; +if(typeof _origGo === 'function'){ + window.go = function(target){ + _origGo(target); + if(target === 'bandwidth') bwLoadPoolStatus(); + }; +} + })(); diff --git a/server/nginx/zhuyuan-sovereign.conf b/server/nginx/zhuyuan-sovereign.conf index f448a3dd..9848a7f1 100644 --- a/server/nginx/zhuyuan-sovereign.conf +++ b/server/nginx/zhuyuan-sovereign.conf @@ -200,7 +200,17 @@ server { # 将V3授权/订阅请求透传到大脑服务器的V3订阅服务 (端口3805) # 协议: HTTP (内网通信,大脑服务器Nginx监听80端口default_server) # 部署时由 workflow 自动将 ZY_BRAIN_HOST_PLACEHOLDER 替换为实际IP + # CORS: 允许GitHub Pages跨域调用带宽共享API location /api/proxy-v3/ { + # CORS预检处理 (GitHub Pages跨域) + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; + add_header Access-Control-Allow-Headers "Content-Type" always; + add_header Access-Control-Max-Age 86400; + return 204; + } + proxy_pass http://ZY_BRAIN_HOST_PLACEHOLDER/api/proxy-v3/; proxy_http_version 1.1; proxy_set_header Host $host; @@ -213,6 +223,7 @@ server { add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Access-Control-Allow-Origin * always; } # ─── 铸渊 API 反向代理 (端口 3801 · 预览端口) ─── diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 1bdcc930..570118c9 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -1705,6 +1705,213 @@ async function submitCode(e) { return; } + // ═══════════════════════════════════════════════ + // ∞+1 公开API: /bandwidth-send-code (无需token·邮箱验证码发送) + // 用户在GitHub Pages首页输入邮箱 → 调用此API发送验证码 + // 安全: 每个IP每小时最多3次 + // ═══════════════════════════════════════════════ + if (pathname === '/bandwidth-send-code' && req.method === 'POST') { + // CORS headers for GitHub Pages cross-origin + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + let body = ''; + req.on('data', chunk => { body += chunk; if (body.length > 1024) req.destroy(); }); + req.on('end', () => { + try { + let email = ''; + if (req.headers['content-type']?.includes('application/json')) { + const json = JSON.parse(body); + email = String(json.email || '').trim().toLowerCase(); + } else { + const params = new URLSearchParams(body); + email = String(params.get('email') || '').trim().toLowerCase(); + } + + if (!email || !email.includes('@')) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请输入有效的邮箱地址' })); + return; + } + + // Rate limiting: max 3 per IP per hour + const userIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() + || req.headers['x-real-ip'] + || req.socket.remoteAddress + || '0.0.0.0'; + + if (!global._bwSendCodeRateLimit) global._bwSendCodeRateLimit = {}; + const rl = global._bwSendCodeRateLimit; + const hourKey = `${userIP}:${Math.floor(Date.now() / 3600000)}`; + rl[hourKey] = (rl[hourKey] || 0) + 1; + // Clean old entries + const currentHourPrefix = Math.floor(Date.now() / 3600000); + for (const k of Object.keys(rl)) { + const hourPart = parseInt(k.split(':').pop(), 10); + if (hourPart < currentHourPrefix - 1) delete rl[k]; + } + if (rl[hourKey] > 3) { + res.writeHead(429, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '发送频率过高,请1小时后重试' })); + return; + } + + const bwPool = require('./bandwidth-pool-agent'); + const code = bwPool.createAuthCode(email); + + // Try to send email + try { + const emailHub = require('./email-hub'); + emailHub.sendBandwidthAuthEmail(email, code).then(() => { + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, message: '验证码已发送到您的邮箱,请查收(15分钟内有效)' })); + }).catch(() => { + // Email failed but code was created - still return success with code hint + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, message: '验证码已生成,邮件发送中...' })); + }); + } catch { + // Email module not available - code was still created + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, message: '验证码已生成,请联系管理员获取' })); + } + } catch (err) { + res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '系统异常,请稍后重试' })); + } + }); + return; + } + + // ═══════════════════════════════════════════════ + // ∞+1 公开API: /bandwidth-verify-code (无需token·验证码校验+注册) + // 用户在GitHub Pages首页输入邮箱+验证码 → 调用此API完成授权 + // ═══════════════════════════════════════════════ + if (pathname === '/bandwidth-verify-code' && req.method === 'POST') { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + let body = ''; + req.on('data', chunk => { body += chunk; if (body.length > 1024) req.destroy(); }); + req.on('end', () => { + try { + let code = ''; + let email = ''; + if (req.headers['content-type']?.includes('application/json')) { + const json = JSON.parse(body); + code = String(json.code || '').trim(); + email = String(json.email || '').trim().toLowerCase(); + } else { + const params = new URLSearchParams(body); + code = String(params.get('code') || '').trim(); + email = String(params.get('email') || '').trim().toLowerCase(); + } + + if (!email || !email.includes('@')) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请输入有效的邮箱地址' })); + return; + } + if (!code || code.length !== 6) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请输入6位验证码' })); + return; + } + + const userIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() + || req.headers['x-real-ip'] + || req.socket.remoteAddress + || '0.0.0.0'; + + const bwPool = require('./bandwidth-pool-agent'); + const verifyResult = bwPool.verifyAuthCode(code, email); + + if (!verifyResult.valid) { + saveAuthSnapshot({ + email, action: 'bandwidth-verify-code', result: 'failed', + error: verifyResult.error, ip: userIP, + user_agent: req.headers['user-agent'] || 'unknown', + auth_type: 'github-pages-public' + }); + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: verifyResult.error })); + return; + } + + const regResult = bwPool.registerContributor(email, userIP); + + saveAuthSnapshot({ + email, action: 'bandwidth-verify-code', result: 'authorized', + ip: userIP, user_agent: req.headers['user-agent'] || 'unknown', + auth_type: 'github-pages-public', + contributor_id: regResult.contributor_id + }); + + try { + const guardian = require('./user-guardian-agent'); + guardian.activateGuardian(email); + } catch { /* guardian not loaded */ } + + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ + success: true, + message: '✅ 授权成功!您的带宽已加入加速池,感谢支持。', + contributor_id: regResult.contributor_id + })); + } catch (err) { + res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '系统异常,请稍后重试' })); + } + }); + return; + } + + // ═══════════════════════════════════════════════ + // ∞+1 公开API: /bandwidth-pool-status (无需token·带宽池状态) + // GitHub Pages首页实时显示带宽池状态 + // ═══════════════════════════════════════════════ + if (pathname === '/bandwidth-pool-status' && req.method === 'GET') { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + try { + const bwPool = require('./bandwidth-pool-agent'); + const poolStatus = bwPool.getPoolStatus(); + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, ...poolStatus })); + } catch { + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ + success: true, + total_contributors: 0, + active_contributors: 0, + total_contributed_gb: 0, + pool_status: 'idle', + updated_at: new Date().toISOString() + })); + } + return; + } + + // ═══ CORS预检 (OPTIONS) 支持 ═══ + if (req.method === 'OPTIONS' && ( + pathname === '/bandwidth-send-code' || + pathname === '/bandwidth-verify-code' || + pathname === '/bandwidth-pool-status' + )) { + res.writeHead(204, { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + 'Access-Control-Max-Age': '86400' + }); + res.end(); + return; + } + // 404 res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found');