// 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); });