koishi-plugin-ll-mc 1.0.1 → 1.0.3

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.
Files changed (2) hide show
  1. package/lib/index.js +96 -8
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -51,6 +51,99 @@ 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
+ const warnThrottle = new Map(); // key -> lastWarnTime
58
+ function cacheGet(map, key) {
59
+ const item = map.get(key);
60
+ return item && Date.now() - item.t < NAME_CACHE_TTL ? item.v : undefined;
61
+ }
62
+ function cacheSet(map, key, value) {
63
+ map.set(key, { v: value, t: Date.now() });
64
+ }
65
+ // 同类警告 10 分钟内只打一次,避免刷屏
66
+ function throttledWarn(key, message) {
67
+ const now = Date.now();
68
+ const last = warnThrottle.get(key) || 0;
69
+ if (now - last < 10 * 60 * 1000)
70
+ return;
71
+ warnThrottle.set(key, now);
72
+ logger.warn(message);
73
+ }
74
+ async function resolveUsername(session, userId) {
75
+ const author = session.author;
76
+ if (author?.nick)
77
+ return author.nick; // OneBot:昵称在这
78
+ if (author?.name)
79
+ return author.name;
80
+ // 官方QQ平台:群消息原始事件自带 member.nick,零 API 调用
81
+ const raw = session.event?._data?.d;
82
+ if (raw?.member?.nick)
83
+ return raw.member.nick;
84
+ if (raw?.author?.username)
85
+ return raw.author.username;
86
+ // 兜底:调用 getGuildMember(仅限群聊;私聊没有群,直接返回 userId)
87
+ const guildId = session.guildId;
88
+ if (!guildId || session.isDirect)
89
+ return userId;
90
+ const key = `${guildId}:${userId}`;
91
+ const cached = cacheGet(nameCache.member, key);
92
+ if (cached)
93
+ return cached;
94
+ if (typeof session.bot?.getGuildMember === "function") {
95
+ try {
96
+ const member = await session.bot.getGuildMember(guildId, userId);
97
+ const name = member?.name || member?.user?.name;
98
+ if (name) {
99
+ cacheSet(nameCache.member, key, name);
100
+ return name;
101
+ }
102
+ }
103
+ catch (e) {
104
+ throttledWarn(`member:${key}`, `[mc] 获取成员昵称失败 ${userId}: ${e?.message}(若为 11253 应用无接口访问权限,请到 QQ 开放平台申请"获取群成员信息"权限)`);
105
+ }
106
+ }
107
+ return userId;
108
+ }
109
+ async function resolveChannelName(session) {
110
+ if (session.event?.channel?.name)
111
+ return session.event.channel.name; // OneBot 走这里
112
+ const channelId = session.channelId;
113
+ // 私聊或没有群上下文:不调用群接口(官机 C2C 的 channelId 是用户 openid,不是群)
114
+ if (session.isDirect || !session.guildId)
115
+ return channelId;
116
+ const cached = cacheGet(nameCache.channel, channelId);
117
+ if (cached)
118
+ return cached;
119
+ const bot = session.bot;
120
+ if (typeof bot?.getChannel === "function") {
121
+ try {
122
+ const ch = await bot.getChannel(channelId);
123
+ if (ch?.name) {
124
+ cacheSet(nameCache.channel, channelId, ch.name);
125
+ return ch.name;
126
+ }
127
+ }
128
+ catch { /* 官方QQ对群无效,继续走 getGuild */ }
129
+ }
130
+ // 官方QQ平台:群信息走 getGuild(内部 getGuildInfo 返回群名)
131
+ const guildId = session.guildId;
132
+ if (typeof bot?.getGuild === "function") {
133
+ try {
134
+ const g = await bot.getGuild(guildId);
135
+ if (g?.name) {
136
+ cacheSet(nameCache.channel, channelId, g.name);
137
+ return g.name;
138
+ }
139
+ }
140
+ catch (e) {
141
+ throttledWarn(`guild:${guildId}`, `[mc] 获取群名失败 ${guildId}: ${e?.message}(若为 11253/11255,请到 QQ 开放平台申请"获取群信息"权限,或确认机器人仍在该群)`);
142
+ }
143
+ }
144
+ return channelId;
145
+ }
146
+ // ===== 补丁结束 =====
54
147
  // --- 定义字体选项常量 ---
55
148
  const FONT_OPTIONS = {
56
149
  TITLE: "HarmonyOS_Sans_Medium",
@@ -503,12 +596,10 @@ async function apply(ctx, config) {
503
596
  return next();
504
597
  session[PROCESSED] = true;
505
598
  const { userId, channelId, author } = session;
506
- let sessionChannelName = session.event.channel.name;
507
- const username = author?.nick || author?.name || userId;
599
+ const username = await resolveUsername(session, userId);
508
600
  const userAvatar = author?.avatar;
509
601
  try {
510
- const channelName = sessionChannelName ||
511
- (channelId ? await getChannelName(session.bot, channelId) : channelId);
602
+ const channelName = channelId ? await resolveChannelName(session) : channelId;
512
603
  await ctx.database.upsert("message_counter_records", (row) => [
513
604
  {
514
605
  channelId,
@@ -536,16 +627,13 @@ async function apply(ctx, config) {
536
627
  if (!session.channelId)
537
628
  return;
538
629
  const { channelId, bot } = session;
539
- let sessionChannelName = session.event.channel.name;
540
630
  const botUser = bot.user;
541
631
  if (!botUser) {
542
632
  logger.warn("Bot user is undefined, skipping bot message tracking.");
543
633
  return;
544
634
  }
545
635
  try {
546
- const channelName = sessionChannelName ||
547
- (await getChannelName(bot, channelId)) ||
548
- channelId;
636
+ const channelName = (await resolveChannelName(session)) || channelId;
549
637
  await ctx.database.upsert("message_counter_records", (row) => [
550
638
  {
551
639
  channelId,
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.1",
4
+ "version": "1.0.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [