Merge pull request #289 from qinfendebingshuo/copilot/add-link-to-3-0-test-version

feat: add send-subscription-v3 for V3 test subscription email delivery
This commit is contained in:
冰朔 2026-04-06 00:29:52 +08:00 committed by GitHub
commit 7f83ffa332
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 205 additions and 6 deletions

View File

@ -28,11 +28,12 @@ on:
- remove-user
- list-users
- send-subscription
- send-subscription-v3
- deploy-v3
- switch-v3
default: 'status'
email:
description: '用户邮箱 (add-user/remove-user/send-subscription时需要)'
description: '用户邮箱 (add-user/remove-user/send-subscription/send-subscription-v3时需要)'
required: false
type: string
quota_gb:
@ -315,3 +316,75 @@ jobs:
echo ""
echo "✅ 完成!${LABEL} (${EMAIL}) 的独立专线已就绪"
# ═══ §4 发送V3测试订阅 ═══
send-subscription-v3:
name: '📧 发送V3测试订阅'
runs-on: ubuntu-latest
if: github.event.inputs.action == 'send-subscription-v3'
steps:
- name: '📥 检出代码'
uses: actions/checkout@v4
- name: '🔑 配置SSH密钥'
env:
SSH_KEY: ${{ secrets.ZY_BRAIN_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/id_brain
chmod 600 ~/.ssh/id_brain
ssh-keyscan -H ${{ secrets.ZY_BRAIN_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: '📧 获取用户信息并发送V3链接'
env:
ZY_SMTP_USER: ${{ secrets.ZY_SMTP_USER }}
ZY_SMTP_PASS: ${{ secrets.ZY_SMTP_PASS }}
ZY_BRAIN_HOST: ${{ secrets.ZY_BRAIN_HOST }}
run: |
EMAIL="${{ github.event.inputs.email }}"
if [ -z "$EMAIL" ]; then
echo "❌ 发送V3订阅需要提供邮箱"
exit 1
fi
echo "📧 获取 $EMAIL 的V3订阅信息..."
# 获取用户token
USER_INFO=$(ssh -i ~/.ssh/id_brain \
${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }} \
"cd /opt/zhuyuan-brain/proxy && \
node -e \"
const um = require('./service/user-manager');
const user = um.loadUsers().users.find(u => u.email === '$EMAIL');
if (user) console.log(JSON.stringify({token: user.token, label: user.label}));
else { console.error('用户不存在'); process.exit(1); }
\"")
if [ -z "$USER_INFO" ]; then
echo "❌ 用户 $EMAIL 不存在,请先运行 add-user"
exit 1
fi
TOKEN=$(echo "$USER_INFO" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.token)")
LABEL=$(echo "$USER_INFO" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.label)")
SUB_URL="http://${ZY_BRAIN_HOST}/api/proxy-v3/sub/${TOKEN}"
DASHBOARD_URL="http://${ZY_BRAIN_HOST}/api/proxy-v3/dashboard/${TOKEN}"
echo "📎 V3订阅链接: ${SUB_URL}"
echo "📊 V3仪表盘: ${DASHBOARD_URL}"
# 通过大脑服务器发送V3邮件
ssh -i ~/.ssh/id_brain \
${{ secrets.ZY_BRAIN_USER }}@${{ secrets.ZY_BRAIN_HOST }} \
"export ZY_SMTP_USER='${ZY_SMTP_USER}' ZY_SMTP_PASS='${ZY_SMTP_PASS}' && \
cd /opt/zhuyuan-brain/proxy && \
node service/send-subscription.js send-v3 '$EMAIL' '$SUB_URL' '$DASHBOARD_URL'" || {
echo ""
echo "⚠️ 邮件发送失败,请手动访问以下链接:"
echo " 订阅: $SUB_URL"
echo " 仪表盘: $DASHBOARD_URL"
}
echo ""
echo "✅ 完成!${LABEL} (${EMAIL}) 的V3光湖语言世界测试订阅已发送"

View File

@ -10,8 +10,9 @@
// 确保敏感信息不经过公开仓库
//
// 用法:
// node send-subscription.js send <email> — 发送订阅链接
// node send-subscription.js alert <msg> — 发送告警邮件
// node send-subscription.js send <email> — 发送V2订阅链接
// node send-subscription.js send-v3 <email> <url> <dashboard> — 发送V3订阅链接
// node send-subscription.js alert <msg> — 发送告警邮件
//
// 环境变量:
// ZY_SMTP_USER, ZY_SMTP_PASS — SMTP认证
@ -206,6 +207,99 @@ function generateSubscriptionEmail(config, urlOverride) {
</html>`;
}
// ── 生成V3订阅邮件HTML (光湖语言世界) ─────────
function generateV3SubscriptionEmail(subUrl, dashboardUrl) {
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
return `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background: #f8f9fa;">
<div style="background: white; border-radius: 12px; padding: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<h1 style="color: #1a1a2e; margin-bottom: 5px;">🌐 光湖语言世界</h1>
<p style="color: #666; margin-top: 0;">铸渊专线 V3 测试版 · 私有订阅链接</p>
<div style="background: #e8f5e9; border: 1px solid #a5d6a7; border-radius: 8px; padding: 12px; margin: 15px 0;">
<strong style="color: #2e7d32;">🧪 V3 测试版</strong>
<span style="color: #555;"> 请测试后反馈体验V2 继续可用</span>
</div>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<h3 style="color: #333;">📱 V3 订阅链接</h3>
<div style="background: #f0f4ff; border: 1px solid #d0d8ff; border-radius: 8px; padding: 15px; word-break: break-all; font-family: monospace; font-size: 13px;">
${subUrl}
</div>
<p style="color: #999; font-size: 12px; margin-top: 8px;">
请勿分享此链接 · 所有订阅共享 2000GB 月流量池
</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<h3 style="color: #333;">📊 V3 流量仪表盘</h3>
<div style="background: #fff8e1; border: 1px solid #ffe082; border-radius: 8px; padding: 15px; word-break: break-all; font-family: monospace; font-size: 13px;">
${dashboardUrl}
</div>
<p style="color: #999; font-size: 12px; margin-top: 8px;">
💡 V3新功能在浏览器中查看实时流量统计和节点状态
</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<h3 style="color: #333;">📋 使用方法</h3>
<h4 style="color: #555;">🍎 iPhone (Shadowrocket)</h4>
<ol style="color: #666; line-height: 1.8;">
<li>打开 Shadowrocket</li>
<li>点击右上角 <strong>+</strong></li>
<li>选择 <strong>Subscribe</strong> ()</li>
<li>粘贴上方 V3 订阅链接</li>
<li>点击完成 选择节点 开启连接</li>
</ol>
<h4 style="color: #555;">💻 Mac / Windows (Clash Verge)</h4>
<ol style="color: #666; line-height: 1.8;">
<li>打开 Clash Verge</li>
<li>点击 <strong>Profiles</strong> ()</li>
<li>粘贴上方 V3 订阅链接到输入框</li>
<li>点击 <strong>Import</strong> ()</li>
<li>选中新配置 开启系统代理</li>
</ol>
<h4 style="color: #555;">🤖 Android (ClashMi / Clash Meta)</h4>
<ol style="color: #666; line-height: 1.8;">
<li>打开 ClashMi</li>
<li>点击 <strong>Profile</strong> <strong>New Profile</strong></li>
<li>选择 <strong>URL</strong></li>
<li>粘贴上方 V3 订阅链接</li>
<li>保存 选中配置 启动</li>
</ol>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<h3 style="color: #333;">🆕 V3 升级亮点</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr><td style="padding: 8px; color: #666;">品牌</td><td style="padding: 8px; font-weight: bold;"></td></tr>
<tr><td style="padding: 8px; color: #666;">流量池</td><td style="padding: 8px; font-weight: bold;">2000 GB / (硬切 · 到量即停)</td></tr>
<tr><td style="padding: 8px; color: #666;">重置日期</td><td style="padding: 8px;">1</td></tr>
<tr><td style="padding: 8px; color: #666;">协议</td><td style="padding: 8px;">VLESS + Reality ()</td></tr>
<tr><td style="padding: 8px; color: #666;">节点位置</td><td style="padding: 8px;">🇸🇬 ()</td></tr>
<tr><td style="padding: 8px; color: #666;">新增</td><td style="padding: 8px;">📊 · · </td></tr>
</table>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #aaa; font-size: 11px; text-align: center;">
光湖语言世界 · 铸渊专线 V3 测试版 · ${now}<br>
国作登字-2026-A-00037559
</p>
</div>
</body>
</html>`;
}
// ── 生成告警邮件HTML ─────────────────────────
function generateAlertEmail(message) {
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
@ -226,12 +320,13 @@ function generateAlertEmail(message) {
// ── 主入口 ───────────────────────────────────
async function main() {
const [,, action, target, urlOverride] = process.argv;
const [,, action, target, urlOverride, dashboardUrl] = process.argv;
if (!action) {
console.log('用法:');
console.log(' node send-subscription.js send <email> — 发送订阅链接');
console.log(' node send-subscription.js alert <msg> — 发送告警邮件');
console.log(' node send-subscription.js send <email> — 发送V2订阅链接');
console.log(' node send-subscription.js send-v3 <email> <url> <dashboard> — 发送V3订阅链接');
console.log(' node send-subscription.js alert <msg> — 发送告警邮件');
process.exit(0);
}
@ -255,6 +350,37 @@ async function main() {
process.exit(1);
}
} else if (action === 'send-v3') {
const email = target;
if (!email) {
console.error('❌ 请指定目标邮箱');
process.exit(1);
}
if (!urlOverride) {
console.error('❌ 请提供V3订阅URL');
process.exit(1);
}
let dashboard = dashboardUrl;
if (!dashboard) {
if (urlOverride.includes('/sub/')) {
dashboard = urlOverride.replace('/sub/', '/dashboard/');
} else {
console.error('❌ 订阅URL中未包含/sub/路径请手动提供仪表盘URL');
process.exit(1);
}
}
console.log(`📧 发送V3订阅链接到: ${email}`);
const html = generateV3SubscriptionEmail(urlOverride, dashboard);
try {
await sendEmail(email, '🌐 光湖语言世界 · V3测试订阅链接', html);
console.log('✅ V3订阅链接已发送');
} catch (err) {
console.error('❌ 发送失败:', err.message);
process.exit(1);
}
} else if (action === 'alert') {
const message = target || '未知告警';
const email = config.smtp_user; // 告警发到管理员邮箱