diff --git a/bot.js b/bot.js index 8f25e9b..4f1a1f3 100644 --- a/bot.js +++ b/bot.js @@ -1,6 +1,7 @@ import {data} from "./wwwroot/core/appData.js"; import JsonManager from "./wwwroot/core/utils/jsonManager.js"; import {Logger} from "./wwwroot/core/logging/logger.js"; +import {NameCardCreator} from "./wwwroot/core/welcome/nameCardCreator.js"; const launch = async () => { try{ @@ -30,7 +31,7 @@ const launch = async () => { }); data.client.on('guildMemberAdd', member => { - const avatar = member.user.avatarURL(); + NameCardCreator.getWelcomeCard("", member.user.avatarURL(), member.user.displayName) }); process.on('SIGINT', async () => { diff --git a/namecard.png b/namecard.png index 9cfafe0..e481b3f 100644 Binary files a/namecard.png and b/namecard.png differ diff --git a/wwwroot/assets/avatar-test.png b/tests/assets/avatar-test.png similarity index 100% rename from wwwroot/assets/avatar-test.png rename to tests/assets/avatar-test.png diff --git a/tests/name-card-test.js b/tests/name-card-test.js index 60f2dc2..67823df 100644 --- a/tests/name-card-test.js +++ b/tests/name-card-test.js @@ -1,6 +1,7 @@ import {NameCardCreator} from "../wwwroot/core/welcome/nameCardCreator.js"; -const templatePath = "./assets/name-card-template.png"; -const avatarPath = "./assets/avatar-test.png"; - -const res = await NameCardCreator.getWelcomeCard(templatePath, avatarPath); \ No newline at end of file +const templatePath = "./wwwroot/assets/name-card-template.png"; +const avatarPath = "./tests/assets/avatar-test.png"; +const name = "Aude Vaiselle"; +const creator = new NameCardCreator(templatePath); +await creator.getWelcomeCard(avatarPath, name) \ No newline at end of file diff --git a/wwwroot/core/welcome/nameCardCreator.js b/wwwroot/core/welcome/nameCardCreator.js index 704868d..5e59f5e 100644 --- a/wwwroot/core/welcome/nameCardCreator.js +++ b/wwwroot/core/welcome/nameCardCreator.js @@ -1,56 +1,58 @@ import sharp from "sharp"; import {Logger} from "../logging/logger.js"; +import fs from "fs"; export class NameCardCreator { + constructor(templatePath) { + this.templatePath = templatePath; + this.fontPath = "./wwwroot/assets/fonts/Fredoka/Fredoka-VariableFont_wdth,wght.ttf"; + this.fontData = this.loadFontData(); + } + + /** + * Loads the template file into a sharp instance + * @returns {sharp.Sharp} sharp image object + */ + loadTemplate() { + return sharp(this.templatePath); + } + /** * Combines a template image with a user avatar and saves it - * @param {string} templatePath - path to the template image - * @param {string} avatarPath - path to the avatar + * @param avatarPath {string} + * @param name {string} * @returns {Promise} resulting image buffer */ - static async getWelcomeCard(templatePath, avatarPath) { + async getWelcomeCard(avatarPath, name) { try{ - const template = await this.loadTemplate(templatePath); + const template = this.loadTemplate(); const avatar = await this.handleAvatar(avatarPath); + const messageBuffer = await this.getMessageBuffer(`Hello ${name}!`); const result = await template - .composite([{ - input: avatar, - top: 215, - left: 200, - }]) + .composite([ + { input: avatar, top: 215, left: 275 }, + { input: messageBuffer, top: 400, left: 1300 } + ]) .toFile("namecard.png") console.log("✅ Welcome card created: welcome-card.png"); return result; } catch(err) { + console.log(err); await Logger.error("Unable to create name card", err); } } /** - * Loads the template file into a sharp instance - * @param {string} path - file path - * @returns {sharp.Sharp} sharp image object + * + * @param avatarPath + * @returns {Promise>} */ - static async loadTemplate(path) { - return sharp(path); - } - - /** - * Overlays an image (item) on top of a base image - * @param {sharp.Sharp|string} base - sharp image or path - * @param {{item:string, x:number, y:number}} overlay - overlay info - * @returns {Promise} combined image - */ - static async mergeBitmaps(base, { item, x, y }) { - return null; - } - - static async handleAvatar(avatarPath) { + async handleAvatar(avatarPath) { const avatarSize = 670; const borderSize = 8; const radius = avatarSize / 2; @@ -61,6 +63,30 @@ export class NameCardCreator { .png() .toBuffer(); + const maskSvg = Buffer.from(` + + + + `); + + const roundedAvatar = await sharp(avatarBuffer) + .composite([{ input: maskSvg, blend: "dest-in" }]) + .png() + .toBuffer(); + + const roundedBorder = Buffer.from(` + + + + `) + return await sharp({ create: { width: totalSize, @@ -68,11 +94,53 @@ export class NameCardCreator { channels: 4, background: "#0000" } - }) - .composite([ - { input: avatarBuffer, top: borderSize, left: borderSize } - ]) + }).composite([ + { input: roundedAvatar, top: 0, left: 0 }, + { input: roundedBorder, top: borderSize, left: borderSize } + ]) .png() .toBuffer(); } + + async getMessageBuffer(message){ + const messageSvg = this.getMessageSvg(message); + return Buffer.from(messageSvg, "utf-8"); + } + + /** + * + * @param message {string} + * @returns {string} + */ + getMessageSvg(message) { + const safeMessage = + message + .replace(/&/g, "&") + .replace(//g, ">"); + + return ` + + + + ${safeMessage} + + `; + } + + loadFontData(){ + return fs.readFileSync(this.fontPath).toString("base64"); + } } \ No newline at end of file