musicard-quartz
Version:
Discord music bot module, with aesthetic features. equipped with a music card that is different from the others.
486 lines (378 loc) • 21.6 kB
JavaScript
const canvas = require("@napi-rs/canvas");
const { colorFetch } = require("../functions/colorFetch");
//canvas.GlobalFonts.registerFromPath(`build/structures/font/circularstd-black.otf`, "circular-std");
//canvas.GlobalFonts.registerFromPath(`build/structures/font/notosans-jp-black.ttf`, "noto-sans-jp");
//canvas.GlobalFonts.registerFromPath(`build/structures/font/notosans-black.ttf`, "noto-sans");
//canvas.GlobalFonts.registerFromPath(`build/structures/font/notoemoji-bold.ttf`, "noto-emoji");
//canvas.GlobalFonts.registerFromPath(`build/structures/font/notosans-kr-black.ttf`, "noto-sans-kr");
//canvas.GlobalFonts.registerFromPath(`build/structures/font/Space.ttf`, "space");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-quartz/build/structures/font/circularstd-black.otf`, "circular-std");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-quartz/build/structures/font/notosans-jp-black.ttf`, "noto-sans-jp");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-quartz/build/structures/font/notosans-black.ttf`, "noto-sans");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-quartz/build/structures/font/notoemoji-bold.ttf`, "noto-emoji");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-quartz/build/structures/font/notosans-kr-black.ttf`, "noto-sans-kr");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-quartz/build/structures/font/Chewy-Regular.ttf`, "chewy");
canvas.GlobalFonts.registerFromPath(`node_modules/musicard-quartz/build/structures/font/Space.ttf`, "space");
class musicCard {
constructor(options) {
this.name = options?.name ?? null;
this.author = options?.author ?? null;
this.color = options?.color ?? null;
this.theme = options?.theme ?? null;
this.brightness = options?.brightness ?? null;
this.thumbnail = options?.thumbnail ?? null;
this.progress = options?.progress ?? null;
this.starttime = options?.startTime ?? null;
this.endtime = options?.endTime ?? null;
}
setName(name) {
this.name = name;
return this;
}
setAuthor(author) {
this.author = author;
return this;
}
setColor(color) {
this.color = color;
return this;
}
setTheme(theme) {
this.theme = theme || 'quartz+';
return this;
}
setBrightness(brightness) {
this.brightness = brightness;
return this;
}
setThumbnail(thumbnail) {
this.thumbnail = thumbnail;
return this;
}
setProgress(progress) {
this.progress = progress;
return this;
}
setStartTime(starttime) {
this.starttime = starttime;
return this;
}
setEndTime(endtime) {
this.endtime = endtime;
return this;
}
async build() {
if (!this.name) throw new Error('Missing name parameter');
if (!this.author) throw new Error('Missing author parameter');
if (!this.color) this.setColor('ff0000');
if (!this.theme) this.setTheme('quartz+');
if (!this.brightness) this.setBrightness(0);
if (!this.thumbnail) this.setThumbnail('https://s6.imgcdn.dev/Opo4a.jpg');
if (!this.progress) this.setProgress(0);
if (!this.starttime) this.setStartTime('0:00');
if (!this.endtime) this.setEndTime('0: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 validatedStartTime = this.starttime || '0:00';
const validatedEndTime = this.endtime || '0:00';
const validatedColor = await colorFetch(
this.color || '270ef5',
parseInt(this.brightness) || 0,
this.thumbnail
);
if (this.name.length > 15) this.name = `${this.name.slice(0, 13)}...`;
if (this.author.length > 15) this.author = `${this.author.slice(0, 13)}...`;
if (this.theme === "quartz+") {
const progressBarWidth = (validatedProgress / 100) * 670;
const circleX = progressBarWidth + 60;
const image = canvas.createCanvas(1280, 450);
const ctx = image.getContext('2d');
const progressBarCanvas = canvas.createCanvas(670, 25);
const progressBarCtx = progressBarCanvas.getContext('2d');
const cornerRadius = 10;
progressBarCtx.beginPath();
progressBarCtx.moveTo(cornerRadius, 0);
progressBarCtx.lineTo(670 - cornerRadius, 0);
progressBarCtx.arc(670 - cornerRadius, cornerRadius, cornerRadius, 1.5 * Math.PI, 2 * Math.PI);
progressBarCtx.lineTo(670, 25 - cornerRadius);
progressBarCtx.arc(670 - cornerRadius, 25 - cornerRadius, cornerRadius, 0, 0.5 * Math.PI);
progressBarCtx.lineTo(cornerRadius, 25);
progressBarCtx.arc(cornerRadius, 25 - cornerRadius, cornerRadius, 0.5 * Math.PI, Math.PI);
progressBarCtx.lineTo(0, cornerRadius);
progressBarCtx.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, 1.5 * Math.PI);
progressBarCtx.closePath();
progressBarCtx.fillStyle = '#ababab';
progressBarCtx.fill();
progressBarCtx.beginPath();
progressBarCtx.moveTo(cornerRadius, 0);
progressBarCtx.lineTo(progressBarWidth - cornerRadius, 0);
progressBarCtx.arc(progressBarWidth - cornerRadius, cornerRadius, cornerRadius, 1.5 * Math.PI, 2 * Math.PI);
progressBarCtx.lineTo(progressBarWidth, 25);
progressBarCtx.lineTo(cornerRadius, 25);
progressBarCtx.arc(cornerRadius, 25 - cornerRadius, cornerRadius, 0.5 * Math.PI, Math.PI);
progressBarCtx.lineTo(0, cornerRadius);
progressBarCtx.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, 1.5 * Math.PI);
progressBarCtx.closePath();
progressBarCtx.fillStyle = `#${validatedColor}`;
progressBarCtx.fill();
const circleCanvas = canvas.createCanvas(1000, 1000);
const circleCtx = circleCanvas.getContext('2d');
const circleRadius = 20;
const circleY = 97;
circleCtx.beginPath();
circleCtx.arc(circleX, circleY, circleRadius, 0, 2 * Math.PI);
circleCtx.fillStyle = `#${validatedColor}`;
circleCtx.fill();
const background = await canvas.loadImage(`https://i.imgur.com/hURm0iS.png`);
const thumbnailCanvas = canvas.createCanvas(650, 650);
const thumbnailCtx = thumbnailCanvas.getContext('2d');
let thumbnailImage;
try {
thumbnailImage = await canvas.loadImage(this.thumbnail, {
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);
const bg2 = await canvas.loadImage("https://i.imgur.com/ASY5GPH.png")
ctx.drawImage(background, 0, 0, 1280, 450);
ctx.drawImage(bg2, 0, 0, 1280, 450);
const gradient = ctx.createLinearGradient(0, 0, 0, 450);
gradient.addColorStop(0, 'rgba(0,0,0,0.1)');
gradient.addColorStop(0.5, 'rgba(0,0,0,0.1)');
gradient.addColorStop(1, 'rgba(0,0,0,0.1)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 1280, 450);
ctx.fillStyle = `#${validatedColor}`;
ctx.font = `90px space, noto-emoji,`;
ctx.fillText(this.name, 450, 200);
ctx.fillStyle = '#b8b8b8';
ctx.font = `40px space, noto-emoji`;
ctx.fillText(this.author, 460, 260);
ctx.fillStyle = '#fff';
ctx.font = '30px space';
ctx.fillText(validatedStartTime, 450, 380);
ctx.fillStyle = '#fff';
ctx.font = '30px space';
ctx.fillText(validatedEndTime, 1050, 380);
ctx.save();
const thumbnailMaskCanvas = canvas.createCanvas(thumbnailCanvas.width, thumbnailCanvas.height);
const thumbnailMaskCtx = thumbnailMaskCanvas.getContext('2d');
const thumbnailMaskRadius = thumbnailCanvas.width / 2;
thumbnailMaskCtx.beginPath();
thumbnailMaskCtx.arc(thumbnailMaskRadius, thumbnailMaskRadius, thumbnailMaskRadius, 0, 2 * Math.PI);
thumbnailMaskCtx.closePath();
thumbnailMaskCtx.fillStyle = '#000';
thumbnailMaskCtx.fill();
thumbnailCtx.globalCompositeOperation = 'destination-in';
thumbnailCtx.drawImage(thumbnailMaskCanvas, 0, 0);
thumbnailCtx.globalCompositeOperation = 'source-over';
ctx.drawImage(thumbnailCanvas, 57, 105, 288, 288);
ctx.drawImage(progressBarCanvas, 450, 310, 670, 25);
ctx.drawImage(circleCanvas, 400, 225, 1000, 1000);
return image.toBuffer('image/png');
} if (this.theme == 'onepiece+') {
const progressBarWidth = (validatedProgress / 100) * 670;
const circleX = progressBarWidth + 60;
const progressBarCanvas = canvas.createCanvas(670, 25);
const progressBarCtx = progressBarCanvas.getContext('2d');
const cornerRadius = 10;
progressBarCtx.beginPath();
progressBarCtx.moveTo(cornerRadius, 0);
progressBarCtx.lineTo(670 - cornerRadius, 0);
progressBarCtx.arc(670 - cornerRadius, cornerRadius, cornerRadius, 1.5 * Math.PI, 2 * Math.PI);
progressBarCtx.lineTo(670, 25 - cornerRadius);
progressBarCtx.arc(670 - cornerRadius, 25 - cornerRadius, cornerRadius, 0, 0.5 * Math.PI);
progressBarCtx.lineTo(cornerRadius, 25);
progressBarCtx.arc(cornerRadius, 25 - cornerRadius, cornerRadius, 0.5 * Math.PI, Math.PI);
progressBarCtx.lineTo(0, cornerRadius);
progressBarCtx.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, 1.5 * Math.PI);
progressBarCtx.closePath();
progressBarCtx.fillStyle = '#ababab';
progressBarCtx.fill();
progressBarCtx.beginPath();
progressBarCtx.moveTo(cornerRadius, 0);
progressBarCtx.lineTo(progressBarWidth - cornerRadius, 0);
progressBarCtx.arc(progressBarWidth - cornerRadius, cornerRadius, cornerRadius, 1.5 * Math.PI, 2 * Math.PI);
progressBarCtx.lineTo(progressBarWidth, 25);
progressBarCtx.lineTo(cornerRadius, 25);
progressBarCtx.arc(cornerRadius, 25 - cornerRadius, cornerRadius, 0.5 * Math.PI, Math.PI);
progressBarCtx.lineTo(0, cornerRadius);
progressBarCtx.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, 1.5 * Math.PI);
progressBarCtx.closePath();
progressBarCtx.fillStyle = `#${validatedColor}`;
progressBarCtx.fill();
const circleCanvas = canvas.createCanvas(1000, 1000);
const circleCtx = circleCanvas.getContext('2d');
const circleRadius = 20;
const circleY = 97;
circleCtx.beginPath();
circleCtx.arc(circleX, circleY, circleRadius, 0, 2 * Math.PI);
circleCtx.fillStyle = `#ff0000`;
circleCtx.fill();
const background = await canvas.loadImage(`https://i.imgur.com/cYRzWfL.png`);
const thumbnailCanvas = canvas.createCanvas(564, 564);
const thumbnailCtx = thumbnailCanvas.getContext('2d');
let thumbnailImage;
try {
thumbnailImage = await canvas.loadImage(this.thumbnail, {
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);
const image = canvas.createCanvas(1280, 450);
const ctx = image.getContext('2d');
ctx.drawImage(background, 0, 0, 1280, 450);
ctx.fillStyle = `#fff`;
ctx.font = `50px circular-std`;
ctx.fillText(this.name, 250, 120);
ctx.fillStyle = '#fff';
ctx.font = `25px circular-std`;
ctx.fillText(this.author, 255, 180);
ctx.fillStyle = '#fff';
ctx.font = '30px circular-std';
ctx.fillText(validatedStartTime, 70, 315);
ctx.fillStyle = '#fff';
ctx.font = '30px circular-std';
ctx.fillText(validatedEndTime, 660, 315);
ctx.drawImage(thumbnailCanvas, 45, 35, 180, 180);
ctx.drawImage(progressBarCanvas, 70, 250, 670, 25);
ctx.drawImage(circleCanvas, 10, 165, 1000, 1000);
return image.toBuffer('image/png');
} else if (this.theme === "vector+") {
const progressBarWidth = (validatedProgress / 100) * 670;
const circleX = progressBarWidth + 60;
const progressBarCanvas = canvas.createCanvas(670, 25);
const progressBarCtx = progressBarCanvas.getContext('2d');
const cornerRadius = 10;
progressBarCtx.beginPath();
progressBarCtx.moveTo(cornerRadius, 0);
progressBarCtx.lineTo(670 - cornerRadius, 0);
progressBarCtx.arc(670 - cornerRadius, cornerRadius, cornerRadius, 1.5 * Math.PI, 2 * Math.PI);
progressBarCtx.lineTo(670, 25 - cornerRadius);
progressBarCtx.arc(670 - cornerRadius, 25 - cornerRadius, cornerRadius, 0, 0.5 * Math.PI);
progressBarCtx.lineTo(cornerRadius, 25);
progressBarCtx.arc(cornerRadius, 25 - cornerRadius, cornerRadius, 0.5 * Math.PI, Math.PI);
progressBarCtx.lineTo(0, cornerRadius);
progressBarCtx.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, 1.5 * Math.PI);
progressBarCtx.closePath();
progressBarCtx.fillStyle = '#ababab';
progressBarCtx.fill();
progressBarCtx.beginPath();
progressBarCtx.moveTo(cornerRadius, 0);
progressBarCtx.lineTo(progressBarWidth - cornerRadius, 0);
progressBarCtx.arc(progressBarWidth - cornerRadius, cornerRadius, cornerRadius, 1.5 * Math.PI, 2 * Math.PI);
progressBarCtx.lineTo(progressBarWidth, 25);
progressBarCtx.lineTo(cornerRadius, 25);
progressBarCtx.arc(cornerRadius, 25 - cornerRadius, cornerRadius, 0.5 * Math.PI, Math.PI);
progressBarCtx.lineTo(0, cornerRadius);
progressBarCtx.arc(cornerRadius, cornerRadius, cornerRadius, Math.PI, 1.5 * Math.PI);
progressBarCtx.closePath();
progressBarCtx.fillStyle = `#${validatedColor}`;
progressBarCtx.fill();
const circleCanvas = canvas.createCanvas(1000, 1000);
const circleCtx = circleCanvas.getContext('2d');
const circleRadius = 20;
const circleY = 97;
circleCtx.beginPath();
circleCtx.arc(circleX, circleY, circleRadius, 0, 2 * Math.PI);
circleCtx.fillStyle = `#${validatedColor}`;
circleCtx.fill();
const background = await canvas.loadImage(`https://i.imgur.com/Kgzsiy9.png`);
const thumbnailCanvas = canvas.createCanvas(650, 650);
const thumbnailCtx = thumbnailCanvas.getContext('2d');
let thumbnailImage;
try {
thumbnailImage = await canvas.loadImage(this.thumbnail, {
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);
const image = canvas.createCanvas(1280, 450);
const ctx = image.getContext('2d');
// Draw the background
ctx.drawImage(background, 0, 0, 1280, 450);
// Apply fade effect between background and upper parts
const gradient = ctx.createLinearGradient(0, 0, 0, 450);
gradient.addColorStop(0, 'rgba(0,0,0,0.7)'); // Fully transparent
gradient.addColorStop(0.5, 'rgba(0,0,0,0.7)'); // 50% transparent
gradient.addColorStop(1, 'rgba(0,0,0,0.7)'); // Fully opaque
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 1280, 450);
ctx.fillStyle = `#${validatedColor}`;
ctx.font = `110px space`;
ctx.fillText(this.name, 490, 200);
ctx.fillStyle = '#b8b8b8';
ctx.font = `55px space`;
ctx.fillText(this.author, 510, 260);
ctx.fillStyle = '#fff';
ctx.font = '30px space';
ctx.fillText(validatedStartTime, 510, 410);
ctx.fillStyle = '#fff';
ctx.font = '30px space';
ctx.fillText(validatedEndTime, 1100, 410);
ctx.drawImage(thumbnailCanvas, 70, 50, 350, 350);
ctx.drawImage(progressBarCanvas, 510, 340, 670, 25);
ctx.drawImage(circleCanvas, 445, 253, 1000, 1000);
return image.toBuffer('image/png');
} else {
throw new Error('Invalid theme parameter, must be one of "quartz+" "onepiece+" "vector+"');
}
}
}
module.exports = { musicCard };