index.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  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('下发')) {
  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.startsWith('下发')) {
  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.startsWith('下发-')) {
  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(/\/bill/, async (msg) => {
  449. const billMessage = await generateBillMessage(msg.chat.id);
  450. sendMessage(msg.chat.id, billMessage, {
  451. reply_markup: generateInlineKeyboard(msg.chat.id)
  452. });
  453. });
  454. // 更新帮助命令
  455. bot.onText(/\/help/, (msg) => {
  456. const helpMessage = `
  457. 🤖 机器人使用指南
  458. 📝 基础命令
  459. • /deposit 数字 - 记录入款
  460. • /withdraw 数字 - 记录下发
  461. • /bill - 查看当前账单
  462. • /help - 显示此帮助信息
  463. ⚡️ 快捷命令
  464. • +数字 - 快速记录入款(例如:+2000)
  465. • -数字 - 快速记录下发(例如:-2000)
  466. 👨‍💼 管理员命令
  467. • /addgroup 群组ID - 添加允许的群组
  468. • /removegroup 群组ID - 移除允许的群组
  469. • /listgroups - 列出所有允许的群组
  470. 💡 使用提示
  471. • 所有金额输入请使用数字
  472. • 账单信息实时更新
  473. • 如需帮助请联系管理员
  474. `;
  475. sendMessage(msg.chat.id, helpMessage);
  476. });
  477. // 生成账单消息
  478. async function generateBillMessage(chatId) {
  479. try {
  480. // 获取群组的最后加入时间和费率信息
  481. const [groupInfo] = await pool.query(
  482. 'SELECT last_join_time, in_fee_rate, in_exchange_rate, out_fee_rate, out_exchange_rate FROM groups WHERE group_id = ?',
  483. [chatId.toString()]
  484. );
  485. if (!groupInfo || groupInfo.length === 0) {
  486. return '暂无交易记录';
  487. }
  488. const lastJoinTime = groupInfo[0].last_join_time;
  489. const inFeeRate = parseFloat(groupInfo[0].in_fee_rate) || 0;
  490. const inExchangeRate = parseFloat(groupInfo[0].in_exchange_rate) || 0;
  491. const outFeeRate = parseFloat(groupInfo[0].out_fee_rate) || 0;
  492. const outExchangeRate = parseFloat(groupInfo[0].out_exchange_rate) || 0;
  493. // 获取机器人加入后的交易记录
  494. const [records] = await pool.query(`
  495. SELECT * FROM transactions
  496. WHERE group_id = ?
  497. AND DATE(time) = CURDATE()
  498. ORDER BY time DESC
  499. `, [chatId.toString()]);
  500. if (!records || records.length === 0) {
  501. return '暂无交易记录';
  502. }
  503. const deposits = records.filter(r => r.type === 'deposit');
  504. const withdrawals = records.filter(r => r.type === 'withdrawal');
  505. const totalDeposit = deposits.reduce((sum, d) => sum + parseFloat(d.amount), 0);
  506. const totalWithdrawal = withdrawals.reduce((sum, w) => sum + parseFloat(w.amount), 0);
  507. const depositFee = totalDeposit * (inFeeRate / 100);
  508. const withdrawalFee = totalWithdrawal * (outFeeRate / 100);
  509. const remaining = totalDeposit - depositFee - totalWithdrawal - withdrawalFee;
  510. const remainingU = (remaining / inExchangeRate).toFixed(2);
  511. // 获取当前日期
  512. const today = new Date();
  513. const version = 'v29'; // 版本号
  514. const dateStr = today.toISOString().split('T')[0].replace(/-/g, '-');
  515. let message = `当前版本:<b>${dateStr}:${version}</b>\n\n`;
  516. // 添加入款记录
  517. if (deposits.length > 0) {
  518. message += `<b>入款笔数</b>:<code>${deposits.length}</code>\n`;
  519. deposits.forEach(deposit => {
  520. message += `<code>${moment(deposit.time).format('HH:mm:ss')} ${parseFloat(deposit.amount).toFixed(2)}</code>\n`;
  521. });
  522. message += '\n';
  523. } else {
  524. message += `<b>入款笔数</b>:<code>0</code>\n\n`;
  525. }
  526. // 添加出款记录
  527. if (withdrawals.length > 0) {
  528. message += `<b>出款笔数</b>:<code>${withdrawals.length}</code>\n`;
  529. withdrawals.forEach(withdrawal => {
  530. message += `<code>${moment(withdrawal.time).format('HH:mm:ss')} ${parseFloat(withdrawal.amount).toFixed(2)}</code>\n`;
  531. });
  532. message += '\n';
  533. } else {
  534. message += `<b>出款笔数</b>:<code>0</code>\n\n`;
  535. }
  536. // 添加费率信息
  537. message += `<b>入款费率</b>:<code>${inFeeRate}%</code>\n`;
  538. message += `<b>入款汇率</b>:<code>${inExchangeRate}</code>\n`;
  539. message += `<b>入款总额</b>:<code>${totalDeposit.toFixed(2)}</code>\n`;
  540. message += `<b>入款合计</b>:<code>${(totalDeposit - depositFee).toFixed(2)}|${((totalDeposit - depositFee) / inExchangeRate).toFixed(2)}U</code>\n\n`;
  541. message += `<b>出款费率</b>:<code>${outFeeRate}%</code>\n`;
  542. message += `<b>出款汇率</b>:<code>${outExchangeRate}</code>\n`;
  543. message += `<b>出款总额</b>:<code>${totalWithdrawal.toFixed(2)}</code>\n`;
  544. message += `<b>出款合计</b>:<code>${(totalWithdrawal - withdrawalFee).toFixed(2)}|${((totalWithdrawal - withdrawalFee) / outExchangeRate).toFixed(2)}U</code>\n\n`;
  545. // 添加余额信息
  546. message += `<b>应下发</b>:<code>${remainingU}U</code>\n`;
  547. message += `<b>已下发</b>:<code>${(totalWithdrawal / outExchangeRate).toFixed(2)}U</code>\n`;
  548. message += `<b>未下发</b>:<code>${(remainingU - (totalWithdrawal / outExchangeRate)).toFixed(2)}U</code>`;
  549. return message;
  550. } catch (error) {
  551. console.error(formatLog('生成账单消息失败', error));
  552. return '获取账单信息失败,请稍后重试';
  553. }
  554. }
  555. // 生成内联键盘
  556. function generateInlineKeyboard(chatId) {
  557. const keyboard = {
  558. inline_keyboard: [
  559. [{
  560. text: '点击跳转完整账单',
  561. callback_data: `bill_page_${chatId}`
  562. }],
  563. [{
  564. text: '24小时商务对接',
  565. callback_data: 'business_contact'
  566. }]
  567. ]
  568. };
  569. return keyboard;
  570. }
  571. // 处理内联按钮回调
  572. bot.on('callback_query', async (callbackQuery) => {
  573. const chatId = callbackQuery.message.chat.id;
  574. const data = callbackQuery.data;
  575. try {
  576. if (data.startsWith('bill_page_')) {
  577. const groupId = data.split('_')[2];
  578. await bot.answerCallbackQuery(callbackQuery.id, {
  579. url: 'https://google.com'
  580. });
  581. } else if (data === 'business_contact') {
  582. await bot.answerCallbackQuery(callbackQuery.id, {
  583. url: 'https://t.me/your_business_account'
  584. });
  585. }
  586. } catch (error) {
  587. console.error(formatLog('处理内联按钮回调失败', error));
  588. await bot.answerCallbackQuery(callbackQuery.id, {
  589. text: '操作失败,请稍后重试',
  590. show_alert: true
  591. });
  592. }
  593. });
  594. // 保存数据
  595. function saveData() {
  596. try {
  597. fs.writeFileSync(process.env.DB_FILE, JSON.stringify(data, null, 2));
  598. } catch (error) {
  599. console.error(formatLog('Error saving data', error));
  600. }
  601. }
  602. // 加载数据
  603. function loadData() {
  604. try {
  605. if (fs.existsSync(process.env.DB_FILE)) {
  606. const savedData = JSON.parse(fs.readFileSync(process.env.DB_FILE));
  607. data = {
  608. ...data,
  609. ...savedData
  610. };
  611. }
  612. } catch (error) {
  613. console.error(formatLog('Error loading data', error));
  614. }
  615. }
  616. // 测试数据库连接并初始化
  617. testConnection().then(() => {
  618. return initDatabase();
  619. }).then(() => {
  620. // 加载数据
  621. loadData();
  622. // 启动服务器
  623. const PORT = process.env.PORT || 3000;
  624. app.listen(PORT, () => {
  625. console.log(formatLog({
  626. PORT: PORT
  627. }));
  628. console.log('机器人已准备就绪!');
  629. });
  630. }).catch(error => {
  631. console.error(formatLog('启动失败', error));
  632. process.exit(1);
  633. });
  634. // 处理机器人被添加到群组
  635. async function handleBotAdded(msg, chatId, chatType, chatIdStr, existingGroup) {
  636. try {
  637. // 获取群组链接
  638. const chatInfo = await bot.getChat(chatId);
  639. const groupInfo = {
  640. ID: chatId,
  641. 名称: msg.chat.title || '未命名群组',
  642. 类型: chatType,
  643. 状态: existingGroup ? '重新激活' : '已激活',
  644. 群组链接: chatInfo.invite_link || '未设置',
  645. 更新时间: new Date().toLocaleString()
  646. };
  647. // console.log('机器人首次被添加到群组');
  648. // console.log(formatLog(groupInfo));
  649. // 如果群组不存在,创建新群组
  650. if (!existingGroup) {
  651. const groupData = {
  652. groupId: chatIdStr,
  653. groupName: msg.chat.title || '未命名群组',
  654. groupType: chatType === 'private' ? 'private' : 'public',
  655. creatorId: msg.from.id.toString()
  656. };
  657. const id = await Group.create({
  658. groupId: groupData.groupId,
  659. groupName: groupData.groupName,
  660. creatorId: groupData.creatorId
  661. });
  662. if (id) {
  663. // 更新内存中的群组列表
  664. if (!data.allowedGroups.includes(chatIdStr)) {
  665. data.allowedGroups.push(chatIdStr);
  666. saveData();
  667. }
  668. console.log('机器人首次被添加到群组');
  669. console.log(formatLog(groupInfo));
  670. await sendMessage(chatId, '感谢添加我为群组成员!使用 /help 查看可用命令。');
  671. } else {
  672. await sendMessage(chatId, '添加群组失败,请联系管理员。');
  673. }
  674. }
  675. } catch (error) {
  676. console.error(formatLog({
  677. 错误: '处理机器人首次加入群组失败',
  678. 详情: error.message
  679. }));
  680. await sendMessage(chatId, '添加群组失败,请联系管理员。');
  681. }
  682. }
  683. // 处理群组信息更新
  684. async function handleGroupUpdate(msg, chatId, chatType, chatIdStr, existingGroup, newStatus) {
  685. const connection = await pool.getConnection();
  686. await connection.beginTransaction();
  687. try {
  688. // 更新群组ID和类型
  689. const newType = chatType === 'private' ? 'private' :
  690. chatType === 'supergroup' ? 'supergroup' : 'group';
  691. const oldGroupId = existingGroup.group_id;
  692. // 如果群组ID发生变化,更新所有相关记录
  693. if (existingGroup.group_type != newType) {
  694. console.log(formatLog({
  695. 操作: '群组类型更新',
  696. 名称: msg.chat.title || existingGroup.group_name,
  697. 旧ID: oldGroupId,
  698. 新ID: chatIdStr,
  699. 旧类型: existingGroup.group_type,
  700. 新类型: newType,
  701. 状态: newStatus !== 'kicked' && newStatus !== 'left' ? '活跃' : '已移除'
  702. }));
  703. // 开始事务
  704. await connection.beginTransaction();
  705. try {
  706. // 检查目标ID是否已存在
  707. const [existingTargetGroup] = await connection.query(
  708. 'SELECT * FROM groups WHERE group_id = ?',
  709. [chatIdStr]
  710. );
  711. if (existingTargetGroup && existingTargetGroup.length > 0) {
  712. // 更新交易记录
  713. await connection.query(`
  714. UPDATE transactions
  715. SET group_id = ?
  716. WHERE group_id = ?
  717. `, [chatIdStr, oldGroupId]);
  718. // 更新资金记录
  719. await connection.query(`
  720. UPDATE money_records
  721. SET group_id = ?
  722. WHERE group_id = ?
  723. `, [chatIdStr, oldGroupId]);
  724. // 删除旧群组记录
  725. await connection.query(
  726. 'DELETE FROM groups WHERE group_id = ?',
  727. [oldGroupId]
  728. );
  729. // 更新目标群组信息
  730. await connection.query(`
  731. UPDATE groups
  732. SET group_type = ?,
  733. group_name = ?,
  734. is_active = ?,
  735. updated_at = CURRENT_TIMESTAMP
  736. WHERE group_id = ?
  737. `, [
  738. newType,
  739. msg.chat.title || existingGroup.group_name,
  740. newStatus !== 'kicked' && newStatus !== 'left',
  741. chatIdStr
  742. ]);
  743. } else {
  744. // 如果目标ID不存在,执行正常的更新操作
  745. // 先删除旧记录
  746. await connection.query(
  747. 'DELETE FROM groups WHERE group_id = ?',
  748. [oldGroupId]
  749. );
  750. // 插入新记录
  751. await connection.query(`
  752. INSERT INTO groups
  753. (group_id, group_name, group_type, creator_id, is_active, last_join_time)
  754. VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
  755. `, [
  756. chatIdStr,
  757. msg.chat.title || existingGroup.group_name,
  758. newType,
  759. existingGroup.creator_id,
  760. newStatus !== 'kicked' && newStatus !== 'left'
  761. ]);
  762. // 更新交易记录
  763. await connection.query(`
  764. UPDATE transactions
  765. SET group_id = ?
  766. WHERE group_id = ?
  767. `, [chatIdStr, oldGroupId]);
  768. // 更新资金记录
  769. await connection.query(`
  770. UPDATE money_records
  771. SET group_id = ?
  772. WHERE group_id = ?
  773. `, [chatIdStr, oldGroupId]);
  774. }
  775. // 提交事务
  776. await connection.commit();
  777. // 更新内存中的群组列表
  778. const index = data.allowedGroups.indexOf(oldGroupId);
  779. if (index > -1) {
  780. if (newStatus === 'kicked' || newStatus === 'left') {
  781. data.allowedGroups.splice(index, 1);
  782. } else {
  783. data.allowedGroups[index] = chatIdStr;
  784. }
  785. saveData();
  786. }
  787. } catch (error) {
  788. // 回滚事务
  789. await connection.rollback();
  790. console.error(formatLog('更新群组信息失败', error));
  791. throw error;
  792. }
  793. } else {
  794. // 如果ID没有变化,只更新其他信息
  795. await connection.query(`
  796. UPDATE groups
  797. SET group_type = ?,
  798. group_name = ?,
  799. is_active = ?,
  800. updated_at = CURRENT_TIMESTAMP
  801. WHERE group_id = ?
  802. `, [
  803. newType,
  804. msg.chat.title || existingGroup.group_name,
  805. newStatus !== 'kicked' && newStatus !== 'left',
  806. chatIdStr
  807. ]);
  808. }
  809. await connection.commit();
  810. console.log('【群组状态变更】');
  811. console.log(formatLog({
  812. ID: oldGroupId + ' -> ' + chatIdStr,
  813. type: existingGroup.group_type + ' -> ' + newType,
  814. name: existingGroup.group_name + ' -> ' + msg.chat.title || existingGroup.group_name,
  815. status: newStatus === 'kicked' || newStatus === 'left' ? '已移除' : '活跃'
  816. }));
  817. } catch (error) {
  818. await connection.rollback();
  819. console.error(formatLog({
  820. 错误: '更新群组信息失败',
  821. 详情: error.message
  822. }));
  823. throw error;
  824. } finally {
  825. connection.release();
  826. }
  827. }
  828. // 处理群组状态变更
  829. bot.on('my_chat_member', async (msg) => {
  830. try {
  831. const chatId = msg.chat.id;
  832. const newStatus = msg.new_chat_member.status;
  833. const oldStatus = msg.old_chat_member.status;
  834. const chatType = msg.chat.type;
  835. const chatIdStr = chatId.toString();
  836. // 查找群组,同时检查新旧ID
  837. const existingGroup = await Group.findByGroupId(chatIdStr) ||
  838. await Group.findByGroupId(chatIdStr.replace('-', ''));
  839. // 获取群组详细信息(如果机器人还在群组中)
  840. if (newStatus !== 'kicked' && newStatus !== 'left') {
  841. try {
  842. const chatInfo = await bot.getChat(chatId);
  843. } catch (error) {
  844. console.log(formatLog('获取群组详细信息失败', error.message));
  845. }
  846. } else {
  847. console.log('机器人已被移出群组,无法获取详细信息');
  848. }
  849. const newType = chatType === 'private' ? 'private' :
  850. chatType === 'supergroup' ? 'supergroup' : 'group';
  851. // 如果是机器人首次被添加到群组(从非成员变为成员)
  852. if (oldStatus === 'left' && newStatus === 'member' && existingGroup.group_type != newType) {
  853. await handleBotAdded(msg, chatId, chatType, chatIdStr, existingGroup);
  854. }
  855. if (existingGroup) {
  856. await handleGroupUpdate(msg, chatId, chatType, chatIdStr, existingGroup, newStatus);
  857. }
  858. } catch (error) {
  859. console.error(formatLog({
  860. 错误: '处理群组状态变更失败',
  861. 详情: error.message
  862. }));
  863. }
  864. });
  865. // 导入公共路由
  866. const publicRoutes = require('./routes/public');
  867. // 注册公共路由
  868. app.use('/api/public', publicRoutes);
  869. // 错误处理中间件
  870. app.use((err, req, res, next) => {
  871. console.error(err.stack);
  872. res.status(500).json({ message: '服务器错误' });
  873. });
  874. // 404 处理
  875. app.use((req, res) => {
  876. res.status(404).json({ message: '未找到请求的资源' });
  877. });
  878. module.exports = app;