import { readFileSync } from 'fs' import axios from 'axios' import { log_error, log_info, log_success } from './utils.mjs' const CHAT_ID_RELEASE = '@clash_verge_re' // 正式发布频道 const CHAT_ID_TEST = '@vergetest' // 测试频道 async function sendTelegramNotification() { if (!process.env.TELEGRAM_BOT_TOKEN) { throw new Error('TELEGRAM_BOT_TOKEN is required') } const version = process.env.VERSION || (() => { const pkg = readFileSync('package.json', 'utf-8') return JSON.parse(pkg).version })() const downloadUrl = process.env.DOWNLOAD_URL || `https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v${version}` const isAutobuild = process.env.BUILD_TYPE === 'autobuild' || version.includes('autobuild') const chatId = isAutobuild ? CHAT_ID_TEST : CHAT_ID_RELEASE const buildType = isAutobuild ? '滚动更新版' : '正式版' log_info(`Preparing Telegram notification for ${buildType} ${version}`) log_info(`Target channel: ${chatId}`) log_info(`Download URL: ${downloadUrl}`) // 读取发布说明和下载地址 let releaseContent = '' try { releaseContent = readFileSync('release.txt', 'utf-8') log_info('成功读取 release.txt 文件') } catch (error) { log_error('无法读取 release.txt,使用默认发布说明', error) releaseContent = '更多新功能现已支持,详细更新日志请查看发布页面。' } // Markdown 转换为 HTML function convertMarkdownToTelegramHTML(content) { // Strip stray HTML tags and markdown bold from heading text const cleanHeading = (text) => text .replace(/<\/?[^>]+>/g, '') .replace(/\*\*/g, '') .trim() return content .split('\n') .map((line) => { if (line.trim().length === 0) { return '' } else if (line.startsWith('## ')) { return `${cleanHeading(line.replace('## ', ''))}` } else if (line.startsWith('### ')) { return `${cleanHeading(line.replace('### ', ''))}` } else if (line.startsWith('#### ')) { return `${cleanHeading(line.replace('#### ', ''))}` } else { let processedLine = line.replace( /\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => { const encodedUrl = encodeURI(url) return `${text}` }, ) processedLine = processedLine.replace(/\*\*([^*]+)\*\*/g, '$1') return processedLine } }) .join('\n') } function normalizeDetailsTags(content) { return content .replace( /\s*\s*(.*?)\s*<\/strong>\s*<\/summary>/g, '\n$1\n', ) .replace(/\s*(.*?)\s*<\/summary>/g, '\n$1\n') .replace(/<\/?details>/g, '') .replace(/<\/?strong>/g, (m) => (m === '' ? '' : '')) .replace(//g, '\n') } // Strip HTML tags not supported by Telegram and escape stray angle brackets function sanitizeTelegramHTML(content) { // Telegram supports: b, strong, i, em, u, ins, s, strike, del, // a, code, pre, blockquote, tg-spoiler, tg-emoji const allowedTags = /^\/?(b|strong|i|em|u|ins|s|strike|del|a|code|pre|blockquote|tg-spoiler|tg-emoji)(\s|>|$)/i return content.replace(/<\/?[^>]*>/g, (tag) => { const inner = tag.replace(/^<\/?/, '').replace(/>$/, '') if (allowedTags.test(inner) || allowedTags.test(tag.slice(1))) { return tag } // Escape unsupported tags so they display as text return tag.replace(//g, '>') }) } releaseContent = normalizeDetailsTags(releaseContent) const formattedContent = sanitizeTelegramHTML( convertMarkdownToTelegramHTML(releaseContent), ) const releaseTitle = isAutobuild ? '滚动更新版发布' : '正式发布' const encodedVersion = encodeURIComponent(version) const releaseTag = isAutobuild ? 'autobuild' : `v${version}` const content = `🎉 Clash Verge Rev v${version} ${releaseTitle}\n\n${formattedContent}` // 发送到 Telegram try { await axios.post( `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, { chat_id: chatId, text: content, link_preview_options: { is_disabled: false, url: `https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v${encodedVersion}`, prefer_large_media: true, }, parse_mode: 'HTML', }, ) log_success(`✅ Telegram 通知发送成功到 ${chatId}`) } catch (error) { log_error( `❌ Telegram 通知发送失败到 ${chatId}:`, error.response?.data || error.message, error, ) process.exit(1) } } // 执行函数 sendTelegramNotification().catch((error) => { log_error('脚本执行失败:', error) process.exit(1) })