diff --git a/eatgif/eatgif.ts b/eatgif/eatgif.ts
index c6286ae4..6101ccc2 100644
--- a/eatgif/eatgif.ts
+++ b/eatgif/eatgif.ts
@@ -72,7 +72,12 @@ const help_text = `🧩 头像动图表情
用法:
${commandName} [list|ls|clear|名称]
• 空/ list:查看表情列表
-• 生成:回复目标并输入名称`;
+• 生成:回复目标并输入名称
+
+指定用户:
+• ${commandName} 名称 @A @B - A 对 B
+• ${commandName} 名称 @B (回复A) - A 对 B
+• ${commandName} 名称 (回复B) - 自己对 B`;
const htmlEscape = (text: string): string =>
String(text || "").replace(
@@ -130,6 +135,9 @@ class EatGifPlugin extends Plugin {
const [, ...args] = parts;
const sub = (args[0] || "").toLowerCase();
+ // 提取 @用户名参数
+ const mentionedUsers = args.slice(1).filter((arg) => arg.startsWith("@"));
+
try {
await ensureConfig();
@@ -156,9 +164,19 @@ class EatGifPlugin extends Plugin {
return;
}
- if (!msg.isReply && !trigger?.isReply) {
+ // 检查是否有足够的用户信息
+ const hasReply = msg.isReply || trigger?.isReply;
+ const hasTwoMentions = mentionedUsers.length >= 2;
+ const hasOneMention = mentionedUsers.length === 1;
+
+ if (!hasReply && !hasTwoMentions) {
await msg.edit({
- text: `💡 请先回复一个用户的消息再执行\n\n使用:${commandName} list 查看表情列表`,
+ text: `💡 请指定两个用户或回复一个用户的消息
+
+用法:
+• ${commandName} ${sub} @A @B - A 对 B
+• ${commandName} ${sub} @B (回复A) - A 对 B
+• ${commandName} ${sub} (回复B) - 自己对 B`,
parseMode: "html",
});
return;
@@ -168,7 +186,7 @@ class EatGifPlugin extends Plugin {
text: `⏳ 正在生成 ${htmlEscape(config[sub].desc)}...`,
parseMode: "html",
});
- await this.generateGif(sub, { msg, trigger });
+ await this.generateGif(sub, { msg, trigger, mentionedUsers });
} catch (e: any) {
await msg.edit({
text: `❌ 失败:${htmlEscape(e?.message || String(e))}`,
@@ -200,21 +218,40 @@ class EatGifPlugin extends Plugin {
private async generateGif(
gifName: string,
- params: { msg: Api.Message; trigger?: Api.Message }
+ params: { msg: Api.Message; trigger?: Api.Message; mentionedUsers?: string[] }
) {
- const { msg, trigger } = params;
+ const { msg, trigger, mentionedUsers = [] } = params;
const gifConfig = await loadGifDetailConfig(config[gifName].url);
- // 由于要生成很多张图片,最好就是保存 self.avatar 以及 you.avatar 不断调用
- const meAvatarBuffer = await this.getSelfAvatarBuffer(msg, trigger);
+ // 获取头像的逻辑:
+ // 1. .eatgif kiss @A @B -> A 对 B
+ // 2. 回复A + .eatgif kiss @B -> A 对 B
+ // 3. 回复B + .eatgif kiss -> 自己对 B
+
+ let meAvatarBuffer: Buffer | undefined;
+ let youAvatarBuffer: Buffer | undefined;
+
+ if (mentionedUsers.length >= 2) {
+ // 情况1: 指定了两个用户 @A @B
+ meAvatarBuffer = await this.getAvatarByUsername(msg, mentionedUsers[0]);
+ youAvatarBuffer = await this.getAvatarByUsername(msg, mentionedUsers[1]);
+ } else if (mentionedUsers.length === 1) {
+ // 情况2: 回复A + 指定@B
+ meAvatarBuffer = await this.getReplyUserAvatarBuffer(msg, trigger);
+ youAvatarBuffer = await this.getAvatarByUsername(msg, mentionedUsers[0]);
+ } else {
+ // 情况3: 回复B(原有逻辑)
+ meAvatarBuffer = await this.getSelfAvatarBuffer(msg, trigger);
+ youAvatarBuffer = await this.getReplyUserAvatarBuffer(msg, trigger);
+ }
+
if (!meAvatarBuffer) {
- await msg.edit({ text: "无法获取自己的头像" });
+ await msg.edit({ text: "❌ 无法获取用户A的头像", parseMode: "html" });
await msg.deleteWithDelay(2000);
return;
}
- const youAvatarBuffer = await this.getYouAvatarBuffer(msg, trigger);
if (!youAvatarBuffer) {
- await msg.edit({ text: "无法获取对方的头像" });
+ await msg.edit({ text: "❌ 无法获取用户B的头像", parseMode: "html" });
await msg.deleteWithDelay(2000);
return;
}
@@ -373,7 +410,7 @@ class EatGifPlugin extends Plugin {
left: role.x,
};
}
- // 获取头像等数据
+ // 获取自己的头像
private async getSelfAvatarBuffer(
msg: Api.Message,
trigger?: Api.Message
@@ -385,10 +422,15 @@ class EatGifPlugin extends Plugin {
const meAvatarBuffer = (await msg.client?.downloadProfilePhoto(meId, {
isBig: false,
})) as Buffer | undefined;
+ // 检查 buffer 是否有效
+ if (!meAvatarBuffer || meAvatarBuffer.length === 0) {
+ return await this.generateDefaultAvatar("Me");
+ }
return meAvatarBuffer;
}
- private async getYouAvatarBuffer(
+ // 获取被回复用户的头像
+ private async getReplyUserAvatarBuffer(
msg: Api.Message,
trigger?: Api.Message
): Promise {
@@ -397,13 +439,77 @@ class EatGifPlugin extends Plugin {
replyTo = await trigger?.getReplyMessage();
}
if (!replyTo?.senderId) return;
- const youAvatarBuffer = await msg.client?.downloadProfilePhoto(
+ const avatarBuffer = await msg.client?.downloadProfilePhoto(
replyTo?.senderId,
{
isBig: false,
}
);
- return youAvatarBuffer as Buffer | undefined;
+ // 检查 buffer 是否有效
+ if (!avatarBuffer || (avatarBuffer as Buffer).length === 0) {
+ // 尝试获取用户名生成默认头像
+ const sender = replyTo.sender as any;
+ const name = sender?.firstName || sender?.username || "User";
+ return await this.generateDefaultAvatar(name);
+ }
+ return avatarBuffer as Buffer | undefined;
+ }
+
+ // 通过用户名获取头像
+ private async getAvatarByUsername(
+ msg: Api.Message,
+ username: string
+ ): Promise {
+ try {
+ // 移除 @ 前缀
+ const cleanUsername = username.startsWith("@") ? username.slice(1) : username;
+ const entity = await msg.client?.getEntity(cleanUsername);
+ if (!entity) return;
+ const avatarBuffer = await msg.client?.downloadProfilePhoto(entity, {
+ isBig: false,
+ });
+ // 检查 buffer 是否有效
+ if (!avatarBuffer || (avatarBuffer as Buffer).length === 0) {
+ // 用户没有头像,生成默认头像
+ return await this.generateDefaultAvatar(cleanUsername);
+ }
+ return avatarBuffer as Buffer | undefined;
+ } catch (e) {
+ console.log(`获取用户 ${username} 头像失败:`, e);
+ return;
+ }
+ }
+
+ // 生成默认头像(当用户没有设置头像时)
+ private async generateDefaultAvatar(name: string): Promise {
+ // 根据名字生成一个颜色
+ const colors = [
+ "#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4",
+ "#FFEAA7", "#DDA0DD", "#98D8C8", "#F7DC6F",
+ "#BB8FCE", "#85C1E9", "#F8B500", "#00CED1"
+ ];
+ const colorIndex = name.charCodeAt(0) % colors.length;
+ const bgColor = colors[colorIndex];
+
+ // 获取首字母
+ const initial = name.charAt(0).toUpperCase();
+
+ // 使用 sharp 生成一个带首字母的圆形头像
+ const size = 200;
+ const svg = `
+
+ `;
+
+ return await sharp(Buffer.from(svg))
+ .resize(size, size)
+ .png()
+ .toBuffer();
}
private async getMediaAvatarBuffer(
diff --git a/plugins.json b/plugins.json
index d0b42f01..32a8decf 100644
--- a/plugins.json
+++ b/plugins.json
@@ -371,6 +371,10 @@
"url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/sum/sum.ts?raw=true",
"desc": "群消息总结"
},
+ "stat": {
+ "url": "https://github.com/TeleBoxOrg/TeleBox_Plugins/blob/main/stat/stat.ts?raw=true",
+ "desc": "Telegram账号统计"
+ },
"t": {
"url": "https://github.com/TeleBoxDev/TeleBox_Plugins/blob/main/t/t.ts?raw=true",
"desc": "文字转语音"
diff --git a/stat/stat.ts b/stat/stat.ts
new file mode 100644
index 00000000..a8c136d4
--- /dev/null
+++ b/stat/stat.ts
@@ -0,0 +1,535 @@
+import { Plugin } from "@utils/pluginBase";
+import { getGlobalClient } from "@utils/globalClient";
+import { getPrefixes } from "@utils/pluginManager";
+import { Api } from "telegram";
+import * as path from "path";
+import * as fs from "fs";
+
+// 获取命令前缀
+const prefixes = getPrefixes();
+const mainPrefix = prefixes[0];
+
+// 帮助文本
+const help_text = `📊 Telegram 账号统计插件
+
+📝 功能描述:
+• 统计账号加入的群组、频道、机器人、私聊
+• 按类型和状态分类统计
+• 支持导出为 TXT 或 JSON 文件
+
+🔧 使用方法:
+• ${mainPrefix}stat - 显示统计概览
+• ${mainPrefix}stat list - 显示详细分类列表
+• ${mainPrefix}stat export txt - 导出为 TXT 文件
+• ${mainPrefix}stat export json - 导出为 JSON 文件
+
+📊 统计维度:
+• 公开群组 / 私有群组
+• 公开频道 / 私有频道
+• 机器人对话 / 用户私聊
+• 静音 / 归档 / 未读状态`;
+
+// 对话信息接口
+interface DialogInfo {
+ id: string;
+ title: string;
+ username: string | null;
+ unreadCount: number;
+ isMuted: boolean;
+ isArchived: boolean;
+ link: string; // 跳转链接
+ type: "user" | "bot" | "group" | "channel";
+}
+
+// 分类统计结果接口
+interface StatResult {
+ publicGroups: DialogInfo[];
+ privateGroups: DialogInfo[];
+ publicChannels: DialogInfo[];
+ privateChannels: DialogInfo[];
+ bots: DialogInfo[];
+ users: DialogInfo[];
+ // 状态统计
+ mutedCount: number;
+ archivedCount: number;
+ unreadDialogs: number;
+}
+
+class StatPlugin extends Plugin {
+ description: string = `Telegram 账号统计插件\n\n${help_text}`;
+
+ cmdHandlers: Record Promise> = {
+ stat: async (msg: Api.Message) => {
+ const client = await getGlobalClient();
+ if (!client) {
+ await msg.edit({ text: "❌ 客户端未初始化", parseMode: "html" });
+ return;
+ }
+
+ // 解析参数
+ const text = msg.text?.trim() || "";
+ const parts = text.split(/\s+/);
+ const subCmd = parts[1]?.toLowerCase() || "";
+ const subArg = parts[2]?.toLowerCase() || "";
+
+ try {
+ // 帮助命令
+ if (subCmd === "help" || subCmd === "h") {
+ await msg.edit({ text: help_text, parseMode: "html" });
+ return;
+ }
+
+ // 显示处理中
+ await msg.edit({
+ text: "🔄 正在获取对话列表...",
+ parseMode: "html"
+ });
+
+ // 获取统计数据
+ const stat = await this.getDialogStats(client);
+
+ // 根据子命令处理
+ if (subCmd === "list") {
+ await this.showDetailList(msg, stat);
+ } else if (subCmd === "export") {
+ await this.exportData(msg, stat, subArg);
+ } else {
+ await this.showOverview(msg, stat);
+ }
+
+ } catch (error: any) {
+ console.error("[stat] 插件执行失败:", error);
+ await msg.edit({
+ text: `❌ 统计失败: ${error.message || "未知错误"}`,
+ parseMode: "html"
+ });
+ }
+ }
+ };
+
+ // 获取对话统计数据
+ private async getDialogStats(client: any): Promise {
+ const result: StatResult = {
+ publicGroups: [],
+ privateGroups: [],
+ publicChannels: [],
+ privateChannels: [],
+ bots: [],
+ users: [],
+ mutedCount: 0,
+ archivedCount: 0,
+ unreadDialogs: 0
+ };
+
+ // 获取所有对话
+ const dialogs = await client.getDialogs({ limit: undefined });
+
+ for (const dialog of dialogs) {
+ const entity = dialog.entity;
+ if (!entity) continue;
+
+ const entityId = entity.id?.toString() || "unknown";
+
+ // 提取对话信息
+ const info: DialogInfo = {
+ id: entityId,
+ title: this.getDialogTitle(entity),
+ username: entity.username || null,
+ unreadCount: dialog.unreadCount || 0,
+ isMuted: this.isMuted(dialog),
+ isArchived: dialog.archived || false,
+ link: "",
+ type: "user"
+ };
+
+ // 按类型分类并生成链接
+ if (entity.className === "Channel") {
+ if (entity.broadcast) {
+ // 频道
+ info.type = "channel";
+ info.link = this.getChannelLink(entity);
+ if (entity.username) {
+ result.publicChannels.push(info);
+ } else {
+ result.privateChannels.push(info);
+ }
+ } else {
+ // 超级群组
+ info.type = "group";
+ info.link = this.getChannelLink(entity);
+ if (entity.username) {
+ result.publicGroups.push(info);
+ } else {
+ result.privateGroups.push(info);
+ }
+ }
+ } else if (entity.className === "Chat") {
+ // 普通群组(都是私有的)
+ info.type = "group";
+ info.link = `tg://openmessage?chat_id=${entityId}`;
+ result.privateGroups.push(info);
+ } else if (entity.className === "User") {
+ if (entity.bot) {
+ info.type = "bot";
+ info.link = this.getUserLink(entity);
+ result.bots.push(info);
+ } else {
+ info.type = "user";
+ info.link = this.getUserLink(entity);
+ result.users.push(info);
+ }
+ }
+
+ // 统计状态
+ if (info.isMuted) result.mutedCount++;
+ if (info.isArchived) result.archivedCount++;
+ if (info.unreadCount > 0) result.unreadDialogs++;
+ }
+
+ return result;
+ }
+
+ // 获取对话标题
+ private getDialogTitle(entity: any): string {
+ if (entity.title) return entity.title;
+ if (entity.firstName) {
+ return entity.lastName
+ ? `${entity.firstName} ${entity.lastName}`
+ : entity.firstName;
+ }
+ if (entity.username) return `@${entity.username}`;
+ return `ID: ${entity.id}`;
+ }
+
+ // 生成用户链接
+ private getUserLink(entity: any): string {
+ if (entity.username) {
+ return `https://t.me/${entity.username}`;
+ }
+ return `tg://user?id=${entity.id}`;
+ }
+
+ // 生成频道/群组链接
+ private getChannelLink(entity: any): string {
+ if (entity.username) {
+ return `https://t.me/${entity.username}`;
+ }
+ // 私有频道/群组使用 c/ 格式
+ return `https://t.me/c/${entity.id}/1`;
+ }
+
+ // 判断是否静音
+ private isMuted(dialog: any): boolean {
+ try {
+ const settings = dialog.notifySettings;
+ if (!settings) return false;
+ // muteUntil > 0 表示静音
+ return settings.muteUntil > 0 || settings.silent === true;
+ } catch {
+ return false;
+ }
+ }
+
+ // 显示统计概览
+ private async showOverview(msg: Api.Message, stat: StatResult): Promise {
+ const totalGroups = stat.publicGroups.length + stat.privateGroups.length;
+ const totalChannels = stat.publicChannels.length + stat.privateChannels.length;
+ const total = totalGroups + totalChannels + stat.bots.length + stat.users.length;
+
+ const text = `📊 Telegram 账号统计
+
+👥 群组: ${totalGroups} 个
+ ├ 公开群组: ${stat.publicGroups.length} 个
+ └ 私有群组: ${stat.privateGroups.length} 个
+
+📢 频道: ${totalChannels} 个
+ ├ 公开频道: ${stat.publicChannels.length} 个
+ └ 私有频道: ${stat.privateChannels.length} 个
+
+🤖 机器人: ${stat.bots.length} 个
+👤 私聊: ${stat.users.length} 个
+
+📌 状态统计:
+ ├ 已静音: ${stat.mutedCount} 个
+ ├ 已归档: ${stat.archivedCount} 个
+ └ 未读对话: ${stat.unreadDialogs} 个
+
+📈 总计: ${total} 个对话
+
+💡 使用 ${mainPrefix}stat list 查看详细列表
+💡 使用 ${mainPrefix}stat export txt/json 导出数据`;
+
+ await msg.edit({ text, parseMode: "html" });
+ }
+
+ // 显示详细列表
+ private async showDetailList(msg: Api.Message, stat: StatResult): Promise {
+ let text = `📊 Telegram 对话详细列表\n`;
+
+ // 公开群组
+ if (stat.publicGroups.length > 0) {
+ text += `\n👥 公开群组 (${stat.publicGroups.length})\n`;
+ text += this.formatDialogList(stat.publicGroups.slice(0, 10));
+ if (stat.publicGroups.length > 10) {
+ text += ` ... 还有 ${stat.publicGroups.length - 10} 个\n`;
+ }
+ }
+
+ // 私有群组
+ if (stat.privateGroups.length > 0) {
+ text += `\n🔒 私有群组 (${stat.privateGroups.length})\n`;
+ text += this.formatDialogList(stat.privateGroups.slice(0, 10));
+ if (stat.privateGroups.length > 10) {
+ text += ` ... 还有 ${stat.privateGroups.length - 10} 个\n`;
+ }
+ }
+
+ // 公开频道
+ if (stat.publicChannels.length > 0) {
+ text += `\n📢 公开频道 (${stat.publicChannels.length})\n`;
+ text += this.formatDialogList(stat.publicChannels.slice(0, 10));
+ if (stat.publicChannels.length > 10) {
+ text += ` ... 还有 ${stat.publicChannels.length - 10} 个\n`;
+ }
+ }
+
+ // 私有频道
+ if (stat.privateChannels.length > 0) {
+ text += `\n🔐 私有频道 (${stat.privateChannels.length})\n`;
+ text += this.formatDialogList(stat.privateChannels.slice(0, 10));
+ if (stat.privateChannels.length > 10) {
+ text += ` ... 还有 ${stat.privateChannels.length - 10} 个\n`;
+ }
+ }
+
+ // 机器人
+ if (stat.bots.length > 0) {
+ text += `\n🤖 机器人 (${stat.bots.length})\n`;
+ text += this.formatDialogList(stat.bots.slice(0, 10));
+ if (stat.bots.length > 10) {
+ text += ` ... 还有 ${stat.bots.length - 10} 个\n`;
+ }
+ }
+
+ // 用户私聊
+ if (stat.users.length > 0) {
+ text += `\n👤 用户私聊 (${stat.users.length})\n`;
+ text += this.formatDialogList(stat.users.slice(0, 10));
+ if (stat.users.length > 10) {
+ text += ` ... 还有 ${stat.users.length - 10} 个\n`;
+ }
+ }
+
+ text += `\n💡 使用 ${mainPrefix}stat export txt 导出完整列表`;
+
+ // 检查消息长度
+ if (text.length > 4096) {
+ text = text.substring(0, 4000) + `\n\n... 内容过长,请使用导出功能查看完整列表`;
+ }
+
+ await msg.edit({ text, parseMode: "html" });
+ }
+
+ // 格式化对话列表
+ private formatDialogList(dialogs: DialogInfo[]): string {
+ let text = "";
+ for (const d of dialogs) {
+ const status = [];
+ if (d.isMuted) status.push("🔇");
+ if (d.isArchived) status.push("📁");
+ if (d.unreadCount > 0) status.push(`💬${d.unreadCount}`);
+
+ const statusStr = status.length > 0 ? ` ${status.join(" ")}` : "";
+ const usernameStr = d.username ? ` (@${d.username})` : "";
+
+ text += ` • ${this.escapeHtml(d.title)}${usernameStr}${statusStr}\n`;
+ }
+ return text;
+ }
+
+ // 导出数据
+ private async exportData(msg: Api.Message, stat: StatResult, format: string): Promise {
+ const client = await getGlobalClient();
+ if (!client) return;
+
+ if (format !== "txt" && format !== "json") {
+ await msg.edit({
+ text: `❌ 不支持的格式: ${format}\n\n💡 支持的格式: txt, json`,
+ parseMode: "html"
+ });
+ return;
+ }
+
+ await msg.edit({
+ text: "📤 正在生成导出文件...",
+ parseMode: "html"
+ });
+
+ const timestamp = new Date().toISOString().slice(0, 10);
+ const tempDir = path.join(process.cwd(), "temp");
+
+ // 确保临时目录存在
+ if (!fs.existsSync(tempDir)) {
+ fs.mkdirSync(tempDir, { recursive: true });
+ }
+
+ let filePath: string;
+ let content: string;
+
+ if (format === "json") {
+ filePath = path.join(tempDir, `telegram_stat_${timestamp}.json`);
+ content = this.generateJson(stat);
+ } else {
+ filePath = path.join(tempDir, `telegram_stat_${timestamp}.txt`);
+ content = this.generateTxt(stat);
+ }
+
+ // 写入文件
+ fs.writeFileSync(filePath, content, "utf-8");
+
+ // 发送文件
+ try {
+ await client.sendFile(msg.chatId, {
+ file: filePath,
+ caption: `📊 Telegram 账号统计导出\n\n📅 导出时间: ${timestamp}\n📄 格式: ${format.toUpperCase()}`,
+ parseMode: "html"
+ });
+
+ await msg.edit({
+ text: `✅ 导出成功\n\n📄 文件已发送`,
+ parseMode: "html"
+ });
+ } finally {
+ // 清理临时文件
+ if (fs.existsSync(filePath)) {
+ fs.unlinkSync(filePath);
+ }
+ }
+ }
+
+ // 生成 JSON 内容
+ private generateJson(stat: StatResult): string {
+ const data = {
+ exportTime: new Date().toISOString(),
+ summary: {
+ totalGroups: stat.publicGroups.length + stat.privateGroups.length,
+ totalChannels: stat.publicChannels.length + stat.privateChannels.length,
+ totalBots: stat.bots.length,
+ totalUsers: stat.users.length,
+ mutedCount: stat.mutedCount,
+ archivedCount: stat.archivedCount,
+ unreadDialogs: stat.unreadDialogs
+ },
+ dialogs: {
+ publicGroups: stat.publicGroups,
+ privateGroups: stat.privateGroups,
+ publicChannels: stat.publicChannels,
+ privateChannels: stat.privateChannels,
+ bots: stat.bots,
+ users: stat.users
+ }
+ };
+ return JSON.stringify(data, null, 2);
+ }
+
+ // 生成 TXT 内容
+ private generateTxt(stat: StatResult): string {
+ const totalGroups = stat.publicGroups.length + stat.privateGroups.length;
+ const totalChannels = stat.publicChannels.length + stat.privateChannels.length;
+ const total = totalGroups + totalChannels + stat.bots.length + stat.users.length;
+
+ let text = `Telegram 账号统计报告
+导出时间: ${new Date().toLocaleString("zh-CN")}
+${"=".repeat(50)}
+
+【统计概览】
+群组总数: ${totalGroups} 个
+ - 公开群组: ${stat.publicGroups.length} 个
+ - 私有群组: ${stat.privateGroups.length} 个
+
+频道总数: ${totalChannels} 个
+ - 公开频道: ${stat.publicChannels.length} 个
+ - 私有频道: ${stat.privateChannels.length} 个
+
+机器人: ${stat.bots.length} 个
+私聊: ${stat.users.length} 个
+
+状态统计:
+ - 已静音: ${stat.mutedCount} 个
+ - 已归档: ${stat.archivedCount} 个
+ - 未读对话: ${stat.unreadDialogs} 个
+
+总计: ${total} 个对话
+
+${"=".repeat(50)}
+【详细列表】
+`;
+
+ // 公开群组
+ if (stat.publicGroups.length > 0) {
+ text += `\n[公开群组 - ${stat.publicGroups.length} 个]\n`;
+ text += this.formatTxtList(stat.publicGroups);
+ }
+
+ // 私有群组
+ if (stat.privateGroups.length > 0) {
+ text += `\n[私有群组 - ${stat.privateGroups.length} 个]\n`;
+ text += this.formatTxtList(stat.privateGroups);
+ }
+
+ // 公开频道
+ if (stat.publicChannels.length > 0) {
+ text += `\n[公开频道 - ${stat.publicChannels.length} 个]\n`;
+ text += this.formatTxtList(stat.publicChannels);
+ }
+
+ // 私有频道
+ if (stat.privateChannels.length > 0) {
+ text += `\n[私有频道 - ${stat.privateChannels.length} 个]\n`;
+ text += this.formatTxtList(stat.privateChannels);
+ }
+
+ // 机器人
+ if (stat.bots.length > 0) {
+ text += `\n[机器人 - ${stat.bots.length} 个]\n`;
+ text += this.formatTxtList(stat.bots);
+ }
+
+ // 用户私聊
+ if (stat.users.length > 0) {
+ text += `\n[用户私聊 - ${stat.users.length} 个]\n`;
+ text += this.formatTxtList(stat.users);
+ }
+
+ return text;
+ }
+
+ // 格式化 TXT 列表
+ private formatTxtList(dialogs: DialogInfo[]): string {
+ let text = "";
+ for (const d of dialogs) {
+ const status = [];
+ if (d.isMuted) status.push("静音");
+ if (d.isArchived) status.push("归档");
+ if (d.unreadCount > 0) status.push(`${d.unreadCount}条未读`);
+
+ const statusStr = status.length > 0 ? ` [${status.join(", ")}]` : "";
+ const usernameStr = d.username ? ` (@${d.username})` : "";
+
+ text += ` - ${d.title}${usernameStr}\n`;
+ text += ` ID: ${d.id}${statusStr}\n`;
+ text += ` 链接: ${d.link}\n`;
+ }
+ return text;
+ }
+
+ // HTML 转义
+ private escapeHtml(text: string): string {
+ return text.replace(/[&<>"']/g, m => ({
+ '&': '&', '<': '<', '>': '>',
+ '"': '"', "'": '''
+ }[m] || m));
+ }
+}
+
+export default new StatPlugin();