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.
- package/assets/fonts/digits.ttf +0 -0
- package/lib/index.js +20 -53
- package/package.json +1 -1
|
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
|
|
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
|
-
|
|
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, "
|
|
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
|
-
//
|
|
2528
|
-
// 所以必须把模板写到 data/messageCounter/ 目录里再用 file://
|
|
2529
|
-
const
|
|
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
|
-
//
|
|
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 });
|