Files
CipherTalk/scripts/send-telegram-release.js
T
2026-04-02 03:27:11 +08:00

184 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require('fs')
const path = require('path')
const rootDir = path.resolve(__dirname, '..')
const releaseDir = path.join(rootDir, 'release')
const contextPath = path.join(releaseDir, 'release-context.json')
const releaseBodyPath = path.join(releaseDir, 'release-body.md')
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || ''
const TELEGRAM_CHAT_IDS = String(process.env.TELEGRAM_CHAT_IDS || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean)
const TELEGRAM_RELEASE_COVER_URL = process.env.TELEGRAM_RELEASE_COVER_URL || ''
const mode = process.env.TELEGRAM_NOTIFY_MODE || 'success'
function escapeHtml(text) {
return String(text || '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
function markdownToPlainSummary(markdown) {
return String(markdown || '')
.replace(/^#+\s*/gm, '')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
.replace(/[*_`>-]/g, '')
.replace(/\n{3,}/g, '\n\n')
.trim()
}
function getContext() {
if (!fs.existsSync(contextPath)) return null
return JSON.parse(fs.readFileSync(contextPath, 'utf8'))
}
function getReleaseBody() {
if (!fs.existsSync(releaseBodyPath)) return ''
return fs.readFileSync(releaseBodyPath, 'utf8')
}
function buildButtons(version) {
const releaseUrl = `https://github.com/ILoveBingLu/CipherTalk/releases/tag/v${version}`
const installerUrl = `https://github.com/ILoveBingLu/CipherTalk/releases/download/v${version}/CipherTalk-${version}-Setup.exe`
return {
inline_keyboard: [
[
{ text: '📦 查看 Release', url: releaseUrl },
{ text: '⬇️ 下载安装包', url: installerUrl }
]
]
}
}
function buildSuccessMessage(context, releaseBody) {
const version = context?.version || process.env.RELEASE_VERSION || 'unknown'
const blockedVersions = context?.forceUpdate?.blockedVersions || []
const minimumSupportedVersion = context?.forceUpdate?.minimumSupportedVersion || ''
const hasForceUpdate = Boolean(minimumSupportedVersion || blockedVersions.length > 0)
const summary = markdownToPlainSummary(releaseBody)
.split('\n')
.filter(Boolean)
.slice(0, 8)
.join('\n')
const thanks = []
const primaryLogins = new Set(['ILoveBingLu'])
const primaryNames = new Set(['ILoveBingLu', 'BingLu', 'ILoveBinglu'])
for (const pr of context?.pullRequests || []) {
if (pr?.authorLogin && !primaryLogins.has(pr.authorLogin)) {
thanks.push(`🙏 感谢 @${pr.authorLogin} 提交 PR #${pr.number}`)
}
}
for (const commit of context?.commits || []) {
const hasPrRef = /#(\d+)/.test(commit.subject || '')
const authorName = String(commit.authorName || '').trim()
if (!hasPrRef && authorName && !primaryNames.has(authorName)) {
thanks.push(`🙏 感谢 ${authorName} 提交改动《${commit.subject}`)
}
}
const lines = [
`🚀 <b>CipherTalk v${escapeHtml(version)} 已发布</b>`,
'',
'📝 <b>本次更新摘要</b>',
escapeHtml(summary || '本次版本已完成发布,可点击下方按钮查看完整说明。'),
]
if (hasForceUpdate) {
lines.push('', '⚠️ <b>强制更新提醒</b>')
if (minimumSupportedVersion) {
lines.push(`- 最低安全版本:<code>${escapeHtml(minimumSupportedVersion)}</code>`)
}
if (blockedVersions.length) {
lines.push(`- 封禁版本:<code>${escapeHtml(blockedVersions.join(', '))}</code>`)
}
}
lines.push('', '🔗 <b>相关链接</b>', `- GitHub Release<a href="https://github.com/ILoveBingLu/CipherTalk/releases/tag/v${encodeURIComponent(version)}">查看发布说明</a>`)
if (thanks.length) {
lines.push('', '🌟 <b>感谢贡献者</b>', ...thanks.map((line) => escapeHtml(line)))
}
return lines.join('\n')
}
function buildFailureMessage() {
const workflowUrl = process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
: ''
const version = process.env.RELEASE_VERSION || process.env.GITHUB_REF_NAME || 'unknown'
const lines = [
`❌ <b>CipherTalk ${escapeHtml(version)} 发布失败</b>`,
'',
'请尽快检查 GitHub Actions 日志。'
]
if (workflowUrl) {
lines.push('', `🔗 <a href="${workflowUrl}">查看失败日志</a>`)
}
return lines.join('\n')
}
async function sendTelegramMessage(chatId, text, replyMarkup) {
const body = {
chat_id: chatId,
text,
parse_mode: 'HTML',
disable_web_page_preview: false,
reply_markup: replyMarkup
}
const endpoint = TELEGRAM_RELEASE_COVER_URL ? 'sendPhoto' : 'sendMessage'
const payload = TELEGRAM_RELEASE_COVER_URL
? {
chat_id: chatId,
photo: TELEGRAM_RELEASE_COVER_URL,
caption: text,
parse_mode: 'HTML',
reply_markup: replyMarkup
}
: body
const response = await fetch(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
if (!response.ok) {
const raw = await response.text()
throw new Error(`Telegram 发送失败 (${response.status}): ${raw}`)
}
}
async function main() {
if (!TELEGRAM_BOT_TOKEN || TELEGRAM_CHAT_IDS.length === 0) {
console.log('️ Telegram 未配置,跳过通知')
return
}
const context = getContext()
const releaseBody = getReleaseBody()
const version = context?.version || process.env.RELEASE_VERSION || 'unknown'
const text = mode === 'failure'
? buildFailureMessage()
: buildSuccessMessage(context, releaseBody)
const replyMarkup = mode === 'failure' ? undefined : buildButtons(version)
for (const chatId of TELEGRAM_CHAT_IDS) {
await sendTelegramMessage(chatId, text, replyMarkup)
}
console.log(`✅ 已发送 Telegram 通知到 ${TELEGRAM_CHAT_IDS.length} 个目标`)
}
main().catch((error) => {
console.error('❌ Telegram 通知失败:', error)
process.exit(1)
})