Files
kt-financial-system/apps/backend/utils/telegram-bot.ts
你的用户名 b68511b2e2
Some checks failed
Deploy to Production / Build and Test (push) Successful in 10m51s
Deploy to Production / Deploy to Server (push) Failing after 6m41s
feat: migrate backend storage to postgres
2025-11-06 22:01:50 +08:00

234 lines
5.6 KiB
TypeScript
Raw Permalink 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 { query } from './db';
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 async function getEnabledNotificationConfigs(
notificationType: string = 'transaction',
): Promise<TelegramNotificationConfig[]> {
const { rows } = await query<{
bot_token: string;
chat_id: string;
id: number;
is_enabled: boolean;
name: string;
notification_types: string;
}>(
`SELECT id, name, bot_token, chat_id, notification_types, is_enabled
FROM telegram_notification_configs
WHERE is_enabled = TRUE`,
);
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,
}))
.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 = await getEnabledNotificationConfigs('transaction');
if (configs.length === 0) {
console.warn('[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.warn(
`[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<{ error?: string; success: boolean }> {
try {
const testMessage = `🤖 KT财务系统\n\n✅ Telegram通知配置测试成功\n\n🕐 ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`;
const success = await sendTelegramMessage(botToken, chatId, testMessage);
return success
? { success: true }
: {
success: false,
error: '发送消息失败请检查Bot Token和Chat ID',
};
} catch (error: unknown) {
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
};
}
}