index.js 36 KB

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