From db65438551e4d4dafb148aff4f41b35fe6c5248d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A6=AC=E5=89=83=20=E5=A4=A9=E6=84=9B=E6=98=9F?= <131457234+TiaraBasori@users.noreply.github.com> Date: Sat, 13 Dec 2025 18:49:46 +0800 Subject: [PATCH] Add autorepeat Plugin --- autorepeat/autorepeat.ts | 345 +++++++++++++++++++++++++++++++++++++++ plugins.json | 4 + 2 files changed, 349 insertions(+) create mode 100644 autorepeat/autorepeat.ts diff --git a/autorepeat/autorepeat.ts b/autorepeat/autorepeat.ts new file mode 100644 index 00000000..37f5b9c1 --- /dev/null +++ b/autorepeat/autorepeat.ts @@ -0,0 +1,345 @@ +import { Plugin } from "@utils/pluginBase"; +import { getGlobalClient } from "@utils/globalClient"; +import { createDirectoryInAssets } from "@utils/pathHelpers"; +import { Api, TelegramClient } from "telegram"; +import { Low } from "lowdb"; +import { JSONFile } from "lowdb/node"; +import path from "path"; + +// ==================== 配置常量 ==================== +const CONFIG = { + CACHE_DB_NAME: "repeat.json", + MESSAGE_AUTO_DELETE: 10, +}; + +// ==================== 帮助文本 ==================== +const HELP_TEXT = `自动复读插件使用说明 + +指令列表: +.autorepeat on - 开启本群自动复读 +.autorepeat off - 关闭本群自动复读 +.autorepeat - 查看当前状态 + +复读规则: +• 触发条件:5分钟内有5位不同用户发送完全相同的内容 +• 每日限制:同一群组内,相同内容每天只会自动复读一次 (UTC+8 0点重置) +• 忽略规则:匿名消息、非文本消息会被忽略 +`; + +// ==================== 缓存管理器 ==================== +type CacheData = { + cache: Record; +}; + +class CacheManager { + private db: Low | null = null; + private static instance: CacheManager; + private initPromise: Promise; + + private constructor() { + this.initPromise = this.initDb(); + } + + static getInstance(): CacheManager { + if (!this.instance) { + this.instance = new CacheManager(); + } + return this.instance; + } + + private async initDb(): Promise { + const dbPath = path.join( + createDirectoryInAssets("aban"), + CONFIG.CACHE_DB_NAME + ); + const adapter = new JSONFile(dbPath); + this.db = new Low(adapter, { cache: {} }); + await this.db.read(); + if (!this.db.data) { + this.db.data = { cache: {} }; + await this.db.write(); + } + } + + async get(key: string): Promise { + await this.initPromise; + if (!this.db) return null; + return this.db.data.cache[key] || null; + } + + async set(key: string, value: any): Promise { + await this.initPromise; + if (!this.db) return; + this.db.data.cache[key] = value; + await this.db.write(); + } +} + +// ==================== 消息管理器 ==================== +class MessageManager { + static async smartEdit( + message: Api.Message, + text: string, + deleteAfter: number = CONFIG.MESSAGE_AUTO_DELETE, + parseMode: "html" | "md" = "html" + ): Promise { + try { + const client = await getGlobalClient(); + if (!client) return message; + + await client.editMessage(message.peerId, { + message: message.id, + text: text, + parseMode: parseMode, + linkPreview: false, + }); + + if (deleteAfter > 0) { + setTimeout(async () => { + try { + await client.deleteMessages(message.peerId, [message.id], { + revoke: true, + }); + } catch (e) { + console.error(`删除消息失败: ${e}`); + } + }, deleteAfter * 1000); + } + + return message; + } catch (error: any) { + console.error(`编辑消息失败: ${error.message || error}`); + return message; + } + } +} + +// ==================== 权限管理器 ==================== +class PermissionManager { + static async checkAdminPermission( + client: TelegramClient, + chatId: any + ): Promise { + try { + const me = await client.getMe(); + const participant = await client.invoke( + new Api.channels.GetParticipant({ + channel: chatId, + participant: me.id + }) + ); + + const p = participant.participant; + if (p instanceof Api.ChannelParticipantCreator) return true; + if (p instanceof Api.ChannelParticipantAdmin) { + // 只要是管理员就行,或者检查具体权限 + return true; + } + return false; + } catch (error) { + return false; + } + } +} + +// ==================== 复读机管理器 ==================== +class AutoRepeatManager { + private static cache = CacheManager.getInstance(); + // 消息记录: groupId -> Array<{userId, text, time}> + private static recentMessages: Map> = new Map(); + // 当日已复读记录: groupId -> Set + private static dailyHistory: Map> = new Map(); + // 设置: groupId -> boolean + private static enabledGroups: Set = new Set(); + + private static lastCleanup = 0; + private static lastDayCheck = 0; + + static async init() { + // 加载设置 + const settings = await this.cache.get("autorepeat_settings"); + if (settings && Array.isArray(settings)) { + this.enabledGroups = new Set(settings); + } + } + + static async toggleGroup(groupId: number, enable: boolean) { + if (enable) { + this.enabledGroups.add(groupId); + } else { + this.enabledGroups.delete(groupId); + } + // 保存设置 + await this.cache.set("autorepeat_settings", Array.from(this.enabledGroups)); + } + + static isEnabled(groupId: number): boolean { + return this.enabledGroups.has(groupId); + } + + static async checkAndRepeat(message: Api.Message) { + try { + if (!message.chatId) return; + const chatId = Number(message.chatId); + + // 检查开关 + if (!this.enabledGroups.has(chatId)) return; + + // 必须是文本消息 + const text = message.message; + if (!text) return; + + const now = Math.floor(Date.now() / 1000); + + // 定期清理过期消息和重置每日记录 + this.maintenance(now); + + // 获取当前群组的消息记录 + let msgs = this.recentMessages.get(chatId) || []; + + // 添加新消息 + const senderId = message.senderId ? Number(message.senderId) : 0; + if (senderId === 0) return; // 忽略匿名发送者 + + msgs.push({ + userId: senderId, + text: text, + time: now + }); + + // 过滤掉超过5分钟的消息 + msgs = msgs.filter(m => now - m.time <= 300); + this.recentMessages.set(chatId, msgs); + + // 检查是否满足复读条件 + await this.tryRepeat(chatId, text, msgs); + + } catch (e) { + console.error(`[AutoRepeat] Error: ${e}`); + } + } + + private static async tryRepeat(chatId: number, text: string, msgs: Array<{ userId: number; text: string; time: number }>) { + // 统计发送由于该内容的不同用户数量 + const senders = new Set(); + for (const msg of msgs) { + if (msg.text === text) { + senders.add(msg.userId); + } + } + + // 条件:至少5人在5分钟内发送 + if (senders.size >= 5) { + // 检查今日是否已复读 + if (!this.dailyHistory.has(chatId)) { + this.dailyHistory.set(chatId, new Set()); + } + + // 简单哈希(或直接用文本,如果文本不太长) + const contentKey = text.length > 50 ? text.substring(0, 50) + text.length : text; + + if (!this.dailyHistory.get(chatId)?.has(contentKey)) { + // [关键修改] 先标记为已复读,防止并发重复 + this.dailyHistory.get(chatId)?.add(contentKey); + + // 执行复读 + const client = await getGlobalClient(); + if (client) { + try { + await client.sendMessage(chatId, { message: text }); + console.log(`[AutoRepeat] Group ${chatId} repeated: ${contentKey}`); + } catch (e) { + // 发送失败则移除标记(可选,视需求而定,为了防刷通常不移除) + console.error(`[AutoRepeat] Failed to send: ${e}`); + } + } + } + } + } + + private static maintenance(now: number) { + // 每分钟清理一次过期消息 + if (now - this.lastCleanup > 60) { + for (const [gid, msgs] of this.recentMessages) { + const valid = msgs.filter(m => now - m.time <= 300); + if (valid.length === 0) { + this.recentMessages.delete(gid); + } else { + this.recentMessages.set(gid, valid); + } + } + this.lastCleanup = now; + } + + // 每天重置复读记录 + const dayKey = Math.floor((now + 8 * 3600) / 86400); // UTC+8 天数 + if (dayKey > this.lastDayCheck) { + this.dailyHistory.clear(); + this.lastDayCheck = dayKey; + } + } +} + +// 初始化 +AutoRepeatManager.init().catch(e => console.error(`[AutoRepeat] Init failed: ${e}`)); + +// ==================== 命令处理器 ==================== +class CommandHandlers { + static async handleAutoRepeatCommand(message: Api.Message) { + try { + const args = message.message?.split(" ").slice(1) || []; + const action = args[0]?.toLowerCase(); + + const chatId = Number(message.chatId); + + if (action === "on") { + await AutoRepeatManager.toggleGroup(chatId, true); + await MessageManager.smartEdit(message, "✅ 自动复读已开启", 2); + } else if (action === "off") { + await AutoRepeatManager.toggleGroup(chatId, false); + await MessageManager.smartEdit(message, "❌ 自动复读已关闭", 2); + } else { + const status = AutoRepeatManager.isEnabled(chatId) ? "开启" : "关闭"; + await MessageManager.smartEdit(message, `🤖 自动复读状态: ${status}`); + } + + } catch (e: any) { + await MessageManager.smartEdit(message, `❌ 设置失败: ${e.message}`); + } + } +} + +// ==================== 插件主类 ==================== +class AutoRepeatPlugin extends Plugin { + description: string = HELP_TEXT; + + cmdHandlers: Record Promise> = { + autorepeat: async (msg) => { + const client = await getGlobalClient(); + if (!client) { + await MessageManager.smartEdit(msg, "❌ 客户端未初始化"); + return; + } + await CommandHandlers.handleAutoRepeatCommand(msg); + }, + }; + + listenMessageHandler = async (msg: Api.Message) => { + // 忽略之前的旧消息(只处理实时消息) + if (Date.now() / 1000 - msg.date > 60) return; + + // 忽略自己发送的消息 + if (msg.out) return; + + // 忽略其他机器人发送的消息 + const sender = await msg.getSender(); + if (sender instanceof Api.User && sender.bot) { + return; + } + + await AutoRepeatManager.checkAndRepeat(msg); + }; +} + +// 导出插件实例 +export default new AutoRepeatPlugin(); diff --git a/plugins.json b/plugins.json index 90d51d59..6101f441 100644 --- a/plugins.json +++ b/plugins.json @@ -390,5 +390,9 @@ "prometheus": { "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/prometheus/prometheus.ts?raw=true", "desc": "突破Telegram保存限制" + }, + "autorepeat": { + "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/autorepeat/autorepeat.ts?raw=true", + "desc": "智能自动复读机" } }