koishi-plugin-group-analysis-mx 1.2.2 → 1.2.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.
@@ -816,14 +816,6 @@ class AnalysisService extends koishi_1.Service {
816
816
  }
817
817
  avatarMap.set(String(user.userId), user.avatar || '');
818
818
  }
819
- // 用户称号列表同样补全头像(LLM 返回的 id 可能是数字类型)
820
- if (Array.isArray(userTitles)) {
821
- userTitles.forEach((title) => {
822
- if (title && !title.avatar && avatarMap.has(String(title.id))) {
823
- title.avatar = avatarMap.get(String(title.id));
824
- }
825
- });
826
- }
827
819
  // 昵称 → 原始昵称 / 头像 映射(金句、称号用),并对 emoji/空白做归一化以增强匹配。
828
820
  // LLM 返回的 sender/name 常丢失 emoji,这里用真实昵称(含 emoji)替换显示。
829
821
  const nicknameAvatarMap = new Map();
@@ -842,6 +834,22 @@ class AnalysisService extends koishi_1.Service {
842
834
  }
843
835
  }
844
836
  }
837
+ // 用户称号:用真实昵称替换 LLM 生成的 name(保留 emoji),并补全头像
838
+ if (Array.isArray(userTitles)) {
839
+ userTitles.forEach((title) => {
840
+ if (title && title.name) {
841
+ const normalized = (0, utils_1.normalizeNickname)(title.name);
842
+ const realName = nicknameMap.get(title.name) ||
843
+ (normalized ? nicknameMap.get(normalized) : '') ||
844
+ '';
845
+ if (realName)
846
+ title.name = realName;
847
+ }
848
+ if (title && !title.avatar && avatarMap.has(String(title.id))) {
849
+ title.avatar = avatarMap.get(String(title.id));
850
+ }
851
+ });
852
+ }
845
853
  // 金句:用真实昵称替换 LLM 生成的昵称(保留 emoji),并补全发送者头像
846
854
  if (Array.isArray(goldenQuotes)) {
847
855
  goldenQuotes.forEach((quote) => {
@@ -29,6 +29,10 @@ class RendererService extends koishi_1.Service {
29
29
  return path_1.default.resolve(this.templateDir, skin, filename);
30
30
  }
31
31
  async imageToBase64(url) {
32
+ // 已内联的 data URI 直接返回,无需再下载
33
+ if (typeof url === 'string' && url.startsWith('data:')) {
34
+ return url;
35
+ }
32
36
  try {
33
37
  this.ctx.logger.debug(`转换图片为 Base64: ${url}`);
34
38
  const response = await fetch(url);
@@ -191,6 +195,15 @@ class RendererService extends koishi_1.Service {
191
195
  // 等待字体加载完成
192
196
  await page.evaluate(() => document.fonts.ready);
193
197
  this.ctx.logger.info('字体加载完成。');
198
+ // 等待页面内图片加载完成(避免截图像素缺失/头像空白),最多等待 8 秒
199
+ await Promise.race([
200
+ page.evaluate(() => Promise.all(Array.from(document.images)
201
+ .filter((img) => !img.complete)
202
+ .map((img) => new Promise((resolve) => {
203
+ img.onload = img.onerror = resolve;
204
+ })))),
205
+ new Promise((resolve) => setTimeout(resolve, 8000))
206
+ ]).catch(() => { });
194
207
  // 设置 3 分钟后自动删除临时文件
195
208
  this.ctx.setTimeout(async () => {
196
209
  try {
@@ -259,12 +272,15 @@ class RendererService extends koishi_1.Service {
259
272
  catch (err) {
260
273
  this.ctx.logger.debug(`等待字体加载超时: ${err}`);
261
274
  }
262
- // 等待图片加载完成(头像等)
263
- await page.evaluate(() => Promise.all(Array.from(document.images)
264
- .filter((img) => !img.complete)
265
- .map((img) => new Promise((resolve) => {
266
- img.onload = img.onerror = resolve;
267
- })))).catch(() => { });
275
+ // 等待图片加载完成(头像等),最多等待 8 秒
276
+ await Promise.race([
277
+ page.evaluate(() => Promise.all(Array.from(document.images)
278
+ .filter((img) => !img.complete)
279
+ .map((img) => new Promise((resolve) => {
280
+ img.onload = img.onerror = resolve;
281
+ })))),
282
+ new Promise((resolve) => setTimeout(resolve, 8000))
283
+ ]).catch(() => { });
268
284
  this.ctx.setTimeout(async () => {
269
285
  try {
270
286
  await fs_1.promises.unlink(outTemplateHtmlPath);
@@ -298,20 +314,33 @@ class RendererService extends koishi_1.Service {
298
314
  : (t.contributors || '')
299
315
  }));
300
316
  // 用户称号:原项目子模板使用 title.avatar_data / title.name / title.title / title.mbti / title.reason
301
- const titles = (data.userTitles || []).map((t) => ({
317
+ // 头像统一转成 base64 内联,避免外网加载失败导致头像空白
318
+ const avatarBase64Cache = new Map();
319
+ const toBase64 = async (url) => {
320
+ if (!url)
321
+ return '';
322
+ if (url.startsWith('data:'))
323
+ return url;
324
+ if (avatarBase64Cache.has(url))
325
+ return avatarBase64Cache.get(url);
326
+ const b64 = await this.imageToBase64(url);
327
+ avatarBase64Cache.set(url, b64);
328
+ return b64;
329
+ };
330
+ const titles = await Promise.all((data.userTitles || []).map(async (t) => ({
302
331
  name: t.name || '',
303
332
  title: t.title || '',
304
333
  mbti: t.mbti || '',
305
334
  reason: t.reason || '',
306
- avatar_data: t.avatar || ''
307
- }));
335
+ avatar_data: t.avatar ? await toBase64(t.avatar) : ''
336
+ })));
308
337
  // 金句:原项目子模板使用 quote.avatar_url / quote.sender / quote.content / quote.reason
309
- const quotes = (data.goldenQuotes || []).map((q) => ({
338
+ const quotes = await Promise.all((data.goldenQuotes || []).map(async (q) => ({
310
339
  sender: q.sender || '',
311
340
  content: q.content || '',
312
341
  reason: q.reason || '',
313
- avatar_url: q.avatar || ''
314
- }));
342
+ avatar_url: q.avatar ? await toBase64(q.avatar) : ''
343
+ })));
315
344
  // 渲染子模板为 HTML 字符串
316
345
  let topicsHtml = '';
317
346
  let titlesHtml = '';
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.2.2",
4
+ "version": "1.2.4",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",