// 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: `
ๆ ้ข: ${title}
ไธ่ฝฝ้พๆฅ: ${downloadUrl}
โ ๅ ๆนๅนฟๆญ็ณป็ป ยท guanghulab.com
`, }); 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); });