231 lines
7.7 KiB
JavaScript
231 lines
7.7 KiB
JavaScript
// scripts/bridge/distribute.js
|
||
// 🌉 桥接·广播分发到 IM
|
||
//
|
||
// 根据分发清单,查询人格体注册表获取通知渠道,
|
||
// 将 PDF 下载链接推送到飞书/钉钉/邮箱
|
||
//
|
||
// 环境变量:
|
||
// NOTION_TOKEN Notion API token(查询人格体注册表)
|
||
// DIST_MANIFEST dist-manifest.json 路径
|
||
// FEISHU_APP_ID 飞书 App ID
|
||
// FEISHU_APP_SECRET 飞书 App Secret
|
||
// DINGTALK_TOKEN 钉钉机器人 Token
|
||
// SMTP_USER 邮件发送者
|
||
// SMTP_PASS 邮件授权码
|
||
|
||
'use strict';
|
||
|
||
const https = require('https');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const OUTPUT_DIR = path.join('data', 'broadcasts', 'pdf');
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// HTTP 请求辅助
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
function httpsPost(hostname, apiPath, body, headers) {
|
||
return new Promise((resolve, reject) => {
|
||
const payload = JSON.stringify(body);
|
||
const opts = {
|
||
hostname,
|
||
port: 443,
|
||
path: apiPath,
|
||
method: 'POST',
|
||
headers: Object.assign({
|
||
'Content-Type': 'application/json',
|
||
'Content-Length': Buffer.byteLength(payload),
|
||
}, headers || {}),
|
||
};
|
||
|
||
const req = https.request(opts, res => {
|
||
let data = '';
|
||
res.on('data', chunk => data += chunk);
|
||
res.on('end', () => {
|
||
resolve({ status: res.statusCode, body: data });
|
||
});
|
||
});
|
||
|
||
req.on('error', reject);
|
||
req.write(payload);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// 飞书分发
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
async function getFeishuToken() {
|
||
const appId = process.env.FEISHU_APP_ID;
|
||
const appSecret = process.env.FEISHU_APP_SECRET;
|
||
|
||
if (!appId || !appSecret) return null;
|
||
|
||
const result = await httpsPost(
|
||
'open.feishu.cn',
|
||
'/open-apis/auth/v3/tenant_access_token/internal',
|
||
{ app_id: appId, app_secret: appSecret }
|
||
);
|
||
|
||
const parsed = JSON.parse(result.body);
|
||
return parsed.tenant_access_token || null;
|
||
}
|
||
|
||
async function sendFeishuMessage(token, chatId, title, downloadUrl) {
|
||
const text = `📡 新广播已生成\n\n📋 ${title}\n\n📎 下载链接: ${downloadUrl}\n\n⏰ ${new Date().toISOString()}`;
|
||
|
||
return httpsPost(
|
||
'open.feishu.cn',
|
||
'/open-apis/im/v1/messages?receive_id_type=chat_id',
|
||
{
|
||
receive_id: chatId,
|
||
msg_type: 'text',
|
||
content: JSON.stringify({ text }),
|
||
},
|
||
{ 'Authorization': 'Bearer ' + token }
|
||
);
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// 钉钉分发
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
async function sendDingtalkMessage(webhookToken, title, downloadUrl) {
|
||
const text = `📡 新广播已生成\n\n📋 ${title}\n\n📎 下载链接: ${downloadUrl}`;
|
||
|
||
return httpsPost(
|
||
'oapi.dingtalk.com',
|
||
`/robot/send?access_token=${webhookToken}`,
|
||
{
|
||
msgtype: 'text',
|
||
text: { content: text },
|
||
}
|
||
);
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// 邮件分发
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
async function sendEmailNotification(to, title, downloadUrl) {
|
||
const smtpUser = process.env.SMTP_USER;
|
||
const smtpPass = process.env.SMTP_PASS;
|
||
|
||
if (!smtpUser || !smtpPass) {
|
||
console.log(' ⚠️ SMTP 未配置,跳过邮件通知');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const nodemailer = require('nodemailer');
|
||
const transporter = nodemailer.createTransport({
|
||
host: 'smtp.qq.com',
|
||
port: 465,
|
||
secure: true,
|
||
auth: { user: smtpUser, pass: smtpPass },
|
||
});
|
||
|
||
await transporter.sendMail({
|
||
from: `"光湖广播" <${smtpUser}>`,
|
||
to: to,
|
||
subject: `📡 ${title}`,
|
||
html: `
|
||
<h2>📡 新广播已生成</h2>
|
||
<p><strong>标题:</strong> ${title}</p>
|
||
<p><strong>下载链接:</strong> <a href="${downloadUrl}">${downloadUrl}</a></p>
|
||
<p><em>— 光湖广播系统 · guanghulab.com</em></p>
|
||
`,
|
||
});
|
||
console.log(` ✅ 邮件已发送: ${to}`);
|
||
} catch (e) {
|
||
console.error(` ❌ 邮件发送失败: ${e.message}`);
|
||
}
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════
|
||
// 主逻辑
|
||
// ══════════════════════════════════════════════════════════
|
||
|
||
async function main() {
|
||
const manifestFile = process.env.DIST_MANIFEST ||
|
||
path.join(OUTPUT_DIR, 'dist-manifest.json');
|
||
|
||
if (!fs.existsSync(manifestFile)) {
|
||
console.log('📭 无 dist-manifest.json,跳过分发');
|
||
process.exit(0);
|
||
}
|
||
|
||
const items = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
|
||
if (items.length === 0) {
|
||
console.log('📭 分发列表为空,跳过');
|
||
process.exit(0);
|
||
}
|
||
|
||
console.log(`📢 开始分发 ${items.length} 条广播…`);
|
||
|
||
// 飞书 Token(一次性获取)
|
||
let feishuToken = null;
|
||
if (process.env.FEISHU_APP_ID) {
|
||
try {
|
||
feishuToken = await getFeishuToken();
|
||
if (feishuToken) console.log(' 🔑 飞书 Token 获取成功');
|
||
} catch (e) {
|
||
console.error(` ⚠️ 飞书 Token 获取失败: ${e.message}`);
|
||
}
|
||
}
|
||
|
||
const dingtalkToken = process.env.DINGTALK_TOKEN;
|
||
let distributed = 0;
|
||
|
||
for (const item of items) {
|
||
const title = item.title || '光湖广播';
|
||
const downloadUrl = item.download_url || '';
|
||
|
||
if (!downloadUrl) {
|
||
console.log(` ⚠️ ${title}: 无下载链接,跳过`);
|
||
continue;
|
||
}
|
||
|
||
console.log(`\n📡 分发: ${title}`);
|
||
|
||
// 飞书分发(广播到默认群)
|
||
if (feishuToken && process.env.FEISHU_BROADCAST_CHAT_ID) {
|
||
try {
|
||
await sendFeishuMessage(feishuToken, process.env.FEISHU_BROADCAST_CHAT_ID, title, downloadUrl);
|
||
console.log(' ✅ 飞书群已推送');
|
||
} catch (e) {
|
||
console.error(` ❌ 飞书推送失败: ${e.message}`);
|
||
}
|
||
}
|
||
|
||
// 钉钉分发
|
||
if (dingtalkToken) {
|
||
try {
|
||
await sendDingtalkMessage(dingtalkToken, title, downloadUrl);
|
||
console.log(' ✅ 钉钉已推送');
|
||
} catch (e) {
|
||
console.error(` ❌ 钉钉推送失败: ${e.message}`);
|
||
}
|
||
}
|
||
|
||
// 邮件分发(发送给冰朔)
|
||
if (process.env.SMTP_USER && process.env.ALERT_EMAIL) {
|
||
await sendEmailNotification(process.env.ALERT_EMAIL, title, downloadUrl);
|
||
}
|
||
|
||
distributed++;
|
||
}
|
||
|
||
console.log(`\n✅ 分发完成 · ${distributed}/${items.length} 条广播已推送`);
|
||
|
||
if (process.env.GITHUB_OUTPUT) {
|
||
fs.appendFileSync(process.env.GITHUB_OUTPUT,
|
||
`distributed_count=${distributed}\n`
|
||
);
|
||
}
|
||
}
|
||
|
||
main().catch(e => { console.error(e); process.exit(1); });
|