feat(telegram): 新增 Telegram 机器人通知与快捷审核功能

- 新增 Telegram 通知功能,支持通过机器人接收新评论通知
- 在 Telegram 消息中提供“批准”和“删除”按钮,支持快捷审核
- 新增后台 Telegram 设置页面,支持配置 Bot Token、Chat ID 和开关
- 新增 `/api/telegram/webhook` 端点处理按钮回调
- 更新邮件通知逻辑,统一使用评论设置中的 `adminEmail` 作为收件人
- 更新相关文档,包括功能说明和 API 文档
This commit is contained in:
anghunk
2026-01-30 17:49:47 +08:00
parent c4954fca89
commit e1568f60ff
16 changed files with 678 additions and 85 deletions

View File

@@ -53,18 +53,25 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
`
}
// 并行获取评论和管理员邮箱
// 对 adminEmail 查询进行错误捕获,防止因 Settings 表不存在导致整个接口失败
const [commentsResult, adminEmailRow] = await Promise.all([
c.env.CWD_DB.prepare(query).bind(...slugList).all(),
c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
.bind('admin_notify_email')
.first<{ value: string }>()
.catch(() => null)
const [commentsResult, adminEmailRows] = await Promise.all([
c.env.CWD_DB.prepare(query).bind(...slugList).all(),
c.env.CWD_DB.prepare('SELECT key, value FROM Settings WHERE key IN (?, ?)')
.bind('comment_admin_email', 'admin_notify_email')
.all<{ key: string; value: string }>()
.catch(() => null)
]);
const results = commentsResult.results;
const adminEmail = adminEmailRow?.value || null;
let adminEmail: string | null = null;
if (adminEmailRows && Array.isArray(adminEmailRows.results)) {
const commentEmailRow = adminEmailRows.results.find(
(row) => row.key === 'comment_admin_email'
);
const legacyEmailRow = adminEmailRows.results.find(
(row) => row.key === 'admin_notify_email'
);
adminEmail = commentEmailRow?.value || legacyEmailRow?.value || null;
}
// 2. 批量处理头像并格式化
const allComments = await Promise.all(results.map(async (row: any) => ({

View File

@@ -11,6 +11,7 @@ import {
loadEmailNotificationSettings,
EmailNotificationSettings
} from '../../utils/email';
import { loadTelegramSettings, sendTelegramMessage } from '../../utils/telegram';
// 检查内容,将<script>标签之间的内容删除
export function checkContent(content: string): string {
@@ -156,7 +157,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
const defaultStatus = requireReview && !isAdminComment ? "pending" : "approved";
try {
const { success } = await c.env.CWD_DB.prepare(`
const result = await c.env.CWD_DB.prepare(`
INSERT INTO Comment (
created, post_slug, name, email, url, ip_address,
os, browser, device, ua, content_text, content_html,
@@ -179,12 +180,14 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
defaultStatus
).run();
if (!success) throw new Error("Database insert failed");
if (!result.success) throw new Error("Database insert failed");
const commentId = result.meta?.last_row_id;
console.log('PostComment:inserted', {
postSlug: post_slug,
hasParent: parentId !== null && parentId !== undefined,
ip
ip,
commentId
});
let notifySettings: EmailNotificationSettings = {
@@ -196,14 +199,19 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
console.error('PostComment:mailDispatch:loadEmailSettingsFailed', e);
}
if (!notifySettings.globalEnabled) {
console.log('PostComment:mailDispatch:disabledByGlobalConfig');
} else {
console.log('PostComment:mailDispatch:start', {
hasParent: parentId !== null && parentId !== undefined
});
c.executionCtx.waitUntil((async () => {
try {
console.log('PostComment:notify:start', {
hasParent: parentId !== null && parentId !== undefined,
emailEnabled: notifySettings.globalEnabled
});
c.executionCtx.waitUntil((async () => {
try {
if (!notifySettings.globalEnabled) {
console.log('PostComment:mailDispatch:disabledByGlobalConfig');
} else {
console.log('PostComment:mailDispatch:start', {
hasParent: parentId !== null && parentId !== undefined
});
if (parentId !== null && parentId !== undefined) {
let adminEmail: string | null = null;
try {
@@ -247,11 +255,44 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
}, notifySettings.smtp, notifySettings.templates?.admin);
console.log('PostComment:mailDispatch:admin:sent');
}
} catch (mailError) {
console.error("Mail Notification Failed:", mailError);
}
})());
}
} catch (mailError) {
console.error("Mail Notification Failed:", mailError);
}
try {
const tgSettings = await loadTelegramSettings(c.env);
if (tgSettings.notifyEnabled && tgSettings.botToken && tgSettings.chatId && commentId) {
const buttons: { text: string; callback_data: string }[] = [];
if (defaultStatus === 'pending') {
buttons.push({ text: "批准", callback_data: `approve:${commentId}` });
buttons.push({ text: "删除", callback_data: `delete:${commentId}` });
} else {
buttons.push({ text: "删除", callback_data: `delete:${commentId}` });
}
const message = `
💬 *新评论*
文章: [${data.post_title || 'Untitled'}](${data.post_url || '#'})
作者: ${name} (${email})
状态: ${defaultStatus === 'pending' ? '⏳ 待审核' : '✅ 已通过'}
${contentText}
#ID:${commentId}
`.trim();
await sendTelegramMessage(tgSettings.botToken, tgSettings.chatId, message, {
reply_markup: {
inline_keyboard: [buttons]
}
});
}
} catch (tgError) {
console.error('Telegram Notification Failed:', tgError);
}
})());
if (defaultStatus === "pending") {
return c.json({