koishi-plugin-ll-mc 3.1.10 → 3.1.12

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.
Binary file
package/lib/index.js CHANGED
@@ -304,6 +304,17 @@ async function apply(ctx, config) {
304
304
  } catch (error) {
305
305
  logger.warn("[ll-mc] 强制更新 NotoEmoji 字体失败:", error?.message);
306
306
  }
307
+ // 官机补丁(HTML 模板):系统装了 emoji 字体(如 font-noto-emoji)时,emoji 字体自带 0-9 字形
308
+ // (给 keycap 用)且字宽很宽,会导致所有数字被撑成 "2 9 1 7"。
309
+ // 这里内嵌一个只含 0-9 的极小正常字体(约 3KB),用 unicode-range 把数字锁死在它上面;
310
+ // 用 base64 data URI 内嵌(而非相对路径),因为 file:// 页面加载 @font-face 本地文件不可靠。
311
+ let digitsFontFaceCss = "";
312
+ try {
313
+ const digitsBuf = await fs.readFile(import_path.default.join(assetsDir, "fonts", "digits.ttf"));
314
+ digitsFontFaceCss = `@font-face { font-family: 'LLMCDigits'; src: url("data:font/ttf;base64,${digitsBuf.toString("base64")}") format('truetype'); unicode-range: U+0030-0039; }`;
315
+ } catch (error) {
316
+ logger.warn("[ll-mc] 加载 digits.ttf 失败,模板数字可能被 emoji 字体撑宽:", error?.message);
317
+ }
307
318
  const avatarCache = /* @__PURE__ */ new Map();
308
319
  let iconCache = [];
309
320
  let barBgImgCache = [];
@@ -2462,7 +2473,7 @@ ${targetUserRecord[0].username}
2462
2473
  </div>`;
2463
2474
  }
2464
2475
  __name(buildLeaderboardRow, "buildLeaderboardRow");
2465
- function buildLeaderboardHtml(title, date, items, backgroundCss, fontFaces) {
2476
+ function buildLeaderboardHtml(title, date, items, backgroundCss) {
2466
2477
  const maxCount = Math.max(1, ...items.map((item) => item.count || 0));
2467
2478
  const rows = items.map((item, index) => buildLeaderboardRow(item, index, maxCount)).join("\n");
2468
2479
  return `<!DOCTYPE html>
@@ -2471,7 +2482,9 @@ ${targetUserRecord[0].username}
2471
2482
  <meta charset="UTF-8">
2472
2483
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2473
2484
  <style>
2474
- ${fontFaces || ""}
2485
+ /* 数字锁定:内嵌一个只含 0-9 的极小正常字体,避免被系统 emoji 字体(自带 0-9 且字宽很宽)撑成 "2 9 1 7"
2486
+ 其余字符(中文/字母/emoji)照常走下面的系统字体栈。 */
2487
+ ${digitsFontFaceCss}
2475
2488
  * { box-sizing: border-box; margin: 0; padding: 0; }
2476
2489
  html, body {
2477
2490
  width: 900px;
@@ -2480,7 +2493,7 @@ ${targetUserRecord[0].username}
2480
2493
  background-color: #F5F7FA;
2481
2494
  }
2482
2495
  body {
2483
- font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "NotoEmoji", "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
2496
+ font-family: "LLMCDigits", "PingFang SC", "Microsoft YaHei", "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
2484
2497
  -webkit-font-smoothing: antialiased;
2485
2498
  }
2486
2499
  .card {
@@ -2524,10 +2537,9 @@ ${rows}
2524
2537
  if (typeof ctx.puppeteer?.page !== "function") return null;
2525
2538
  let page;
2526
2539
  try {
2527
- // 复用图表的内置字体(含纯 emoji 的 NotoEmoji),以相对路径 fonts/<file> 加载,
2528
- // 所以必须把模板写到 data/messageCounter/ 目录里再用 file:// 访问,相对路径才能解析。
2529
- const fontFaces = generateFontFacesCSS(["HarmonyOS_Sans_Medium.ttf", "NotoEmoji.ttf"]);
2530
- const html = buildLeaderboardHtml(title, date, items, backgroundCss, fontFaces);
2540
+ // 官机补丁:模板里的数字用内置文本字体通过 @font-face(unicode-range) 锁定,
2541
+ // 所以必须把模板写到 data/messageCounter/ 目录里再用 file:// 访问,相对路径 fonts/ 才能解析。
2542
+ const html = buildLeaderboardHtml(title, date, items, backgroundCss);
2531
2543
  page = await ctx.puppeteer.page();
2532
2544
  await page.setViewport({ width: 900, height: 400, deviceScaleFactor: 2 });
2533
2545
  const pageUrl = templateHtmlPath.includes(":") ? `file:///${templateHtmlPath}` : `file://${templateHtmlPath}`;
@@ -2546,54 +2558,9 @@ ${rows}
2546
2558
  } else {
2547
2559
  await page.setContent(html, { waitUntil: "networkidle0", timeout: 20000 }).catch(() => {});
2548
2560
  }
2549
- // 确保 emoji 字体就绪再截图
2561
+ // 确保字体就绪再截图
2550
2562
  await page.evaluate(() => document.fonts.ready).catch(() => {});
2551
2563
  await page.waitForSelector(".card").catch(() => {});
2552
- // 官机补丁:背景图右侧可能是深色人物,导致排在右下角的发言数字看不清。
2553
- // 这里对每个数字的位置采样一次背景亮度,凡是背景偏暗的数字改成白色(去掉全局描边)。
2554
- try {
2555
- const dpr = 2;
2556
- // 先隐藏所有文字以采样"纯背景"像素
2557
- await page.evaluate(() => {
2558
- document.querySelectorAll(".bar-content").forEach((el) => { el.style.visibility = "hidden"; });
2559
- });
2560
- const bgShot = await page.screenshot({ type: "png", fullPage: true });
2561
- await page.evaluate(() => {
2562
- document.querySelectorAll(".bar-content").forEach((el) => { el.style.visibility = ""; });
2563
- });
2564
- const bgDataUrl = `data:image/png;base64,${bgShot.toString("base64")}`;
2565
- const luminances = await page.evaluate(async (dataUrl) => {
2566
- const img = new Image();
2567
- img.src = dataUrl;
2568
- await img.decode();
2569
- const canvas = document.createElement("canvas");
2570
- canvas.width = img.naturalWidth;
2571
- canvas.height = img.naturalHeight;
2572
- const ctx = canvas.getContext("2d");
2573
- ctx.drawImage(img, 0, 0);
2574
- const scale = window.devicePixelRatio || 1;
2575
- const out = [];
2576
- document.querySelectorAll(".row-count").forEach((el) => {
2577
- const r = el.getBoundingClientRect();
2578
- const x = Math.max(0, Math.min(Math.round((r.left + r.width / 2) * scale), canvas.width - 1));
2579
- const y = Math.max(0, Math.min(Math.round((r.top + r.height / 2) * scale), canvas.height - 1));
2580
- const d = ctx.getImageData(x, y, 1, 1).data;
2581
- out.push(0.2126 * d[0] + 0.7152 * d[1] + 0.0722 * d[2]);
2582
- });
2583
- return out;
2584
- }, bgDataUrl);
2585
- await page.evaluate((lums) => {
2586
- const nodes = document.querySelectorAll(".row-count");
2587
- nodes.forEach((el, i) => {
2588
- const lum = lums[i];
2589
- if (lum !== undefined && lum < 120) {
2590
- el.style.color = "#FFFFFF";
2591
- }
2592
- });
2593
- }, luminances);
2594
- } catch (sampleError) {
2595
- logger.warn("背景亮度采样失败,使用默认数字颜色:", sampleError?.message);
2596
- }
2597
2564
  // 背景(自定义 CSS/API 背景)是加在 html 上的,必须截整页才能把卡片外的背景一起截进来;
2598
2565
  // body 高度由内容撑开(无 min-height),所以 fullPage 不会产生多余空白。
2599
2566
  const buf = await page.screenshot({ type: "png", fullPage: true });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-ll-mc",
3
3
  "description": "消息数量统计插件(基于 koishi-plugin-message-counter 重做,官方QQ机器人兼容)",
4
- "version": "3.1.10",
4
+ "version": "3.1.12",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [