koishi-plugin-ll-mc 1.0.2 → 1.0.4

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 +43 -8
  2. package/package.json +45 -45
package/lib/index.js CHANGED
@@ -54,6 +54,7 @@ const logger = new koishi_1.Logger("mc");
54
54
  // ===== 官方QQ平台 群名/群昵称 修复补丁 =====
55
55
  const nameCache = { channel: new Map(), member: new Map() };
56
56
  const NAME_CACHE_TTL = 60 * 60 * 1000; // 缓存 1 小时,避免官方接口限频
57
+ const warnThrottle = new Map(); // key -> lastWarnTime
57
58
  function cacheGet(map, key) {
58
59
  const item = map.get(key);
59
60
  return item && Date.now() - item.t < NAME_CACHE_TTL ? item.v : undefined;
@@ -61,6 +62,15 @@ function cacheGet(map, key) {
61
62
  function cacheSet(map, key, value) {
62
63
  map.set(key, { v: value, t: Date.now() });
63
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
+ }
64
74
  async function resolveUsername(session, userId) {
65
75
  const author = session.author;
66
76
  if (author?.nick)
@@ -73,13 +83,15 @@ async function resolveUsername(session, userId) {
73
83
  return raw.member.nick;
74
84
  if (raw?.author?.username)
75
85
  return raw.author.username;
76
- // 兜底:调用 getGuildMember(官方 adapter 已实现)
77
- const guildId = session.guildId || session.channelId;
86
+ // 兜底:调用 getGuildMember(仅限群聊;私聊没有群,直接返回 userId)
87
+ const guildId = session.guildId;
88
+ if (!guildId || session.isDirect)
89
+ return userId;
78
90
  const key = `${guildId}:${userId}`;
79
91
  const cached = cacheGet(nameCache.member, key);
80
92
  if (cached)
81
93
  return cached;
82
- if (guildId && typeof session.bot?.getGuildMember === "function") {
94
+ if (typeof session.bot?.getGuildMember === "function") {
83
95
  try {
84
96
  const member = await session.bot.getGuildMember(guildId, userId);
85
97
  const name = member?.name || member?.user?.name;
@@ -89,7 +101,7 @@ async function resolveUsername(session, userId) {
89
101
  }
90
102
  }
91
103
  catch (e) {
92
- logger.warn(`[mc] 获取成员昵称失败 ${userId}:`, e);
104
+ throttledWarn(`member:${key}`, `[mc] 获取成员昵称失败 ${userId}: ${e?.message}(若为 11253 应用无接口访问权限,请到 QQ 开放平台申请"获取群成员信息"权限)`);
93
105
  }
94
106
  }
95
107
  return userId;
@@ -98,6 +110,9 @@ async function resolveChannelName(session) {
98
110
  if (session.event?.channel?.name)
99
111
  return session.event.channel.name; // OneBot 走这里
100
112
  const channelId = session.channelId;
113
+ // 私聊或没有群上下文:不调用群接口(官机 C2C 的 channelId 是用户 openid,不是群)
114
+ if (session.isDirect || !session.guildId)
115
+ return channelId;
101
116
  const cached = cacheGet(nameCache.channel, channelId);
102
117
  if (cached)
103
118
  return cached;
@@ -113,8 +128,8 @@ async function resolveChannelName(session) {
113
128
  catch { /* 官方QQ对群无效,继续走 getGuild */ }
114
129
  }
115
130
  // 官方QQ平台:群信息走 getGuild(内部 getGuildInfo 返回群名)
116
- const guildId = session.guildId || channelId;
117
- if (guildId && typeof bot?.getGuild === "function") {
131
+ const guildId = session.guildId;
132
+ if (typeof bot?.getGuild === "function") {
118
133
  try {
119
134
  const g = await bot.getGuild(guildId);
120
135
  if (g?.name) {
@@ -123,7 +138,7 @@ async function resolveChannelName(session) {
123
138
  }
124
139
  }
125
140
  catch (e) {
126
- logger.warn(`[mc] 获取群名失败 ${guildId}:`, e);
141
+ throttledWarn(`guild:${guildId}`, `[mc] 获取群名失败 ${guildId}: ${e?.message}(若为 11253/11255,请到 QQ 开放平台申请"获取群信息"权限,或确认机器人仍在该群)`);
127
142
  }
128
143
  }
129
144
  return channelId;
@@ -1943,11 +1958,31 @@ async function apply(ctx, config) {
1943
1958
  (channelInfo[channelId]?.channelName || `群聊${channelId}`),
1944
1959
  // 使用 channelId 作为 RankingData 的 userId 和头像源
1945
1960
  userId: channelId,
1946
- avatar: `https://p.qlogo.cn/gh/${channelId === "#" ? "426230045" : channelId}/${channelId === "#" ? "426230045" : channelId}/100`, // QQ群头像URL格式
1961
+ avatar: groupAvatarUrl(channelId),
1947
1962
  count,
1948
1963
  percentage: calculatePercentage(count, totalCount),
1949
1964
  }));
1950
1965
  }
1966
+ /**
1967
+ * 群头像 URL:
1968
+ * - "#":占位群号
1969
+ * - 数字群号(OneBot/NapCat):p.qlogo.cn/gh/{群号}
1970
+ * - 官机 openid(32位十六进制):官方 API 不提供群头像,尝试与成员头像同源的 qqapp 端点;
1971
+ * 若无效,下载失败会走内置默认头像兜底
1972
+ */
1973
+ function groupAvatarUrl(channelId) {
1974
+ if (channelId === "#" || !channelId) {
1975
+ return `https://p.qlogo.cn/gh/426230045/426230045/100`;
1976
+ }
1977
+ if (/^\d+$/.test(channelId)) {
1978
+ return `https://p.qlogo.cn/gh/${channelId}/${channelId}/100`;
1979
+ }
1980
+ const qqBot = (ctx.bots || []).find(b => b.platform === "qq" && b.config?.id);
1981
+ if (qqBot?.config?.id) {
1982
+ return `https://q.qlogo.cn/qqapp/${qqBot.config.id}/${channelId}/640`;
1983
+ }
1984
+ return `https://p.qlogo.cn/gh/426230045/426230045/100`;
1985
+ }
1951
1986
  // --- 辅助函数:图表生成 ---
1952
1987
  /**
1953
1988
  * 生成图表的静态 CSS 样式。
package/package.json CHANGED
@@ -1,45 +1,45 @@
1
- {
2
- "name": "koishi-plugin-ll-mc",
3
- "description": "消息统计插件(克隆自 message-counter),支持排除指定群聊",
4
- "version": "1.0.2",
5
- "main": "lib/index.js",
6
- "typings": "lib/index.d.ts",
7
- "files": [
8
- "lib",
9
- "assets"
10
- ],
11
- "koishi": {
12
- "description": {
13
- "zh": "消息统计插件:发言排行榜、群排行榜,支持排除指定群聊"
14
- },
15
- "service": {
16
- "required": [
17
- "database",
18
- "cron"
19
- ],
20
- "optional": [
21
- "markdownToImage",
22
- "puppeteer",
23
- "canvas"
24
- ]
25
- }
26
- },
27
- "author": "lee",
28
- "license": "MIT",
29
- "keywords": [
30
- "koishi",
31
- "koishi-plugin",
32
- "message-counter",
33
- "rank"
34
- ],
35
- "peerDependencies": {
36
- "koishi": "^4.18.9"
37
- },
38
- "devDependencies": {
39
- "koishi": "^4.18.11",
40
- "koishi-plugin-cron": "^3.1.0",
41
- "koishi-plugin-markdown-to-image-service": "^1.3.6",
42
- "koishi-plugin-puppeteer": "^3.9.0",
43
- "typescript": "^5.4.0"
44
- }
45
- }
1
+ {
2
+ "name": "koishi-plugin-ll-mc",
3
+ "description": "消息统计插件(克隆自 message-counter),支持排除指定群聊",
4
+ "version": "1.0.4",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "files": [
8
+ "lib",
9
+ "assets"
10
+ ],
11
+ "koishi": {
12
+ "description": {
13
+ "zh": "消息统计插件:发言排行榜、群排行榜,支持排除指定群聊"
14
+ },
15
+ "service": {
16
+ "required": [
17
+ "database",
18
+ "cron"
19
+ ],
20
+ "optional": [
21
+ "markdownToImage",
22
+ "puppeteer",
23
+ "canvas"
24
+ ]
25
+ }
26
+ },
27
+ "author": "lee",
28
+ "license": "MIT",
29
+ "keywords": [
30
+ "koishi",
31
+ "koishi-plugin",
32
+ "message-counter",
33
+ "rank"
34
+ ],
35
+ "peerDependencies": {
36
+ "koishi": "^4.18.9"
37
+ },
38
+ "devDependencies": {
39
+ "koishi": "^4.18.11",
40
+ "koishi-plugin-cron": "^3.1.0",
41
+ "koishi-plugin-markdown-to-image-service": "^1.3.6",
42
+ "koishi-plugin-puppeteer": "^3.9.0",
43
+ "typescript": "^5.4.0"
44
+ }
45
+ }