From edf3cfedd4d261d818fc94acf8568cd3fe21fbe0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:40:14 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E5=8F=91=E9=80=81:=20TLS=20error=20handler=E9=87=8D=E6=9E=84?= =?UTF-8?q?=20+=20=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E9=80=8F=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/98f7f760-839a-42c7-b716-fa39b6021391 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/proxy/service/email-hub.js | 140 +++++++++--------- .../proxy/service/subscription-server-v3.js | 6 +- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/server/proxy/service/email-hub.js b/server/proxy/service/email-hub.js index e6f3d35b..c3444ca3 100644 --- a/server/proxy/service/email-hub.js +++ b/server/proxy/service/email-hub.js @@ -176,96 +176,94 @@ async function sendEmail(to, subject, htmlBody) { return new Promise((resolve, reject) => { let settled = false; + let socket; + + const settle = (fn, val) => { + if (settled) return; + settled = true; + clearTimeout(timeoutId); + try { if (socket) socket.destroy(); } catch { /* ignore */ } + fn(val); + }; + const timeoutId = setTimeout(() => { - if (!settled) { - settled = true; - try { socket.destroy(); } catch { /* ignore */ } - reject(new Error('SMTP超时(30s)')); - } + settle(reject, new Error(`SMTP超时(30s) → ${smtpHost}:${smtpPort}`)); }, 30000); - const socket = tls.connect(smtpPort, smtpHost, {}, () => { - let step = 0; - let buffer = ''; - const from = config.smtp_user; + try { + socket = tls.connect(smtpPort, smtpHost, {}, () => { + let step = 0; + let buffer = ''; + const from = config.smtp_user; - const commands = [ - `EHLO zy-proxy\r\n`, - `AUTH LOGIN\r\n`, - `${Buffer.from(from).toString('base64')}\r\n`, - `${Buffer.from(config.smtp_pass).toString('base64')}\r\n`, - `MAIL FROM:<${from}>\r\n`, - `RCPT TO:<${to}>\r\n`, - `DATA\r\n`, - `From: =?UTF-8?B?${Buffer.from('光湖语言世界').toString('base64')}?= <${from}>\r\nTo: <${to}>\r\nSubject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=\r\nContent-Type: text/html; charset=utf-8\r\nMIME-Version: 1.0\r\n\r\n${htmlBody}\r\n.\r\n`, - `QUIT\r\n` - ]; + const commands = [ + `EHLO zy-proxy\r\n`, + `AUTH LOGIN\r\n`, + `${Buffer.from(from).toString('base64')}\r\n`, + `${Buffer.from(config.smtp_pass).toString('base64')}\r\n`, + `MAIL FROM:<${from}>\r\n`, + `RCPT TO:<${to}>\r\n`, + `DATA\r\n`, + `From: =?UTF-8?B?${Buffer.from('光湖语言世界').toString('base64')}?= <${from}>\r\nTo: <${to}>\r\nSubject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=\r\nContent-Type: text/html; charset=utf-8\r\nMIME-Version: 1.0\r\n\r\n${htmlBody}\r\n.\r\n`, + `QUIT\r\n` + ]; - // SMTP多行响应正确处理: - // 多行响应格式: "250-xxx\r\n" (中间行用减号), 最终行: "250 xxx\r\n" (用空格) - // 只有收到最终行(NNN + 空格)时才发送下一条命令 - socket.on('data', (chunk) => { - buffer += chunk.toString(); + // SMTP多行响应正确处理: + // 多行响应格式: "250-xxx\r\n" (中间行用减号), 最终行: "250 xxx\r\n" (用空格) + // 只有收到最终行(NNN + 空格)时才发送下一条命令 + socket.on('data', (chunk) => { + buffer += chunk.toString(); - // 逐行处理完整的SMTP响应 - while (buffer.includes('\r\n')) { - const lineEnd = buffer.indexOf('\r\n'); - const line = buffer.substring(0, lineEnd); - buffer = buffer.substring(lineEnd + 2); + // 逐行处理完整的SMTP响应 + while (buffer.includes('\r\n')) { + const lineEnd = buffer.indexOf('\r\n'); + const line = buffer.substring(0, lineEnd); + buffer = buffer.substring(lineEnd + 2); - // 检查是否为最终响应行 (NNN + 空格 或 NNN 结束) - const match = line.match(/^(\d{3})([ -])/); - if (!match) continue; + // 检查是否为最终响应行 (NNN + 空格 或 NNN 结束) + const match = line.match(/^(\d{3})([ -])/); + if (!match) continue; - const statusCode = parseInt(match[1], 10); - const isFinal = match[2] === ' '; // 空格=最终行, 减号=还有后续行 + const statusCode = parseInt(match[1], 10); + const isFinal = match[2] === ' '; // 空格=最终行, 减号=还有后续行 - if (!isFinal) continue; // 多行响应中间行,等待最终行 + if (!isFinal) continue; // 多行响应中间行,等待最终行 - // 检查错误响应 (4xx/5xx) - if (statusCode >= 400) { - if (!settled) { - settled = true; - clearTimeout(timeoutId); - try { socket.destroy(); } catch { /* ignore */ } - reject(new Error(`SMTP错误(${statusCode}): ${line}`)); + // 检查错误响应 (4xx/5xx) + if (statusCode >= 400) { + settle(reject, new Error(`SMTP错误(${statusCode}): ${line}`)); + return; } - return; - } - // 发送下一条命令 - if (step < commands.length) { - socket.write(commands[step]); - step++; - } + // 发送下一条命令 + if (step < commands.length) { + socket.write(commands[step]); + step++; + } - // 所有命令已发送且收到最终响应 - if (step >= commands.length && !settled) { - settled = true; - clearTimeout(timeoutId); - try { socket.destroy(); } catch { /* ignore */ } - resolve(true); - return; + // 所有命令已发送且收到最终响应 + if (step >= commands.length) { + settle(resolve, true); + return; + } } - } + }); }); + // 连接级错误 (DNS解析失败/连接拒绝/TLS握手失败) + // 必须在connect callback外部注册,否则连接失败时handler不触发 socket.on('error', (err) => { + settle(reject, new Error(`SMTP连接失败(${smtpHost}): ${err.message}`)); + }); + + socket.on('close', () => { if (!settled) { - settled = true; - clearTimeout(timeoutId); - reject(err); + settle(reject, new Error(`SMTP连接被关闭(${smtpHost})`)); } }); - }); - - socket.on('error', (err) => { - if (!settled) { - settled = true; - clearTimeout(timeoutId); - reject(err); - } - }); + } catch (err) { + settle(reject, new Error(`SMTP初始化失败: ${err.message}`)); + } }); } @@ -1064,7 +1062,7 @@ async function sendBandwidthAuthEmail(email, code) { } catch (err) { console.error(`[邮件中枢] ❌ 带宽验证码发送失败: ${err.message}`); logEmail('bandwidth-auth', email, false, err.message); - return { sent: 0, failed: 1 }; + return { sent: 0, failed: 1, error: err.message }; } } diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 753f364a..a1add3f1 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -1972,13 +1972,15 @@ async function submitCode(e) { res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(JSON.stringify({ success: true, message: '验证码已发送到您的邮箱,请查收(15分钟内有效)' })); } else { + const errDetail = result.error || '未知原因'; + console.error('[bandwidth-send-code] 邮件发送失败:', errDetail); res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); - res.end(JSON.stringify({ success: false, message: '📧 邮件发送失败,请稍后重试或联系冰朔获取验证码' })); + res.end(JSON.stringify({ success: false, message: `📧 邮件发送失败(${errDetail}),请稍后重试或联系冰朔获取验证码` })); } }).catch((err) => { console.error('[bandwidth-send-code] 邮件发送异常:', err.message || err); res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); - res.end(JSON.stringify({ success: false, message: '📧 邮件发送失败,请稍后重试或联系冰朔获取验证码' })); + res.end(JSON.stringify({ success: false, message: `📧 邮件发送异常(${err.message}),请稍后重试或联系冰朔获取验证码` })); }); } catch { // Email module not available - code was still created From 9e7c40a0252b8662ec3930b8340d43402aea288a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:43:28 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BB=AA=E8=A1=A8=E7=9B=98=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=8A=A0=E9=80=9F=E7=8A=B6=E6=80=81=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=20+=20Claude=E4=B8=93=E7=BA=BF=E5=A7=8B=E7=BB=88=E5=8F=AF?= =?UTF-8?q?=E9=80=89=20+=20=E8=AE=A2=E9=98=85=E6=9B=B4=E6=96=B0=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=20+=20bandwidth-user-status=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/98f7f760-839a-42c7-b716-fa39b6021391 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- .../proxy/service/subscription-server-v3.js | 167 ++++++++++++++++-- 1 file changed, 150 insertions(+), 17 deletions(-) diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index a1add3f1..876d146e 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -307,19 +307,20 @@ ${nodeNames} : ` - "♻️ 自动选择"\n${nodeNames}`) : nodeNames; - // Claude专线独立代理组: 只包含SV节点 + 备选 - // 用户可以在VPN软件中手动下拉选择,精准定位Claude专线 + // Claude专线独立代理组: 始终显示为下拉菜单 (select类型) + // 无论是否有硅谷节点,都创建Claude专线组,让用户手动选择 + // 有SV节点时优先SV,无SV节点时使用现有节点 const claudeProxies = svNode ? ` - "${svNode.name}"\n${nodes.length > 1 ? ' - "♻️ 自动选择"\n' : ''}${nodeNames}` - : toolProxies; + : (nodes.length > 1 ? ` - "♻️ 自动选择"\n${nodeNames}` : nodeNames); - // Claude专线代理组YAML块 - const claudeGroupBlock = svNode ? ` + // Claude专线代理组YAML块 — 始终生成 (下拉选择·select类型) + const claudeGroupBlock = ` - name: "🇺🇸 Claude专线" type: select proxies: ${claudeProxies} -` : ''; +`; return `# 光湖语言世界 · ${user.label} 的独立专线 — 冰朔开发维护 # 自动生成 · ${new Date().toISOString()} @@ -451,9 +452,9 @@ ${toolProxies} # ── 路由规则 ────────────────────────────── rules: - # Claude专线 (独立代理组·手动选择·精准定位) - - DOMAIN-SUFFIX,claude.ai,${svNode ? '🇺🇸 Claude专线' : '🤖 AI服务'} - - DOMAIN-SUFFIX,anthropic.com,${svNode ? '🇺🇸 Claude专线' : '🤖 AI服务'} + # Claude专线 (独立代理组·下拉选择·始终可选) + - DOMAIN-SUFFIX,claude.ai,🇺🇸 Claude专线 + - DOMAIN-SUFFIX,anthropic.com,🇺🇸 Claude专线 # AI服务 (其他AI) - DOMAIN-SUFFIX,openai.com,🤖 AI服务 @@ -803,18 +804,28 @@ mode: direct boostStatus = bs.current?.bbr?.is_bbr ? '✅ BBR加速中' : '⚠️ 未加速'; } catch { /* ignore */ } - // 读取用户带宽共享状态 + // 读取用户带宽共享状态 (系统内部状态) let bwContribStatus = '未加入'; let bwContribActive = false; + let bwContribAuthTime = ''; + let bwIpAuthorized = false; try { const bwPool = require('./bandwidth-pool-agent'); const contribInfo = bwPool.isContributor(user.email); if (contribInfo.is_contributor) { bwContribActive = contribInfo.status === 'active'; + bwIpAuthorized = true; bwContribStatus = bwContribActive ? '🚀 加速已生效' : '⚠️ 已暂停'; + bwContribAuthTime = contribInfo.authorized_at || ''; } } catch { /* ignore */ } + // 读取带宽池状态 (加速池整体数据) + let bwPoolInfo = { total_contributors: 0, active_contributors: 0, total_contributed_gb: 0, pool_status: 'idle' }; + try { + bwPoolInfo = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'bandwidth-pool-status.json'), 'utf8')); + } catch { /* ignore */ } + // 读取今日流量快照 let todayGB = '—'; try { @@ -825,6 +836,9 @@ mode: direct } } catch { /* ignore */ } + // Claude专线节点检测 + const svNode = nodes.find(n => n.region === 'us-sv'); + const poolBarColor = poolPct > 90 ? '#e74c3c' : poolPct > 70 ? '#f39c12' : '#2ecc71'; // HTML转义防止XSS @@ -853,6 +867,12 @@ mode: direct .node:last-child { border-bottom: none; } .online { color: #2ecc71; } .footer { text-align: center; padding: 20px 0; color: #555; font-size: 0.75em; } + .accel-badge { display: inline-block; padding: 3px 10px; border-radius: 6px; font-size: 0.8em; font-weight: 600; } + .accel-on { background: rgba(46,204,113,0.15); color: #2ecc71; border: 1px solid rgba(46,204,113,0.3); } + .accel-off { background: rgba(241,196,15,0.1); color: #f1c40f; border: 1px solid rgba(241,196,15,0.2); } + .refresh-btn { display: inline-block; padding: 8px 16px; background: linear-gradient(135deg,#667eea,#764ba2); color: #fff; border: none; border-radius: 8px; font-size: 0.85em; font-weight: 600; cursor: pointer; text-decoration: none; } + .refresh-btn:active { opacity: 0.8; } + .sub-btn { display: block; width: 100%; padding: 12px; background: linear-gradient(135deg,#f093fb,#f5576c); color: #fff; border: none; border-radius: 10px; font-size: 0.95em; font-weight: 600; cursor: pointer; margin-top: 8px; text-decoration: none; text-align: center; }
@@ -861,6 +881,20 @@ mode: direct${esc(user.label)} 的专属仪表盘 — 冰朔开发维护
+ ++ 合并代码后,VPN配置自动更新。您只需在VPN软件中刷新订阅即可获取最新配置。 +
+