koishi-plugin-group-analysis-mx 1.0.16 → 1.0.17
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/service/analysis.js +52 -11
- package/lib/utils.js +22 -0
- package/package.json +1 -1
package/lib/service/analysis.js
CHANGED
|
@@ -193,18 +193,18 @@ class AnalysisService extends koishi_1.Service {
|
|
|
193
193
|
if (bot.platform === 'onebot') {
|
|
194
194
|
userGroupInfo = await bot.internal.getGroupMemberInfo(group.guildId, record.userId, true);
|
|
195
195
|
}
|
|
196
|
-
else {
|
|
196
|
+
else if (bot?.getGuildMember) {
|
|
197
197
|
userGroupInfo = await bot.getGuildMember(group.channelId || group.guildId, record.userId);
|
|
198
198
|
}
|
|
199
|
-
if (userGroupInfo == null) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
199
|
}
|
|
203
200
|
catch (error) {
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
// 官方 QQ 机器人等平台可能没有获取群成员信息的权限,
|
|
202
|
+
// 不中断画像分析,仅跳过角色信息(roles 保持为空)。
|
|
203
|
+
this.ctx.logger.debug(`获取用户 ${record.userId} 的群组信息失败 (${group.channelId || group.guildId}),将跳过角色信息: ${error}`);
|
|
204
|
+
}
|
|
205
|
+
if (userGroupInfo) {
|
|
206
|
+
record.roles = (userGroupInfo.roles || []).map((r) => (typeof r === 'string' ? r : r.name || r.id || ''));
|
|
206
207
|
}
|
|
207
|
-
record.roles = (userGroupInfo.roles || []).map((r) => (typeof r === 'string' ? r : r.name || r.id || ''));
|
|
208
208
|
const history = await this.ctx.group_analysis_message.getHistoricalMessages({
|
|
209
209
|
guildId: group.guildId,
|
|
210
210
|
channelId: group.channelId,
|
|
@@ -614,13 +614,14 @@ class AnalysisService extends koishi_1.Service {
|
|
|
614
614
|
user = await bot.getGuildMember(session.channelId || session.guildId, userId);
|
|
615
615
|
}
|
|
616
616
|
catch (error) {
|
|
617
|
-
|
|
617
|
+
// 官方 QQ 机器人等平台可能没有获取群成员信息的权限,回退到本地记录
|
|
618
|
+
this.ctx.logger.debug(`获取用户 ${userId} 的成员信息失败,将回退到本地记录: ${error}`);
|
|
618
619
|
}
|
|
619
620
|
const cache = await this.ensurePersonaCache(recordId, {
|
|
620
621
|
platform: session.platform,
|
|
621
622
|
selfId: session.selfId,
|
|
622
623
|
userId,
|
|
623
|
-
username: user?.nick || user?.name || userId
|
|
624
|
+
username: user?.nick || user?.name || user?.user?.name || userId
|
|
624
625
|
});
|
|
625
626
|
let displayName = cache.record.username;
|
|
626
627
|
const cacheExpired = (0, utils_1.isCacheExpiredByDays)(cache.record.lastAnalysisAt, this.config.personaCacheLifetimeDays);
|
|
@@ -648,17 +649,36 @@ class AnalysisService extends koishi_1.Service {
|
|
|
648
649
|
}
|
|
649
650
|
const profile = cache.parsedPersona;
|
|
650
651
|
if (user) {
|
|
651
|
-
|
|
652
|
+
// Koishi Universal.GuildMember 结构:{ user: { id, name, avatar }, name, ... }
|
|
653
|
+
avatar = user.avatar || user.user?.avatar;
|
|
652
654
|
const resolvedName = user.nick ||
|
|
653
|
-
user.name
|
|
655
|
+
user.name ||
|
|
656
|
+
user.user?.name;
|
|
654
657
|
if (resolvedName) {
|
|
655
658
|
displayName = resolvedName;
|
|
656
659
|
cache.record.username = resolvedName;
|
|
657
660
|
}
|
|
658
661
|
}
|
|
662
|
+
// 平台化头像解析:onebot 走 QQ 号接口,官方 QQ 机器人走 openid 模板
|
|
659
663
|
if (session.platform === 'onebot') {
|
|
660
664
|
avatar = (0, utils_1.getAvatarUrl)(userId);
|
|
661
665
|
}
|
|
666
|
+
else if (!avatar) {
|
|
667
|
+
avatar = (0, utils_1.resolveUserAvatar)(bot, userId);
|
|
668
|
+
}
|
|
669
|
+
// 昵称/头像兜底:从数据库已记录的消息中回退用户资料
|
|
670
|
+
if (!displayName || displayName === userId || !avatar) {
|
|
671
|
+
const fallback = await this.lookupUserProfileFromMessages(session.platform, session.selfId, userId);
|
|
672
|
+
if (fallback) {
|
|
673
|
+
if ((!displayName || displayName === userId) && fallback.username) {
|
|
674
|
+
displayName = fallback.username;
|
|
675
|
+
cache.record.username = fallback.username;
|
|
676
|
+
}
|
|
677
|
+
if (!avatar && fallback.avatarUrl) {
|
|
678
|
+
avatar = fallback.avatarUrl;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
662
682
|
profile.analysisDate = cache.record.lastAnalysisAt.toLocaleString();
|
|
663
683
|
const image = await this.ctx.group_analysis_renderer.renderUserPersona(profile, displayName, avatar, this.config);
|
|
664
684
|
message =
|
|
@@ -673,6 +693,27 @@ class AnalysisService extends koishi_1.Service {
|
|
|
673
693
|
}
|
|
674
694
|
await session.send(message);
|
|
675
695
|
}
|
|
696
|
+
async lookupUserProfileFromMessages(platform, selfId, userId) {
|
|
697
|
+
try {
|
|
698
|
+
const records = await this.ctx.database
|
|
699
|
+
.select('group_analysis_messages')
|
|
700
|
+
.where({ platform, selfId, userId })
|
|
701
|
+
.orderBy(($) => $.timestamp, 'desc')
|
|
702
|
+
.limit(1)
|
|
703
|
+
.execute();
|
|
704
|
+
const latest = records[0];
|
|
705
|
+
if (!latest)
|
|
706
|
+
return null;
|
|
707
|
+
return {
|
|
708
|
+
username: latest.username || null,
|
|
709
|
+
avatarUrl: latest.avatarUrl || null
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
this.ctx.logger.debug(`从消息记录回退用户资料失败 (${platform}/${userId}): ${error}`);
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
676
717
|
async analyzeGroupMessages(messages, selfId, target, context) {
|
|
677
718
|
this.ctx.logger.info(`开始分析 ${messages.length} 条消息...`);
|
|
678
719
|
const { userStats, totalChars, totalEmojiCount, allMessagesText } = (0, utils_1.calculateBasicStats)(messages);
|
package/lib/utils.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.calculateBasicStats = calculateBasicStats;
|
|
|
4
4
|
exports.buildPersonaRecordId = buildPersonaRecordId;
|
|
5
5
|
exports.generateTextReport = generateTextReport;
|
|
6
6
|
exports.getAvatarUrl = getAvatarUrl;
|
|
7
|
+
exports.resolveUserAvatar = resolveUserAvatar;
|
|
7
8
|
exports.formatUserStats = formatUserStats;
|
|
8
9
|
exports.formatGoldenQuotes = formatGoldenQuotes;
|
|
9
10
|
exports.formatUserTitles = formatUserTitles;
|
|
@@ -154,6 +155,27 @@ function generateTextReport(result) {
|
|
|
154
155
|
function getAvatarUrl(userId) {
|
|
155
156
|
return `http://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`;
|
|
156
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* 按平台解析用户头像 URL。
|
|
160
|
+
* - onebot: 使用 QQ 号头像接口(q1.qlogo.cn)
|
|
161
|
+
* - qq(官方 QQ 机器人): 使用 openid 头像模板 q.qlogo.cn/qqapp/{appid}/{openid}/640
|
|
162
|
+
* - 其他平台: 返回 null,由调用方回退到事件头像或默认头像
|
|
163
|
+
*/
|
|
164
|
+
function resolveUserAvatar(bot, userId) {
|
|
165
|
+
if (!bot || !userId)
|
|
166
|
+
return null;
|
|
167
|
+
if (bot.platform === 'onebot') {
|
|
168
|
+
return getAvatarUrl(userId);
|
|
169
|
+
}
|
|
170
|
+
if (bot.platform === 'qq') {
|
|
171
|
+
const appid = bot.config?.id;
|
|
172
|
+
if (appid) {
|
|
173
|
+
return `https://q.qlogo.cn/qqapp/${appid}/${userId}/640`;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
157
179
|
function formatUserStats(userStats, skin = 'md3') {
|
|
158
180
|
const renderer = skins_1.skinRegistry.getSafe(skin);
|
|
159
181
|
return renderer.formatUserStats(userStats);
|
package/package.json
CHANGED