diff --git a/clean/clean.ts b/clean/clean.ts new file mode 100644 index 00000000..884a9119 --- /dev/null +++ b/clean/clean.ts @@ -0,0 +1,592 @@ +import { Plugin } from "@utils/pluginBase"; +import { Api } from "telegram"; +import { getGlobalClient } from "@utils/globalClient"; +import { banUser, getBannedUsers, unbanUser } from "@utils/banUtils"; + +// HTML 转义函数 +const htmlEscape = (text: string): string => + text.replace(/[&<>"']/g, m => ({ + '&': '&', '<': '<', '>': '>', + '"': '"', "'": ''' + }[m] || m)); + +// 延迟函数 +const sleep = (ms: number): Promise => + new Promise(resolve => setTimeout(resolve, ms)); + +// 帮助文本 +const HELP_TEXT = `🧹 账号清理工具 Pro + +📝 功能概述: +• 删除账号清理: 扫描并清理已注销/删除的账号 +• 拉黑用户清理: 解除双向拉黑状态 +• 被封禁实体解封: 解封群组中被封禁的用户/频道/群组 + +🔧 命令列表: + +删除账号清理: +• .clean deleted pm - 扫描私聊中的已注销账号 +• .clean deleted pm rm - 扫描并删除已注销账号的私聊 +• .clean deleted member - 扫描群组中的已注销账号 +• .clean deleted member rm - 扫描并清理群组中的已注销账号 + +拉黑用户清理: +• .clean blocked pm - 清理拉黑用户(智能模式) +• .clean blocked pm all - 清理所有拉黑用户(全量模式) + +被封禁实体解封: +• .clean blocked member - 解封自己封禁的实体 +• .clean blocked member all - 解封所有被封禁的实体 + +帮助信息: +• .clean help - 显示此帮助信息 + +⚠️ 权限要求: +• 群组操作需要管理员权限 +• 封禁清理需要封禁用户权限 +• 私聊清理仅操作机器人自身对话`; + +class CleanPlugin { + description: string | ((...args: any[]) => string | void) | ((...args: any[]) => Promise); + cmdHandlers: Record Promise>; + + // 清理进度状态 + private cleanupStartTime: number = 0; + private blockedCleanupStartTime: number = 0; + + constructor() { + this.description = HELP_TEXT; + this.cmdHandlers = { + clean: this.handleClean.bind(this) + }; + } + + // 主命令处理器 + private async handleClean(msg: Api.Message): Promise { + const client = await getGlobalClient(); + if (!client) { + await this.editMessage(msg, "❌ 客户端未就绪"); + return; + } + + try { + const text = msg.text || ""; + const parts = text.trim().split(/\s+/); + const subCommand = parts[1]?.toLowerCase(); + + // 显示帮助 + if (!subCommand || subCommand === "help" || subCommand === "h") { + await this.editMessage(msg, HELP_TEXT); + return; + } + + await this.editMessage(msg, "🔄 正在处理请求..."); + + // 路由到对应功能模块 + switch (subCommand) { + case "deleted": + await this.handleDeletedClean(client, msg, parts); + break; + case "blocked": + await this.handleBlockedClean(client, msg, parts); + break; + default: + await this.sendError(msg, `未知子命令: ${htmlEscape(subCommand)}`); + break; + } + + } catch (error: any) { + await this.handleError(msg, error); + } + } + + // 处理删除账号清理 + private async handleDeletedClean(client: any, msg: Api.Message, parts: string[]): Promise { + const action = parts[2]?.toLowerCase(); + const operation = parts[3]?.toLowerCase(); + + if (!action) { + await this.sendError(msg, "请指定清理类型: pm (私聊) 或 member (群组)"); + return; + } + + switch (action) { + case "pm": + await this.cleanDeletedPM(client, msg, operation === "rm"); + break; + case "member": + await this.cleanDeletedMember(client, msg, operation === "rm"); + break; + default: + await this.sendError(msg, `未知类型: ${htmlEscape(action)}`); + break; + } + } + + // 处理拉黑/解封清理 + private async handleBlockedClean(client: any, msg: Api.Message, parts: string[]): Promise { + const action = parts[2]?.toLowerCase(); + const mode = parts[3]?.toLowerCase(); + + if (!action) { + await this.sendError(msg, "请指定清理类型: pm (私聊拉黑) 或 member (群组封禁)"); + return; + } + + switch (action) { + case "pm": + await this.cleanBlockedPM(client, msg, mode === "all"); + break; + case "member": + await this.unblockMember(client, msg, mode === "all"); + break; + default: + await this.sendError(msg, `未知类型: ${htmlEscape(action)}`); + break; + } + } + + // 私聊已注销账号清理 + private async cleanDeletedPM(client: any, msg: Api.Message, deleteDialogs: boolean = false): Promise { + await this.editMessage(msg, deleteDialogs + ? "🔍 正在扫描并清理私聊已注销账号..." + : "🔍 正在扫描私聊已注销账号..."); + + const deletedUsers: Array<{id: string, username?: string}> = []; + const dialogs = await client.getDialogs(); + + for (const dialog of dialogs) { + if (dialog.isUser && dialog.entity?.className === "User") { + const user = dialog.entity as Api.User; + + if (user.deleted) { + const userId = user.id.toString(); + const username = user.username || "未知"; + deletedUsers.push({ id: userId, username }); + + if (deleteDialogs) { + try { + await client.deleteDialog(user.id, { revoke: true }); + await sleep(100); + } catch (error: any) { + if (error.message?.includes("FLOOD_WAIT")) { + await this.handleFloodWait(msg, error); + return; + } + } + } + } + } + } + + let result = ""; + if (deletedUsers.length === 0) { + result = "✅ 扫描完成\n\n未找到已注销账号的私聊会话。"; + } else { + result = deleteDialogs + ? `✅ 清理完成\n\n已清理 ${deletedUsers.length} 个已注销账号的私聊会话:\n\n` + : `✅ 扫描完成\n\n共找到 ${deletedUsers.length} 个已注销账号的私聊会话:\n\n`; + + deletedUsers.slice(0, 15).forEach((user, index) => { + const userLink = user.username && user.username !== "未知" + ? `@${user.username}` + : `${user.id}`; + result += `• ${userLink}\n`; + }); + + if (deletedUsers.length > 15) { + result += `\n... 还有 ${deletedUsers.length - 15} 个未显示\n`; + } + + if (!deleteDialogs) { + result += `\n💡 使用 .clean deleted pm rm 清理这些会话`; + } + } + + await this.editMessage(msg, result); + } + + // 群组已注销账号清理 + private async cleanDeletedMember(client: any, msg: Api.Message, cleanMembers: boolean = false): Promise { + const chat = await msg.getChat(); + if (!chat || !(chat instanceof Api.Chat || chat instanceof Api.Channel)) { + await this.sendError(msg, "此命令仅在群组中可用"); + return; + } + + await this.editMessage(msg, cleanMembers + ? "🔍 正在扫描并清理群组中的已注销账号..." + : "🔍 正在扫描群组群组中的已注销账号..."); + + const chatId = chat.id; + if (cleanMembers && !await this.checkBanPermission(client, chatId)) { + await this.sendError(msg, "没有封禁用户权限,无法执行清理"); + return; + } + + let deletedCount = 0; + const deletedUsers: Array<{id: string, username?: string}> = []; + + const participants = client.iterParticipants(chatId); + for await (const participant of participants) { + if (participant instanceof Api.User && participant.deleted) { + deletedCount++; + deletedUsers.push({ + id: participant.id.toString(), + username: participant.username || "未知" + }); + + if (cleanMembers) { + try { + await banUser(client, chatId, participant.id); + await sleep(100); + } catch (error: any) { + if (error.message?.includes("FLOOD_WAIT")) { + await this.handleFloodWait(msg, error); + return; + } + } + } + } + } + + let result = ""; + if (deletedCount === 0) { + result = "✅ 扫描完成\n\n此群组中没有发现已注销账号。"; + } else { + result = cleanMembers + ? `✅ 清理完成\n\n已清理 ${deletedCount} 个已注销账号:\n\n` + : `✅ 扫描完成\n\n此群组的已注销账号数: ${deletedCount}:\n\n`; + + deletedUsers.slice(0, 15).forEach(user => { + result += `• ${user.id}\n`; + }); + + if (deletedCount > 15) { + result += `\n... 还有 ${deletedCount - 15} 个未显示\n`; + } + + if (!cleanMembers) { + result += `\n💡 使用 .clean deleted member rm 清理这些已注销账号`; + } + } + + await this.editMessage(msg, result); + } + + // 拉黑用户清理 + private async cleanBlockedPM(client: any, msg: Api.Message, includeAll: boolean = false): Promise { + this.blockedCleanupStartTime = Date.now(); + + await this.editMessage(msg, + `🧹 开始清理拉黑用户\n\n模式: ${includeAll ? '全量清理' : '智能清理'}`); + + let offset = 0; + let success = 0, failed = 0, skipped = 0, totalUsers = 0, processedUsers = 0; + let consecutiveErrors = 0; + + // 获取总数 + try { + const initialBlocked = await client.invoke(new Api.contacts.GetBlocked({ offset: 0, limit: 1 })); + totalUsers = initialBlocked.className === 'contacts.BlockedSlice' + ? (initialBlocked as any).count || 0 + : (await client.invoke(new Api.contacts.GetBlocked({ offset: 0, limit: 1000 })))?.users?.length || 0; + } catch (error) { + console.error("获取用户总数失败:", error); + } + + while (true) { + try { + const blocked = await client.invoke(new Api.contacts.GetBlocked({ offset, limit: 100 })); + if (!blocked.users?.length) break; + + for (const user of blocked.users) { + processedUsers++; + + // 智能模式跳过机器人/诈骗账户 + if (!includeAll && (user.bot || user.scam || user.fake)) { + skipped++; + continue; + } + + try { + await client.invoke(new Api.contacts.Unblock({ id: user })); + success++; + + // 动态延迟 + const delay = this.getDynamicDelay(user, includeAll, consecutiveErrors); + await sleep(delay); + consecutiveErrors = 0; + } catch (error: any) { + if (error.message?.includes('FLOOD_WAIT_')) { + await this.handleFloodWait(msg, error); + continue; + } else { + failed++; + consecutiveErrors++; + await sleep(this.getDynamicDelay(user, includeAll, consecutiveErrors)); + } + } + + // 更新进度 + if (processedUsers % 10 === 0) { + await this.updateBlockedProgress(msg, processedUsers, totalUsers, success, failed, skipped, includeAll); + } + } + + offset += 100; + if (blocked.className === 'contacts.BlockedSlice' && offset >= (blocked as any).count) break; + + // 批次间延迟 + const batchDelay = consecutiveErrors > 0 ? 3000 + (consecutiveErrors * 1000) : 2000; + await sleep(Math.min(batchDelay, 10000)); + + } catch (error: any) { + console.error("获取拉黑列表失败:", error); + if (error.message?.includes('FLOOD_WAIT')) { + await this.handleFloodWait(msg, error); + continue; + } + break; + } + } + + const result = this.buildBlockedResult(success, failed, skipped, totalUsers, includeAll); + await this.editMessage(msg, result); + } + + // 群组解封 + private async unblockMember(client: any, msg: Api.Message, includeAll: boolean = false): Promise { + if (!msg.isChannel && !msg.isGroup) { + await this.sendError(msg, "此命令只能在群组中使用"); + return; + } + + await this.editMessage(msg, "🔓 正在获取被封禁实体列表..."); + + const me = await client.getMe(); + const myId = Number(me.id); + const chatEntity = msg.peerId; + + let bannedUsers = await getBannedUsers(client, chatEntity); + if (!includeAll) { + bannedUsers = bannedUsers.filter(u => u.kickedBy === myId); + } + + if (bannedUsers.length === 0) { + await this.editMessage(msg, "ℹ️ 没有找到需要解封的实体"); + await sleep(3000); + await this.safeDelete(msg); + return; + } + + await this.editMessage(msg, `⚡ 正在解封 ${bannedUsers.length} 个实体...`); + + const entityStats = { users: 0, channels: 0, chats: 0 }; + bannedUsers.forEach(entity => { + if (entity.type === 'user') entityStats.users++; + else if (entity.type === 'channel') entityStats.channels++; + else if (entity.type === 'chat') entityStats.chats++; + }); + + let successCount = 0, failedCount = 0; + const failedEntities: string[] = []; + + for (const entity of bannedUsers) { + const success = await unbanUser(client, chatEntity, entity.id); + if (success) { + successCount++; + } else { + failedCount++; + const displayName = entity.type === 'user' + ? `${entity.firstName}(${entity.id})` + : `${entity.title || entity.firstName}[${entity.type}](${entity.id})`; + failedEntities.push(displayName); + } + await sleep(500); + } + + let statsText = ""; + if (entityStats.users > 0) statsText += `👤 用户: ${entityStats.users} `; + if (entityStats.channels > 0) statsText += `📢 频道: ${entityStats.channels} `; + if (entityStats.chats > 0) statsText += `💬 群组: ${entityStats.chats}`; + + let resultText = ""; + if (failedCount > 0) { + resultText = `✅ 解封完成\n\n${statsText}\n成功: ${successCount} 个\n失败: ${failedCount} 个`; + } else { + resultText = `✅ 解封完成\n\n${statsText}\n已成功解封 ${successCount} 个实体`; + } + + await this.editMessage(msg, resultText); + await sleep(5000); + await this.safeDelete(msg); + } + + // 工具函数 + private async editMessage(msg: Api.Message, text: string): Promise { + try { + await msg.edit({ text, parseMode: "html" }); + } catch (error) { + console.error("编辑消息失败:", error); + } + } + + private async sendError(msg: Api.Message, errorMsg: string): Promise { + await this.editMessage(msg, `❌ 错误: ${htmlEscape(errorMsg)}`); + } + + private async handleError(msg: Api.Message, error: any): Promise { + console.error(`[CleanPlugin] 错误:`, error); + + let errorMsg = `❌ 操作失败: ${htmlEscape(error.message || "未知错误")}`; + + if (error.message?.includes("FLOOD_WAIT")) { + const waitTime = parseInt(error.message.match(/\d+/)?.[0] || "60"); + errorMsg = `⏳ 请求过于频繁\n\n需要等待 ${waitTime} 秒后重试`; + } else if (error.message?.includes("CHAT_ADMIN_REQUIRED")) { + errorMsg = "🔒 权限不足\n\n需要管理员权限"; + } else if (error.message?.includes("USER_NOT_PARTICIPANT")) { + errorMsg = "❌ 未加入群组\n\n机器人需要先加入群组"; + } + + await this.editMessage(msg, errorMsg); + } + + private async handleFloodWait(msg: Api.Message, error: any): Promise { + const waitTime = parseInt(error.message.match(/\d+/)?.[0] || "60"); + await this.editMessage(msg, `⏳ 需要等待 ${waitTime} 秒后继续`); + await sleep((waitTime + 1) * 1000); + } + + private async safeDelete(msg: Api.Message): Promise { + try { + await msg.delete({ revoke: true }); + } catch (error) { + // 忽略删除错误 + } + } + + private getDynamicDelay(user: any, includeAll: boolean, consecutiveErrors: number): number { + let delay = 200; + + if (user.bot) delay = 1500; + else if (user.scam || user.fake) delay = 800; + + if (includeAll) delay = Math.max(delay, 1000); + if (consecutiveErrors > 0) delay = delay * (1 + consecutiveErrors * 0.5); + + return Math.min(delay, 5000); + } + + private async updateBlockedProgress(msg: Api.Message, processed: number, total: number, + success: number, failed: number, skipped: number, includeAll: boolean): Promise { + try { + const percentage = total > 0 ? Math.round((processed / total) * 100) : 0; + const progressBar = '█'.repeat(Math.round((percentage / 100) * 20)) + '░'.repeat(20 - Math.round((percentage / 100) * 20)); + + const progressText = `🧹 清理拉黑用户进行中 + +📊 进度: ${percentage}% (${processed}/${total}) +${progressBar} + +📈 统计: +• ✅ 成功: ${success} +• ❌ 失败: ${failed} +• ⏭️ 跳过: ${skipped} + +⚙️ 模式: ${includeAll ? '全量清理' : '智能清理'} + +⏱️ 剩余时间: ${this.estimateRemainingTime(processed, total, Date.now() - this.blockedCleanupStartTime)}`; + + await this.editMessage(msg, progressText); + } catch (e) { + // 忽略编辑错误 + } + } + + private estimateRemainingTime(processed: number, total: number, elapsedMs: number): string { + if (processed === 0 || total === 0) return "计算中..."; + + const avgTimePerUser = elapsedMs / processed; + const remaining = total - processed; + const estimatedMs = avgTimePerUser * remaining; + + if (estimatedMs < 1000) return "即将完成"; + if (estimatedMs < 60000) return `约 ${Math.ceil(estimatedMs / 1000)} 秒`; + return `约 ${Math.ceil(estimatedMs / 60000)} 分钟`; + } + + private buildBlockedResult(success: number, failed: number, skipped: number, total: number, includeAll: boolean): string { + const efficiency = total > 0 ? Math.round((success / total) * 100) : 0; + const totalProcessed = success + failed + skipped; + + let statusEmoji = "✅", statusText = "成功完成"; + if (failed > 0 && failed > success) { + statusEmoji = "⚠️"; statusText = "部分完成"; + } else if (success === 0) { + statusEmoji = "ℹ️"; statusText = "无需清理"; + } + + return `${statusEmoji} 清理拉黑用户${statusText} + +📊 统计结果: +• 总计用户: ${total} +• 成功清理: ${success} +• 清理失败: ${failed} +• 跳过处理: ${skipped} +• 成功率: ${efficiency}% + +⚙️ 清理模式: ${includeAll ? '全量清理' : '智能清理'} + +📈 处理详情: +• 已处理: ${totalProcessed}/${total} +${skipped > 0 ? `• 跳过原因: ${includeAll ? '系统限制' : '机器人/诈骗/虚假账户'}` : ''} + +💡 提示: ${failed > 0 ? '部分失败可能是由于API限制或网络问题' : '所有操作已成功完成'}`; + } + + private async checkBanPermission(client: any, chatId: any): Promise { + try { + const me = await client.getMe(); + + let participant; + if (chatId instanceof Api.Channel) { + participant = await client.invoke(new Api.channels.GetParticipant({ channel: chatId, participant: me })); + } else { + participant = await client.invoke(new Api.messages.GetFullChat({ chatId })); + } + + if (participant instanceof Api.channels.ChannelParticipant) { + const participantObj = participant.participant; + if (participantObj instanceof Api.ChannelParticipantCreator) return true; + if (participantObj instanceof Api.ChannelParticipantAdmin) return participantObj.adminRights.banUsers || false; + } + + if (participant instanceof Api.messages.ChatFull) { + const fullChat = participant.fullChat; + if (fullChat instanceof Api.ChatFull) { + const participants = fullChat.participants; + if (participants instanceof Api.ChatParticipants) { + const meParticipant = participants.participants.find((p: any) => p.userId?.equals(me.id)); + if (meParticipant instanceof Api.ChatParticipantCreator || meParticipant instanceof Api.ChatParticipantAdmin) { + return true; + } + } + } + } + + return false; + + } catch (error: any) { + if (error.message?.includes("CHAT_ADMIN_REQUIRED") || + error.message?.includes("USER_NOT_PARTICIPANT") || + error.message?.includes("PEER_ID_INVALID")) { + return false; + } + return true; + } + } +} + +export default new CleanPlugin(); \ No newline at end of file diff --git a/cleanda/cleanda.ts b/cleanda/cleanda.ts deleted file mode 100644 index 7833b465..00000000 --- a/cleanda/cleanda.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Plugin } from "@utils/pluginBase"; -import { Api } from "telegram"; -import { getGlobalClient } from "@utils/globalClient"; - -const htmlEscape = (text: string): string => - text.replace(/[&<>"']/g, m => ({ - '&': '&', '<': '<', '>': '>', - '"': '"', "'": ''' - }[m] || m)); - -const HELP_TEXT = `🧹 CleanDa - 已注销账号清理工具 - -功能: -查找所有与已注销Telegram账号的私聊会话 - -命令: -• .cleanda - 扫描已注销账号的私聊会话 - -说明: -该命令会扫描您的所有私聊对话,找出那些账号已注销的用户。 -扫描完成后会列出这些用户的ID,您可以根据需要手动清理这些对话。`; - -class CleanDaPlugin extends Plugin { - description = HELP_TEXT; - - cmdHandlers = { - cleanda: this.handleCleanDa.bind(this) - }; - - private async handleCleanDa(msg: Api.Message): Promise { - const client = await getGlobalClient(); - if (!client) { - await msg.edit({ - text: "❌ 错误:无法获取Telegram客户端", - parseMode: "html" - }); - return; - } - - try { - // 更新消息状态 - await msg.edit({ - text: "🔄 正在扫描私聊会话...\n请稍候,这可能需要一些时间。", - parseMode: "html" - }); - - const deletedUsers: string[] = []; - - // 获取所有对话 - const dialogs = await client.getDialogs(); - - for (const dialog of dialogs) { - // 只处理私聊对话 - if (dialog.isUser) { - try { - const entity = dialog.entity; - - // 检查是否为用户实体 - if (entity && entity.className === "User") { - const user = entity as Api.User; - - // 检查用户是否已注销 - if (user.deleted) { - const userId = user.id.toString(); - deletedUsers.push(userId); - } - } - } catch (error) { - // 忽略获取用户信息时的错误,继续处理下一个对话 - console.warn(`[CleanDa] 获取用户信息失败:`, error); - } - } - } - - // 生成结果消息 - let resultMessage = ""; - - if (deletedUsers.length === 0) { - resultMessage = "✅ 扫描完成\n\n未找到与已注销账号的私聊会话。"; - } else { - resultMessage = `✅ 扫描完成\n\n共找到 ${deletedUsers.length} 个与已注销账号的私聊会话:\n\n`; - - // 为每个已注销用户生成链接 - deletedUsers.forEach(userId => { - resultMessage += `• ${userId}\n`; - }); - - resultMessage += `\n💡 操作建议:\n点击上面的用户ID可以快速跳转到对话,建议手动清理这些对话。`; - } - - await msg.edit({ - text: resultMessage, - parseMode: "html" - }); - - } catch (error: any) { - console.error(`[CleanDa] 扫描失败:`, error); - - await msg.edit({ - text: `❌ 扫描失败:${htmlEscape(error.message || "未知错误")}`, - parseMode: "html" - }); - } - } -} - -export default new CleanDaPlugin(); diff --git a/clearblocked/clearblocked.ts b/clearblocked/clearblocked.ts deleted file mode 100644 index f788f3c4..00000000 --- a/clearblocked/clearblocked.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { getPrefixes } from "@utils/pluginManager"; -import { Plugin } from "@utils/pluginBase"; -import { Api } from "telegram"; -import { getGlobalClient } from "@utils/globalClient"; - -// 获取命令前缀 -const prefixes = getPrefixes(); -const mainPrefix = prefixes[0]; - -// HTML转义工具 -const htmlEscape = (text: string): string => - text.replace(/[&<>"']/g, m => ({ - '&': '&', '<': '<', '>': '>', - '"': '"', "'": ''' - }[m] || m)); - -// 延迟函数 -const sleep = (ms: number): Promise => - new Promise(resolve => setTimeout(resolve, ms)); - -// 动态延迟策略 -const getDynamicDelay = (user: any, includeAll: boolean, consecutiveErrors: number): number => { - // 基础延迟 - let delay = 200; - - // 根据用户类型调整 - if (user.bot) { - delay = 1500; // 机器人需要更长延迟 - } else if (user.scam || user.fake) { - delay = 800; // 诈骗/虚假账户中等延迟 - } - - // 全量清理模式增加延迟 - if (includeAll) { - delay = Math.max(delay, 1000); - } - - // 根据连续错误次数增加延迟 - if (consecutiveErrors > 0) { - delay = delay * (1 + consecutiveErrors * 0.5); - } - - return Math.min(delay, 5000); // 最大延迟5秒 -}; - -// 帮助文档 -const help_text = `🧹 清理拉黑用户插件 - -📝 功能描述: -• 🚫 批量清理:清理所有已拉黑的用户 -• ⚡ 智能过滤:默认跳过机器人、诈骗和虚假账户 -• 🤖 全量清理:可选择清理包括机器人在内的所有用户 -• 📊 详细统计:显示成功、失败和跳过的数量 -• 🔄 防洪处理:自动处理 Telegram API 限制 -• ⏱️ 智能限速:机器人清理时自动延迟避免API限制 - -🔧 使用方法: -• ${mainPrefix}clearblocked - 清理拉黑用户(跳过机器人) -• ${mainPrefix}clearblocked all - 清理所有拉黑用户(包括机器人) -• ${mainPrefix}clearblocked help - 显示此帮助 - -⚠️ 注意事项: -• 此操作需要管理员权限 -• 清理过程中请勿关闭程序 -• 大量用户清理可能需要较长时间 -• 使用 all 参数会清理所有类型的用户,包括机器人 -• 清理机器人时会自动添加延迟以避免API限制 - -💡 示例: -• ${mainPrefix}clearblocked - 智能清理(跳过机器人) -• ${mainPrefix}clearblocked all - 全量清理(包括机器人,较慢)`; - -class ClearBlockedPlugin extends Plugin { - private startTime: number = 0; - description: string = `批量取消拉黑所有用户\n\n${help_text}`; - - cmdHandlers: Record Promise> = { - clearblocked: async (msg: Api.Message, trigger?: Api.Message) => { - const client = await getGlobalClient(); - if (!client) { - await msg.edit({ text: "❌ 客户端未初始化", parseMode: "html" }); - return; - } - - // 参数解析(严格按acron.ts模式) - const lines = msg.text?.trim()?.split(/\r?\n/g) || []; - const parts = lines?.[0]?.split(/\s+/) || []; - const [, ...args] = parts; // 跳过命令本身 - const sub = (args[0] || "").toLowerCase(); - - try { - // 无参数时显示错误提示,不自动显示帮助 - if (!sub) { - // 默认行为:智能清理(跳过机器人) - const result = await this.clearBlockedUsers(client, msg, false); - - await msg.edit({ - text: this.buildCompletionMessage(result, false), - parseMode: "html" - }); - return; - } - - // 明确请求帮助时才显示 - if (sub === "help" || sub === "h") { - await msg.edit({ - text: help_text, - parseMode: "html" - }); - return; - } - - // 检查是否为 all 参数 - const includeAll = sub === "all"; - - // 如果有未知参数,显示错误提示 - if (sub !== "all") { - await msg.edit({ - text: `❌ 未知参数: ${htmlEscape(sub)}\n\n💡 使用 ${mainPrefix}clearblocked help 查看帮助`, - parseMode: "html" - }); - return; - } - - // 开始清理拉黑用户 - const result = await this.clearBlockedUsers(client, msg, includeAll); - - await msg.edit({ - text: this.buildCompletionMessage(result, includeAll), - parseMode: "html" - }); - - } catch (error: any) { - console.error("[clearblocked] 插件执行失败:", error); - const errorMessage = error.message || "未知错误"; - await msg.edit({ - text: `❌ 清理失败\n\n错误信息: ${htmlEscape(errorMessage)}\n\n💡 如遇到频繁错误,请稍后重试或使用 ${mainPrefix}clearblocked help 查看帮助`, - parseMode: "html" - }); - } - } - }; - - private async clearBlockedUsers( - client: any, - msg: Api.Message, - includeAll: boolean = false - ): Promise<{success: number, failed: number, skipped: number, total: number}> { - this.startTime = Date.now(); // 记录开始时间 - let offset = 0; - let success = 0, failed = 0, skipped = 0; - let totalUsers = 0; - let processedUsers = 0; - let lastUpdateTime = Date.now(); - const updateInterval = 500; // 更新间隔毫秒数 - let consecutiveErrors = 0; // 连续错误计数 - - // 首先获取总数用于进度计算 - try { - const initialBlocked = await client.invoke(new Api.contacts.GetBlocked({ - offset: 0, - limit: 1 - })); - - if (initialBlocked.className === 'contacts.BlockedSlice') { - totalUsers = (initialBlocked as any).count || 0; - } else { - // 如果是 contacts.Blocked 类型,需要获取所有用户来计算总数 - const allBlocked = await client.invoke(new Api.contacts.GetBlocked({ - offset: 0, - limit: 1000 - })); - totalUsers = allBlocked.users?.length || 0; - } - - await msg.edit({ - text: `🧹 开始清理拉黑用户\n\n📊 发现拉黑用户: ${totalUsers} 个\n🔄 清理模式: ${includeAll ? '全量清理(包括机器人)' : '智能清理(跳过机器人)'}\n\n⏳ 正在初始化...`, - parseMode: "html" - }); - - await sleep(1000); - } catch (error) { - console.error("[clearblocked] 获取用户总数失败:", error); - } - - while (true) { - try { - // 获取拉黑用户列表 - const blocked = await client.invoke(new Api.contacts.GetBlocked({ - offset: offset, - limit: 100 - })); - - if (!blocked.users || blocked.users.length === 0) { - break; - } - - for (const user of blocked.users) { - processedUsers++; - - // 根据 includeAll 参数决定是否跳过机器人、诈骗和虚假账户 - // includeAll=false 时跳过机器人等,includeAll=true 时清理所有用户 - if (!includeAll && (user.bot || user.scam || user.fake)) { - skipped += 1; - - // 限制更新频率,避免过于频繁的消息编辑 - if (Date.now() - lastUpdateTime > updateInterval) { - await this.updateProgress(msg, processedUsers, totalUsers, success, failed, skipped, user, "跳过", includeAll); - lastUpdateTime = Date.now(); - } - continue; - } - - // 限制更新频率 - if (Date.now() - lastUpdateTime > updateInterval) { - await this.updateProgress(msg, processedUsers, totalUsers, success, failed, skipped, user, "处理中", includeAll); - lastUpdateTime = Date.now(); - } - - try { - await client.invoke(new Api.contacts.Unblock({ - id: user - })); - success += 1; - - // 限制更新频率 - if (Date.now() - lastUpdateTime > updateInterval) { - await this.updateProgress(msg, processedUsers, totalUsers, success, failed, skipped, user, "成功", includeAll); - lastUpdateTime = Date.now(); - } - - // 使用动态延迟策略 - const delay = getDynamicDelay(user, includeAll, consecutiveErrors); - await sleep(delay); - consecutiveErrors = 0; // 重置错误计数 - } catch (error: any) { - // 处理 FloodWait 错误 - if (error.message && error.message.includes('FLOOD_WAIT_')) { - const waitTime = parseInt(error.message.match(/\d+/)?.[0] || "60"); - - try { - await msg.edit({ - text: `🧹 清理拉黑用户中...\n\n⏳ 需要等待 ${waitTime} 秒后继续`, - parseMode: "html" - }); - } catch (e) { - // 忽略消息编辑错误 - } - - await sleep((waitTime + 1) * 1000); - - try { - await msg.edit({ - text: "🧹 继续清理拉黑用户...", - parseMode: "html" - }); - } catch (e) { - // 忽略消息编辑错误 - } - - // 重试取消拉黑 - await client.invoke(new Api.contacts.Unblock({ - id: user - })); - success += 1; - - // 限制更新频率 - if (Date.now() - lastUpdateTime > updateInterval) { - await this.updateProgress(msg, processedUsers, totalUsers, success, failed, skipped, user, "成功", includeAll); - lastUpdateTime = Date.now(); - } - - // 重试成功后使用动态延迟 - const delay = getDynamicDelay(user, includeAll, 0); - await sleep(delay); - consecutiveErrors = 0; // 重置错误计数 - } else { - failed += 1; - consecutiveErrors++; // 增加错误计数 - - // 限制更新频率 - if (Date.now() - lastUpdateTime > updateInterval) { - await this.updateProgress(msg, processedUsers, totalUsers, success, failed, skipped, user, "失败", includeAll); - lastUpdateTime = Date.now(); - } - - // 错误后增加延迟 - const errorDelay = getDynamicDelay(user, includeAll, consecutiveErrors); - await sleep(errorDelay); - } - } - } - - offset += 100; - - // 检查是否还有更多用户 - if (blocked.className === 'contacts.BlockedSlice') { - if (offset >= (blocked as any).count) { - break; - } - } else { - // contacts.Blocked 类型表示已获取所有用户 - break; - } - - // 批次间延迟,根据错误情况动态调整 - const batchDelay = consecutiveErrors > 0 ? 3000 + (consecutiveErrors * 1000) : 2000; - await sleep(Math.min(batchDelay, 10000)); // 最大延迟10秒 - - } catch (error: any) { - console.error("[clearblocked] 获取拉黑列表失败:", error); - - // 处理特定的 API 错误 - if (error.message?.includes('FLOOD_WAIT')) { - const waitTime = parseInt(error.message.match(/\d+/)?.[0] || "60"); - await msg.edit({ - text: `⏳ API 限制,需要等待 ${waitTime} 秒后继续...`, - parseMode: "html" - }); - await sleep(waitTime * 1000); - continue; // 继续循环 - } - - throw new Error(`获取拉黑列表失败: ${error.message || '未知错误'}`); - } - } - - return { success, failed, skipped, total: totalUsers }; - } - - private buildCompletionMessage( - result: {success: number, failed: number, skipped: number, total: number}, - includeAll: boolean - ): string { - const totalProcessed = result.success + result.failed + result.skipped; - const efficiency = result.total > 0 ? Math.round((result.success / result.total) * 100) : 0; - - let statusEmoji = "✅"; - let statusText = "成功完成"; - - if (result.failed > 0 && result.failed > result.success) { - statusEmoji = "⚠️"; - statusText = "部分完成"; - } else if (result.success === 0) { - statusEmoji = "ℹ️"; - statusText = "无需清理"; - } - - return `🧹 清理拉黑用户${statusText} - -${statusEmoji} 清理模式: ${includeAll ? '全量清理(包括机器人)' : '智能清理(跳过机器人)'} - -📊 统计结果: -• 📋 总计用户: ${result.total} -• ✅ 成功清理: ${result.success} -• ❌ 清理失败: ${result.failed} -• ⏭️ 跳过处理: ${result.skipped} -• 📈 成功率: ${efficiency}% - -⏱️ 处理详情: -• 已处理: ${totalProcessed}/${result.total} -${result.skipped > 0 ? `• 跳过原因: ${includeAll ? '系统限制' : '机器人/诈骗/虚假账户'}` : ''} - -💡 提示: ${result.failed > 0 ? '部分用户清理失败可能是由于API限制或网络问题' : '所有操作已成功完成'}`; - } - - private async updateProgress( - msg: Api.Message, - processed: number, - total: number, - success: number, - failed: number, - skipped: number, - currentUser: any, - status: string, - includeAll: boolean - ) { - try { - const percentage = total > 0 ? Math.round((processed / total) * 100) : 0; - const progressBarLength = 20; - const filledLength = Math.round((percentage / 100) * progressBarLength); - const progressBar = '█'.repeat(filledLength) + '░'.repeat(progressBarLength - filledLength); - - // 用户类型标识 - let userType = "👤 普通用户"; - if (currentUser.bot) userType = "🤖 机器人"; - else if (currentUser.scam) userType = "⚠️ 诈骗账户"; - else if (currentUser.fake) userType = "🚫 虚假账户"; - else if (currentUser.deleted) userType = "❌ 已删除账户"; - else if (currentUser.verified) userType = "✓ 认证用户"; - - // 状态图标 - let statusIcon = ""; - switch (status) { - case "处理中": statusIcon = "🔄"; break; - case "成功": statusIcon = "✅"; break; - case "跳过": statusIcon = "⏭️"; break; - case "失败": statusIcon = "❌"; break; - } - - // 用户名显示(改进逻辑) - let userName = "未知用户"; - if (currentUser.firstName || currentUser.lastName) { - userName = `${currentUser.firstName || ''} ${currentUser.lastName || ''}`.trim(); - } else if (currentUser.username) { - userName = `@${currentUser.username}`; - } else if (currentUser.id) { - userName = `ID:${currentUser.id}`; - } - - const progressText = `🧹 清理拉黑用户进行中 - -📊 总体进度: ${percentage}% (${processed}/${total}) -${progressBar} - -📈 统计信息: -• ✅ 成功: ${success} -• ❌ 失败: ${failed} -• ⏭️ 跳过: ${skipped} - -🔄 当前处理: -${statusIcon} ${status} - ${userType} -👤 用户: ${htmlEscape(userName)} - -⚙️ 清理模式: ${includeAll ? '全量清理(包括机器人)' : '智能清理(跳过机器人)'} - -⏱️ 预计剩余时间: ${this.estimateRemainingTime(processed, total, Date.now() - this.startTime)}`; - - await msg.edit({ - text: progressText, - parseMode: "html" - }); - } catch (e) { - // 忽略消息编辑错误,避免影响主要流程 - } - } - - private estimateRemainingTime(processed: number, total: number, elapsedMs: number): string { - if (processed === 0 || total === 0) return "计算中..."; - - const avgTimePerUser = elapsedMs / processed; - const remaining = total - processed; - const estimatedMs = avgTimePerUser * remaining; - - if (estimatedMs < 1000) return "即将完成"; - if (estimatedMs < 60000) return `约 ${Math.ceil(estimatedMs / 1000)} 秒`; - if (estimatedMs < 3600000) return `约 ${Math.ceil(estimatedMs / 60000)} 分钟`; - return `约 ${Math.ceil(estimatedMs / 3600000)} 小时`; - } -} - -export default new ClearBlockedPlugin(); diff --git a/getdel/getdel.ts b/getdel/getdel.ts deleted file mode 100644 index c3f1b4e0..00000000 --- a/getdel/getdel.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { Plugin } from "@utils/pluginBase"; -import { Api } from "telegram"; -import { getGlobalClient } from "@utils/globalClient"; -import { banUser } from "@utils/banUtils"; - -// HTML 转义函数(必需) -const htmlEscape = (text: string): string => - text.replace(/[&<>"']/g, m => ({ - '&': '&', '<': '<', '>': '>', - '"': '"', "'": ''' - }[m] || m)); - -// 帮助文本 -const HELP_TEXT = `🧹 死号检测与清理 - -命令格式: -.getdel - 统计死号数量 -.getdel 清理 - 统计并自动清理死号 - -说明: -• 仅在群组中可用 -• 需要管理员权限 -• 清理功能需要封禁用户权限`; - -class GetDelPlugin extends Plugin { - name = "getdel"; - description = HELP_TEXT; - - cmdHandlers = { - getdel: this.handleGetDel.bind(this) - }; - - private async handleGetDel(msg: Api.Message): Promise { - const client = await getGlobalClient(); - if (!client) { - await this.sendError(msg, "客户端未就绪"); - return; - } - - try { - // 检查是否为群组 - const chat = await msg.getChat(); - if (!chat || !(chat instanceof Api.Chat || chat instanceof Api.Channel)) { - await this.sendError(msg, "此命令仅在群组中可用"); - return; - } - - // 解析参数 - const text = msg.text || ""; - const parts = text.trim().split(/\s+/); - const needClean = parts.length > 1 && parts[1] === "清理"; - - await msg.edit({ - text: "🔍 遍历成员中...", - parseMode: "html" - }); - - let deletedCount = 0; - const chatId = chat.id; - - // 如果需要清理,检查权限 - if (needClean) { - const hasBanPermission = await this.checkBanPermissionWithGramJS(client, chatId); - if (!hasBanPermission) { - await this.sendError(msg, "没有封禁用户权限,无法执行清理操作"); - return; - } - } - - // 遍历所有成员 - const participants = client.iterParticipants(chatId); - for await (const participant of participants) { - if (participant instanceof Api.User && participant.deleted) { - deletedCount++; - - // 如果需要清理,则封禁死号 - if (needClean) { - try { - // 使用 banUtils 封禁用户,设置5分钟封禁时间(与原版行为一致) - await banUser(client, chatId, participant.id); - - // 短暂延迟避免 FloodWait - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error: any) { - // 处理 FloodWait 错误 - if (error.message?.includes("FLOOD_WAIT")) { - const waitTime = parseInt(error.message.match(/\d+/)?.[0] || "60"); - await this.sendError(msg, `处理失败,受到 TG 服务器限制,需要等待 ${waitTime} 秒`); - return; - } - // 忽略其他封禁错误,继续处理下一个用户 - console.warn(`封禁用户 ${participant.id} 失败:`, error.message); - } - } - } - } - - // 发送结果 - let resultText: string; - if (needClean) { - resultText = `✅ 清理完成\n\n此群组的死号数:${deletedCount},并且已经清理完毕。`; - } else { - resultText = `📊 统计完成\n\n此群组的死号数:${deletedCount}。`; - } - - await msg.edit({ - text: resultText, - parseMode: "html" - }); - - } catch (error: any) { - await this.handleError(msg, error); - } - } - - /** - * 使用 gramJS 推荐的方式检查封禁权限 - * 通过获取参与者的管理员权限来验证 - */ - private async checkBanPermissionWithGramJS(client: any, chatId: any): Promise { - try { - // 获取当前机器人的信息 - const me = await client.getMe(); - - // 获取机器人在群组中的参与者信息 - let participant; - if (chatId instanceof Api.Channel) { - // 对于频道/超级群组 - participant = await client.invoke( - new Api.channels.GetParticipant({ - channel: chatId, - participant: me - }) - ); - } else { - // 对于普通群组 - participant = await client.invoke( - new Api.messages.GetFullChat({ - chatId: chatId - }) - ); - } - - // 检查权限 - if (participant instanceof Api.channels.ChannelParticipant) { - const participantObj = participant.participant; - - // 如果是创建者,拥有所有权限 - if (participantObj instanceof Api.ChannelParticipantCreator) { - return true; - } - - // 如果是管理员,检查封禁权限 - if (participantObj instanceof Api.ChannelParticipantAdmin) { - return participantObj.adminRights.banUsers || false; - } - } - - // 对于普通群组,检查是否有管理员权限 - if (participant instanceof Api.messages.ChatFull) { - const fullChat = participant.fullChat; - if (fullChat instanceof Api.ChatFull) { - // 在普通群组中,检查是否是管理员 - const participants = fullChat.participants; - if (participants instanceof Api.ChatParticipants) { - const meParticipant = participants.participants.find( - (p: any) => p.userId && p.userId.equals(me.id) - ); - // 如果是创建者或管理员,则认为有封禁权限 - if (meParticipant instanceof Api.ChatParticipantCreator || - meParticipant instanceof Api.ChatParticipantAdmin) { - return true; - } - } - } - } - - return false; - - } catch (error: any) { - console.error("检查封禁权限失败:", error); - - // 根据错误类型判断权限 - if (error.message?.includes("CHAT_ADMIN_REQUIRED") || - error.message?.includes("USER_NOT_PARTICIPANT") || - error.message?.includes("PEER_ID_INVALID")) { - return false; - } - - // 其他错误可能表示网络问题,默认认为有权限,在实际操作中会再次验证 - return true; - } - } - - /** - * 备用的权限检查方法:通过尝试获取管理员列表来验证权限 - */ - private async checkBanPermissionByAdminList(client: any, chatId: any): Promise { - try { - // 尝试获取管理员列表,如果有权限获取,说明是管理员 - await client.getParticipants(chatId, { - filter: new Api.ChannelParticipantsAdmins() - }); - return true; - } catch (error: any) { - console.error("通过管理员列表检查权限失败:", error); - - if (error.message?.includes("CHAT_ADMIN_REQUIRED") || - error.message?.includes("USER_NOT_PARTICIPANT")) { - return false; - } - - return true; - } - } - - private async sendError(msg: Api.Message, errorMsg: string): Promise { - await msg.edit({ - text: `❌ 错误: ${htmlEscape(errorMsg)}`, - parseMode: "html" - }); - } - - private async handleError(msg: Api.Message, error: any): Promise { - console.error(`[GetDelPlugin] 错误:`, error); - - let errorMsg: string; - - if (error.message?.includes("FLOOD_WAIT")) { - const waitTime = parseInt(error.message.match(/\d+/)?.[0] || "60"); - errorMsg = `⏳ 请求过于频繁\n\n需要等待 ${waitTime} 秒后重试`; - } else if (error.message?.includes("CHAT_ADMIN_REQUIRED")) { - errorMsg = "🔒 权限不足\n\n您需要管理员权限才能使用此命令"; - } else if (error.message?.includes("USER_NOT_PARTICIPANT")) { - errorMsg = "❌ 未加入群组\n\n机器人需要先加入群组才能执行此操作"; - } else if (error.message?.includes("USER_NOT_MUTUAL_CONTACT")) { - errorMsg = "❌ 无法操作\n\n目标用户不是双向联系人"; - } else if (error.message?.includes("ADMIN_RANK_EMOJI_NOT_ALLOWED")) { - errorMsg = "❌ 权限不足\n\n您的管理员等级不足以执行此操作"; - } else if (error.message?.includes("CHANNEL_PRIVATE")) { - errorMsg = "❌ 无法访问\n\n机器人没有权限访问此频道"; - } else { - errorMsg = `❌ 操作失败: ${htmlEscape(error.message || "未知错误")}`; - } - - await msg.edit({ - text: errorMsg, - parseMode: "html" - }); - } -} - -export default new GetDelPlugin(); diff --git a/plugins.json b/plugins.json index f72d9985..90d51d59 100644 --- a/plugins.json +++ b/plugins.json @@ -119,10 +119,6 @@ "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/moyu/moyu.ts?raw=true", "desc": "摸鱼日报" }, - "clearblocked": { - "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/clearblocked/clearblocked.ts?raw=true", - "desc": "批量清理已拉黑用户" - }, "news": { "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/news/news.ts?raw=true", "desc": "每日新闻" @@ -163,10 +159,6 @@ "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/speedlink/speedlink.ts?raw=true", "desc": "对其他服务器测速" }, - "sunremove": { - "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/sunremove/sunremove.ts?raw=true", - "desc": "定向批量解除封禁用户" - }, "whois": { "url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/whois/whois.ts?raw=true", "desc": "域名查询" @@ -351,10 +343,6 @@ "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/listusernames/listusernames.ts?raw=true", "desc": "列出属于自己的公开群组/频道" }, - "getdel": { - "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/getdel/getdel.ts?raw=true", - "desc": "死号检测清理" - }, "annualreport": { "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/annualreport/annualreport.ts?raw=true", "desc": "年度报告" @@ -367,9 +355,9 @@ "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/paolu/paolu.ts?raw=true", "desc": "群组一键跑路" }, - "cleanda": { - "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/cleanda/cleanda.ts?raw=true", - "desc": "已注销账号清理" + "clean": { + "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/clean/clean.ts?raw=true", + "desc": "账号清理工具 Pro" }, "diss": { "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/diss/diss.ts?raw=true", diff --git a/sunremove/sunremove.ts b/sunremove/sunremove.ts deleted file mode 100644 index 6d961a76..00000000 --- a/sunremove/sunremove.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { Plugin } from "@utils/pluginBase"; -import { Api } from "telegram"; -import { getGlobalClient } from "@utils/globalClient"; -import { getPrefixes } from "@utils/pluginManager"; -import { getBannedUsers, unbanUser } from "@utils/banUtils"; - -const prefixes = getPrefixes(); -const mainPrefix = prefixes[0]; - -const htmlEscape = (text: string): string => - text.replace(/[&<>"']/g, m => ({ - '&': '&', '<': '<', '>': '>', - '"': '"', "'": ''' - }[m] || m)); - -const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - -// 帮助文档 -const help_text = `🔓 一键解封工具 - -命令格式: -${mainPrefix}sunremove [子命令] [参数] - -可用命令: -• ${mainPrefix}sunremove - 解封自己封禁的实体 -• ${mainPrefix}sunremove all - 解封所有被封禁的实体 -• ${mainPrefix}sunremove help - 显示此帮助 - -支持类型: -👤 用户 - 普通用户账号 -📢 频道 - Telegram 频道 -💬 群组 - Telegram 群组 - -说明: -此命令用于批量解封被封禁的群组成员、频道和群组,解封后这些实体可以重新加入群组。 - -使用示例: -${mainPrefix}sunremove - 解封我封禁的实体 -${mainPrefix}sunremove all - 解封所有实体`; -const sunremove = async (msg: Api.Message) => { - const client = await getGlobalClient(); - if (!client) { - await msg.edit({ text: "❌ 客户端未初始化", parseMode: "html" }); - return; - } - - // 标准参数解析 - const lines = msg.text?.trim()?.split(/\r?\n/g) || []; - const parts = lines?.[0]?.split(/\s+/) || []; - const [, ...args] = parts; - const sub = (args[0] || "").toLowerCase(); - - try { - // 处理 help 在前的情况:.sunremove help [subcommand] - if (sub === "help" || sub === "h") { - await msg.edit({ text: help_text, parseMode: "html" }); - return; - } - - // 处理 help 在后的情况:.sunremove [subcommand] help - if (args[1] && (args[1].toLowerCase() === "help" || args[1].toLowerCase() === "h")) { - await msg.edit({ text: help_text, parseMode: "html" }); - return; - } - - // 检查是否在群组中 - if (!msg.isChannel && !msg.isGroup) { - await msg.edit({ - text: "❌ 此命令只能在群组中使用", - parseMode: "html" - }); - return; - } - - // 处理具体的子命令 - let mode = "mine"; - if (sub === "all") { - mode = "all"; - } else if (sub !== "" && sub !== "help" && sub !== "h") { - // 未知命令 - await msg.edit({ - text: `❌ 未知命令: ${htmlEscape(sub)}\n\n💡 使用 ${mainPrefix}sunremove help 查看帮助`, - parseMode: "html" - }); - return; - } - // 无参数时执行默认操作(mode = "mine") - - const me = await client.getMe(); - const myId = Number(me.id); - - const chatEntity = msg.peerId; - - await msg.edit({ - text: `🔍 正在获取被封禁实体列表...`, - parseMode: "html" - }); - - let bannedUsers = await getBannedUsers(client, chatEntity); - - if (mode === "mine") { - bannedUsers = bannedUsers.filter(u => u.kickedBy === myId); - } - - if (bannedUsers.length === 0) { - await msg.edit({ - text: `ℹ️ 没有找到需要解封的实体`, - parseMode: "html" - }); - await sleep(3000); - await msg.delete(); - return; - } - - await msg.edit({ - text: `⚡ 正在解封 ${bannedUsers.length} 个实体...`, - parseMode: "html" - }); - - let progressMsg: Api.Message | null = null; - try { - const chat = await client.getEntity(chatEntity); - const chatTitle = 'title' in chat ? chat.title : "未知群组"; - progressMsg = await client.sendMessage("me", { - message: `🔓 解封任务进度\n\n群组: ${chatTitle}\n总数: ${bannedUsers.length} 个实体\n进度: 0/${bannedUsers.length}`, - parseMode: "html" - }); - } catch (e) { - console.error("发送进度消息失败:", e); - } - - let successCount = 0; - let failedCount = 0; - const failedEntities: string[] = []; - const entityStats = { users: 0, channels: 0, chats: 0 }; - - // 统计实体类型 - for (const entity of bannedUsers) { - if (entity.type === 'user') entityStats.users++; - else if (entity.type === 'channel') entityStats.channels++; - else if (entity.type === 'chat') entityStats.chats++; - } - - for (const entity of bannedUsers) { - const success = await unbanUser(client, chatEntity, entity.id); - if (success) { - successCount++; - } else { - failedCount++; - const displayName = entity.type === 'user' - ? `${entity.firstName}(${entity.id})` - : `${entity.title || entity.firstName}[${entity.type}](${entity.id})`; - failedEntities.push(displayName); - } - - if (progressMsg && (successCount + failedCount) % 5 === 0) { - try { - const chat = await client.getEntity(chatEntity); - const chatTitle = 'title' in chat ? chat.title : "未知群组"; - let statsText = ""; - if (entityStats.users > 0) statsText += `👤 用户: ${entityStats.users} `; - if (entityStats.channels > 0) statsText += `📢 频道: ${entityStats.channels} `; - if (entityStats.chats > 0) statsText += `💬 群组: ${entityStats.chats}`; - - await client.editMessage("me", { - message: progressMsg.id, - text: `🔓 解封任务进度\n\n群组: ${chatTitle}\n总数: ${bannedUsers.length} 个实体\n${statsText}\n进度: ${successCount + failedCount}/${bannedUsers.length}\n\n✅ 成功: ${successCount}\n❌ 失败: ${failedCount}`, - parseMode: "html" - }); - } catch (e) { - console.error("更新进度消息失败:", e); - } - } - - await sleep(500); - } - - if (progressMsg) { - try { - const chat = await client.getEntity(chatEntity); - const chatTitle = 'title' in chat ? chat.title : "未知群组"; - let statsText = ""; - if (entityStats.users > 0) statsText += `👤 用户: ${entityStats.users} `; - if (entityStats.channels > 0) statsText += `📢 频道: ${entityStats.channels} `; - if (entityStats.chats > 0) statsText += `💬 群组: ${entityStats.chats}`; - - let finalText = `🔓 解封任务完成\n\n群组: ${chatTitle}\n总数: ${bannedUsers.length} 个实体\n${statsText}\n\n`; - if (failedCount > 0) { - finalText += `✅ 成功: ${successCount} 个\n❌ 失败: ${failedCount} 个\n`; - if (failedEntities.length <= 5) { - finalText += `\n失败实体: ${failedEntities.map(u => htmlEscape(u)).join(", ")}`; - } - } else { - finalText += `✅ 已成功解封所有 ${successCount} 个实体`; - } - - await client.editMessage("me", { - message: progressMsg.id, - text: finalText, - parseMode: "html" - }); - } catch (e) { - console.error("更新最终结果失败:", e); - } - } - - let resultText = ""; - let statsText = ""; - if (entityStats.users > 0) statsText += `👤 ${entityStats.users} `; - if (entityStats.channels > 0) statsText += `📢 ${entityStats.channels} `; - if (entityStats.chats > 0) statsText += `💬 ${entityStats.chats}`; - - if (failedCount > 0) { - resultText = `✅ 解封完成\n\n` + - `${statsText}\n` + - `成功: ${successCount} 个\n` + - `失败: ${failedCount} 个`; - } else { - resultText = `✅ 解封完成\n\n${statsText}\n已成功解封 ${successCount} 个实体`; - } - - await msg.edit({ - text: resultText, - parseMode: "html" - }); - - await sleep(5000); - await msg.delete(); - - } catch (error: any) { - console.error("[sunremove] 插件执行失败:", error); - await msg.edit({ - text: `❌ 插件执行失败: ${htmlEscape(error.message || "未知错误")}`, - parseMode: "html" - }); - } -}; - -class SunRemovePlugin extends Plugin { - description: string = `一键解封工具\n\n${help_text}`; - cmdHandlers: Record Promise> = { - sunremove - }; -} - -export default new SunRemovePlugin();