index.js 36 KB

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