index.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. require('dotenv').config();
  2. const express = require('express');
  3. const cors = require('cors');
  4. const path = require('path');
  5. const TelegramBot = require('node-telegram-bot-api');
  6. const fs = require('fs');
  7. const moment = require('moment');
  8. const {
  9. pool,
  10. testConnection
  11. } = require('./config/database');
  12. const initDatabase = require('./config/initDb');
  13. const Group = require('./models/Group');
  14. const Transaction = require('./models/Transaction');
  15. // 日志格式化函数
  16. function formatLog(data) {
  17. const separator = '-'.repeat(30);
  18. let logMessage = `${separator}\n`;
  19. if (typeof data === 'object') {
  20. logMessage += Object.entries(data)
  21. .map(([key, value]) => `${key}: ${value}`)
  22. .join('\n');
  23. } else {
  24. logMessage += data;
  25. }
  26. logMessage += `\n${separator}\n`;
  27. return logMessage;
  28. }
  29. const app = express();
  30. // 初始化数据存储
  31. let data = {
  32. deposits: [], // 入款记录
  33. withdrawals: [], // 下发记录
  34. lastUpdate: null,
  35. allowedGroups: [] // 允许使用的群组ID
  36. };
  37. // 创建机器人实例
  38. const bot = new TelegramBot(process.env.BOT_TOKEN, {
  39. polling: true
  40. });
  41. // 中间件
  42. app.use(cors());
  43. app.use(express.json());
  44. app.use(express.urlencoded({ extended: true }));
  45. app.use('/admin/views', express.static(path.join(__dirname, 'views')));
  46. // 路由
  47. app.get('/', (req, res) => {
  48. res.sendFile(path.join(__dirname, 'views', 'login.html'));
  49. });
  50. app.use('/api/users', require('./routes/userRoutes'));
  51. app.use('/api/groups', require('./routes/groupRoutes'));
  52. app.use('/api/transactions', require('./routes/transactionRoutes'));
  53. app.use('/api/statistics', require('./routes/statisticsRoutes'));
  54. app.use('/api/settings', require('./routes/settingsRoutes'));
  55. // 检查群组权限
  56. function isGroupAllowed(chatId) {
  57. const chatIdStr = chatId.toString();
  58. return data.allowedGroups.includes(chatIdStr) ||
  59. data.allowedGroups.includes(chatIdStr.replace('-', ''));
  60. }
  61. // 检查是否是管理员
  62. function isAdmin(userId) {
  63. return process.env.ADMIN_IDS.split(',').includes(userId.toString());
  64. }
  65. // 处理消息发送
  66. async function sendMessage(chatId, text, options = {}) {
  67. try {
  68. // 如果包含内联键盘,验证URL
  69. if (options.reply_markup && options.reply_markup.inline_keyboard) {
  70. const keyboard = generateInlineKeyboard(chatId);
  71. if (!keyboard) {
  72. // 如果键盘无效,发送不带键盘的消息
  73. return await bot.sendMessage(chatId, text, {
  74. parse_mode: 'HTML'
  75. });
  76. }
  77. options.reply_markup = keyboard;
  78. }
  79. return await bot.sendMessage(chatId, text, {
  80. ...options,
  81. parse_mode: 'HTML'
  82. });
  83. } catch (error) {
  84. console.error('发送消息失败:', error);
  85. if (error.message.includes('bot was kicked from the group chat')) {
  86. const index = data.allowedGroups.indexOf(chatId.toString());
  87. if (index > -1) {
  88. data.allowedGroups.splice(index, 1);
  89. saveData();
  90. console.log(`群组 ${chatId} 已被移除出允许列表`);
  91. }
  92. }
  93. return null;
  94. }
  95. }
  96. // 处理快捷命令
  97. bot.on('message', async (msg) => {
  98. if (!isGroupAllowed(msg.chat.id)) return;
  99. const text = msg.text?.trim();
  100. if (!text) return;
  101. // 处理入款命令
  102. if (text.startsWith('+')) {
  103. const amount = parseFloat(text.substring(1));
  104. if (!isNaN(amount)) {
  105. const transactionData = {
  106. groupId: msg.chat.id.toString(),
  107. groupName: msg.chat.title || '未命名群组',
  108. amount: amount,
  109. type: 'deposit'
  110. };
  111. try {
  112. const result = await Transaction.deposit(transactionData);
  113. if (result.success) {
  114. const billMessage = await generateBillMessage(msg.chat.id);
  115. if (billMessage) {
  116. await sendMessage(msg.chat.id, billMessage, {
  117. reply_markup: generateInlineKeyboard(msg.chat.id)
  118. });
  119. console.log(`入款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  120. } else {
  121. await sendMessage(msg.chat.id, '入款成功,但获取账单信息失败');
  122. console.log(`入款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  123. }
  124. } else {
  125. await sendMessage(msg.chat.id, result.message || '入款失败');
  126. console.log(`入款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
  127. }
  128. } catch (error) {
  129. console.error('快捷入款失败:', error);
  130. await sendMessage(msg.chat.id, '记录入款失败,请稍后重试');
  131. }
  132. }
  133. }
  134. // 处理入款修正命令
  135. else if (text.startsWith('-') && !text.includes('u')) {
  136. const amount = parseFloat(text.substring(1));
  137. if (!isNaN(amount)) {
  138. const transactionData = {
  139. groupId: msg.chat.id.toString(),
  140. groupName: msg.chat.title || '未命名群组',
  141. amount: -amount,
  142. type: 'deposit'
  143. };
  144. try {
  145. const result = await Transaction.deposit(transactionData);
  146. if (result.success) {
  147. const billMessage = await generateBillMessage(msg.chat.id);
  148. if (billMessage) {
  149. await sendMessage(msg.chat.id, billMessage, {
  150. reply_markup: generateInlineKeyboard(msg.chat.id)
  151. });
  152. console.log(`入款修正成功 - 群组: ${msg.chat.title}, 金额: -${amount}, 时间: ${new Date().toLocaleString()}`);
  153. } else {
  154. await sendMessage(msg.chat.id, '入款修正成功,但获取账单信息失败');
  155. console.log(`入款修正成功(无账单) - 群组: ${msg.chat.title}, 金额: -${amount}, 时间: ${new Date().toLocaleString()}`);
  156. }
  157. } else {
  158. await sendMessage(msg.chat.id, result.message || '入款修正失败');
  159. console.log(`入款修正失败 - 群组: ${msg.chat.title}, 金额: -${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
  160. }
  161. } catch (error) {
  162. console.error('快捷入款修正失败:', error);
  163. await sendMessage(msg.chat.id, '记录入款修正失败,请稍后重试');
  164. }
  165. }
  166. }
  167. // 处理回款命令
  168. else if (text.toLowerCase().includes('u')) {
  169. const amount = parseFloat(text.replace(/[^0-9.-]/g, ''));
  170. if (!isNaN(amount)) {
  171. const transactionData = {
  172. groupId: msg.chat.id.toString(),
  173. groupName: msg.chat.title || '未命名群组',
  174. amount: amount,
  175. type: 'withdrawal'
  176. };
  177. try {
  178. const result = await Transaction.withdrawal(transactionData);
  179. if (result.success) {
  180. const billMessage = await generateBillMessage(msg.chat.id);
  181. if (billMessage) {
  182. await sendMessage(msg.chat.id, billMessage, {
  183. reply_markup: generateInlineKeyboard(msg.chat.id)
  184. });
  185. console.log(`回款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  186. } else {
  187. await sendMessage(msg.chat.id, '回款成功,但获取账单信息失败');
  188. console.log(`回款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  189. }
  190. } else {
  191. await sendMessage(msg.chat.id, result.message || '回款失败');
  192. console.log(`回款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
  193. }
  194. } catch (error) {
  195. console.error('快捷回款失败:', error);
  196. await sendMessage(msg.chat.id, '记录回款失败,请稍后重试');
  197. }
  198. }
  199. }
  200. // 处理回款修正命令
  201. else if (text.toLowerCase().includes('u') && text.includes('-')) {
  202. const amount = parseFloat(text.replace(/[^0-9.-]/g, ''));
  203. if (!isNaN(amount)) {
  204. const transactionData = {
  205. groupId: msg.chat.id.toString(),
  206. groupName: msg.chat.title || '未命名群组',
  207. amount: -amount,
  208. type: 'withdrawal'
  209. };
  210. try {
  211. const result = await Transaction.withdrawal(transactionData);
  212. if (result.success) {
  213. const billMessage = await generateBillMessage(msg.chat.id);
  214. if (billMessage) {
  215. await sendMessage(msg.chat.id, billMessage, {
  216. reply_markup: generateInlineKeyboard(msg.chat.id)
  217. });
  218. console.log(`回款修正成功 - 群组: ${msg.chat.title}, 金额: -${amount}, 时间: ${new Date().toLocaleString()}`);
  219. } else {
  220. await sendMessage(msg.chat.id, '回款修正成功,但获取账单信息失败');
  221. console.log(`回款修正成功(无账单) - 群组: ${msg.chat.title}, 金额: -${amount}, 时间: ${new Date().toLocaleString()}`);
  222. }
  223. } else {
  224. await sendMessage(msg.chat.id, result.message || '回款修正失败');
  225. console.log(`回款修正失败 - 群组: ${msg.chat.title}, 金额: -${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
  226. }
  227. } catch (error) {
  228. console.error('快捷回款修正失败:', error);
  229. await sendMessage(msg.chat.id, '记录回款修正失败,请稍后重试');
  230. }
  231. }
  232. }
  233. });
  234. // 处理新成员加入
  235. bot.on('new_chat_members', async (msg) => {
  236. const chatId = msg.chat.id;
  237. const newMembers = msg.new_chat_members;
  238. for (const member of newMembers) {
  239. if (member.id === (await bot.getMe()).id) {
  240. // 检查群组是否在允许列表中
  241. const chatIdStr = chatId.toString();
  242. try {
  243. // 先检查数据库中是否存在该群组
  244. const existingGroup = await Group.findByGroupId(chatIdStr);
  245. if (existingGroup) {
  246. // 如果群组存在,更新群组状态为活跃,同时更新群组名称和加入时间
  247. await pool.query(`
  248. UPDATE groups
  249. SET is_active = true,
  250. group_name = ?,
  251. last_join_time = CURRENT_TIMESTAMP
  252. WHERE group_id = ?
  253. `, [msg.chat.title || existingGroup.group_name, chatIdStr]);
  254. // 更新内存中的群组列表
  255. if (!data.allowedGroups.includes(chatIdStr)) {
  256. data.allowedGroups.push(chatIdStr);
  257. saveData();
  258. }
  259. // console.log(formatLog({
  260. // ID: chatIdStr,
  261. // 名称: msg.chat.title || existingGroup.group_name,
  262. // 类型: msg.chat.type,
  263. // 状态: '重新激活',
  264. // 更新时间: new Date().toLocaleString()
  265. // }));
  266. // 发送欢迎消息并显示当前账单
  267. await sendMessage(chatId, '感谢重新添加我为群组成员!');
  268. const billMessage = await generateBillMessage(chatId);
  269. if (billMessage) {
  270. await sendMessage(chatId, billMessage, {
  271. reply_markup: generateInlineKeyboard(chatId)
  272. });
  273. }
  274. } else {
  275. // 如果是新群组,打印被添加到新群组的信息
  276. console.log(formatLog({
  277. ID: chatId,
  278. 名称: msg.chat.title || '未命名群组',
  279. 类型: msg.chat.type,
  280. 描述: msg.chat.description || '无',
  281. '添加者信息': {
  282. ID: msg.from.id,
  283. 用户名: msg.from.username || '无',
  284. 姓名: msg.from.first_name,
  285. ...(msg.from.last_name && {
  286. 姓: msg.from.last_name
  287. })
  288. },
  289. 添加时间: new Date().toLocaleString()
  290. }));
  291. // 如果群组不存在,创建新群组
  292. const groupData = {
  293. groupId: chatIdStr,
  294. groupName: msg.chat.title || '未命名群组',
  295. groupType: msg.chat.type === 'private' ? 'personal' : msg.chat.type,
  296. creatorId: msg.from.id.toString()
  297. };
  298. console.log(formatLog(groupData));
  299. try {
  300. // 直接使用 SQL 插入群组数据
  301. const [result] = await pool.query(`
  302. INSERT INTO groups
  303. (group_id, group_name, group_type, creator_id, is_active, last_join_time)
  304. VALUES (?, ?, ?, ?, true, CURRENT_TIMESTAMP)
  305. `, [
  306. groupData.groupId,
  307. groupData.groupName,
  308. groupData.groupType,
  309. groupData.creatorId
  310. ]);
  311. console.log(formatLog(result));
  312. // 更新内存中的群组列表
  313. if (!data.allowedGroups.includes(chatIdStr)) {
  314. data.allowedGroups.push(chatIdStr);
  315. saveData();
  316. console.log(formatLog({
  317. ID: chatIdStr,
  318. 名称: groupData.groupName,
  319. 类型: groupData.groupType,
  320. 状态: '已启用',
  321. 添加时间: new Date().toLocaleString(),
  322. 操作者: msg.from.username || msg.from.first_name + ' (' + msg.from.id + ')'
  323. }));
  324. }
  325. console.log(formatLog({
  326. ID: chatIdStr,
  327. 名称: groupData.groupName,
  328. 类型: groupData.groupType,
  329. 状态: '已启用',
  330. 添加时间: new Date().toLocaleString(),
  331. 操作者: msg.from.username || msg.from.first_name + ' (' + msg.from.id + ')'
  332. }));
  333. try {
  334. // 尝试发送欢迎消息
  335. const welcomeMessage = await bot.sendMessage(chatId, '感谢添加我为群组成员!使用 /help 查看可用命令。', {
  336. parse_mode: 'HTML'
  337. });
  338. console.log(formatLog(welcomeMessage));
  339. // 尝试发送账单消息
  340. const billMessage = await generateBillMessage(chatId);
  341. if (billMessage) {
  342. const billResult = await bot.sendMessage(chatId, billMessage, {
  343. parse_mode: 'HTML',
  344. reply_markup: generateInlineKeyboard(chatId)
  345. });
  346. console.log(formatLog(billResult));
  347. }
  348. } catch (messageError) {
  349. console.error(formatLog(messageError));
  350. }
  351. } catch (error) {
  352. console.error(formatLog('创建群组过程中出错', error));
  353. try {
  354. await bot.sendMessage(chatId, '添加群组失败,请联系管理员。', {
  355. parse_mode: 'HTML'
  356. });
  357. } catch (messageError) {
  358. console.error(formatLog('发送错误消息失败', messageError));
  359. }
  360. }
  361. }
  362. } catch (error) {
  363. console.error(formatLog('处理群组加入失败', error));
  364. await sendMessage(chatId, '添加群组失败,请联系管理员。');
  365. }
  366. } else {
  367. // 其他新成员
  368. console.log(formatLog({
  369. member: member.username || member.first_name + ' (' + member.id + ')'
  370. }));
  371. await sendMessage(chatId, `欢迎 ${member.username || member.first_name} 加入群组!`);
  372. }
  373. }
  374. });
  375. // 处理机器人被移出群组
  376. bot.on('left_chat_member', async (msg) => {
  377. if (msg.left_chat_member.id === (await bot.getMe()).id) {
  378. const chatId = msg.chat.id.toString();
  379. try {
  380. // 更新数据库中的群组状态
  381. await pool.query(`
  382. UPDATE groups
  383. SET is_active = false,
  384. last_leave_time = CURRENT_TIMESTAMP
  385. WHERE group_id = ?
  386. `, [chatId]);
  387. // 从内存中的允许列表中移除
  388. const index = data.allowedGroups.indexOf(chatId);
  389. if (index > -1) {
  390. data.allowedGroups.splice(index, 1);
  391. saveData();
  392. }
  393. // console.log(formatLog({
  394. // ID: chatId,
  395. // 名称: msg.chat.title || '未命名群组',
  396. // 类型: msg.chat.type,
  397. // 状态: '已移除',
  398. // 移除时间: new Date().toLocaleString(),
  399. // 操作者: msg.from.username || msg.from.first_name + ' (' + msg.from.id + ')'
  400. // }));
  401. } catch (error) {
  402. console.error(formatLog('处理机器人被移出群组失败', error));
  403. }
  404. }
  405. });
  406. // 处理管理员命令
  407. bot.onText(/\/addgroup (.+)/, async (msg, match) => {
  408. if (!isAdmin(msg.from.id)) {
  409. sendMessage(msg.chat.id, '您没有权限执行此命令。');
  410. return;
  411. }
  412. const groupId = match[1].trim();
  413. if (!data.allowedGroups.includes(groupId)) {
  414. try {
  415. // 使用 createGroup 创建新群组
  416. const groupData = {
  417. groupId: groupId,
  418. groupName: '手动添加的群组',
  419. groupType: 'public',
  420. creatorId: msg.from.id.toString()
  421. };
  422. const result = await createGroup({
  423. body: groupData
  424. });
  425. if (result) {
  426. data.allowedGroups.push(groupId);
  427. saveData();
  428. console.log(formatLog({
  429. ID: groupId,
  430. 名称: groupData.groupName,
  431. 状态: '已启用',
  432. 添加时间: new Date().toLocaleString(),
  433. 操作者: msg.from.username || msg.from.first_name + ' (' + msg.from.id + ')'
  434. }));
  435. sendMessage(msg.chat.id, `群组 ${groupId} 已添加到允许列表。`);
  436. } else {
  437. sendMessage(msg.chat.id, '添加群组失败,请检查群组ID是否正确。');
  438. }
  439. } catch (error) {
  440. console.error(formatLog('创建群组失败', error));
  441. sendMessage(msg.chat.id, '添加群组失败,请稍后重试。');
  442. }
  443. } else {
  444. sendMessage(msg.chat.id, '该群组已在允许列表中。');
  445. }
  446. });
  447. // 处理入款命令
  448. bot.onText(/\/deposit (.+)/, async (msg, match) => {
  449. if (!isGroupAllowed(msg.chat.id)) {
  450. sendMessage(msg.chat.id, '该群组未授权使用此功能');
  451. return;
  452. }
  453. const amount = parseFloat(match[1]);
  454. if (isNaN(amount)) {
  455. sendMessage(msg.chat.id, '请输入有效的金额');
  456. return;
  457. }
  458. const transactionData = {
  459. groupId: msg.chat.id.toString(),
  460. groupName: msg.chat.title || '未命名群组',
  461. amount: amount
  462. };
  463. try {
  464. const result = await Transaction.deposit(transactionData);
  465. if (result.success) {
  466. const billMessage = await generateBillMessage(msg.chat.id);
  467. if (billMessage) {
  468. await sendMessage(msg.chat.id, billMessage, {
  469. reply_markup: generateInlineKeyboard(msg.chat.id)
  470. });
  471. console.log(`入款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  472. } else {
  473. await sendMessage(msg.chat.id, '入款成功,但获取账单信息失败');
  474. console.log(`入款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  475. }
  476. } else {
  477. await sendMessage(msg.chat.id, result.message || '入款失败');
  478. console.log(`入款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
  479. }
  480. } catch (error) {
  481. console.error(formatLog('记录入款失败', error));
  482. await sendMessage(msg.chat.id, '记录入款失败,请稍后重试');
  483. }
  484. });
  485. // 处理下发命令
  486. bot.onText(/\/withdraw (.+)/, async (msg, match) => {
  487. if (!isGroupAllowed(msg.chat.id)) {
  488. sendMessage(msg.chat.id, '该群组未授权使用此功能');
  489. return;
  490. }
  491. const amount = parseFloat(match[1]);
  492. if (isNaN(amount)) {
  493. sendMessage(msg.chat.id, '请输入有效的金额');
  494. return;
  495. }
  496. const transactionData = {
  497. groupId: msg.chat.id.toString(),
  498. groupName: msg.chat.title || '未命名群组',
  499. amount: amount
  500. };
  501. try {
  502. const result = await Transaction.withdrawal(transactionData);
  503. if (result.success) {
  504. const billMessage = await generateBillMessage(msg.chat.id);
  505. if (billMessage) {
  506. await sendMessage(msg.chat.id, billMessage, {
  507. reply_markup: generateInlineKeyboard(msg.chat.id)
  508. });
  509. console.log(`出款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  510. } else {
  511. await sendMessage(msg.chat.id, '出款成功,但获取账单信息失败');
  512. console.log(`出款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
  513. }
  514. } else {
  515. await sendMessage(msg.chat.id, result.message || '出款失败');
  516. console.log(`出款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
  517. }
  518. } catch (error) {
  519. console.error(formatLog('记录出款失败', error));
  520. await sendMessage(msg.chat.id, '记录出款失败,请稍后重试');
  521. }
  522. });
  523. // 处理查看账单命令
  524. bot.onText(/\/bill/, async (msg) => {
  525. const billMessage = await generateBillMessage(msg.chat.id);
  526. sendMessage(msg.chat.id, billMessage, {
  527. reply_markup: generateInlineKeyboard(msg.chat.id)
  528. });
  529. });
  530. // 更新帮助命令
  531. bot.onText(/\/help/, (msg) => {
  532. const helpMessage = `
  533. 🤖 机器人使用指南
  534. 📝 基础命令
  535. • /deposit 数字 - 记录入款
  536. • /withdraw 数字 - 记录下发
  537. • /bill - 查看当前账单
  538. • /help - 显示此帮助信息
  539. ⚡️ 快捷命令
  540. • +数字 - 快速记录入款(例如:+2000)
  541. • -数字 - 快速记录下发(例如:-2000)
  542. 👨‍💼 管理员命令
  543. • /addgroup 群组ID - 添加允许的群组
  544. • /removegroup 群组ID - 移除允许的群组
  545. • /listgroups - 列出所有允许的群组
  546. 💡 使用提示
  547. • 所有金额输入请使用数字
  548. • 账单信息实时更新
  549. • 如需帮助请联系管理员
  550. `;
  551. sendMessage(msg.chat.id, helpMessage);
  552. });
  553. // 生成账单消息
  554. async function generateBillMessage(chatId) {
  555. try {
  556. // 获取群组的最后加入时间和费率信息
  557. const [groupInfo] = await pool.query(
  558. 'SELECT last_join_time, in_fee_rate, in_exchange_rate, out_fee_rate, out_exchange_rate FROM groups WHERE group_id = ?',
  559. [chatId.toString()]
  560. );
  561. if (!groupInfo || groupInfo.length === 0) {
  562. return '暂无交易记录';
  563. }
  564. const lastJoinTime = groupInfo[0].last_join_time;
  565. const inFeeRate = parseFloat(groupInfo[0].in_fee_rate) || 0;
  566. const inExchangeRate = parseFloat(groupInfo[0].in_exchange_rate) || 0;
  567. const outFeeRate = parseFloat(groupInfo[0].out_fee_rate) || 0;
  568. const outExchangeRate = parseFloat(groupInfo[0].out_exchange_rate) || 0;
  569. // 获取机器人加入后的交易记录
  570. const [records] = await pool.query(`
  571. SELECT * FROM transactions
  572. WHERE group_id = ?
  573. AND DATE(time) = CURDATE()
  574. ORDER BY time DESC
  575. `, [chatId.toString()]);
  576. if (!records || records.length === 0) {
  577. return '暂无交易记录';
  578. }
  579. const deposits = records.filter(r => r.type === 'deposit');
  580. const withdrawals = records.filter(r => r.type === 'withdrawal');
  581. const totalDeposit = deposits.reduce((sum, d) => sum + parseFloat(d.amount), 0);
  582. const totalWithdrawal = withdrawals.reduce((sum, w) => sum + parseFloat(w.amount), 0);
  583. const depositFee = totalDeposit * (inFeeRate / 100);
  584. const withdrawalFee = totalWithdrawal * (outFeeRate / 100);
  585. const remaining = totalDeposit - depositFee - totalWithdrawal - withdrawalFee;
  586. const remainingU = (remaining / inExchangeRate).toFixed(2);
  587. // 获取当前日期
  588. const today = new Date();
  589. const version = 'v29'; // 版本号
  590. const dateStr = today.toISOString().split('T')[0].replace(/-/g, '-');
  591. let message = `当前版本:<b>${dateStr}:${version}</b>\n\n`;
  592. // 添加入款记录
  593. if (deposits.length > 0) {
  594. message += `<b>入款笔数</b>:<code>${deposits.length}</code>\n`;
  595. deposits.forEach(deposit => {
  596. message += `<code>${moment(deposit.time).format('HH:mm:ss')} ${parseFloat(deposit.amount).toFixed(2)}</code>\n`;
  597. });
  598. message += '\n';
  599. } else {
  600. message += `<b>入款笔数</b>:<code>0</code>\n\n`;
  601. }
  602. // 添加出款记录
  603. if (withdrawals.length > 0) {
  604. message += `<b>出款笔数</b>:<code>${withdrawals.length}</code>\n`;
  605. withdrawals.forEach(withdrawal => {
  606. message += `<code>${moment(withdrawal.time).format('HH:mm:ss')} ${parseFloat(withdrawal.amount).toFixed(2)}</code>\n`;
  607. });
  608. message += '\n';
  609. } else {
  610. message += `<b>出款笔数</b>:<code>0</code>\n\n`;
  611. }
  612. // 添加费率信息
  613. message += `<b>入款费率</b>:<code>${inFeeRate}%</code>\n`;
  614. message += `<b>入款汇率</b>:<code>${inExchangeRate}</code>\n`;
  615. message += `<b>入款总额</b>:<code>${totalDeposit.toFixed(2)}</code>\n`;
  616. message += `<b>入款合计</b>:<code>${(totalDeposit - depositFee).toFixed(2)}|${((totalDeposit - depositFee) / inExchangeRate).toFixed(2)}U</code>\n\n`;
  617. message += `<b>出款费率</b>:<code>${outFeeRate}%</code>\n`;
  618. message += `<b>出款汇率</b>:<code>${outExchangeRate}</code>\n`;
  619. message += `<b>出款总额</b>:<code>${totalWithdrawal.toFixed(2)}</code>\n`;
  620. message += `<b>出款合计</b>:<code>${(totalWithdrawal - withdrawalFee).toFixed(2)}|${((totalWithdrawal - withdrawalFee) / outExchangeRate).toFixed(2)}U</code>\n\n`;
  621. // 添加余额信息
  622. message += `<b>应下发</b>:<code>${remainingU}U</code>\n`;
  623. message += `<b>已下发</b>:<code>${(totalWithdrawal / outExchangeRate).toFixed(2)}U</code>\n`;
  624. message += `<b>未下发</b>:<code>${(remainingU - (totalWithdrawal / outExchangeRate)).toFixed(2)}U</code>`;
  625. return message;
  626. } catch (error) {
  627. console.error(formatLog('生成账单消息失败', error));
  628. return '获取账单信息失败,请稍后重试';
  629. }
  630. }
  631. // 生成内联键盘
  632. function generateInlineKeyboard(chatId) {
  633. const keyboard = {
  634. inline_keyboard: [
  635. [{
  636. text: '点击跳转完整账单',
  637. callback_data: `bill_page_${chatId}`
  638. }],
  639. [{
  640. text: '24小时商务对接',
  641. callback_data: 'business_contact'
  642. }]
  643. ]
  644. };
  645. return keyboard;
  646. }
  647. // 处理内联按钮回调
  648. bot.on('callback_query', async (callbackQuery) => {
  649. const chatId = callbackQuery.message.chat.id;
  650. const data = callbackQuery.data;
  651. try {
  652. if (data.startsWith('bill_page_')) {
  653. const groupId = data.split('_')[2];
  654. await bot.answerCallbackQuery(callbackQuery.id, {
  655. url: 'https://google.com'
  656. });
  657. } else if (data === 'business_contact') {
  658. await bot.answerCallbackQuery(callbackQuery.id, {
  659. url: 'https://t.me/your_business_account'
  660. });
  661. }
  662. } catch (error) {
  663. console.error(formatLog('处理内联按钮回调失败', error));
  664. await bot.answerCallbackQuery(callbackQuery.id, {
  665. text: '操作失败,请稍后重试',
  666. show_alert: true
  667. });
  668. }
  669. });
  670. // 保存数据
  671. function saveData() {
  672. try {
  673. fs.writeFileSync(process.env.DB_FILE, JSON.stringify(data, null, 2));
  674. } catch (error) {
  675. console.error(formatLog('Error saving data', error));
  676. }
  677. }
  678. // 加载数据
  679. function loadData() {
  680. try {
  681. if (fs.existsSync(process.env.DB_FILE)) {
  682. const savedData = JSON.parse(fs.readFileSync(process.env.DB_FILE));
  683. data = {
  684. ...data,
  685. ...savedData
  686. };
  687. }
  688. } catch (error) {
  689. console.error(formatLog('Error loading data', error));
  690. }
  691. }
  692. // 测试数据库连接并初始化
  693. testConnection().then(() => {
  694. return initDatabase();
  695. }).then(() => {
  696. // 加载数据
  697. loadData();
  698. // 启动服务器
  699. const PORT = process.env.PORT || 3000;
  700. app.listen(PORT, () => {
  701. console.log(formatLog({
  702. PORT: PORT
  703. }));
  704. console.log('机器人已准备就绪!');
  705. });
  706. }).catch(error => {
  707. console.error(formatLog('启动失败', error));
  708. process.exit(1);
  709. });
  710. // 处理机器人被添加到群组
  711. async function handleBotAdded(msg, chatId, chatType, chatIdStr, existingGroup) {
  712. try {
  713. // 获取群组链接
  714. const chatInfo = await bot.getChat(chatId);
  715. const groupInfo = {
  716. ID: chatId,
  717. 名称: msg.chat.title || '未命名群组',
  718. 类型: chatType,
  719. 状态: existingGroup ? '重新激活' : '已激活',
  720. 群组链接: chatInfo.invite_link || '未设置',
  721. 更新时间: new Date().toLocaleString()
  722. };
  723. // console.log('机器人首次被添加到群组');
  724. // console.log(formatLog(groupInfo));
  725. // 如果群组不存在,创建新群组
  726. if (!existingGroup) {
  727. const groupData = {
  728. groupId: chatIdStr,
  729. groupName: msg.chat.title || '未命名群组',
  730. groupType: chatType === 'private' ? 'private' : 'public',
  731. creatorId: msg.from.id.toString()
  732. };
  733. const id = await Group.create({
  734. groupId: groupData.groupId,
  735. groupName: groupData.groupName,
  736. creatorId: groupData.creatorId
  737. });
  738. if (id) {
  739. // 更新内存中的群组列表
  740. if (!data.allowedGroups.includes(chatIdStr)) {
  741. data.allowedGroups.push(chatIdStr);
  742. saveData();
  743. }
  744. console.log('机器人首次被添加到群组');
  745. console.log(formatLog(groupInfo));
  746. await sendMessage(chatId, '感谢添加我为群组成员!使用 /help 查看可用命令。');
  747. } else {
  748. await sendMessage(chatId, '添加群组失败,请联系管理员。');
  749. }
  750. }
  751. } catch (error) {
  752. console.error(formatLog({
  753. 错误: '处理机器人首次加入群组失败',
  754. 详情: error.message
  755. }));
  756. await sendMessage(chatId, '添加群组失败,请联系管理员。');
  757. }
  758. }
  759. // 处理群组信息更新
  760. async function handleGroupUpdate(msg, chatId, chatType, chatIdStr, existingGroup, newStatus) {
  761. const connection = await pool.getConnection();
  762. await connection.beginTransaction();
  763. try {
  764. // 更新群组ID和类型
  765. const newType = chatType === 'private' ? 'private' :
  766. chatType === 'supergroup' ? 'supergroup' : 'group';
  767. const oldGroupId = existingGroup.group_id;
  768. // 如果群组ID发生变化,更新所有相关记录
  769. if (existingGroup.group_type != newType) {
  770. console.log(formatLog({
  771. 操作: '群组类型更新',
  772. 名称: msg.chat.title || existingGroup.group_name,
  773. 旧ID: oldGroupId,
  774. 新ID: chatIdStr,
  775. 旧类型: existingGroup.group_type,
  776. 新类型: newType,
  777. 状态: newStatus !== 'kicked' && newStatus !== 'left' ? '活跃' : '已移除'
  778. }));
  779. // 开始事务
  780. await connection.beginTransaction();
  781. try {
  782. // 检查目标ID是否已存在
  783. const [existingTargetGroup] = await connection.query(
  784. 'SELECT * FROM groups WHERE group_id = ?',
  785. [chatIdStr]
  786. );
  787. if (existingTargetGroup && existingTargetGroup.length > 0) {
  788. // 更新交易记录
  789. await connection.query(`
  790. UPDATE transactions
  791. SET group_id = ?
  792. WHERE group_id = ?
  793. `, [chatIdStr, oldGroupId]);
  794. // 更新资金记录
  795. await connection.query(`
  796. UPDATE money_records
  797. SET group_id = ?
  798. WHERE group_id = ?
  799. `, [chatIdStr, oldGroupId]);
  800. // 删除旧群组记录
  801. await connection.query(
  802. 'DELETE FROM groups WHERE group_id = ?',
  803. [oldGroupId]
  804. );
  805. // 更新目标群组信息
  806. await connection.query(`
  807. UPDATE groups
  808. SET group_type = ?,
  809. group_name = ?,
  810. is_active = ?,
  811. updated_at = CURRENT_TIMESTAMP
  812. WHERE group_id = ?
  813. `, [
  814. newType,
  815. msg.chat.title || existingGroup.group_name,
  816. newStatus !== 'kicked' && newStatus !== 'left',
  817. chatIdStr
  818. ]);
  819. } else {
  820. // 如果目标ID不存在,执行正常的更新操作
  821. // 先删除旧记录
  822. await connection.query(
  823. 'DELETE FROM groups WHERE group_id = ?',
  824. [oldGroupId]
  825. );
  826. // 插入新记录
  827. await connection.query(`
  828. INSERT INTO groups
  829. (group_id, group_name, group_type, creator_id, is_active, last_join_time)
  830. VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
  831. `, [
  832. chatIdStr,
  833. msg.chat.title || existingGroup.group_name,
  834. newType,
  835. existingGroup.creator_id,
  836. newStatus !== 'kicked' && newStatus !== 'left'
  837. ]);
  838. // 更新交易记录
  839. await connection.query(`
  840. UPDATE transactions
  841. SET group_id = ?
  842. WHERE group_id = ?
  843. `, [chatIdStr, oldGroupId]);
  844. // 更新资金记录
  845. await connection.query(`
  846. UPDATE money_records
  847. SET group_id = ?
  848. WHERE group_id = ?
  849. `, [chatIdStr, oldGroupId]);
  850. }
  851. // 提交事务
  852. await connection.commit();
  853. // 更新内存中的群组列表
  854. const index = data.allowedGroups.indexOf(oldGroupId);
  855. if (index > -1) {
  856. if (newStatus === 'kicked' || newStatus === 'left') {
  857. data.allowedGroups.splice(index, 1);
  858. } else {
  859. data.allowedGroups[index] = chatIdStr;
  860. }
  861. saveData();
  862. }
  863. } catch (error) {
  864. // 回滚事务
  865. await connection.rollback();
  866. console.error(formatLog('更新群组信息失败', error));
  867. throw error;
  868. }
  869. } else {
  870. // 如果ID没有变化,只更新其他信息
  871. await connection.query(`
  872. UPDATE groups
  873. SET group_type = ?,
  874. group_name = ?,
  875. is_active = ?,
  876. updated_at = CURRENT_TIMESTAMP
  877. WHERE group_id = ?
  878. `, [
  879. newType,
  880. msg.chat.title || existingGroup.group_name,
  881. newStatus !== 'kicked' && newStatus !== 'left',
  882. chatIdStr
  883. ]);
  884. }
  885. await connection.commit();
  886. console.log('【群组状态变更】');
  887. console.log(formatLog({
  888. ID: oldGroupId + ' -> ' + chatIdStr,
  889. type: existingGroup.group_type + ' -> ' + newType,
  890. name: existingGroup.group_name + ' -> ' + msg.chat.title || existingGroup.group_name,
  891. status: newStatus === 'kicked' || newStatus === 'left' ? '已移除' : '活跃'
  892. }));
  893. } catch (error) {
  894. await connection.rollback();
  895. console.error(formatLog({
  896. 错误: '更新群组信息失败',
  897. 详情: error.message
  898. }));
  899. throw error;
  900. } finally {
  901. connection.release();
  902. }
  903. }
  904. // 处理群组状态变更
  905. bot.on('my_chat_member', async (msg) => {
  906. try {
  907. const chatId = msg.chat.id;
  908. const newStatus = msg.new_chat_member.status;
  909. const oldStatus = msg.old_chat_member.status;
  910. const chatType = msg.chat.type;
  911. const chatIdStr = chatId.toString();
  912. // 查找群组,同时检查新旧ID
  913. const existingGroup = await Group.findByGroupId(chatIdStr) ||
  914. await Group.findByGroupId(chatIdStr.replace('-', ''));
  915. // 获取群组详细信息(如果机器人还在群组中)
  916. if (newStatus !== 'kicked' && newStatus !== 'left') {
  917. try {
  918. const chatInfo = await bot.getChat(chatId);
  919. } catch (error) {
  920. console.log(formatLog('获取群组详细信息失败', error.message));
  921. }
  922. } else {
  923. console.log('机器人已被移出群组,无法获取详细信息');
  924. }
  925. const newType = chatType === 'private' ? 'private' :
  926. chatType === 'supergroup' ? 'supergroup' : 'group';
  927. // 如果是机器人首次被添加到群组(从非成员变为成员)
  928. if (oldStatus === 'left' && newStatus === 'member' && existingGroup.group_type != newType) {
  929. await handleBotAdded(msg, chatId, chatType, chatIdStr, existingGroup);
  930. }
  931. if (existingGroup) {
  932. await handleGroupUpdate(msg, chatId, chatType, chatIdStr, existingGroup, newStatus);
  933. }
  934. } catch (error) {
  935. console.error(formatLog({
  936. 错误: '处理群组状态变更失败',
  937. 详情: error.message
  938. }));
  939. }
  940. });
  941. // 导入公共路由
  942. const publicRoutes = require('./routes/public');
  943. // 注册公共路由
  944. app.use('/api/public', publicRoutes);
  945. // 错误处理中间件
  946. app.use((err, req, res, next) => {
  947. console.error(err.stack);
  948. res.status(500).json({ message: '服务器错误' });
  949. });
  950. // 404 处理
  951. app.use((req, res) => {
  952. res.status(404).json({ message: '未找到请求的资源' });
  953. });
  954. module.exports = app;