index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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 { pool, testConnection } = require('./config/database');
  9. const initDatabase = require('./config/initDb');
  10. const Group = require('./models/Group');
  11. const app = express();
  12. // 初始化数据存储
  13. let data = {
  14. deposits: [], // 入款记录
  15. withdrawals: [], // 下发记录
  16. lastUpdate: null,
  17. allowedGroups: ['4754375683'] // 允许使用的群组ID
  18. };
  19. // 创建机器人实例
  20. const bot = new TelegramBot(process.env.BOT_TOKEN, { polling: true });
  21. // 中间件
  22. app.use(cors());
  23. app.use(express.json());
  24. app.use(express.static('views'));
  25. // 路由
  26. app.get('/', (req, res) => {
  27. res.sendFile(path.join(__dirname, 'views', 'login.html'));
  28. });
  29. app.use('/api/users', require('./routes/userRoutes'));
  30. app.use('/api/groups', require('./routes/groupRoutes'));
  31. app.use('/api/transactions', require('./routes/transactionRoutes'));
  32. // 检查群组权限
  33. function isGroupAllowed(chatId) {
  34. const chatIdStr = chatId.toString();
  35. return data.allowedGroups.includes(chatIdStr) ||
  36. data.allowedGroups.includes(chatIdStr.replace('-', ''));
  37. }
  38. // 检查是否是管理员
  39. function isAdmin(userId) {
  40. return process.env.ADMIN_IDS.split(',').includes(userId.toString());
  41. }
  42. // 处理消息发送
  43. async function sendMessage(chatId, text, options = {}) {
  44. try {
  45. return await bot.sendMessage(chatId, text, options);
  46. } catch (error) {
  47. console.error('发送消息失败:', error);
  48. if (error.message.includes('bot was kicked from the group chat')) {
  49. const index = data.allowedGroups.indexOf(chatId.toString());
  50. if (index > -1) {
  51. data.allowedGroups.splice(index, 1);
  52. saveData();
  53. console.log(`群组 ${chatId} 已被移除出允许列表`);
  54. }
  55. }
  56. return null;
  57. }
  58. }
  59. // 处理所有消息
  60. bot.on('message', (msg) => {
  61. console.log('收到消息:', {
  62. chatId: msg.chat.id,
  63. chatType: msg.chat.type,
  64. chatTitle: msg.chat.title,
  65. fromId: msg.from.id,
  66. fromUsername: msg.from.username,
  67. text: msg.text,
  68. date: new Date(msg.date * 1000).toLocaleString()
  69. });
  70. });
  71. // 处理新成员加入
  72. bot.on('new_chat_members', async (msg) => {
  73. const chatId = msg.chat.id;
  74. const newMembers = msg.new_chat_members;
  75. for (const member of newMembers) {
  76. if (member.id === (await bot.getMe()).id) {
  77. // 机器人被添加到群组
  78. console.log(`机器人被添加到群组: ${chatId}`);
  79. // 检查群组是否在允许列表中
  80. const chatIdStr = chatId.toString();
  81. if (!data.allowedGroups.includes(chatIdStr)) {
  82. try {
  83. // 使用 createGroup 创建新群组
  84. const groupData = {
  85. groupId: chatIdStr,
  86. groupName: msg.chat.title || '未命名群组',
  87. groupType: msg.chat.type === 'private' ? 'personal' : 'public',
  88. creatorId: msg.from.id.toString()
  89. };
  90. const id = await Group.create({
  91. groupId: groupData.groupId,
  92. groupName: groupData.groupName,
  93. creatorId: groupData.creatorId
  94. });
  95. const group = await Group.findById(id);
  96. if (group) {
  97. // 更新内存中的群组列表
  98. data.allowedGroups.push(chatIdStr);
  99. saveData();
  100. sendMessage(chatId, '感谢添加我为群组成员!使用 /help 查看可用命令。');
  101. } else {
  102. sendMessage(chatId, '添加群组失败,请联系管理员。');
  103. }
  104. } catch (error) {
  105. console.error('创建群组失败:', error);
  106. sendMessage(chatId, '添加群组失败,请联系管理员。');
  107. }
  108. } else {
  109. sendMessage(chatId, '感谢添加我为群组成员!使用 /help 查看可用命令。');
  110. }
  111. } else {
  112. // 其他新成员
  113. console.log(`新成员加入群组: ${member.username || member.first_name} (${member.id})`);
  114. sendMessage(chatId, `欢迎 ${member.username || member.first_name} 加入群组!`);
  115. }
  116. }
  117. });
  118. // 处理管理员命令
  119. bot.onText(/\/addgroup (.+)/, async (msg, match) => {
  120. if (!isAdmin(msg.from.id)) {
  121. sendMessage(msg.chat.id, '您没有权限执行此命令。');
  122. return;
  123. }
  124. const groupId = match[1].trim();
  125. if (!data.allowedGroups.includes(groupId)) {
  126. try {
  127. // 使用 createGroup 创建新群组
  128. const groupData = {
  129. groupId: groupId,
  130. groupName: '手动添加的群组',
  131. groupType: 'public',
  132. creatorId: msg.from.id.toString()
  133. };
  134. const result = await createGroup({ body: groupData });
  135. if (result) {
  136. data.allowedGroups.push(groupId);
  137. saveData();
  138. sendMessage(msg.chat.id, `群组 ${groupId} 已添加到允许列表。`);
  139. } else {
  140. sendMessage(msg.chat.id, '添加群组失败,请检查群组ID是否正确。');
  141. }
  142. } catch (error) {
  143. console.error('创建群组失败:', error);
  144. sendMessage(msg.chat.id, '添加群组失败,请稍后重试。');
  145. }
  146. } else {
  147. sendMessage(msg.chat.id, '该群组已在允许列表中。');
  148. }
  149. });
  150. bot.onText(/\/removegroup (.+)/, async (msg, match) => {
  151. if (!isAdmin(msg.from.id)) {
  152. sendMessage(msg.chat.id, '您没有权限执行此命令。');
  153. return;
  154. }
  155. const groupId = match[1].trim();
  156. try {
  157. // 使用 updateGroup 更新群组状态
  158. const result = await updateGroup({
  159. params: { id: groupId },
  160. body: { isActive: false }
  161. });
  162. if (result) {
  163. const index = data.allowedGroups.indexOf(groupId);
  164. if (index > -1) {
  165. data.allowedGroups.splice(index, 1);
  166. saveData();
  167. sendMessage(msg.chat.id, `群组 ${groupId} 已从允许列表中移除。`);
  168. } else {
  169. sendMessage(msg.chat.id, '该群组不在允许列表中。');
  170. }
  171. } else {
  172. sendMessage(msg.chat.id, '移除群组失败,请稍后重试。');
  173. }
  174. } catch (error) {
  175. console.error('更新群组状态失败:', error);
  176. sendMessage(msg.chat.id, '移除群组失败,请稍后重试。');
  177. }
  178. });
  179. bot.onText(/\/listgroups/, async (msg) => {
  180. if (!isAdmin(msg.from.id)) {
  181. sendMessage(msg.chat.id, '您没有权限执行此命令。');
  182. return;
  183. }
  184. try {
  185. const groups = await pool.query('SELECT group_id, group_name, group_type, is_active FROM groups WHERE is_active = 1');
  186. if (groups.length === 0) {
  187. sendMessage(msg.chat.id, '当前没有允许的群组。');
  188. return;
  189. }
  190. const groupsList = groups.map(group =>
  191. `ID: ${group.group_id}\n名称: ${group.group_name}\n类型: ${group.group_type}\n状态: ${group.is_active ? '启用' : '禁用'}`
  192. ).join('\n\n');
  193. sendMessage(msg.chat.id, `允许的群组列表:\n\n${groupsList}`);
  194. } catch (error) {
  195. console.error('获取群组列表失败:', error);
  196. sendMessage(msg.chat.id, '获取群组列表失败,请稍后重试。');
  197. }
  198. });
  199. // 处理入款命令
  200. bot.onText(/\/deposit (.+)/, (msg, match) => {
  201. const amount = parseFloat(match[1]);
  202. if (isNaN(amount)) {
  203. sendMessage(msg.chat.id, '请输入有效的金额');
  204. return;
  205. }
  206. data.deposits.push({
  207. time: new Date(),
  208. amount: amount
  209. });
  210. data.lastUpdate = new Date();
  211. saveData();
  212. sendMessage(msg.chat.id, generateBillMessage(), {
  213. reply_markup: generateInlineKeyboard()
  214. });
  215. });
  216. // 处理下发命令
  217. bot.onText(/\/withdraw (.+)/, (msg, match) => {
  218. const amount = parseFloat(match[1]);
  219. if (isNaN(amount)) {
  220. sendMessage(msg.chat.id, '请输入有效的金额');
  221. return;
  222. }
  223. data.withdrawals.push({
  224. time: new Date(),
  225. amount: amount
  226. });
  227. data.lastUpdate = new Date();
  228. saveData();
  229. sendMessage(msg.chat.id, generateBillMessage(), {
  230. reply_markup: generateInlineKeyboard()
  231. });
  232. });
  233. // 处理查看账单命令
  234. bot.onText(/\/bill/, (msg) => {
  235. sendMessage(msg.chat.id, generateBillMessage(), {
  236. reply_markup: generateInlineKeyboard()
  237. });
  238. });
  239. // 更新帮助命令
  240. bot.onText(/\/help/, (msg) => {
  241. const helpMessage = `
  242. 可用命令:
  243. /deposit <金额> - 记录入款
  244. /withdraw <金额> - 记录下发
  245. /bill - 查看当前账单
  246. /help - 显示此帮助信息
  247. 管理员命令:
  248. /addgroup <群组ID> - 添加允许的群组
  249. /removegroup <群组ID> - 移除允许的群组
  250. /listgroups - 列出所有允许的群组
  251. `;
  252. sendMessage(msg.chat.id, helpMessage);
  253. });
  254. // 生成账单消息
  255. function generateBillMessage() {
  256. const now = moment();
  257. const deposits = data.deposits || [];
  258. const withdrawals = data.withdrawals || [];
  259. const totalDeposit = deposits.reduce((sum, d) => sum + d.amount, 0);
  260. const totalWithdrawal = withdrawals.reduce((sum, w) => sum + w.amount, 0);
  261. const depositFee = totalDeposit * process.env.DEPOSIT_FEE_RATE;
  262. const withdrawalFee = totalWithdrawal * process.env.WITHDRAWAL_FEE_RATE;
  263. const remaining = totalDeposit - depositFee - totalWithdrawal - withdrawalFee;
  264. let message = `入款(${deposits.length})笔:\n`;
  265. // 添加入款记录
  266. deposits.forEach(deposit => {
  267. message += `${moment(deposit.time).format('HH:mm:ss')} ${deposit.amount.toFixed(2)}\n`;
  268. });
  269. message += `\n下发(${withdrawals.length})笔:\n`;
  270. // 添加下发记录
  271. withdrawals.forEach(withdrawal => {
  272. message += `${moment(withdrawal.time).format('HH:mm:ss')} ${withdrawal.amount.toFixed(2)}\n`;
  273. });
  274. message += `\n总入款:${totalDeposit.toFixed(2)}\n`;
  275. message += `入款费率:${(process.env.DEPOSIT_FEE_RATE * 100).toFixed(1)}%\n`;
  276. message += `下发费率:${(process.env.WITHDRAWAL_FEE_RATE * 100).toFixed(1)}%\n`;
  277. message += `应下发:${(totalDeposit - depositFee).toFixed(2)}\n`;
  278. message += `总下发:${totalWithdrawal.toFixed(2)}\n`;
  279. message += `下发单笔附加费:0.0\n`;
  280. message += `单笔附费加总计:0.0\n`;
  281. message += `余:${remaining.toFixed(2)}`;
  282. return message;
  283. }
  284. // 生成内联键盘
  285. function generateInlineKeyboard() {
  286. return {
  287. inline_keyboard: [
  288. [
  289. {
  290. text: '点击跳转完整账单',
  291. url: `${process.env.BILL_PAGE_BASE_URL}?groupId=${data.allowedGroups[0]}`
  292. }
  293. ],
  294. [
  295. {
  296. text: '24小时商务对接',
  297. url: process.env.BUSINESS_ACCOUNT_URL
  298. }
  299. ]
  300. ]
  301. };
  302. }
  303. // 保存数据
  304. function saveData() {
  305. try {
  306. fs.writeFileSync(process.env.DB_FILE, JSON.stringify(data, null, 2));
  307. } catch (error) {
  308. console.error('Error saving data:', error);
  309. }
  310. }
  311. // 加载数据
  312. function loadData() {
  313. try {
  314. if (fs.existsSync(process.env.DB_FILE)) {
  315. const savedData = JSON.parse(fs.readFileSync(process.env.DB_FILE));
  316. data = { ...data, ...savedData };
  317. }
  318. } catch (error) {
  319. console.error('Error loading data:', error);
  320. }
  321. }
  322. // 测试数据库连接并初始化
  323. testConnection().then(() => {
  324. return initDatabase();
  325. }).then(() => {
  326. // 加载数据
  327. loadData();
  328. // 启动服务器
  329. const PORT = process.env.PORT || 3000;
  330. app.listen(PORT, () => {
  331. console.log(`服务器运行在端口 ${PORT}`);
  332. console.log('机器人已准备就绪!');
  333. });
  334. }).catch(error => {
  335. console.error('启动失败:', error);
  336. process.exit(1);
  337. });