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.
@@ -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
- this.ctx.logger.warn(`获取用户 ${record.userId} 的群组信息失败 (${group.channelId || group.guildId}),可能是未加入该群聊。将跳过此群组的信息获取。`, error);
205
- continue;
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
- this.ctx.logger.warn(`获取用户 ${userId} 信息失败: ${error}`);
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
- avatar = user.avatar;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-group-analysis-mx",
3
3
  "description": "群聊分析插件 - 生成精美群聊统计报告,支持 AI 话题总结(独立版,不依赖 ChatLuna)",
4
- "version": "1.0.16",
4
+ "version": "1.0.17",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",