✨ 新功能: - 添加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接口说明和示例 - 常见问题解答
230 lines
5.6 KiB
TypeScript
230 lines
5.6 KiB
TypeScript
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 : '未知错误',
|
||
};
|
||
}
|
||
}
|