From 77b8f8a0374088fd21a4857bb2b871244dc8fcc5 Mon Sep 17 00:00:00 2001 From: Prince527 Date: Tue, 26 Dec 2023 08:47:25 -0400 Subject: [PATCH] Created a lb image --- assets/api/canvas/index.js | 10 +++- assets/api/canvas/leaderboard.js | 94 ++++++++++++++++++++++++++++++++ commands/xp/leaderboard.js | 42 ++++++++++---- 3 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 assets/api/canvas/leaderboard.js diff --git a/assets/api/canvas/index.js b/assets/api/canvas/index.js index d15da27..e8265c1 100644 --- a/assets/api/canvas/index.js +++ b/assets/api/canvas/index.js @@ -31,7 +31,11 @@ function shortenText(text, len) { } function abbreviateNumber(num) { - return num >= 1000 ? Math.floor(num / 1000) + (num % 1000 >= 500 ? 1 : 0) + "k" : num.toString(); + if (num >= 1e12) return Math.floor(num / 1e9) + 'B'; + else if (num >= 1e9) return Math.floor(num / 1e9) + (num % 1e9 >= 500e6 ? 1 : 0) + 'B'; + else if (num >= 1e6) return Math.floor(num / 1e6) + (num % 1e6 >= 500e3 ? 1 : 0) + 'M'; + else if (num >= 1000) return Math.floor(num / 1000) + (num % 1000 >= 500 ? 1 : 0) + 'K'; + else return num.toString(); } async function drawRoundedImage(image, cornerRadius, rounded) { @@ -84,9 +88,9 @@ function roundContext(ctx, x, y, width, height, radius) { ctx.closePath(); } -async function combineImages(imageBuffers, { width = 1000, columns = 4, padding = 10 }) { +async function combineImages(imageBuffers, { width = 1000, height = null, columns = 4, padding = 10 }) { const imageWidth = (width - (padding * (columns + 1))) / columns; - const imageHeight = imageWidth; + const imageHeight = height || imageWidth; const canvasRows = Math.ceil(imageBuffers.length / columns); const canvasHeight = (imageHeight * canvasRows) + (padding * (canvasRows + 1)); diff --git a/assets/api/canvas/leaderboard.js b/assets/api/canvas/leaderboard.js new file mode 100644 index 0000000..2236292 --- /dev/null +++ b/assets/api/canvas/leaderboard.js @@ -0,0 +1,94 @@ +const { createCanvas, loadImage } = require('@napi-rs/canvas'); + +const { drawRoundedImage, combineImages, shortenText, abbreviateNumber } = require("./index"); +const { isTooBlackOrWhite } = require("../color"); + +async function generateLeaderboard(users) { + const buffers = []; + + for (let i = 0; i < users.length; i++) { + const userCanvas = createCanvas(680, 70); + const userCtx = userCanvas.getContext('2d'); + + const user = users[i]; + + userCtx.fillStyle = '#393f4475'; + userCtx.fillRect(0, 0, 680, 70); + + const avatar = await loadImage(user.avatar); + userCtx.drawImage(avatar, 0, 0, 70, 70); + + userCtx.fillStyle = 'white'; + userCtx.font = '24px Noto Sans'; + userCtx.fillText(`${shortenText(user.username, 11)}`, 80, 45); + + userCtx.textAlign = 'right'; + userCtx.font = '24px Noto Sans Bold'; + i === 0 ? userCtx.fillStyle = '#e7ba16' : i === 1 ? userCtx.fillStyle = '#9e9e9e' : i === 2? userCtx.fillStyle = '#94610f' : null; + userCtx.fillText(`#${user.position}`, 670, 45); + + const barX = 275; + const barY = 10; + const borderRadius = 18.75; + const barWidth = 300; + const barHeight = 50; + + const progressWidth = (user.currentXP / user.requiredXP) * barWidth; + + userCtx.fillStyle = "#484b4E"; + userCtx.beginPath(); + userCtx.moveTo(barX + borderRadius, barY); + userCtx.lineTo(barX + barWidth - borderRadius, barY); + userCtx.arc(barX + barWidth - borderRadius, barY + borderRadius, borderRadius, -Math.PI / 2, 0); + userCtx.lineTo(barX + barWidth, barY + barHeight - borderRadius); + userCtx.arc(barX + barWidth - borderRadius, barY + barHeight - borderRadius, borderRadius, 0, Math.PI / 2); + userCtx.lineTo(barX + borderRadius, barY + barHeight); + userCtx.arc(barX + borderRadius, barY + barHeight - borderRadius, borderRadius, Math.PI / 2, Math.PI); + userCtx.lineTo(barX, barY + borderRadius); + userCtx.arc(barX + borderRadius, barY + borderRadius, borderRadius, Math.PI, -Math.PI / 2); + userCtx.closePath(); + userCtx.fill(); + + userCtx.fillStyle = user.progressBar || "#ffffff"; + userCtx.beginPath(); + userCtx.moveTo(barX + borderRadius, barY); + userCtx.lineTo(barX + progressWidth - borderRadius, barY); + userCtx.arc(barX + progressWidth - borderRadius, barY + borderRadius, borderRadius, -Math.PI / 2, 0); + userCtx.lineTo(barX + progressWidth, barY + barHeight - borderRadius); + userCtx.arc(barX + progressWidth - borderRadius, barY + barHeight - borderRadius, borderRadius, 0, Math.PI / 2); + userCtx.lineTo(barX + borderRadius, barY + barHeight); + userCtx.arc(barX + borderRadius, barY + barHeight - borderRadius, borderRadius, Math.PI / 2, Math.PI); + userCtx.lineTo(barX, barY + borderRadius); + userCtx.arc(barX + borderRadius, barY + borderRadius, borderRadius, Math.PI, -Math.PI / 2); + userCtx.closePath(); + userCtx.fill(); + + const textColor = isTooBlackOrWhite(user.progressBar, "#ffffff").boolean === true ? "#000000" : "#ffffff"; + + userCtx.textAlign = 'left'; + userCtx.font = '24px Noto Sans'; + userCtx.fillStyle = textColor; + userCtx.fillText(`${abbreviateNumber(user.currentXP)}/${abbreviateNumber(user.requiredXP)} (${user.level})`, 280, 44); + + const result = await drawRoundedImage(await loadImage(userCanvas.toBuffer("image/png")), 10, true) + + buffers.push(result.toBuffer("image/png")); + } + + const buffer = await combineImages(buffers, { width: 680, height: 70, columns: 1, padding: 5 }); + + const canvas = createCanvas(680, buffers.length * 75); + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = '#23272a'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const lb = await loadImage(buffer); + ctx.drawImage(lb, 0, 0, 680, buffers.length * 75); + + return canvas.toBuffer("image/png"); +} + +module.exports = { + generateLeaderboard +} \ No newline at end of file diff --git a/commands/xp/leaderboard.js b/commands/xp/leaderboard.js index 2154dc9..8cacfdc 100644 --- a/commands/xp/leaderboard.js +++ b/commands/xp/leaderboard.js @@ -1,5 +1,8 @@ -const { fetchLeaderboard, computeLeaderboard } = require("../../assets/api/xp"); -const { Message, Client, EmbedBuilder } = require("discord.js"); +const { fetchLeaderboard, computeLeaderboard, xpFor } = require("../../assets/api/xp"); +const { generateLeaderboard } = require("../../assets/api/canvas/leaderboard"); +const { Message, Client, AttachmentBuilder } = require("discord.js"); +const guildRankcard = require("../../models/server/guild-rankcard"); +const userRankcard = require("../../models/user/user-rankcard"); const xpSchema = require("../../models/server/xp"); module.exports = { @@ -19,15 +22,34 @@ module.exports = { let lb = await fetchLeaderboard(message.guild.id); if (lb.length < 1) return reply("Nobody's in leaderboard yet."); + const guildCardData = await guildRankcard.findOne({ Guild: message.guild.id }); + lb = await computeLeaderboard(client, lb, true); - lb = lb.map(e => `**${e.position}**. *${e.username}*\nLevel: \`${e.level}\`\nXP: \`${e.xp.toLocaleString()}\``); - - message.channel.send({ - embeds: [ - new EmbedBuilder() - .setTitle("**Leaderboard**:") - .setDescription(`${lb.join("\n\n")}`) - .setColor("Random") + lb = await Promise.all(lb.map(async(user) => { + const userCardData = await userRankcard.findOne({ User: user.userID }); + + const progressColor = userCardData?.ProgressBar ? userCardData.ProgressBar : guildCardData?.ProgressBar ? guildCardData.ProgressBar : "#ffffff"; + const avatar = client.users.cache.get(user.userID).displayAvatarURL({ extension: 'png' }); + const requiredXP = xpFor(user.level + 1); + + return { + guild: user.guildID, + user: user.userID, + currentXP: user.xp, + requiredXP: requiredXP, + level: user.level, + position: user.position, + username: user.username, + avatar: avatar, + progressBar: progressColor || "#ffffff" + } + })); + + lb = await generateLeaderboard(lb); + + return message.channel.send({ + files: [ + new AttachmentBuilder(lb, { name: "lb.png" }) ] }); }