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 +2 -0
- package/lib/index.js +93 -10
- package/package.json +4 -4
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
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
|
}
|