koishi-plugin-group-analysis-mx 1.2.9 → 1.3.1
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/renderer.js +60 -34
- package/package.json +1 -1
- package/resources/fonts/NotoEmoji.ttf +0 -0
- package/resources/fonts/00.woff2 +0 -0
- package/resources/fonts/01.woff2 +0 -0
- package/resources/fonts/02.woff2 +0 -0
- package/resources/fonts/03.woff2 +0 -0
- package/resources/fonts/04.woff2 +0 -0
- package/resources/fonts/05.woff2 +0 -0
- package/resources/fonts/06.woff2 +0 -0
- package/resources/fonts/07.woff2 +0 -0
- package/resources/fonts/08.woff2 +0 -0
- package/resources/fonts/09.woff2 +0 -0
package/lib/service/renderer.js
CHANGED
|
@@ -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
|
|
76
|
+
async injectEmojiFont(html) {
|
|
77
77
|
if (!html || typeof html !== 'string')
|
|
78
78
|
return html;
|
|
79
|
-
|
|
80
|
-
const
|
|
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
|
-
/**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
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,24 @@ 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
|
+
}
|
|
158
|
+
/** 确保用于建立 file:// base 的空 HTML 占位存在(不依赖 init 顺序)。 */
|
|
159
|
+
async ensureEmptyHtml() {
|
|
160
|
+
const p = this.emptyHtmlPath();
|
|
161
|
+
try {
|
|
162
|
+
await fs_1.promises.access(p);
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
await fs_1.promises.mkdir(path_1.default.dirname(p), { recursive: true });
|
|
166
|
+
await fs_1.promises.writeFile(p, '');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
156
169
|
async renderGroupAnalysisToPdf(data) {
|
|
157
170
|
let theme = this.config.theme;
|
|
158
171
|
if (theme === 'auto') {
|
|
@@ -235,11 +248,15 @@ class RendererService extends koishi_1.Service {
|
|
|
235
248
|
dynamicAvatarUrl: dynamicAvatarBase64
|
|
236
249
|
});
|
|
237
250
|
// 写入临时 HTML 文件
|
|
238
|
-
await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml
|
|
251
|
+
await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml));
|
|
239
252
|
this.ctx.logger.info('HTML 模板填充完成,正在调用 Puppeteer 进行渲染...');
|
|
240
253
|
const page = await this.ctx.puppeteer.page();
|
|
241
|
-
//
|
|
242
|
-
await
|
|
254
|
+
// 先 goto 空 HTML 建立 file:// base,再 setContent 注入内容(@font-face 相对字体才能被加载)
|
|
255
|
+
await this.ensureEmptyHtml();
|
|
256
|
+
await page.goto('file://' + this.emptyHtmlPath(), {
|
|
257
|
+
waitUntil: 'domcontentloaded'
|
|
258
|
+
});
|
|
259
|
+
await page.setContent(await this.injectEmojiFont(filledHtml), {
|
|
243
260
|
waitUntil: 'domcontentloaded'
|
|
244
261
|
});
|
|
245
262
|
this.ctx.logger.info('网页加载完成,开始等待字体加载。');
|
|
@@ -306,14 +323,19 @@ class RendererService extends koishi_1.Service {
|
|
|
306
323
|
const html = env.render('html_template.html', context);
|
|
307
324
|
const randomId = Math.random().toString(36).substring(2, 15);
|
|
308
325
|
const outTemplateHtmlPath = path_1.default.resolve(skinDir, `${randomId}.html`);
|
|
309
|
-
|
|
326
|
+
const emojiHtmlOriginal = await this.injectEmojiFont(html);
|
|
327
|
+
await fs_1.promises.writeFile(outTemplateHtmlPath, emojiHtmlOriginal);
|
|
310
328
|
this.ctx.logger.info(`原项目皮肤 ${skin} HTML 生成完成,正在调用 Puppeteer 渲染...`);
|
|
311
329
|
const page = await this.ctx.puppeteer.page();
|
|
312
330
|
await page.setViewport({ width: 1080, height: 1920, deviceScaleFactor: 2 });
|
|
313
|
-
await
|
|
331
|
+
await this.ensureEmptyHtml();
|
|
332
|
+
await page.goto('file://' + this.emptyHtmlPath(), {
|
|
333
|
+
waitUntil: 'domcontentloaded'
|
|
334
|
+
});
|
|
335
|
+
await page.setContent(emojiHtmlOriginal, {
|
|
314
336
|
waitUntil: 'networkidle0',
|
|
315
337
|
timeout: 60000
|
|
316
|
-
}).catch(() => page.
|
|
338
|
+
}).catch(() => page.setContent(emojiHtmlOriginal, {
|
|
317
339
|
waitUntil: 'domcontentloaded',
|
|
318
340
|
timeout: 60000
|
|
319
341
|
}));
|
|
@@ -522,11 +544,15 @@ class RendererService extends koishi_1.Service {
|
|
|
522
544
|
theme,
|
|
523
545
|
dynamicAvatarUrl: dynamicAvatarBase64
|
|
524
546
|
});
|
|
525
|
-
await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml
|
|
547
|
+
await fs_1.promises.writeFile(outTemplateHtmlPath, await this.injectEmojiFont(filledHtml));
|
|
526
548
|
this.ctx.logger.info('用户画像 HTML 模板填充完成,正在调用 Puppeteer 进行渲染...');
|
|
527
549
|
const page = await this.ctx.puppeteer.page();
|
|
528
|
-
//
|
|
529
|
-
await
|
|
550
|
+
// 先 goto 空 HTML 建立 file:// base,再 setContent 注入内容(@font-face 相对字体才能被加载)
|
|
551
|
+
await this.ensureEmptyHtml();
|
|
552
|
+
await page.goto('file://' + this.emptyHtmlPath(), {
|
|
553
|
+
waitUntil: 'domcontentloaded'
|
|
554
|
+
});
|
|
555
|
+
await page.setContent(await this.injectEmojiFont(filledHtml), {
|
|
530
556
|
waitUntil: 'domcontentloaded'
|
|
531
557
|
});
|
|
532
558
|
this.ctx.logger.info('网页加载完成,开始等待字体加载。');
|
package/package.json
CHANGED
|
Binary file
|
package/resources/fonts/00.woff2
DELETED
|
Binary file
|
package/resources/fonts/01.woff2
DELETED
|
Binary file
|
package/resources/fonts/02.woff2
DELETED
|
Binary file
|
package/resources/fonts/03.woff2
DELETED
|
Binary file
|
package/resources/fonts/04.woff2
DELETED
|
Binary file
|
package/resources/fonts/05.woff2
DELETED
|
Binary file
|
package/resources/fonts/06.woff2
DELETED
|
Binary file
|
package/resources/fonts/07.woff2
DELETED
|
Binary file
|
package/resources/fonts/08.woff2
DELETED
|
Binary file
|
package/resources/fonts/09.woff2
DELETED
|
Binary file
|