UNPKG

koishi-plugin-monetary-rank

Version:

Koishi 的通用货币排行榜、查询余额插件。

980 lines (941 loc) 51 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, inject: () => inject, name: () => name, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi = require("koishi"); var import_path = __toESM(require("path")); var import_fs = __toESM(require("fs")); // src/data/fallbackBase64.json var fallbackBase64_default = [ "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" ]; // src/index.ts var name = "monetary-rank"; var inject = { required: ["database", "monetary"], optional: ["puppeteer", "canvas"] }; var usage = `## 注意事项 - 用户第一次发言时记录用户数据,刚开始使用时没有数据。 ## 自定义水平柱状图 2(与发言排行榜共用文件夹) 1. 用户图标: - 支持为同一用户添加多个图标,它们会同时显示。 - 在 \`data/messageCounter/icons\` 文件夹下添加用户图标,文件名为用户 ID (例如 \`1234567890.png\`)。 - 多个图标的文件名需形如 \`1234567890-1.png\`、 \`1234567890-2.png\` 。 2. 柱状条背景: - 支持为同一用户添加多个背景图片,插件会随机选择一个显示。 - 在 \`data/messageCounter/barBgImgs\` 文件夹下添加水平柱状条背景图片。 - 多个图片的文件名需形如 \`1234567890-1.png\`、\`1234567890-2.png\`。 - 建议图片尺寸为 850x50 像素,文件名为用户 ID (例如\`1234567890.png\`)。 > 重启插件以使更改生效。 ## QQ 群 - 956758505`; var Config = import_koishi.Schema.intersect([ import_koishi.Schema.object({ defaultLeaderboardDisplayCount: import_koishi.Schema.number().min(1).default(10).description("默认排行榜显示数量。") }).description("排行榜显示设置"), import_koishi.Schema.object({ isLeaderboardDisplayedAsImage: import_koishi.Schema.boolean().default(false).description(`排行榜是否以图片形式显示,需要启用 \`puppeteer\` 服务。`), style: import_koishi.Schema.union([ import_koishi.Schema.const("2").description("样式 2(水平柱状图)"), import_koishi.Schema.const("3").description("样式 3(deer-pipe插件的排行榜样式)") ]).role("radio").default("2").description("排行榜样式。"), waitUntil: import_koishi.Schema.union([ "load", "domcontentloaded", "networkidle0", "networkidle2" ]).default("networkidle0").description("等待页面加载的事件。"), horizontalBarBackgroundFullOpacity: import_koishi.Schema.number().min(0).max(1).default(0).description( "(仅样式 2)自定义水平柱状条背景整条的不透明度,值越小则越透明。" ), horizontalBarBackgroundOpacity: import_koishi.Schema.number().min(0).max(1).default(0.6).description( "(仅样式 2)自定义水平柱状条背景的不透明度,值越小则越透明。" ), shouldMoveIconToBarEndLeft: import_koishi.Schema.boolean().default(true).description( "(仅样式 2)是否将自定义图标移动到水平柱状条末端的左侧,关闭后将放在用户名的右侧。" ) }).description("图片转换功能设置") ]); async function apply(ctx, config) { const messageCounterRoot = import_path.default.join(ctx.baseDir, "data", "messageCounter"); const iconsPath = import_path.default.join(messageCounterRoot, "icons"); const barBgImgsPath = import_path.default.join(messageCounterRoot, "barBgImgs"); const emptyHtmlPath = import_path.default.join(messageCounterRoot, "emptyHtml.html").replace(/\\/g, "/"); await ensureDirExists(iconsPath); await ensureDirExists(barBgImgsPath); if (!import_fs.default.existsSync(emptyHtmlPath)) { import_fs.default.writeFileSync( emptyHtmlPath, '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body></body></html>' ); } const logger = ctx.logger("monetaryRank"); const iconData = readIconsFromFolder(iconsPath); const barBgImgs = readBgImgsFromFolder(barBgImgsPath); ctx.model.extend( "username", { id: "unsigned", uid: "unsigned", userId: "string", avatar: "string", platform: "string", username: "string", channelId: "string" }, { primary: "id", autoInc: true } ); ctx.on("message", async (session) => { const user = await ctx.database.get("username", { userId: session.userId, channelId: session.channelId }); const username = session.author.nick ? session.author.nick : session.author.name ? session.author.name : "神秘人"; const avatar = session.author.avatar ? session.author.avatar : "https://th.bing.com/th/id/OIP.s5N_QuGWAIWBmUyeNemQagHaHZ?w=512&h=512&c=7&r=0&o=5&dpr=1.3&pid=1.7"; if (user.length === 0) { const binding = await ctx.database.get("binding", { pid: session.userId, platform: session.platform }); const uid = binding[0].aid; await ctx.database.create("username", { uid, userId: session.userId, avatar, platform: session.platform, username, channelId: session.channelId }); } else if (user[0].username !== username || user[0].avatar !== avatar) { await ctx.database.set( "username", { userId: session.userId, channelId: session.channelId }, { username, avatar } ); } }); ctx.command("monetaryRank", "查看货币排行榜帮助").action(async ({ session }) => { await session.execute(`monetaryRank -h`); }); ctx.command( "monetaryRank.本群个人货币排行榜 [displaySize:number]", "查看本群个人货币排行榜" ).action( async ({ session }, displaySize = config.defaultLeaderboardDisplayCount) => { if (!isValidDisplaySize(displaySize)) { displaySize = config.defaultLeaderboardDisplayCount; } const monetaries = await ctx.database.get("monetary", {}); const usernames = await ctx.database.get("username", { platform: session.platform, channelId: session.channelId }); const bindings = await ctx.database.get("binding", { platform: session.platform }); const monetaryRanks = generateMonetaryRanks( monetaries, usernames, bindings ).filter((rank) => rank.channelId === session.channelId).slice(0, displaySize); if (config.isLeaderboardDisplayedAsImage) { const rankTitle = `本群个人货币排行榜`; await htmlToBufferAndSendMessage(session, rankTitle, monetaryRanks); } else { await sendMessage( session, `本群个人货币排行榜: ${monetaryRanks.map( (rank, index) => `${index + 1}. ${rank.username}(${rank.userId}) - ${rank.value}` ).join("\n")}` ); } } ); ctx.command( "monetaryRank.跨群个人货币排行榜 [displaySize:number]", "查看跨群个人货币排行榜" ).action( async ({ session }, displaySize = config.defaultLeaderboardDisplayCount) => { if (!isValidDisplaySize(displaySize)) { displaySize = config.defaultLeaderboardDisplayCount; } const monetaries = await ctx.database.get("monetary", {}); const usernames = await ctx.database.get("username", { platform: session.platform }); const bindings = await ctx.database.get("binding", { platform: session.platform }); const monetaryRanks = generateMonetaryRanks( monetaries, usernames, bindings ).slice(0, displaySize); if (config.isLeaderboardDisplayedAsImage) { const rankTitle = `跨群个人货币排行榜`; await htmlToBufferAndSendMessage(session, rankTitle, monetaryRanks); } else { await sendMessage( session, `跨群个人货币排行榜: ${monetaryRanks.map( (rank, index) => `${index + 1}. ${rank.username}(${rank.userId}) - ${rank.value}` ).join("\n")}` ); } } ); ctx.command("monetaryRank.查询货币 [userArg]", "查询货币余额").option("currency", "-c <currency:string> 指定查询的货币种类").action(async ({ session, options }, userArg) => { let targetUserId = session.userId; let targetUsername = session.username; let parsedUser; if (userArg) { parsedUser = import_koishi.h.parse(userArg)[0]; if (!parsedUser || parsedUser.type !== "at" || !parsedUser.attrs.id) { await session.send( "请正确 @ 用户\n示例:monetaryRank.查询货币 @用户" ); return; } targetUserId = parsedUser.attrs.id; targetUsername = parsedUser.attrs.name || targetUserId; } let uid; try { const bindingRecord = await ctx.database.get("binding", { pid: targetUserId, platform: session.platform }); if (bindingRecord.length === 0) { await session.send(`未找到用户 ${targetUsername} 的账户信息。`); return; } uid = bindingRecord[0].aid; } catch (error) { logger.error(`获取用户绑定信息失败: ${error}`); await session.send("查询用户信息失败,请稍后重试。"); return; } const currencyOption = options?.currency; if (currencyOption) { const monetaryData = await ctx.database.get("monetary", { uid, currency: currencyOption }); if (monetaryData.length === 0) { await session.send( `${targetUsername} 没有 ${currencyOption} 货币的记录。` ); return; } const balance = monetaryData[0].value; await session.send( `${targetUsername}${currencyOption} 货币余额为 ${balance}` ); } else { const allMonetaryData = await ctx.database.get("monetary", { uid }); if (allMonetaryData.length === 0) { await session.send(`${targetUsername} 没有任何货币记录。`); return; } if (allMonetaryData.length === 1 && allMonetaryData[0].currency === "default") { await session.send( `${targetUsername} 的货币余额为 ${allMonetaryData[0].value}` ); } else { const balanceList = allMonetaryData.map((item) => `${item.currency}: ${item.value}`).join("\n"); await session.send(`${targetUsername} 的货币余额: ${balanceList}`); } } }); function _getChartBaseStyles() { return ` html { min-height: 100%; } body { font-family: sans-serif; margin: 0; padding: 20px; width: 100%; min-height: 100%; box-sizing: border-box; } .ranking-title { text-align: center; margin-bottom: 20px; color: #333; font-style: normal; font-family: "Microsoft YaHei", sans-serif; } .font-preload { display: none; } `; } __name(_getChartBaseStyles, "_getChartBaseStyles"); function _prepareBackgroundStyle() { return `html { background: linear-gradient(135deg, #f6f8f9 0%, #e5ebee 100%); }`; } __name(_prepareBackgroundStyle, "_prepareBackgroundStyle"); function _getClientScript() { return ` async ({ rankingData, iconData, barBgImgs, config }) => { // --- 主绘制函数 --- async function drawRanking() { const maxCount = rankingData.reduce((max, item) => Math.max(max, item.count), 0) || 1; const userNum = rankingData.length; const userAvatarSize = 50; const tableWidth = 200 + 7 * 100; // 固定宽度 const canvasHeight = 50 * userNum; const canvas = document.getElementById('rankingCanvas'); let context = canvas.getContext('2d'); context.font = '30px "Microsoft YaHei", sans-serif'; // 找到拥有最大计数的条目,因为它的文本通常最长 const maxCountData = rankingData.find(d => d.count === maxCount) || rankingData[0] || { count: 1 }; let maxCountText = maxCountData.count.toString(); const maxCountTextWidth = context.measureText(maxCountText).width; // 最长进度条的宽度是固定的 const maxBarWidth = 150 + 700; // 进度条区域总宽度 // 计算最终画布宽度 canvas.width = 50 + maxBarWidth + 10 + maxCountTextWidth + 20; canvas.height = canvasHeight; // 重新获取上下文,因为尺寸变化会重置状态 context = canvas.getContext('2d'); // 按顺序绘制图层 await drawRankingBars(context, maxCount, userAvatarSize, tableWidth); await drawAvatars(context, userAvatarSize); drawVerticalLines(context, canvas.height, tableWidth); } async function drawRankingBars(context, maxCount, userAvatarSize, canvasWidth) { for (const [index, data] of rankingData.entries()) { const countBarWidth = 150 + (700 * data.count) / maxCount; const countBarX = 50; const countBarY = 50 * index; let avgColor = await getAverageColor(data.avatarBase64); const colorWithOpacity = addOpacityToColor(avgColor, 0.5); // 绘制底色进度条 context.fillStyle = avgColor; context.fillRect(countBarX, countBarY, countBarWidth, userAvatarSize); // 绘制自定义背景图 const userBarBgImgs = findAssets(data.userId, barBgImgs, 'barBgImgBase64'); if (userBarBgImgs.length > 0) { const randomBarBgImgBase64 = userBarBgImgs[Math.floor(Math.random() * userBarBgImgs.length)]; const newAvgColor = await drawCustomBarBackground(context, randomBarBgImgBase64, countBarX, countBarY, countBarWidth, userAvatarSize, canvasWidth); if (newAvgColor) avgColor = newAvgColor; } // 绘制剩余部分灰色背景 const remainingBarX = countBarX + countBarWidth; context.fillStyle = colorWithOpacity; context.fillRect(remainingBarX, countBarY, canvasWidth - remainingBarX, userAvatarSize); // 绘制文本和图标 await drawTextAndIcons(context, data, index, avgColor, countBarX, countBarY, countBarWidth, userAvatarSize); } } async function drawCustomBarBackground(context, base64, x, y, barWidth, barHeight, canvasWidth) { return new Promise(async (resolve) => { const barBgImg = new Image(); barBgImg.src = "data:image/png;base64," + base64; barBgImg.onload = async () => { context.save(); if (config.horizontalBarBackgroundFullOpacity > 0) { context.globalAlpha = config.horizontalBarBackgroundFullOpacity; context.drawImage(barBgImg, x, y, canvasWidth - x, barHeight); } context.globalAlpha = config.horizontalBarBackgroundOpacity; context.drawImage(barBgImg, 0, 0, barWidth, barHeight, x, y, barWidth, barHeight); context.restore(); resolve(await getAverageColor(base64)); }; barBgImg.onerror = () => resolve(undefined); // On error, resolve with undefined to not change the color }); } async function drawTextAndIcons(context, data, index, avgColor, barX, barY, barWidth, barHeight) { context.font = '30px "Microsoft YaHei", sans-serif'; const textY = barY + barHeight / 2 + 10.5; let countText = data.count.toString(); const countTextWidth = context.measureText(countText).width; const countTextX = barX + barWidth + 10; if (countTextX + countTextWidth > context.canvas.width - 5) { context.fillStyle = chooseColorAdjustmentMethod(avgColor); context.textAlign = "right"; context.fillText(countText, barX + barWidth - 10, textY); } else { context.fillStyle = "rgba(0, 0, 0, 1)"; context.textAlign = "left"; context.fillText(countText, countTextX, textY); } context.fillStyle = chooseColorAdjustmentMethod(avgColor); context.textAlign = "left"; let nameText = data.name; const maxNameWidth = barWidth - 60; if (context.measureText(nameText).width > maxNameWidth) { const ellipsis = "..."; while (context.measureText(nameText + ellipsis).width > maxNameWidth && nameText.length > 0) { nameText = nameText.slice(0, -1); } nameText += ellipsis; } const nameTextX = barX + 10; context.fillText(nameText, nameTextX, textY); const userIcons = findAssets(data.userId, iconData, 'iconBase64'); if (userIcons.length > 0) { await drawUserIcons(context, userIcons, { nameText: data.name, nameTextX: context.measureText(nameText).width + nameTextX, barX: barX, barWidth: barWidth, textY: textY }); } } async function drawUserIcons(context, icons, positions) { const { nameTextX, barX, barWidth, textY } = positions; // 使用 Promise.all 等待所有图片加载和绘制 await Promise.all(icons.map((iconBase64, i) => { return new Promise((resolve, reject) => { const icon = new Image(); icon.src = "data:image/png;base64," + iconBase64; icon.onload = () => { const iconSize = 40; const iconY = textY - 30; let iconX = config.shouldMoveIconToBarEndLeft ? barX + barWidth - (iconSize * (i + 1)) : nameTextX + (iconSize * i) + 5; context.drawImage(icon, iconX, iconY, iconSize, iconSize); resolve(); // 图片绘制成功 }; icon.onerror = () => { console.error("Failed to load user icon."); resolve(); // 即使单个图标加载失败,也继续执行,不中断整个排行榜生成 }; }); })); } async function drawAvatars(context, userAvatarSize) { for (const [index, data] of rankingData.entries()) { const image = new Image(); image.src = "data:image/png;base64," + data.avatarBase64; await new Promise(resolve => { image.onload = () => { context.drawImage(image, 0, 50 * index, userAvatarSize, userAvatarSize); resolve(); }; image.onerror = resolve; }); } } function drawVerticalLines(context, canvasHeight, tableWidth) { context.fillStyle = "rgba(0, 0, 0, 0.12)"; const verticalLineWidth = 3; const firstLineX = 200; for (let i = 0; i < 8; i++) { context.fillRect(firstLineX + 100 * i, 0, verticalLineWidth, canvasHeight); } } function findAssets(userId, assetList, key) { return assetList.filter(data => data.userId === userId).map(data => data[key]); } function addOpacityToColor(color, opacity) { const opacityHex = Math.round(opacity * 255).toString(16).padStart(2, "0"); return \`\${color}\${opacityHex}\`; } function chooseColorAdjustmentMethod(hexcolor) { const rgb = hexToRgb(hexcolor) const yiqBrightness = calculateYiqBrightness(rgb) if (yiqBrightness > 0.2 && yiqBrightness < 0.8) { return adjustColorHsl(hexcolor) } else { return adjustColorYiq(hexcolor) } } function calculateYiqBrightness(rgb) { return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 / 255 } function adjustColorYiq(hexcolor) { const rgb = hexToRgb(hexcolor) const yiqBrightness = calculateYiqBrightness(rgb) return yiqBrightness >= 0.8 ? "#000000" : "#FFFFFF" } function adjustColorHsl(hexcolor) { const rgb = hexToRgb(hexcolor) let hsl = rgbToHsl(rgb.r, rgb.g, rgb.b) hsl.l = hsl.l < 0.5 ? hsl.l + 0.3 : hsl.l - 0.3 hsl.s = hsl.s < 0.5 ? hsl.s + 0.3 : hsl.s - 0.3 const contrastRgb = hslToRgb(hsl.h, hsl.s, hsl.l) return rgbToHex(contrastRgb.r, contrastRgb.g, contrastRgb.b) } function hexToRgb(hex) { const sanitizedHex = String(hex).replace("#", "") const bigint = parseInt(sanitizedHex, 16) const r = (bigint >> 16) & 255 const g = (bigint >> 8) & 255 const b = bigint & 255 return {r, g, b} } function rgbToHsl(r, g, b) { r /= 255, g /= 255, b /= 255 const max = Math.max(r, g, b), min = Math.min(r, g, b) let h, s, l = (max + min) / 2 if (max === min) { h = s = 0 } else { const d = max - min s = l > 0.5 ? d / (2 - max - min) : d / (max + min) switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break case g: h = (b - r) / d + 2; break case b: h = (r - g) / d + 4; break } h /= 6 } return {h, s, l} } function hslToRgb(h, s, l) { let r, g, b if (s === 0) { r = g = b = l } else { const hue2rgb = (p, q, t) => { if (t < 0) t += 1 if (t > 1) t -= 1 if (t < 1 / 6) return p + (q - p) * 6 * t if (t < 1 / 2) return q if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6 return p } const q = l < 0.5 ? l * (1 + s) : l + s - l * s const p = 2 * l - q r = hue2rgb(p, q, h + 1 / 3) g = hue2rgb(p, q, h) b = hue2rgb(p, q, h - 1 / 3) } return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) } } function rgbToHex(r, g, b) { const toHex = c => { const hex = c.toString(16) return hex.length === 1 ? "0" + hex : hex } return \`#\${toHex(r)}\${toHex(g)}\${toHex(b)}\` } async function getAverageColor(base64) { const image = new Image(); image.src = "data:image/png;base64," + base64; await new Promise(r => image.onload = r); const canvas = document.createElement('canvas'); const ctx = canvas.getContext("2d", { willReadFrequently: true }); canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); const data = ctx.getImageData(0, 0, image.width, image.height).data; let r = 0, g = 0, b = 0; for (let i = 0; i < data.length; i += 4) { r += data[i]; g += data[i+1]; b += data[i+2]; } const count = data.length / 4; r = ~~(r / count); g = ~~(g / count); b = ~~(b / count); return \`#\${r.toString(16).padStart(2, "0")}\${g.toString(16).padStart(2, "0")}\${b.toString(16).padStart(2, "0")}\`; } await drawRanking(); } `; } __name(_getClientScript, "_getClientScript"); function _getChartHtmlContent(params) { const { rankTimeTitle, rankTitle, data, iconCache, barBgImgCache, backgroundStyle, chartConfig } = params; const clientData = { rankingData: data, iconData: iconCache.map((d) => ({ userId: d.userId, iconBase64: d.iconBase64 })), barBgImgs: barBgImgCache.map((d) => ({ userId: d.userId, barBgImgBase64: d.barBgImgBase64 })), config: chartConfig }; return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>排行榜</title> <style>${_getChartBaseStyles()}</style> <style>${backgroundStyle}</style> </head> <body> <h1 class="ranking-title">${rankTimeTitle}</h1> <h1 class="ranking-title">${rankTitle}</h1> <canvas id="rankingCanvas"></canvas> <script> (async () => { const drawFunction = ${_getClientScript()}; await drawFunction(${JSON.stringify(clientData)}); })(); </script> </body> </html> `; } __name(_getChartHtmlContent, "_getChartHtmlContent"); async function getAvatarAsBase64(url) { if (!url || !ctx.canvas) { return fallbackBase64_default[0]; } try { const response = await ctx.http.get(url, { responseType: "arraybuffer", timeout: 5e3 }); const image = await ctx.canvas.loadImage(response); const canvas = await ctx.canvas.createCanvas(50, 50); const context = canvas.getContext("2d"); context.drawImage(image, 0, 0, 50, 50); return (await canvas.toBuffer("image/png")).toString("base64"); } catch (e) { logger.warn(`获取或处理头像失败 (${url}),将使用默认头像: ${e.message}`); return fallbackBase64_default[0]; } } __name(getAvatarAsBase64, "getAvatarAsBase64"); async function generateRankingChart({ rankTimeTitle, rankTitle, data }) { if (!ctx.puppeteer || !ctx.canvas) { throw new Error("Puppeteer or Canvas service is not enabled."); } const page = await ctx.puppeteer.page(); try { await Promise.all( data.map(async (item) => { item.avatarBase64 = await getAvatarAsBase64(item.avatar); }) ); const backgroundStyle = _prepareBackgroundStyle(); const chartConfigForClient = { shouldMoveIconToBarEndLeft: config.shouldMoveIconToBarEndLeft, horizontalBarBackgroundOpacity: config.horizontalBarBackgroundOpacity, horizontalBarBackgroundFullOpacity: config.horizontalBarBackgroundFullOpacity, isUserMessagePercentageVisible: false // 货币榜不显示百分比 }; const htmlContent = _getChartHtmlContent({ rankTimeTitle, rankTitle, data, iconCache: iconData, barBgImgCache: barBgImgs, backgroundStyle, chartConfig: chartConfigForClient }); await page.goto(`file://${emptyHtmlPath}`); await page.setContent(import_koishi.h.unescape(htmlContent), { waitUntil: config.waitUntil }); const calculatedWidth = await page.evaluate(() => { const canvas = document.getElementById( "rankingCanvas" ); return canvas ? canvas.width + 40 : 1080; }); await page.setViewport({ width: Math.ceil(calculatedWidth), height: 256, // 高度由 fullPage 自动调整 deviceScaleFactor: 1 }); return await page.screenshot({ type: "png", fullPage: true }); } finally { await page.close(); } } __name(generateRankingChart, "generateRankingChart"); function readBgImgsFromFolder(folderPath) { const barBgImgs2 = []; try { const files = import_fs.default.readdirSync(folderPath); files.forEach((file) => { const userId = import_path.default.parse(file).name.split("-")[0].trim(); const filePath = import_path.default.join(folderPath, file); const fileData = import_fs.default.readFileSync(filePath); const barBgImgBase64 = fileData.toString("base64"); barBgImgs2.push({ userId, barBgImgBase64 }); }); } catch (err) { logger.error("读取水平柱状图背景图时出错:", err); } return barBgImgs2; } __name(readBgImgsFromFolder, "readBgImgsFromFolder"); function readIconsFromFolder(folderPath) { const iconData2 = []; try { const files = import_fs.default.readdirSync(folderPath); files.forEach((file) => { const userId = import_path.default.parse(file).name.split("-")[0].trim(); const filePath = import_path.default.join(folderPath, file); const fileData = import_fs.default.readFileSync(filePath); const iconBase64 = fileData.toString("base64"); iconData2.push({ userId, iconBase64 }); }); } catch (err) { logger.error("读取图标时出错:", err); } return iconData2; } __name(readIconsFromFolder, "readIconsFromFolder"); async function ensureDirExists(dirPath) { if (!import_fs.default.existsSync(dirPath)) { import_fs.default.mkdirSync(dirPath, { recursive: true }); } } __name(ensureDirExists, "ensureDirExists"); async function htmlToBufferAndSendMessage(session, rankTitle, monetaryRanks) { try { let buffer; if (config.style === "2") { const data = convertMonetaryRanksToRankingData(monetaryRanks); const rankTimeTitle = getCurrentBeijingTime(); buffer = await generateRankingChart({ rankTimeTitle, rankTitle, data }); } else if (config.style === "3") { const html = await generateLeaderboardHtmlStyle3( rankTitle, monetaryRanks ); const page = await ctx.puppeteer.page(); try { await page.setViewport({ width: 550, height: 256, deviceScaleFactor: 2 }); await page.goto(`file://${emptyHtmlPath}`); await page.setContent(import_koishi.h.unescape(html), { waitUntil: config.waitUntil }); buffer = await page.screenshot({ type: "png", fullPage: true }); } finally { await page.close(); } } if (buffer) { await sendMessage(session, import_koishi.h.image(buffer, "image/png")); } } catch (error) { logger.error("生成排行榜图片失败:", error); await session.send("生成排行榜图片失败,请检查后台日志。"); } } __name(htmlToBufferAndSendMessage, "htmlToBufferAndSendMessage"); async function generateLeaderboardHtmlStyle3(rankTitle, monetaryRanks) { const rankData = monetaryRanks.map((rank, index) => ({ order: index + 1, card: rank.username, sum: rank.value, channels: rank.channelId })); const leaderboardHTML = ` <!DOCTYPE html > <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content = "width=device-width, initial-scale=1.0" > <title>鹿管排行榜 </title> <style> body { font-family: 'Microsoft YaHei', Arial, sans-serif; background-color: #f0f4f8; margin: 0; padding: 0px; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .container { background-color: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 30px; width: 100%; max-width: 500px; } h1 { text-align: center; color: #2c3e50; margin-bottom: 30px; font-size: 28px; } .ranking-list { list-style-type: none; padding: 0; margin: 0; } .ranking-item { display: flex; align-items: center; padding: 15px 10px; border-bottom: 1px solid #ecf0f1; transition: background-color 0.3s; } .ranking-item:hover { background-color: #f8f9fa; } .ranking-number { font-size: 18px; font-weight: bold; margin-right: 15px; min-width: 30px; color: #7f8c8d; } .medal { font-size: 24px; margin-right: 15px; } .name { flex-grow: 1; font-size: 18px; } .channels { font-size: 14px; color: #7f8c8d; margin-left: 10px; } .count { font-weight: bold; color: #e74c3c; font-size: 18px; } .count::after { content: ' 币'; font-size: 14px; color: #95a5a6; } </style> </head> <body> <div class="container" > <h1>🦌 货币排行榜 🦌</h1> <ol class="ranking-list"> ${rankData.map( (deer) => ` <li class="ranking-item"> <span class="ranking-number">${deer.order}</span> ${deer.order === 1 ? '<span class="medal">🥇</span>' : ""} ${deer.order === 2 ? '<span class="medal">🥈</span>' : ""} ${deer.order === 3 ? '<span class="medal">🥉</span>' : ""} <span class="name">${deer.card}</span> <!--span class="channels">${deer.channels}</span--> <span class="count">${deer.sum}</span> </li> ` ).join("")} </ol> </div> </body> </html> `; return leaderboardHTML; } __name(generateLeaderboardHtmlStyle3, "generateLeaderboardHtmlStyle3"); function getCurrentBeijingTime() { const beijingTime = (/* @__PURE__ */ new Date()).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }); const date = beijingTime.split(" ")[0]; const time = beijingTime.split(" ")[1]; return `${date} ${time}`; } __name(getCurrentBeijingTime, "getCurrentBeijingTime"); function convertMonetaryRanksToRankingData(monetaryRanks) { const totalValue = monetaryRanks.reduce((sum, rank) => sum + rank.value, 0); const rankingDatas = monetaryRanks.map((rank) => ({ name: rank.username, userId: rank.userId, avatar: rank.avatar || "", count: rank.value, percentage: totalValue > 0 ? rank.value / totalValue * 100 : 0 })); rankingDatas.sort((a, b) => b.count - a.count); return rankingDatas; } __name(convertMonetaryRanksToRankingData, "convertMonetaryRanksToRankingData"); function isValidDisplaySize(displaySize) { return Number.isInteger(displaySize) && displaySize > 0; } __name(isValidDisplaySize, "isValidDisplaySize"); function generateMonetaryRanks(monetaries, usernames, bindings) { const usernameMap = /* @__PURE__ */ new Map(); usernames.forEach((user) => usernameMap.set(user.uid, user)); const bindingMap = /* @__PURE__ */ new Map(); bindings.forEach((binding) => bindingMap.set(binding.aid, binding)); const monetaryRanks = monetaries.map((monetary) => { const username = usernameMap.get(monetary.uid); const binding = bindingMap.get(monetary.uid); const userId = username?.userId || binding?.pid || "123456789"; const platform = username?.platform || binding?.platform || "unknown"; const avatar = platform === "onebot" ? `https://q.qlogo.cn/g?b=qq&s=640&nk=${userId}` : username?.avatar || "https://th.bing.com/th/id/OIP.s5N_QuGWAIWBmUyeNemQagHaHZ?w=185&h=184&c=7&r=0&o=5&dpr=1.3&pid=1.7"; const rank = { uid: monetary.uid, value: Math.round(monetary.value), userId, avatar, username: username?.username || "神秘人", currency: monetary.currency, platform, channelId: username?.channelId || "unknown" }; return rank; }); monetaryRanks.sort((a, b) => b.value - a.value); return monetaryRanks; } __name(generateMonetaryRanks, "generateMonetaryRanks"); async function sendMessage(session, message) { await session.send(message); } __name(sendMessage, "sendMessage"); } __name(apply, "apply"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply, inject, name, usage });