Files
kt-financial-system/apps/backend/utils/telegram-bot.ts
你的用户名 a4e4168c00
Some checks failed
Deploy to Production / Build and Test (push) Has been cancelled
Deploy to Production / Deploy to Server (push) Has been cancelled
feat: 添加Telegram Bot通知功能
 新功能:
- 添加Telegram Bot通知支持
- 账目记录自动推送到Telegram
- 支持多个Bot配置管理
- 支持群组和个人通知

📊 数据库:
- 新增telegram_notification_configs表
- 存储Bot配置和通知类型

🔧 后端API:
- GET /api/telegram/notifications - 获取所有配置
- POST /api/telegram/notifications - 创建配置
- PUT /api/telegram/notifications/:id - 更新配置
- DELETE /api/telegram/notifications/:id - 删除配置
- POST /api/telegram/test - 测试Bot配置

💬 通知功能:
- 自动发送账目记录通知
- 包含交易类型、金额、分类、账户等信息
- 支持格式化显示(类型图标、状态标识)
- 配置创建时自动测试有效性

📝 文档:
- 添加完整的使用说明文档
- API接口说明和示例
- 常见问题解答
2025-11-04 23:15:19 +08:00

230 lines
5.6 KiB
TypeScript
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.

import db from './sqlite';
interface TelegramNotificationConfig {
id: number;
name: string;
botToken: string;
chatId: string;
notificationTypes: string[];
isEnabled: boolean;
}
interface TransactionNotificationData {
id: number;
type: string;
amount: number;
currency: string;
categoryName?: string;
accountName?: string;
transactionDate: string;
description?: string;
status: string;
}
/**
* 获取所有启用的Telegram通知配置
*/
export function getEnabledNotificationConfigs(
notificationType: string = 'transaction',
): TelegramNotificationConfig[] {
const rows = db
.prepare<{ id: number; name: string; bot_token: string; chat_id: string; notification_types: string; is_enabled: number }>(
`
SELECT id, name, bot_token, chat_id, notification_types, is_enabled
FROM telegram_notification_configs
WHERE is_enabled = 1
`,
)
.all();
return rows
.map((row) => ({
id: row.id,
name: row.name,
botToken: row.bot_token,
chatId: row.chat_id,
notificationTypes: JSON.parse(row.notification_types) as string[],
isEnabled: row.is_enabled === 1,
}))
.filter((config) => config.notificationTypes.includes(notificationType));
}
/**
* 格式化交易金额
*/
function formatAmount(amount: number, currency: string): string {
const formatted = amount.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
return `${currency} ${formatted}`;
}
/**
* 格式化交易类型
*/
function formatTransactionType(type: string): string {
const typeMap: Record<string, string> = {
income: '💰 收入',
expense: '💸 支出',
transfer: '🔄 转账',
};
return typeMap[type] || type;
}
/**
* 格式化交易状态
*/
function formatTransactionStatus(status: string): string {
const statusMap: Record<string, string> = {
draft: '📝 草稿',
pending: '⏳ 待审核',
approved: '✅ 已批准',
rejected: '❌ 已拒绝',
paid: '💵 已支付',
};
return statusMap[status] || status;
}
/**
* 构建交易通知消息
*/
function buildTransactionMessage(
transaction: TransactionNotificationData,
action: string = 'created',
): string {
const actionMap: Record<string, string> = {
created: '📋 新增账目记录',
updated: '✏️ 更新账目记录',
deleted: '🗑️ 删除账目记录',
};
const lines: string[] = [
`${actionMap[action] || '📋 账目记录'}`,
'',
`类型:${formatTransactionType(transaction.type)}`,
`金额:${formatAmount(transaction.amount, transaction.currency)}`,
`日期:${transaction.transactionDate}`,
];
if (transaction.categoryName) {
lines.push(`分类:${transaction.categoryName}`);
}
if (transaction.accountName) {
lines.push(`账户:${transaction.accountName}`);
}
lines.push(`状态:${formatTransactionStatus(transaction.status)}`);
if (transaction.description) {
lines.push(``, `备注:${transaction.description}`);
}
lines.push(
``,
`🕐 记录时间:${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`,
);
return lines.join('\n');
}
/**
* 发送Telegram消息
*/
async function sendTelegramMessage(
botToken: string,
chatId: string,
message: string,
): Promise<boolean> {
try {
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
chat_id: chatId,
text: message,
parse_mode: 'HTML',
}),
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
console.error(
'[telegram-bot] Failed to send message:',
response.status,
error,
);
return false;
}
return true;
} catch (error) {
console.error('[telegram-bot] Error sending message:', error);
return false;
}
}
/**
* 通知交易记录
*/
export async function notifyTransaction(
transaction: TransactionNotificationData,
action: string = 'created',
): Promise<void> {
const configs = getEnabledNotificationConfigs('transaction');
if (configs.length === 0) {
console.log('[telegram-bot] No enabled notification configs found');
return;
}
const message = buildTransactionMessage(transaction, action);
const results = await Promise.allSettled(
configs.map((config) =>
sendTelegramMessage(config.botToken, config.chatId, message),
),
);
results.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value) {
console.log(
`[telegram-bot] Sent notification via config: ${configs[index].name}`,
);
} else {
console.error(
`[telegram-bot] Failed to send notification via config: ${configs[index].name}`,
);
}
});
}
/**
* 测试Telegram Bot配置
*/
export async function testTelegramConfig(
botToken: string,
chatId: string,
): Promise<{ success: boolean; error?: string }> {
try {
const testMessage = `🤖 KT财务系统\n\n✅ Telegram通知配置测试成功\n\n🕐 ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`;
const success = await sendTelegramMessage(botToken, chatId, testMessage);
if (success) {
return { success: true };
} else {
return { success: false, error: '发送消息失败请检查Bot Token和Chat ID' };
}
} catch (error: unknown) {
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
};
}
}