koishi-plugin-ll-mc 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export interface Config {
19
19
  hiddenChannelIdsInLeaderboard: string[];
20
20
  /** 排除统计的频道 ID 列表,这些群的消息不会被计数。 */
21
21
  excludedChannelIds: string[];
22
+ /** 排除统计的用户 ID 列表,这些用户的消息不会被计数。 */
23
+ excludedUserIds: string[];
22
24
  /** 是否将文本排行榜转为 Markdown 图片。 */
23
25
  isTextToImageConversionEnabled: boolean;
24
26
  /** 是否将排行榜渲染为水平柱状图。 */
package/lib/index.js CHANGED
@@ -51,6 +51,84 @@ exports.inject = {
51
51
  optional: ["markdownToImage", "puppeteer", "canvas"],
52
52
  };
53
53
  const logger = new koishi_1.Logger("mc");
54
+ // ===== 官方QQ平台 群名/群昵称 修复补丁 =====
55
+ const nameCache = { channel: new Map(), member: new Map() };
56
+ const NAME_CACHE_TTL = 60 * 60 * 1000; // 缓存 1 小时,避免官方接口限频
57
+ function cacheGet(map, key) {
58
+ const item = map.get(key);
59
+ return item && Date.now() - item.t < NAME_CACHE_TTL ? item.v : undefined;
60
+ }
61
+ function cacheSet(map, key, value) {
62
+ map.set(key, { v: value, t: Date.now() });
63
+ }
64
+ async function resolveUsername(session, userId) {
65
+ const author = session.author;
66
+ if (author?.nick)
67
+ return author.nick; // OneBot:昵称在这
68
+ if (author?.name)
69
+ return author.name;
70
+ // 官方QQ平台:群消息原始事件自带 member.nick,零 API 调用
71
+ const raw = session.event?._data?.d;
72
+ if (raw?.member?.nick)
73
+ return raw.member.nick;
74
+ if (raw?.author?.username)
75
+ return raw.author.username;
76
+ // 兜底:调用 getGuildMember(官方 adapter 已实现)
77
+ const guildId = session.guildId || session.channelId;
78
+ const key = `${guildId}:${userId}`;
79
+ const cached = cacheGet(nameCache.member, key);
80
+ if (cached)
81
+ return cached;
82
+ if (guildId && typeof session.bot?.getGuildMember === "function") {
83
+ try {
84
+ const member = await session.bot.getGuildMember(guildId, userId);
85
+ const name = member?.name || member?.user?.name;
86
+ if (name) {
87
+ cacheSet(nameCache.member, key, name);
88
+ return name;
89
+ }
90
+ }
91
+ catch (e) {
92
+ logger.warn(`[mc] 获取成员昵称失败 ${userId}:`, e);
93
+ }
94
+ }
95
+ return userId;
96
+ }
97
+ async function resolveChannelName(session) {
98
+ if (session.event?.channel?.name)
99
+ return session.event.channel.name; // OneBot 走这里
100
+ const channelId = session.channelId;
101
+ const cached = cacheGet(nameCache.channel, channelId);
102
+ if (cached)
103
+ return cached;
104
+ const bot = session.bot;
105
+ if (typeof bot?.getChannel === "function") {
106
+ try {
107
+ const ch = await bot.getChannel(channelId);
108
+ if (ch?.name) {
109
+ cacheSet(nameCache.channel, channelId, ch.name);
110
+ return ch.name;
111
+ }
112
+ }
113
+ catch { /* 官方QQ对群无效,继续走 getGuild */ }
114
+ }
115
+ // 官方QQ平台:群信息走 getGuild(内部 getGuildInfo 返回群名)
116
+ const guildId = session.guildId || channelId;
117
+ if (guildId && typeof bot?.getGuild === "function") {
118
+ try {
119
+ const g = await bot.getGuild(guildId);
120
+ if (g?.name) {
121
+ cacheSet(nameCache.channel, channelId, g.name);
122
+ return g.name;
123
+ }
124
+ }
125
+ catch (e) {
126
+ logger.warn(`[mc] 获取群名失败 ${guildId}:`, e);
127
+ }
128
+ }
129
+ return channelId;
130
+ }
131
+ // ===== 补丁结束 =====
54
132
  // --- 定义字体选项常量 ---
55
133
  const FONT_OPTIONS = {
56
134
  TITLE: "HarmonyOS_Sans_Medium",
@@ -86,6 +164,9 @@ exports.Config = koishi_1.Schema.intersect([
86
164
  excludedChannelIds: koishi_1.Schema.array(String)
87
165
  .role("table")
88
166
  .description("排除统计的频道 ID 列表,这些群的消息不会被计数。"),
167
+ excludedUserIds: koishi_1.Schema.array(String)
168
+ .role("table")
169
+ .description("排除统计的用户 ID 列表,这些用户的消息不会被计数。"),
89
170
  }).description("排行榜设置"),
90
171
  // --- 图片生成 ---
91
172
  koishi_1.Schema.intersect([
@@ -495,14 +576,15 @@ async function apply(ctx, config) {
495
576
  // 排除指定群聊的统计
496
577
  if (config.excludedChannelIds?.includes(session.channelId))
497
578
  return next();
579
+ // 排除指定用户的统计
580
+ if (config.excludedUserIds?.includes(session.userId))
581
+ return next();
498
582
  session[PROCESSED] = true;
499
583
  const { userId, channelId, author } = session;
500
- let sessionChannelName = session.event.channel.name;
501
- const username = author?.nick || author?.name || userId;
584
+ const username = await resolveUsername(session, userId);
502
585
  const userAvatar = author?.avatar;
503
586
  try {
504
- const channelName = sessionChannelName ||
505
- (channelId ? await getChannelName(session.bot, channelId) : channelId);
587
+ const channelName = channelId ? await resolveChannelName(session) : channelId;
506
588
  await ctx.database.upsert("message_counter_records", (row) => [
507
589
  {
508
590
  channelId,
@@ -530,16 +612,13 @@ async function apply(ctx, config) {
530
612
  if (!session.channelId)
531
613
  return;
532
614
  const { channelId, bot } = session;
533
- let sessionChannelName = session.event.channel.name;
534
615
  const botUser = bot.user;
535
616
  if (!botUser) {
536
617
  logger.warn("Bot user is undefined, skipping bot message tracking.");
537
618
  return;
538
619
  }
539
620
  try {
540
- const channelName = sessionChannelName ||
541
- (await getChannelName(bot, channelId)) ||
542
- channelId;
621
+ const channelName = (await resolveChannelName(session)) || channelId;
543
622
  await ctx.database.upsert("message_counter_records", (row) => [
544
623
  {
545
624
  channelId,
@@ -2525,13 +2604,17 @@ async function apply(ctx, config) {
2525
2604
  return content;
2526
2605
  }
2527
2606
  async function getChannelName(bot, channelId) {
2607
+ // 部分适配器(如官方 bot)未实现 getChannel 方法,此时直接回退为 channelId
2608
+ if (!bot || typeof bot.getChannel !== "function") {
2609
+ return channelId;
2610
+ }
2528
2611
  try {
2529
2612
  const channel = await bot.getChannel(channelId);
2530
- return channel?.name;
2613
+ return channel?.name || channelId;
2531
2614
  }
2532
2615
  catch (error) {
2533
2616
  logger.warn(`Failed to get channelId name for ${channelId}:`, error);
2534
- return undefined;
2617
+ return channelId;
2535
2618
  }
2536
2619
  }
2537
2620
  function parseList(str) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-ll-mc",
3
3
  "description": "消息统计插件(克隆自 message-counter),支持排除指定群聊",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -36,10 +36,10 @@
36
36
  "koishi": "^4.18.9"
37
37
  },
38
38
  "devDependencies": {
39
- "koishi": "^4.18.9",
40
- "typescript": "^5.4.0",
39
+ "koishi": "^4.18.11",
41
40
  "koishi-plugin-cron": "^3.1.0",
42
41
  "koishi-plugin-markdown-to-image-service": "^1.3.6",
43
- "koishi-plugin-puppeteer": "^3.9.0"
42
+ "koishi-plugin-puppeteer": "^3.9.0",
43
+ "typescript": "^5.4.0"
44
44
  }
45
45
  }