From 2309984c89366f0b84bcfa98b8aa471496032e5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:11:06 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20SMTP=E5=A4=9A=E8=A1=8C=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E8=A7=A3=E6=9E=90+=E9=82=AE=E4=BB=B6=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=8F=8D=E9=A6=88+=E5=8A=A0=E9=80=9F=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=98=BE=E7=A4=BA+Claude=E4=B8=93=E7=BA=BF=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. email-hub.js: 重写SMTP data handler,正确处理多行EHLO响应 - 使用buffer逐行解析,只在最终响应行(NNN+空格)后发送下一命令 - 添加SMTP错误码检查(4xx/5xx) 2. subscription-server-v3.js: - /bandwidth-send-code: 邮件发送失败时正确返回 success:false - 仪表盘新增"带宽共享"加速状态行(🚀 加速已生效) - 验证成功后前端实时更新加速状态 - Clash YAML新增独立 🇺🇸 Claude专线 代理组(type:select) - claude.ai/anthropic.com 路由至Claude专线组 3. bandwidth-pool-agent.js: - 新增 isContributor(email) 函数 - 导出新函数 Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/875a729b-385f-406d-bb55-05f05b00db9b Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- server/proxy/service/bandwidth-pool-agent.js | 19 ++++++ server/proxy/service/email-hub.js | 56 +++++++++++++++--- .../proxy/service/subscription-server-v3.js | 59 +++++++++++++++---- 3 files changed, 114 insertions(+), 20 deletions(-) diff --git a/server/proxy/service/bandwidth-pool-agent.js b/server/proxy/service/bandwidth-pool-agent.js index d07f91b6..2550d55f 100644 --- a/server/proxy/service/bandwidth-pool-agent.js +++ b/server/proxy/service/bandwidth-pool-agent.js @@ -259,6 +259,24 @@ function getPoolStatus() { }; } +/** + * 检查指定邮箱是否为活跃带宽贡献者 + * @param {string} email 用户邮箱 + * @returns {{ is_contributor: boolean, status: string, authorized_at: string|null }} + */ +function isContributor(email) { + const data = readContributors(); + const contributor = data.contributors.find(c => c.email === email); + if (!contributor) { + return { is_contributor: false, status: 'none', authorized_at: null }; + } + return { + is_contributor: true, + status: contributor.status, + authorized_at: contributor.authorized_at || null + }; +} + /** * 紧急切断指定用户的带宽共享 * @param {string} email 用户邮箱 @@ -426,6 +444,7 @@ module.exports = { // 贡献者管理 registerContributor, getPoolStatus, + isContributor, readContributors, // 安全操作 diff --git a/server/proxy/service/email-hub.js b/server/proxy/service/email-hub.js index 1a8bcd33..e6f3d35b 100644 --- a/server/proxy/service/email-hub.js +++ b/server/proxy/service/email-hub.js @@ -186,6 +186,7 @@ async function sendEmail(to, subject, htmlBody) { const socket = tls.connect(smtpPort, smtpHost, {}, () => { let step = 0; + let buffer = ''; const from = config.smtp_user; const commands = [ @@ -200,15 +201,52 @@ async function sendEmail(to, subject, htmlBody) { `QUIT\r\n` ]; - socket.on('data', () => { - if (step < commands.length) { - socket.write(commands[step]); - step++; - } - if (step >= commands.length && !settled) { - settled = true; - clearTimeout(timeoutId); - resolve(true); + // 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); + + // 检查是否为最终响应行 (NNN + 空格 或 NNN 结束) + const match = line.match(/^(\d{3})([ -])/); + if (!match) continue; + + const statusCode = parseInt(match[1], 10); + const isFinal = match[2] === ' '; // 空格=最终行, 减号=还有后续行 + + 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}`)); + } + return; + } + + // 发送下一条命令 + 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; + } } }); diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 53022e22..753f364a 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -307,6 +307,20 @@ ${nodeNames} : ` - "♻️ 自动选择"\n${nodeNames}`) : nodeNames; + // Claude专线独立代理组: 只包含SV节点 + 备选 + // 用户可以在VPN软件中手动下拉选择,精准定位Claude专线 + const claudeProxies = svNode + ? ` - "${svNode.name}"\n${nodes.length > 1 ? ' - "♻️ 自动选择"\n' : ''}${nodeNames}` + : toolProxies; + + // Claude专线代理组YAML块 + const claudeGroupBlock = svNode ? ` + - name: "🇺🇸 Claude专线" + type: select + proxies: +${claudeProxies} +` : ''; + return `# 光湖语言世界 · ${user.label} 的独立专线 — 冰朔开发维护 # 自动生成 · ${new Date().toISOString()} # ⚠️ 此配置为 ${user.email} 专属,请勿分享 @@ -419,7 +433,7 @@ proxy-groups: type: select proxies: ${mainProxies} -${autoSelectBlock} +${autoSelectBlock}${claudeGroupBlock} - name: "🤖 AI服务" type: select proxies: @@ -437,10 +451,12 @@ ${toolProxies} # ── 路由规则 ────────────────────────────── rules: - # AI服务 + # Claude专线 (独立代理组·手动选择·精准定位) + - DOMAIN-SUFFIX,claude.ai,${svNode ? '🇺🇸 Claude专线' : '🤖 AI服务'} + - DOMAIN-SUFFIX,anthropic.com,${svNode ? '🇺🇸 Claude专线' : '🤖 AI服务'} + + # AI服务 (其他AI) - DOMAIN-SUFFIX,openai.com,🤖 AI服务 - - DOMAIN-SUFFIX,anthropic.com,🤖 AI服务 - - DOMAIN-SUFFIX,claude.ai,🤖 AI服务 - DOMAIN-SUFFIX,chatgpt.com,🤖 AI服务 - DOMAIN-SUFFIX,gemini.google.com,🤖 AI服务 - DOMAIN-SUFFIX,perplexity.ai,🤖 AI服务 @@ -787,6 +803,18 @@ mode: direct boostStatus = bs.current?.bbr?.is_bbr ? '✅ BBR加速中' : '⚠️ 未加速'; } catch { /* ignore */ } + // 读取用户带宽共享状态 + let bwContribStatus = '未加入'; + let bwContribActive = false; + try { + const bwPool = require('./bandwidth-pool-agent'); + const contribInfo = bwPool.isContributor(user.email); + if (contribInfo.is_contributor) { + bwContribActive = contribInfo.status === 'active'; + bwContribStatus = bwContribActive ? '🚀 加速已生效' : '⚠️ 已暂停'; + } + } catch { /* ignore */ } + // 读取今日流量快照 let todayGB = '—'; try { @@ -853,6 +881,7 @@ mode: direct

⚡ 系统状态

服务版本V3.0
反向加速${boostStatus}
+
带宽共享${bwContribStatus}
在线用户${poolStatus.users_count}
智能选路${nodes.length > 1 ? '✅ url-test' : '单节点'}
@@ -991,6 +1020,9 @@ function bwVerifyCode() { result.style.background = '#1a3a2a'; result.style.color = '#2ecc71'; btn.textContent = '✅ 授权成功'; + // 更新系统状态中的加速状态 + var accelEl = document.getElementById('bwAccelStatus'); + if (accelEl) accelEl.textContent = '🚀 加速已生效'; } else { result.style.background = '#3a1a1a'; result.style.color = '#e74c3c'; @@ -1935,18 +1967,23 @@ async function submitCode(e) { // Try to send email try { const emailHub = require('./email-hub'); - emailHub.sendBandwidthAuthEmail(email, code).then(() => { + emailHub.sendBandwidthAuthEmail(email, code).then((result) => { + if (result.sent > 0) { + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, message: '验证码已发送到您的邮箱,请查收(15分钟内有效)' })); + } else { + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '📧 邮件发送失败,请稍后重试或联系冰朔获取验证码' })); + } + }).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: 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: '验证码已生成,邮件发送中...' })); + res.end(JSON.stringify({ success: false, 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: '验证码已生成,请联系管理员获取' })); + res.end(JSON.stringify({ success: false, message: '邮件服务暂不可用,请联系冰朔获取验证码' })); } } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });