|
@@ -0,0 +1,604 @@
|
|
|
+const TelegramBot = require('node-telegram-bot-api');
|
|
|
+const logger = require('../utils/logger');
|
|
|
+const { Subscription } = require('../models');
|
|
|
+const MultiSubscriptionManager = require('./multiSubscriptionManager');
|
|
|
+
|
|
|
+class BotManager {
|
|
|
+ constructor() {
|
|
|
+ this.botToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
|
+ this.allowedChatIds = process.env.TELEGRAM_CHAT_ID?.split(',').map(id => id.trim()) || [];
|
|
|
+ this.subscriptionManager = new MultiSubscriptionManager();
|
|
|
+
|
|
|
+ // 可选的代理配置
|
|
|
+ this.proxyConfig = null;
|
|
|
+ if (process.env.TELEGRAM_PROXY_URL) {
|
|
|
+ this.proxyConfig = {
|
|
|
+ host: process.env.TELEGRAM_PROXY_HOST,
|
|
|
+ port: parseInt(process.env.TELEGRAM_PROXY_PORT) || 1080,
|
|
|
+ protocol: process.env.TELEGRAM_PROXY_PROTOCOL || 'http'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.botToken) {
|
|
|
+ logger.warn('Telegram Bot Token未配置,机器人功能将被禁用');
|
|
|
+ this.enabled = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const botOptions = {
|
|
|
+ polling: true,
|
|
|
+ request: {
|
|
|
+ timeout: 30000, // 增加超时时间到30秒
|
|
|
+ connect_timeout: 30000
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 如果配置了代理,添加代理设置
|
|
|
+ if (this.proxyConfig) {
|
|
|
+ const { HttpsProxyAgent } = require('https-proxy-agent');
|
|
|
+ const proxyUrl = `${this.proxyConfig.protocol}://${this.proxyConfig.host}:${this.proxyConfig.port}`;
|
|
|
+ botOptions.request.httpsAgent = new HttpsProxyAgent(proxyUrl);
|
|
|
+ logger.info(`使用代理连接Telegram: ${proxyUrl}`);
|
|
|
+ } else {
|
|
|
+ botOptions.request.agent = false; // 禁用代理,避免网络问题
|
|
|
+ }
|
|
|
+
|
|
|
+ this.bot = new TelegramBot(this.botToken, botOptions);
|
|
|
+
|
|
|
+ // 添加错误处理
|
|
|
+ this.bot.on('polling_error', (error) => {
|
|
|
+ logger.warn('Telegram轮询错误,尝试重连...', {
|
|
|
+ error: error.message,
|
|
|
+ code: error.code
|
|
|
+ });
|
|
|
+
|
|
|
+ // 如果是网络超时,尝试重新启动轮询
|
|
|
+ if (error.code === 'EFATAL' || error.message.includes('ESOCKETTIMEDOUT')) {
|
|
|
+ setTimeout(() => {
|
|
|
+ try {
|
|
|
+ this.bot.stopPolling();
|
|
|
+ setTimeout(() => {
|
|
|
+ this.bot.startPolling();
|
|
|
+ logger.info('Telegram机器人重连成功');
|
|
|
+ }, 5000);
|
|
|
+ } catch (reconnectError) {
|
|
|
+ logger.error('Telegram机器人重连失败', { error: reconnectError.message });
|
|
|
+ }
|
|
|
+ }, 10000);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.enabled = true;
|
|
|
+ this.setupCommands();
|
|
|
+ this.setupMessageHandlers();
|
|
|
+ logger.info('Telegram机器人管理器初始化成功');
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('Telegram机器人管理器初始化失败', { error: error.message });
|
|
|
+ this.enabled = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置机器人命令
|
|
|
+ */
|
|
|
+ setupCommands() {
|
|
|
+ if (!this.enabled) return;
|
|
|
+
|
|
|
+ this.bot.setMyCommands([
|
|
|
+ { command: '/start', description: '开始使用机器人' },
|
|
|
+ { command: '/help', description: '显示帮助信息' },
|
|
|
+ { command: '/status', description: '查看系统状态' },
|
|
|
+ { command: '/subscriptions', description: '查看所有订阅' },
|
|
|
+ { command: '/add_subscription', description: '添加订阅链接' },
|
|
|
+ { command: '/remove_subscription', description: '删除订阅' },
|
|
|
+ { command: '/update_subscriptions', description: '手动更新订阅' },
|
|
|
+ { command: '/test_speed', description: '手动触发测速' }
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置消息处理器
|
|
|
+ */
|
|
|
+ setupMessageHandlers() {
|
|
|
+ if (!this.enabled) return;
|
|
|
+
|
|
|
+ // 处理 /start 命令
|
|
|
+ this.bot.onText(/\/start/, async (msg) => {
|
|
|
+ await this.handleStart(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理 /help 命令
|
|
|
+ this.bot.onText(/\/help/, async (msg) => {
|
|
|
+ await this.handleHelp(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理 /status 命令
|
|
|
+ this.bot.onText(/\/status/, async (msg) => {
|
|
|
+ await this.handleStatus(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理 /subscriptions 命令
|
|
|
+ this.bot.onText(/\/subscriptions/, async (msg) => {
|
|
|
+ await this.handleSubscriptions(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理 /add_subscription 命令
|
|
|
+ this.bot.onText(/\/add_subscription/, async (msg) => {
|
|
|
+ await this.handleAddSubscription(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理 /remove_subscription 命令
|
|
|
+ this.bot.onText(/\/remove_subscription/, async (msg) => {
|
|
|
+ await this.handleRemoveSubscription(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理 /update_subscriptions 命令
|
|
|
+ this.bot.onText(/\/update_subscriptions/, async (msg) => {
|
|
|
+ await this.handleUpdateSubscriptions(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理 /test_speed 命令
|
|
|
+ this.bot.onText(/\/test_speed/, async (msg) => {
|
|
|
+ await this.handleTestSpeed(msg);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理普通消息(用于添加订阅链接)
|
|
|
+ this.bot.on('message', async (msg) => {
|
|
|
+ await this.handleMessage(msg);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查用户权限
|
|
|
+ */
|
|
|
+ isAuthorized(chatId) {
|
|
|
+ return this.allowedChatIds.includes(chatId.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送消息
|
|
|
+ */
|
|
|
+ async sendMessage(chatId, message, options = {}) {
|
|
|
+ try {
|
|
|
+ return await this.bot.sendMessage(chatId, message, {
|
|
|
+ parse_mode: 'Markdown',
|
|
|
+ disable_web_page_preview: true,
|
|
|
+ ...options
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('发送Telegram消息失败', { error: error.message, chatId });
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /start 命令
|
|
|
+ */
|
|
|
+ async handleStart(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const message = `🤖 *欢迎使用测速机器人!*
|
|
|
+
|
|
|
+📋 *可用命令:*
|
|
|
+• /help - 显示帮助信息
|
|
|
+• /status - 查看系统状态
|
|
|
+• /subscriptions - 查看所有订阅
|
|
|
+• /add_subscription - 添加订阅链接
|
|
|
+• /remove_subscription - 删除订阅
|
|
|
+• /update_subscriptions - 手动更新订阅
|
|
|
+• /test_speed - 手动触发测速
|
|
|
+
|
|
|
+💡 *使用提示:*
|
|
|
+• 发送订阅链接可直接添加订阅
|
|
|
+• 支持多种格式:Clash、Shadowsocks、Vmess等
|
|
|
+• 系统会自动解析并更新节点信息`;
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /help 命令
|
|
|
+ */
|
|
|
+ async handleHelp(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const message = `📖 *帮助信息*
|
|
|
+
|
|
|
+🔧 *订阅管理:*
|
|
|
+• 直接发送订阅链接即可添加
|
|
|
+• 支持格式:Clash、Shadowsocks、Vmess、Trojan
|
|
|
+• 系统会自动解析并去重
|
|
|
+
|
|
|
+⚡ *测速功能:*
|
|
|
+• 自动定时测速(默认每10分钟)
|
|
|
+• 支持手动触发测速
|
|
|
+• 节点故障自动通知
|
|
|
+
|
|
|
+📊 *状态监控:*
|
|
|
+• 实时监控节点状态
|
|
|
+• 延迟和速度测试
|
|
|
+• 故障节点自动标记
|
|
|
+
|
|
|
+🔔 *通知功能:*
|
|
|
+• 节点故障通知
|
|
|
+• 节点恢复通知
|
|
|
+• 测试摘要报告
|
|
|
+
|
|
|
+💡 *使用示例:*
|
|
|
+• 发送:\`https://example.com/subscription\`
|
|
|
+• 发送:\`ss://...\`
|
|
|
+• 发送:\`vmess://...\``;
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /status 命令
|
|
|
+ */
|
|
|
+ async handleStatus(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const status = await this.subscriptionManager.getStatus();
|
|
|
+ const subscriptions = await Subscription.findAll({
|
|
|
+ where: { isActive: true }
|
|
|
+ });
|
|
|
+
|
|
|
+ const totalNodes = subscriptions.reduce((sum, sub) => sum + (sub.nodeCount || 0), 0);
|
|
|
+
|
|
|
+ const message = `📊 *系统状态*
|
|
|
+
|
|
|
+📡 *订阅信息:*
|
|
|
+• 活跃订阅:${subscriptions.length} 个
|
|
|
+• 总节点数:${totalNodes} 个
|
|
|
+
|
|
|
+🔄 *更新状态:*
|
|
|
+• 自动更新:${status.autoUpdateEnabled ? '✅ 启用' : '❌ 禁用'}
|
|
|
+• 更新间隔:${status.updateInterval || '未设置'} 秒
|
|
|
+• 最后更新:${status.lastUpdateTime || '未更新'}
|
|
|
+
|
|
|
+⚡ *测速状态:*
|
|
|
+• 测速触发器:${status.speedTestTrigger ? '✅ 已设置' : '❌ 未设置'}
|
|
|
+• 定时测速:${process.env.ENABLE_SCHEDULED_SPEED_TEST !== 'false' ? '✅ 启用' : '❌ 禁用'}
|
|
|
+
|
|
|
+🛠️ *系统信息:*
|
|
|
+• 运行时间:${this.getUptime()}
|
|
|
+• 内存使用:${this.getMemoryUsage()}`;
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('获取状态失败', { error: error.message });
|
|
|
+ await this.sendMessage(chatId, '❌ 获取系统状态失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /subscriptions 命令
|
|
|
+ */
|
|
|
+ async handleSubscriptions(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const subscriptions = await Subscription.findAll({
|
|
|
+ where: { isActive: true },
|
|
|
+ order: [['createdAt', 'DESC']]
|
|
|
+ });
|
|
|
+
|
|
|
+ if (subscriptions.length === 0) {
|
|
|
+ await this.sendMessage(chatId, '📭 暂无订阅,请使用 /add_subscription 添加订阅');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let message = `📡 *订阅列表*\n\n`;
|
|
|
+
|
|
|
+ subscriptions.forEach((sub, index) => {
|
|
|
+ const status = sub.isActive ? '✅' : '❌';
|
|
|
+ const lastUpdate = sub.lastUpdateTime
|
|
|
+ ? new Date(sub.lastUpdateTime).toLocaleString('zh-CN')
|
|
|
+ : '未更新';
|
|
|
+
|
|
|
+ message += `${index + 1}. ${status} *${sub.name}*\n`;
|
|
|
+ message += ` 📊 节点数:${sub.nodeCount || 0}\n`;
|
|
|
+ message += ` 🔗 URL:\`${sub.url}\`\n`;
|
|
|
+ message += ` ⏰ 最后更新:${lastUpdate}\n\n`;
|
|
|
+ });
|
|
|
+
|
|
|
+ message += `💡 *操作提示:*\n`;
|
|
|
+ message += `• 使用 /remove_subscription 删除订阅\n`;
|
|
|
+ message += `• 使用 /update_subscriptions 手动更新\n`;
|
|
|
+ message += `• 直接发送新链接添加订阅`;
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('获取订阅列表失败', { error: error.message });
|
|
|
+ await this.sendMessage(chatId, '❌ 获取订阅列表失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /add_subscription 命令
|
|
|
+ */
|
|
|
+ async handleAddSubscription(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.sendMessage(chatId,
|
|
|
+ `📥 *添加订阅*\n\n` +
|
|
|
+ `请发送订阅链接,支持以下格式:\n` +
|
|
|
+ `• Clash配置:\`https://example.com/clash.yaml\`\n` +
|
|
|
+ `• Shadowsocks:\`ss://...\`\n` +
|
|
|
+ `• Vmess:\`vmess://...\`\n` +
|
|
|
+ `• Trojan:\`trojan://...\`\n\n` +
|
|
|
+ `💡 直接发送链接即可添加订阅`
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /remove_subscription 命令
|
|
|
+ */
|
|
|
+ async handleRemoveSubscription(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const subscriptions = await Subscription.findAll({
|
|
|
+ where: { isActive: true },
|
|
|
+ order: [['createdAt', 'DESC']]
|
|
|
+ });
|
|
|
+
|
|
|
+ if (subscriptions.length === 0) {
|
|
|
+ await this.sendMessage(chatId, '📭 暂无订阅可删除');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let message = `🗑️ *删除订阅*\n\n`;
|
|
|
+ message += `请选择要删除的订阅(发送数字):\n\n`;
|
|
|
+
|
|
|
+ subscriptions.forEach((sub, index) => {
|
|
|
+ message += `${index + 1}. *${sub.name}*\n`;
|
|
|
+ message += ` 📊 节点数:${sub.nodeCount || 0}\n`;
|
|
|
+ message += ` 🔗 \`${sub.url}\`\n\n`;
|
|
|
+ });
|
|
|
+
|
|
|
+ message += `💡 发送数字(如:1)来删除对应订阅`;
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('获取订阅列表失败', { error: error.message });
|
|
|
+ await this.sendMessage(chatId, '❌ 获取订阅列表失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /update_subscriptions 命令
|
|
|
+ */
|
|
|
+ async handleUpdateSubscriptions(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.sendMessage(chatId, '🔄 开始手动更新订阅...');
|
|
|
+
|
|
|
+ const results = await this.subscriptionManager.manualUpdate();
|
|
|
+
|
|
|
+ let message = `✅ *订阅更新完成*\n\n`;
|
|
|
+
|
|
|
+ results.forEach(result => {
|
|
|
+ if (result.error) {
|
|
|
+ message += `❌ *${result.subscriptionName}*\n`;
|
|
|
+ message += ` 错误:${result.error}\n\n`;
|
|
|
+ } else {
|
|
|
+ message += `✅ *${result.subscriptionName}*\n`;
|
|
|
+ message += ` 新增:${result.added} 个\n`;
|
|
|
+ message += ` 更新:${result.updated} 个\n`;
|
|
|
+ message += ` 移除:${result.removed} 个\n\n`;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('手动更新订阅失败', { error: error.message });
|
|
|
+ await this.sendMessage(chatId, '❌ 更新订阅失败:' + error.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 /test_speed 命令
|
|
|
+ */
|
|
|
+ async handleTestSpeed(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.sendMessage(chatId, '⚡ 开始手动测速...');
|
|
|
+
|
|
|
+ // 这里需要调用测速功能
|
|
|
+ // 暂时发送一个简单的消息
|
|
|
+ await this.sendMessage(chatId, '✅ 测速功能开发中,请稍后...');
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('手动测速失败', { error: error.message });
|
|
|
+ await this.sendMessage(chatId, '❌ 测速失败:' + error.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理普通消息
|
|
|
+ */
|
|
|
+ async handleMessage(msg) {
|
|
|
+ const chatId = msg.chat.id;
|
|
|
+ const text = msg.text;
|
|
|
+
|
|
|
+ if (!this.isAuthorized(chatId)) {
|
|
|
+ await this.sendMessage(chatId, '❌ 您没有权限使用此机器人');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否是订阅链接
|
|
|
+ if (this.isSubscriptionUrl(text)) {
|
|
|
+ await this.addSubscription(chatId, text);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否是删除订阅的数字
|
|
|
+ if (/^\d+$/.test(text)) {
|
|
|
+ await this.removeSubscriptionByIndex(chatId, parseInt(text));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他消息
|
|
|
+ await this.sendMessage(chatId,
|
|
|
+ `💡 *消息处理*\n\n` +
|
|
|
+ `收到消息:\`${text}\`\n\n` +
|
|
|
+ `💡 *使用提示:*\n` +
|
|
|
+ `• 发送订阅链接可直接添加订阅\n` +
|
|
|
+ `• 使用 /help 查看所有命令\n` +
|
|
|
+ `• 使用 /subscriptions 查看订阅列表`
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否是订阅链接
|
|
|
+ */
|
|
|
+ isSubscriptionUrl(text) {
|
|
|
+ return /^(https?:\/\/|ss:\/\/|vmess:\/\/|trojan:\/\/)/.test(text);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加订阅
|
|
|
+ */
|
|
|
+ async addSubscription(chatId, url) {
|
|
|
+ try {
|
|
|
+ await this.sendMessage(chatId, '📥 正在添加订阅...');
|
|
|
+
|
|
|
+ // 创建新订阅
|
|
|
+ const subscription = await Subscription.create({
|
|
|
+ name: `订阅_${Date.now()}`,
|
|
|
+ url: url,
|
|
|
+ isActive: true,
|
|
|
+ nodeCount: 0
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新订阅
|
|
|
+ const result = await this.subscriptionManager.manualUpdateSubscription(subscription.id);
|
|
|
+
|
|
|
+ if (result.error) {
|
|
|
+ await this.sendMessage(chatId, `❌ 添加订阅失败:${result.error}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const message = `✅ *订阅添加成功*\n\n` +
|
|
|
+ `📡 订阅名称:*${subscription.name}*\n` +
|
|
|
+ `🔗 URL:\`${url}\`\n` +
|
|
|
+ `📊 节点数:${result.actualNodeCount || 0}\n` +
|
|
|
+ `➕ 新增:${result.added || 0} 个\n` +
|
|
|
+ `🔄 更新:${result.updated || 0} 个\n` +
|
|
|
+ `🗑️ 移除:${result.removed || 0} 个`;
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('添加订阅失败', { error: error.message, url });
|
|
|
+ await this.sendMessage(chatId, '❌ 添加订阅失败:' + error.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据索引删除订阅
|
|
|
+ */
|
|
|
+ async removeSubscriptionByIndex(chatId, index) {
|
|
|
+ try {
|
|
|
+ const subscriptions = await Subscription.findAll({
|
|
|
+ where: { isActive: true },
|
|
|
+ order: [['createdAt', 'DESC']]
|
|
|
+ });
|
|
|
+
|
|
|
+ if (index < 1 || index > subscriptions.length) {
|
|
|
+ await this.sendMessage(chatId, '❌ 无效的订阅编号');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const subscription = subscriptions[index - 1];
|
|
|
+
|
|
|
+ // 标记为非活跃
|
|
|
+ await subscription.update({ isActive: false });
|
|
|
+
|
|
|
+ const message = `🗑️ *订阅删除成功*\n\n` +
|
|
|
+ `📡 订阅名称:*${subscription.name}*\n` +
|
|
|
+ `🔗 URL:\`${subscription.url}\`\n` +
|
|
|
+ `📊 节点数:${subscription.nodeCount || 0}`;
|
|
|
+
|
|
|
+ await this.sendMessage(chatId, message);
|
|
|
+ } catch (error) {
|
|
|
+ logger.error('删除订阅失败', { error: error.message, index });
|
|
|
+ await this.sendMessage(chatId, '❌ 删除订阅失败:' + error.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取运行时间
|
|
|
+ */
|
|
|
+ getUptime() {
|
|
|
+ const uptime = process.uptime();
|
|
|
+ const hours = Math.floor(uptime / 3600);
|
|
|
+ const minutes = Math.floor((uptime % 3600) / 60);
|
|
|
+ return `${hours}小时${minutes}分钟`;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取内存使用情况
|
|
|
+ */
|
|
|
+ getMemoryUsage() {
|
|
|
+ const usage = process.memoryUsage();
|
|
|
+ const used = Math.round(usage.heapUsed / 1024 / 1024);
|
|
|
+ const total = Math.round(usage.heapTotal / 1024 / 1024);
|
|
|
+ return `${used}MB / ${total}MB`;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止机器人
|
|
|
+ */
|
|
|
+ stop() {
|
|
|
+ if (this.enabled && this.bot) {
|
|
|
+ this.bot.stopPolling();
|
|
|
+ logger.info('Telegram机器人已停止');
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = BotManager;
|