rankcard-main
Version:
Experience your Discord bot's rank in a visual format.
240 lines (186 loc) • 8.86 kB
JavaScript
const canvas = require("@napi-rs/canvas");
const { colorFetch } = require("../functions/colorFetch");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-bun/build/structures/font/circularstd-black.otf`, "circular-std");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-bun/build/structures/font/notosans-jp-black.ttf`, "noto-sans-jp");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-bun/build/structures/font/notosans-black.ttf`, "noto-sans");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-bun/build/structures/font/notoemoji-bold.ttf`, "noto-emoji");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-bun/build/structures/font/notosans-kr-black.ttf`, "noto-sans-kr");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-bun/build/structures/font/Chewy-Regular.ttf`, "chewy");
class RankCard {
constructor(options) {
this.name = options?.name ?? null;
this.level = options?.level ?? null;
this.color = options?.color ?? null;
this.brightness = options?.brightness ?? null;
this.avatar = options?.avatar ?? null;
this.progress = options?.progress ?? null;
this.rank = options?.rank ?? null;
this.requiredXp = options?.requiredXp ?? null;
this.currentXp = options?.currentXp ?? null;
this.showXp = options?.showXp ?? true;
this.width = options?.width ?? 1280;
this.height = options?.height ?? 450;
}
setName(name) {
this.name = name;
return this;
}
setLevel(level) {
this.level = level;
return this;
}
setColor(color) {
this.color = color;
return this;
}
setBrightness(brightness) {
this.brightness = brightness;
return this;
}
setAvatar(avatar) {
this.avatar = avatar;
return this;
}
setProgress(progress) {
this.progress = progress;
return this;
}
setRank(rank) {
this.rank = rank;
return this;
}
setRequiredXp(requiredXp) {
this.requiredXp = requiredXp;
return this;
}
setCurrentXp(currentXp) {
this.currentXp = currentXp;
return this;
}
setShowXp(showXp) {
this.showXp = showXp;
return this;
}
async build() {
if (!this.name) throw new Error('Missing name parameter');
if (!this.level) throw new Error('Missing author parameter');
if (!this.color) this.setColor('ff0000');
if (!this.brightness) this.setBrightness(0);
if (!this.avatar) this.setAvatar('https://i.imgur.com/LENSTKE.png');
if (!this.progress) this.setProgress(0);
if (!this.rank) this.setRank("00")
if (!this.requiredXp) this.setRequiredXp("00")
if (!this.currentXp) this.setCurrentXp("00");
let validatedProgress = parseFloat(this.progress);
if (Number.isNaN(validatedProgress) || validatedProgress < 0 || validatedProgress > 100) throw new Error('Invalid progress parameter, must be between 0 to 100');
if (validatedProgress < 2) validatedProgress = 2;
if (validatedProgress > 99) validatedProgress = 99;
const validatedColor = await colorFetch(
this.color || 'ff0000',
parseInt(this.brightness) || 0,
this.avatar
);
if (this.name.length > 10) this.name = `${this.name.slice(0, 10)}...`;
if (this.level.length > 10) this.level = `${this.level.slice(0, 10)}...`;
if (this.rank.length > 5) this.rank = `99999`;
const frame = canvas.createCanvas(this.width, this.height);
const ctx = frame.getContext("2d");
let background = await canvas.loadImage(`https://i.imgur.com/fsDq7ju.png`);
ctx.drawImage(background, 0, 0, frame.width, frame.height)
const thumbnailCanvas = canvas.createCanvas(650, 650);
const thumbnailCtx = thumbnailCanvas.getContext('2d');
let thumbnailImage;
try {
thumbnailImage = await canvas.loadImage(this.avatar, {
requestOptions: {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
}
}
});
} catch (error) {
throw new Error(`Failed to load Thumnail Image\n The Image URl is invalid or the image is not accessible\n${error}`);
}
const thumbnailSize = Math.min(thumbnailImage.width, thumbnailImage.height);
const thumbnailX = (thumbnailImage.width - thumbnailSize) / 2;
const thumbnailY = (thumbnailImage.height - thumbnailSize) / 2;
const cornerRadius2 = 45;
thumbnailCtx.beginPath();
thumbnailCtx.moveTo(0 + cornerRadius2, 0);
thumbnailCtx.arcTo(thumbnailCanvas.width, 0, thumbnailCanvas.width, thumbnailCanvas.height, cornerRadius2);
thumbnailCtx.arcTo(thumbnailCanvas.width, thumbnailCanvas.height, 0, thumbnailCanvas.height, cornerRadius2);
thumbnailCtx.arcTo(0, thumbnailCanvas.height, 0, 0, cornerRadius2);
thumbnailCtx.arcTo(0, 0, thumbnailCanvas.width, 0, cornerRadius2);
thumbnailCtx.closePath();
thumbnailCtx.clip();
thumbnailCtx.drawImage(thumbnailImage, thumbnailX, thumbnailY, thumbnailSize, thumbnailSize, 0, 0, thumbnailCanvas.width, thumbnailCanvas.height);
ctx.save();
ctx.drawImage(thumbnailCanvas, 60, 69, 308, 313);
ctx.font = "bold 90px circular-std, noto-emoji, noto-sans-jp, noto-sans, noto-sans-kr";
ctx.fillStyle = `#${validatedColor}`;
ctx.fillText(this.name, 431, 200);
ctx.font = "bold 60px circular-std, noto-emoji, noto-sans-jp, noto-sans, noto-sans-kr";
ctx.fillStyle = "#787878";
ctx.fillText(this.level, 431, 270);
if(this.rank.length == 1) {
ctx.font = "bold 60px chewy";
ctx.fillStyle = "#787878";
ctx.fillText(`#${this.rank}`, 1080, 250);
} else if(this.rank.length == 2) {
ctx.font = "bold 60px chewy";
ctx.fillStyle = "#787878";
ctx.fillText(`#${this.rank}`, 1060, 250);
} else if(this.rank.length == 3) {
ctx.font = "bold 60px chewy";
ctx.fillStyle = "#787878";
ctx.fillText(`#${this.rank}`, 1050, 250);
} else if(this.rank.length == 4) {
ctx.font = "bold 55px chewy";
ctx.fillStyle = "#787878";
ctx.fillText(`#${this.rank}`, 1040, 250);
} else if(this.rank.length == 5) {
ctx.font = "bold 50px chewy";
ctx.fillStyle = "#787878";
ctx.fillText(`#${this.rank}`, 1035, 250);
}
const abbreviateNumber = (value) => {
const suffixes = ['', 'K', 'M', 'B', 'T', 'Tr'];
let suffixNum = 0;
while (value >= 1000) {
suffixNum++;
value /= 1000;
}
let shortValue = value;
if (shortValue % 1 != 0) {
shortValue = shortValue.toFixed(1);
}
if (suffixNum > 0) {
shortValue += suffixes[suffixNum];
}
return shortValue;
}
if(this.showXp) {
ctx.font = "thin 55px circular-std, noto-emoji, noto-sans-jp, noto-sans, noto-sans-kr";
ctx.fillStyle = "#787878";
ctx.fillText(`Exp: `, 440, 350);
ctx.font = "thin 55px chewy";
ctx.fillStyle = "#787878";
ctx.fillText(`${abbreviateNumber(`${this.currentXp}`)} / ${abbreviateNumber(`${this.requiredXp}`)}`, 580, 350);
}
ctx.beginPath();
ctx.arc(1115, 235, 100, 0, Math.PI * 2, true);
ctx.closePath();
ctx.lineWidth = 20;
ctx.strokeStyle = "#242323";
ctx.stroke();
const progress = validatedProgress;
const angle = (progress / 100) * Math.PI * 2;
ctx.beginPath();
ctx.arc(1115, 235, 100, -Math.PI / 2, -Math.PI / 2 + angle, false);
ctx.lineWidth = 20;
ctx.strokeStyle = `#${validatedColor}`;
ctx.stroke();
return frame.toBuffer("image/png");
}
}
module.exports = { RankCard };