koishi-plugin-group-analysis-mx 1.2.9 → 1.3.0

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.
@@ -73,42 +73,37 @@ class RendererService extends koishi_1.Service {
73
73
  }
74
74
  }
75
75
  /** 往渲染 HTML 注入 emoji 字体 @font-face 与字体栈,避免 puppeteer 截图时 emoji 显示为方框/问号。 */
76
- async injectEmojiFont(html, htmlDir) {
76
+ async injectEmojiFont(html) {
77
77
  if (!html || typeof html !== 'string')
78
78
  return html;
79
- // 字体目录相对于当前临时 HTML 所在目录,供 @font-face 用相对路径加载(Chromium 最稳定)
80
- const fontsAbs = path_1.default.resolve(this.templateDir, 'fonts');
81
- let rel = 'fonts';
82
- try {
83
- rel = path_1.default.relative(path_1.default.resolve(htmlDir || this.templateDir), fontsAbs).replace(/\\/g, '/');
84
- if (rel && !rel.startsWith('.'))
85
- rel = './' + rel;
86
- }
87
- catch (e) { /* use default */ }
88
- const emojiCss = this.buildEmojiFontCss(rel);
89
- const style = `<style>${emojiCss}html,body{font-family:-apple-system,'Segoe UI','Segoe UI Emoji','NotoColorEmoji','Noto Sans SC','Noto Color Emoji','Apple Color Emoji','Microsoft YaHei','Hiragino Sans GB',sans-serif !important;}</style>`;
79
+ const emojiCss = this.buildEmojiFontCss();
80
+ const style = `<style>${emojiCss}html,body{font-family:-apple-system,'Segoe UI','Segoe UI Emoji','Noto Color Emoji','NotoEmoji','Apple Color Emoji','Microsoft YaHei','Hiragino Sans GB',sans-serif !important;}</style>`;
90
81
  if (html.includes('</head>'))
91
82
  return html.replace('</head>', style + '</head>');
92
83
  return html;
93
84
  }
94
- /** 生成引用磁盘上 emoji 字体分片的 @font-face(相对路径,Chromium 稳定加载)。 */
95
- buildEmojiFontCss(relPath) {
96
- const rel = relPath || 'fonts';
97
- let css = '';
98
- for (let i = 0; i < EMOJI_RANGES.length; i++) {
99
- const file = `${String(i).padStart(2, '0')}.woff2`;
100
- css += `@font-face{font-family:'NotoColorEmoji';font-style:normal;font-weight:400;src:url('${rel}/${file}') format('woff2');unicode-range:${EMOJI_RANGES[i]};}`;
101
- }
102
- return css;
85
+ /**
86
+ * 生成引用同层 fonts/NotoEmoji.ttf 的 @font-face(单文件 ttf,Chromium 稳定加载)。
87
+ * 注意:必须与渲染 HTML 处于同一目录树(HTML <skin>/,字体在 <skin>/fonts/),跨目录 file:// 加载会被 Chromium 拒绝。
88
+ */
89
+ buildEmojiFontCss() {
90
+ return `@font-face{font-family:'NotoEmoji';font-style:normal;font-weight:400;src:url('fonts/NotoEmoji.ttf') format('truetype');}`;
103
91
  }
104
92
  async init() {
105
93
  const resourcesDir = __dirname + '/../../resources';
106
94
  const templateDir = this.templateDir;
107
95
  const skin = this.config.skin || 'md3';
108
- // 拷贝内嵌的 emoji 字体到 data 目录,供 @font-face 以相对路径引用(Chromium 最稳定的加载方式)
109
- const fontsDestDir = path_1.default.resolve(templateDir, 'fonts');
110
- await fs_1.promises.mkdir(fontsDestDir, { recursive: true });
111
- await fs_1.promises.cp(path_1.default.resolve(resourcesDir, 'fonts'), fontsDestDir, { recursive: true });
96
+ // 拷贝内嵌的 emoji 字体到当前皮肤目录的 fonts/ 子目录(与渲染 HTML 同层;跨目录 file:// 加载会被 Chromium 拒绝)
97
+ const skinFontsDir = ORIGINAL_SKINS.includes(skin)
98
+ ? path_1.default.resolve(templateDir, 'original', skin, 'fonts')
99
+ : path_1.default.resolve(templateDir, skin, 'fonts');
100
+ await fs_1.promises.mkdir(skinFontsDir, { recursive: true });
101
+ await fs_1.promises.cp(path_1.default.resolve(resourcesDir, 'fonts'), skinFontsDir, { recursive: true });
102
+ // 创建空 HTML 占位:渲染时先 goto 它(建立 file:// base),再 setContent 注入内容,@font-face 相对字体才能被加载
103
+ const emptyHtmlPath = ORIGINAL_SKINS.includes(skin)
104
+ ? path_1.default.resolve(templateDir, 'original', skin, 'emptyHtml.html')
105
+ : path_1.default.resolve(templateDir, skin, 'emptyHtml.html');
106
+ await fs_1.promises.writeFile(emptyHtmlPath, '');
112
107
  // 拷贝原项目皮肤模板(nunjucks 渲染用)
113
108
  const originalSourceDir = path_1.default.resolve(resourcesDir, 'original');
114
109
  const originalDestDir = path_1.default.resolve(templateDir, 'original');
@@ -153,6 +148,13 @@ class RendererService extends koishi_1.Service {
153
148
  }
154
149
  }, 3 * 60 * 1000);
155
150
  }
151
+ /** 当前皮肤目录下用于建立 file:// base 的空 HTML 占位路径。 */
152
+ emptyHtmlPath() {
153
+ const skin = this.config.skin || 'md3';
154
+ return ORIGINAL_SKINS.includes(skin)
155
+ ? path_1.default.resolve(this.templateDir, 'original', skin, 'emptyHtml.html')
156
+ : path_1.default.resolve(this.templateDir, skin, 'emptyHtml.html');
157
+ }
156
158
  async renderGroupAnalysisToPdf(data) {
157
159
  let theme = this.config.theme;
158
160
  if (theme === 'auto') {
@@ -235,11 +237,14 @@ class RendererService extends koishi_1.Service {
235
237
  dynamicAvatarUrl: dynamicAvatarBase64
236
238
  });
237
239
  // 写入临时 HTML 文件
238
- await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml, path_1.default.dirname(outTemplateHtmlPath)));
240
+ await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml));
239
241
  this.ctx.logger.info('HTML 模板填充完成,正在调用 Puppeteer 进行渲染...');
240
242
  const page = await this.ctx.puppeteer.page();
241
- // 重新加载页面并使用 goto 访问本地文件
242
- await page.goto('file://' + outTemplateHtmlPath, {
243
+ // goto 空 HTML 建立 file:// base,再 setContent 注入内容(@font-face 相对字体才能被加载)
244
+ await page.goto('file://' + this.emptyHtmlPath(), {
245
+ waitUntil: 'domcontentloaded'
246
+ });
247
+ await page.setContent(await this.injectEmojiFont(filledHtml), {
243
248
  waitUntil: 'domcontentloaded'
244
249
  });
245
250
  this.ctx.logger.info('网页加载完成,开始等待字体加载。');
@@ -306,14 +311,18 @@ class RendererService extends koishi_1.Service {
306
311
  const html = env.render('html_template.html', context);
307
312
  const randomId = Math.random().toString(36).substring(2, 15);
308
313
  const outTemplateHtmlPath = path_1.default.resolve(skinDir, `${randomId}.html`);
309
- await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(html, path_1.default.dirname(outTemplateHtmlPath)));
314
+ const emojiHtmlOriginal = await this.injectEmojiFont(html);
315
+ await fs_1.promises.writeFile(outTemplateHtmlPath, emojiHtmlOriginal);
310
316
  this.ctx.logger.info(`原项目皮肤 ${skin} HTML 生成完成,正在调用 Puppeteer 渲染...`);
311
317
  const page = await this.ctx.puppeteer.page();
312
318
  await page.setViewport({ width: 1080, height: 1920, deviceScaleFactor: 2 });
313
- await page.goto('file://' + outTemplateHtmlPath, {
319
+ await page.goto('file://' + this.emptyHtmlPath(), {
320
+ waitUntil: 'domcontentloaded'
321
+ });
322
+ await page.setContent(emojiHtmlOriginal, {
314
323
  waitUntil: 'networkidle0',
315
324
  timeout: 60000
316
- }).catch(() => page.goto('file://' + outTemplateHtmlPath, {
325
+ }).catch(() => page.setContent(emojiHtmlOriginal, {
317
326
  waitUntil: 'domcontentloaded',
318
327
  timeout: 60000
319
328
  }));
@@ -522,11 +531,14 @@ class RendererService extends koishi_1.Service {
522
531
  theme,
523
532
  dynamicAvatarUrl: dynamicAvatarBase64
524
533
  });
525
- await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml, path_1.default.dirname(outTemplateHtmlPath)));
534
+ await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml));
526
535
  this.ctx.logger.info('用户画像 HTML 模板填充完成,正在调用 Puppeteer 进行渲染...');
527
536
  const page = await this.ctx.puppeteer.page();
528
- // 重新加载页面并使用 goto 访问本地文件
529
- await page.goto('file://' + outTemplateHtmlPath, {
537
+ // goto 空 HTML 建立 file:// base,再 setContent 注入内容(@font-face 相对字体才能被加载)
538
+ await page.goto('file://' + this.emptyHtmlPath(), {
539
+ waitUntil: 'domcontentloaded'
540
+ });
541
+ await page.setContent(await this.injectEmojiFont(filledHtml), {
530
542
  waitUntil: 'domcontentloaded'
531
543
  });
532
544
  this.ctx.logger.info('网页加载完成,开始等待字体加载。');
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.9",
4
+ "version": "1.3.0",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file