123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- require('dotenv').config();
- const express = require('express');
- const cors = require('cors');
- const path = require('path');
- const TelegramBot = require('node-telegram-bot-api');
- const fs = require('fs');
- const moment = require('moment');
- const { pool, testConnection } = require('./config/database');
- const initDatabase = require('./config/initDb');
- const Group = require('./models/Group');
- const Transaction = require('./models/Transaction');
- const app = express();
- // 初始化数据存储
- let data = {
- deposits: [], // 入款记录
- withdrawals: [], // 下发记录
- lastUpdate: null,
- allowedGroups: ['4754375683'] // 允许使用的群组ID
- };
- // 创建机器人实例
- const bot = new TelegramBot(process.env.BOT_TOKEN, { polling: true });
- // 中间件
- app.use(cors());
- app.use(express.json());
- app.use(express.static('views'));
- // 路由
- app.get('/', (req, res) => {
- res.sendFile(path.join(__dirname, 'views', 'login.html'));
- });
- app.use('/api/users', require('./routes/userRoutes'));
- app.use('/api/groups', require('./routes/groupRoutes'));
- app.use('/api/transactions', require('./routes/transactionRoutes'));
- // 检查群组权限
- function isGroupAllowed(chatId) {
- const chatIdStr = chatId.toString();
- return data.allowedGroups.includes(chatIdStr) ||
- data.allowedGroups.includes(chatIdStr.replace('-', ''));
- }
- // 检查是否是管理员
- function isAdmin(userId) {
- return process.env.ADMIN_IDS.split(',').includes(userId.toString());
- }
- // 处理消息发送
- async function sendMessage(chatId, text, options = {}) {
- try {
- // 如果包含内联键盘,验证URL
- if (options.reply_markup && options.reply_markup.inline_keyboard) {
- const keyboard = generateInlineKeyboard(chatId);
- if (!keyboard) {
- // 如果键盘无效,发送不带键盘的消息
- return await bot.sendMessage(chatId, text, { parse_mode: 'HTML' });
- }
- options.reply_markup = keyboard;
- }
- return await bot.sendMessage(chatId, text, { ...options, parse_mode: 'HTML' });
- } catch (error) {
- console.error('发送消息失败:', error);
- if (error.message.includes('bot was kicked from the group chat')) {
- const index = data.allowedGroups.indexOf(chatId.toString());
- if (index > -1) {
- data.allowedGroups.splice(index, 1);
- saveData();
- console.log(`群组 ${chatId} 已被移除出允许列表`);
- }
- }
- return null;
- }
- }
- // 处理快捷命令
- bot.on('message', async (msg) => {
- if (!isGroupAllowed(msg.chat.id)) return;
- const text = msg.text?.trim();
- if (!text) return;
- if (text.startsWith('+')) {
- const amount = parseFloat(text.substring(1));
- if (!isNaN(amount)) {
- const transactionData = {
- groupId: msg.chat.id.toString(),
- groupName: msg.chat.title || '未命名群组',
- amount: amount
- };
- try {
- const result = await Transaction.deposit(transactionData);
- if (result.success) {
- const billMessage = await generateBillMessage(msg.chat.id);
- if (billMessage) {
- await sendMessage(msg.chat.id, billMessage, {
- reply_markup: generateInlineKeyboard(msg.chat.id)
- });
- console.log(`入款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- } else {
- await sendMessage(msg.chat.id, '入款成功,但获取账单信息失败');
- console.log(`入款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- }
- } else {
- await sendMessage(msg.chat.id, result.message || '入款失败');
- console.log(`入款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
- }
- } catch (error) {
- console.error('快捷入款失败:', error);
- await sendMessage(msg.chat.id, '记录入款失败,请稍后重试');
- }
- }
- } else if (text.startsWith('-')) {
- const amount = parseFloat(text.substring(1));
- if (!isNaN(amount)) {
- const transactionData = {
- groupId: msg.chat.id.toString(),
- groupName: msg.chat.title || '未命名群组',
- amount: amount
- };
- try {
- const result = await Transaction.withdrawal(transactionData);
- if (result.success) {
- const billMessage = await generateBillMessage(msg.chat.id);
- if (billMessage) {
- await sendMessage(msg.chat.id, billMessage, {
- reply_markup: generateInlineKeyboard(msg.chat.id)
- });
- console.log(`出款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- } else {
- await sendMessage(msg.chat.id, '出款成功,但获取账单信息失败');
- console.log(`出款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- }
- } else {
- await sendMessage(msg.chat.id, result.message || '出款失败');
- console.log(`出款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
- }
- } catch (error) {
- console.error('快捷出款失败:', error);
- await sendMessage(msg.chat.id, '记录出款失败,请稍后重试');
- }
- }
- }
- });
- // 处理新成员加入
- bot.on('new_chat_members', async (msg) => {
- const chatId = msg.chat.id;
- const newMembers = msg.new_chat_members;
-
- for (const member of newMembers) {
- if (member.id === (await bot.getMe()).id) {
- // 机器人被添加到群组
- console.log(`机器人被添加到群组: ${chatId}`);
-
- // 检查群组是否在允许列表中
- const chatIdStr = chatId.toString();
- try {
- // 先检查数据库中是否存在该群组
- const existingGroup = await Group.findByGroupId(chatIdStr);
-
- if (existingGroup) {
- // 如果群组存在,更新群组状态为活跃,同时更新群组名称和加入时间
- await pool.query(`
- UPDATE groups
- SET is_active = true,
- group_name = ?,
- last_join_time = CURRENT_TIMESTAMP
- WHERE group_id = ?
- `, [msg.chat.title || existingGroup.group_name, chatIdStr]);
- // 更新内存中的群组列表
- if (!data.allowedGroups.includes(chatIdStr)) {
- data.allowedGroups.push(chatIdStr);
- saveData();
- }
- // 发送欢迎消息并显示当前账单
- await sendMessage(chatId, '感谢重新添加我为群组成员!');
- const billMessage = await generateBillMessage(chatId);
- if (billMessage) {
- await sendMessage(chatId, billMessage, {
- reply_markup: generateInlineKeyboard(chatId)
- });
- }
- } else {
- // 如果群组不存在,创建新群组
- const groupData = {
- groupId: chatIdStr,
- groupName: msg.chat.title || '未命名群组',
- groupType: msg.chat.type === 'private' ? 'personal' : 'public',
- creatorId: msg.from.id.toString()
- };
-
- const id = await Group.create({
- groupId: groupData.groupId,
- groupName: groupData.groupName,
- creatorId: groupData.creatorId
- });
- const group = await Group.findById(id);
- if (group) {
- // 更新内存中的群组列表
- data.allowedGroups.push(chatIdStr);
- saveData();
- await sendMessage(chatId, '感谢添加我为群组成员!使用 /help 查看可用命令。');
- } else {
- await sendMessage(chatId, '添加群组失败,请联系管理员。');
- }
- }
- } catch (error) {
- console.error('处理群组加入失败:', error);
- await sendMessage(chatId, '添加群组失败,请联系管理员。');
- }
- } else {
- // 其他新成员
- console.log(`新成员加入群组: ${member.username || member.first_name} (${member.id})`);
- await sendMessage(chatId, `欢迎 ${member.username || member.first_name} 加入群组!`);
- }
- }
- });
- // 处理管理员命令
- bot.onText(/\/addgroup (.+)/, async (msg, match) => {
- if (!isAdmin(msg.from.id)) {
- sendMessage(msg.chat.id, '您没有权限执行此命令。');
- return;
- }
- const groupId = match[1].trim();
- if (!data.allowedGroups.includes(groupId)) {
- try {
- // 使用 createGroup 创建新群组
- const groupData = {
- groupId: groupId,
- groupName: '手动添加的群组',
- groupType: 'public',
- creatorId: msg.from.id.toString()
- };
-
- const result = await createGroup({ body: groupData });
- if (result) {
- data.allowedGroups.push(groupId);
- saveData();
- sendMessage(msg.chat.id, `群组 ${groupId} 已添加到允许列表。`);
- } else {
- sendMessage(msg.chat.id, '添加群组失败,请检查群组ID是否正确。');
- }
- } catch (error) {
- console.error('创建群组失败:', error);
- sendMessage(msg.chat.id, '添加群组失败,请稍后重试。');
- }
- } else {
- sendMessage(msg.chat.id, '该群组已在允许列表中。');
- }
- });
- bot.onText(/\/removegroup (.+)/, async (msg, match) => {
- if (!isAdmin(msg.from.id)) {
- sendMessage(msg.chat.id, '您没有权限执行此命令。');
- return;
- }
- const groupId = match[1].trim();
- try {
- // 使用 updateGroup 更新群组状态
- const result = await updateGroup({
- params: { id: groupId },
- body: { isActive: false }
- });
-
- if (result) {
- const index = data.allowedGroups.indexOf(groupId);
- if (index > -1) {
- data.allowedGroups.splice(index, 1);
- saveData();
- sendMessage(msg.chat.id, `群组 ${groupId} 已从允许列表中移除。`);
- } else {
- sendMessage(msg.chat.id, '该群组不在允许列表中。');
- }
- } else {
- sendMessage(msg.chat.id, '移除群组失败,请稍后重试。');
- }
- } catch (error) {
- console.error('更新群组状态失败:', error);
- sendMessage(msg.chat.id, '移除群组失败,请稍后重试。');
- }
- });
- bot.onText(/\/listgroups/, async (msg) => {
- if (!isAdmin(msg.from.id)) {
- sendMessage(msg.chat.id, '您没有权限执行此命令。');
- return;
- }
- try {
- const groups = await pool.query('SELECT group_id, group_name, group_type, is_active FROM groups WHERE is_active = 1');
- if (groups.length === 0) {
- sendMessage(msg.chat.id, '当前没有允许的群组。');
- return;
- }
- const groupsList = groups.map(group =>
- `ID: ${group.group_id}\n名称: ${group.group_name}\n类型: ${group.group_type}\n状态: ${group.is_active ? '启用' : '禁用'}`
- ).join('\n\n');
-
- sendMessage(msg.chat.id, `允许的群组列表:\n\n${groupsList}`);
- } catch (error) {
- console.error('获取群组列表失败:', error);
- sendMessage(msg.chat.id, '获取群组列表失败,请稍后重试。');
- }
- });
- // 处理入款命令
- bot.onText(/\/deposit (.+)/, async (msg, match) => {
- if (!isGroupAllowed(msg.chat.id)) {
- sendMessage(msg.chat.id, '该群组未授权使用此功能');
- return;
- }
- const amount = parseFloat(match[1]);
- if (isNaN(amount)) {
- sendMessage(msg.chat.id, '请输入有效的金额');
- return;
- }
- const transactionData = {
- groupId: msg.chat.id.toString(),
- groupName: msg.chat.title || '未命名群组',
- amount: amount
- };
- try {
- const result = await Transaction.deposit(transactionData);
- if (result.success) {
- const billMessage = await generateBillMessage(msg.chat.id);
- if (billMessage) {
- await sendMessage(msg.chat.id, billMessage, {
- reply_markup: generateInlineKeyboard(msg.chat.id)
- });
- console.log(`入款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- } else {
- await sendMessage(msg.chat.id, '入款成功,但获取账单信息失败');
- console.log(`入款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- }
- } else {
- await sendMessage(msg.chat.id, result.message || '入款失败');
- console.log(`入款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
- }
- } catch (error) {
- console.error('记录入款失败:', error);
- await sendMessage(msg.chat.id, '记录入款失败,请稍后重试');
- }
- });
- // 处理下发命令
- bot.onText(/\/withdraw (.+)/, async (msg, match) => {
- if (!isGroupAllowed(msg.chat.id)) {
- sendMessage(msg.chat.id, '该群组未授权使用此功能');
- return;
- }
- const amount = parseFloat(match[1]);
- if (isNaN(amount)) {
- sendMessage(msg.chat.id, '请输入有效的金额');
- return;
- }
- const transactionData = {
- groupId: msg.chat.id.toString(),
- groupName: msg.chat.title || '未命名群组',
- amount: amount
- };
- try {
- const result = await Transaction.withdrawal(transactionData);
- if (result.success) {
- const billMessage = await generateBillMessage(msg.chat.id);
- if (billMessage) {
- await sendMessage(msg.chat.id, billMessage, {
- reply_markup: generateInlineKeyboard(msg.chat.id)
- });
- console.log(`出款成功 - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- } else {
- await sendMessage(msg.chat.id, '出款成功,但获取账单信息失败');
- console.log(`出款成功(无账单) - 群组: ${msg.chat.title}, 金额: ${amount}, 时间: ${new Date().toLocaleString()}`);
- }
- } else {
- await sendMessage(msg.chat.id, result.message || '出款失败');
- console.log(`出款失败 - 群组: ${msg.chat.title}, 金额: ${amount}, 原因: ${result.message}, 时间: ${new Date().toLocaleString()}`);
- }
- } catch (error) {
- console.error('记录出款失败:', error);
- await sendMessage(msg.chat.id, '记录出款失败,请稍后重试');
- }
- });
- // 处理查看账单命令
- bot.onText(/\/bill/, async (msg) => {
- const billMessage = await generateBillMessage(msg.chat.id);
- sendMessage(msg.chat.id, billMessage, {
- reply_markup: generateInlineKeyboard(msg.chat.id)
- });
- });
- // 更新帮助命令
- bot.onText(/\/help/, (msg) => {
- const helpMessage = `
- 🤖 *机器人使用指南*
- 📝 *基础命令*
- • /deposit <金额> - 记录入款
- • /withdraw <金额> - 记录下发
- • /bill - 查看当前账单
- • /help - 显示此帮助信息
- ⚡️ *快捷命令*
- • +<金额> - 快速记录入款(例如:+2000)
- • -<金额> - 快速记录下发(例如:-2000)
- 👨💼 *管理员命令*
- • /addgroup <群组ID> - 添加允许的群组
- • /removegroup <群组ID> - 移除允许的群组
- • /listgroups - 列出所有允许的群组
- 💡 *使用提示*
- • 所有金额输入请使用数字
- • 账单信息实时更新
- • 如需帮助请联系管理员
- `;
- sendMessage(msg.chat.id, helpMessage, { parse_mode: 'Markdown' });
- });
- // 生成账单消息
- async function generateBillMessage(chatId) {
- try {
- // 获取群组的最后加入时间
- const [groupInfo] = await pool.query(
- 'SELECT last_join_time FROM groups WHERE group_id = ?',
- [chatId.toString()]
- );
- if (!groupInfo || groupInfo.length === 0) {
- return '暂无交易记录';
- }
- const lastJoinTime = groupInfo[0].last_join_time;
- // 获取机器人加入后的交易记录
- const [records] = await pool.query(`
- SELECT * FROM transactions
- WHERE group_id = ?
- AND time >= ?
- ORDER BY time DESC
- LIMIT 10
- `, [chatId.toString(), lastJoinTime]);
- if (!records || records.length === 0) {
- return '暂无交易记录';
- }
- const deposits = records.filter(r => r.type === 'deposit');
- const withdrawals = records.filter(r => r.type === 'withdrawal');
-
- const totalDeposit = deposits.reduce((sum, d) => sum + parseFloat(d.amount), 0);
- const totalWithdrawal = withdrawals.reduce((sum, w) => sum + parseFloat(w.amount), 0);
- const depositFee = totalDeposit * (process.env.DEPOSIT_FEE_RATE || 0);
- const withdrawalFee = totalWithdrawal * (process.env.WITHDRAWAL_FEE_RATE || 0);
- const remaining = totalDeposit - depositFee - totalWithdrawal - withdrawalFee;
- let message = `📊 *账单明细*\n\n`;
- // 添加入款记录
- if (deposits.length > 0) {
- message += `💰 *入款记录* (${deposits.length}笔)\n`;
- deposits.forEach(deposit => {
- message += `• <code>${moment(deposit.time).format('HH:mm:ss')}</code> | ${parseFloat(deposit.amount).toFixed(2)}\n`;
- });
- message += '\n';
- }
- // 添加下发记录
- if (withdrawals.length > 0) {
- message += `💸 *下发记录* (${withdrawals.length}笔)\n`;
- withdrawals.forEach(withdrawal => {
- message += `• <code>${moment(withdrawal.time).format('HH:mm:ss')}</code> | ${parseFloat(withdrawal.amount).toFixed(2)}\n`;
- });
- message += '\n';
- }
- // 添加统计信息
- message += `📈 *统计信息*\n`;
- message += `• 总入款:${totalDeposit.toFixed(2)}\n`;
- message += `• 入款费率:${((process.env.DEPOSIT_FEE_RATE || 0) * 100).toFixed(1)}%\n`;
- message += `• 下发费率:${((process.env.WITHDRAWAL_FEE_RATE || 0) * 100).toFixed(1)}%\n`;
- message += `• 应下发:${(totalDeposit - depositFee).toFixed(2)}\n`;
- message += `• 总下发:${totalWithdrawal.toFixed(2)}\n`;
- message += `• 下发单笔附加费:0.0\n`;
- message += `• 单笔附费加总计:0.0\n\n`;
- message += `💵 *余额:${remaining.toFixed(2)}*`;
- return message;
- } catch (error) {
- console.error('生成账单消息失败:', error);
- return '获取账单信息失败,请稍后重试';
- }
- }
- // 生成内联键盘
- function generateInlineKeyboard(chatId) {
- const keyboard = {
- inline_keyboard: [
- [
- {
- text: '点击跳转完整账单',
- callback_data: `bill_page_${chatId}`
- }
- ],
- [
- {
- text: '24小时商务对接',
- callback_data: 'business_contact'
- }
- ]
- ]
- };
- return keyboard;
- }
- // 处理内联按钮回调
- bot.on('callback_query', async (callbackQuery) => {
- const chatId = callbackQuery.message.chat.id;
- const data = callbackQuery.data;
- try {
- if (data.startsWith('bill_page_')) {
- const groupId = data.split('_')[2];
- await bot.answerCallbackQuery(callbackQuery.id, {
- url: `${process.env.BILL_PAGE_BASE_URL}?groupId=${groupId}`
- });
- } else if (data === 'business_contact') {
- await bot.answerCallbackQuery(callbackQuery.id, {
- url: 'https://t.me/your_business_account'
- });
- }
- } catch (error) {
- console.error('处理内联按钮回调失败:', error);
- await bot.answerCallbackQuery(callbackQuery.id, {
- text: '操作失败,请稍后重试',
- show_alert: true
- });
- }
- });
- // 保存数据
- function saveData() {
- try {
- fs.writeFileSync(process.env.DB_FILE, JSON.stringify(data, null, 2));
- } catch (error) {
- console.error('Error saving data:', error);
- }
- }
- // 加载数据
- function loadData() {
- try {
- if (fs.existsSync(process.env.DB_FILE)) {
- const savedData = JSON.parse(fs.readFileSync(process.env.DB_FILE));
- data = { ...data, ...savedData };
- }
- } catch (error) {
- console.error('Error loading data:', error);
- }
- }
- // 测试数据库连接并初始化
- testConnection().then(() => {
- return initDatabase();
- }).then(() => {
- // 加载数据
- loadData();
-
- // 启动服务器
- const PORT = process.env.PORT || 3000;
- app.listen(PORT, () => {
- console.log(`服务器运行在端口 ${PORT}`);
- console.log('机器人已准备就绪!');
- });
- }).catch(error => {
- console.error('启动失败:', error);
- process.exit(1);
- });
|