diff --git a/bot.js b/bot.js index 4f1a1f3..1356ecc 100644 --- a/bot.js +++ b/bot.js @@ -1,7 +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"; +import {launchWorker} from "./wwwroot/core/namecards/workerLauncher.js"; const launch = async () => { try{ @@ -30,8 +30,19 @@ const launch = async () => { } }); - data.client.on('guildMemberAdd', member => { - NameCardCreator.getWelcomeCard("", member.user.avatarURL(), member.user.displayName) + data.client.on('guildMemberAdd', async member => { + try { + launchWorker({ + templatePath : data.nameCardTemplate, + avatarURL: member.user.avatarURL, + displayName: member.user.displayName, + }).then(buffer => { + //Send name card here... + console.log('Name card sent!'); + }).catch(console.error); + } catch (err) { + console.error('Failed to generate/send name card:', err); + } }); process.on('SIGINT', async () => { diff --git a/tests/name-card-test.js b/tests/name-card-test.js index eafe77c..3d9048b 100644 --- a/tests/name-card-test.js +++ b/tests/name-card-test.js @@ -1,7 +1,21 @@ -import {NameCardCreator} from "../wwwroot/core/welcome/nameCardCreator-2.js"; +import {NameCardCreator} from "../wwwroot/core/namecards/nameCardCreator.js"; +import {launchWorker} from "../wwwroot/core/namecards/workerLauncher.js"; +import {data} from "../wwwroot/core/appData.js"; 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 + + +try { + launchWorker({ + templatePath : data.nameCardTemplate, + avatarURL: avatarPath, + username: name, + }).then(buffer => { + //Send name card here... + console.log('Name card sent!'); + }).catch(console.error); +} catch (err) { + console.error('Failed to generate/send name card:', err); +} \ No newline at end of file diff --git a/wwwroot/core/appData.js b/wwwroot/core/appData.js index b0b3e6b..1e1e0dd 100644 --- a/wwwroot/core/appData.js +++ b/wwwroot/core/appData.js @@ -2,8 +2,6 @@ import 'dotenv/config'; import {Client, GatewayIntentBits} from "discord.js"; import {MessageSender} from "./base/MessageSender.js"; import {InstagramTokenManager} from "./instagram/instagramTokenManager.js"; -import {TikTokTokenManager} from "./tiktok/tiktokTokenManager.js"; -import JsonManager from "./utils/jsonManager.js"; import {InstagramPoller} from "./instagram/instagramPoller.js"; import {UsersToken} from "./usersToken.js"; @@ -16,6 +14,7 @@ const client = new Client({ ] }); +const nameCardTemplate = "./wwwroot/assets/name-card-template.png"; const sender = new MessageSender(client); const instagramID = process.env.INSTAGRAM_CLIENT_ID; @@ -41,6 +40,7 @@ export const data = { updateChannelID, socialChannelID, instagramTokenManager, + nameCardTemplate //tiktokTokenManager }; diff --git a/wwwroot/core/logging/logger.js b/wwwroot/core/logging/logger.js index bbabc5b..debb62f 100644 --- a/wwwroot/core/logging/logger.js +++ b/wwwroot/core/logging/logger.js @@ -30,6 +30,7 @@ export class Logger{ await mkdir(logsDir, { recursive: true }); await appendFile(fullPath, `${time} - ${message} -> ${error.message} \n`); + console.error(error.stackTrace); console.error(`An error occured. The incident has been logged in ${fullPath}`) } catch (err) { console.error("Error writing log:", err); diff --git a/wwwroot/core/welcome/nameCardCreator-2.js b/wwwroot/core/namecards/nameCardCreator.js similarity index 81% rename from wwwroot/core/welcome/nameCardCreator-2.js rename to wwwroot/core/namecards/nameCardCreator.js index 497232c..61e24a7 100644 --- a/wwwroot/core/welcome/nameCardCreator-2.js +++ b/wwwroot/core/namecards/nameCardCreator.js @@ -8,12 +8,12 @@ export class NameCardCreator { this.loadFont("./wwwroot/assets/fonts/Fredoka/static/Fredoka-Bold.ttf") } - loadFont(fontPath){ + loadFont(fontPath) { const fullPath = path.resolve(fontPath); if (!fs.existsSync(fullPath)) { throw new Error(`Font file not found at ${fullPath}`); } - registerFont(fullPath, { family: "Fredoka Bold" }); + registerFont(fullPath, {family: "Fredoka Bold"}); } /** @@ -78,13 +78,31 @@ export class NameCardCreator { ctx.fillText(line, messageX, startY + i * lineHeight); }); - const buffer = canvas.toBuffer("image/png"); - fs.writeFileSync("./tests/result/namecard.png", buffer); + const result = this.scallDown(canvas, 1500, 500) + + fs.writeFileSync("./tests/result/namecard.png", result); console.log("✅ Name card created: namecard.png"); - return buffer; + return result; } catch (err) { console.error("Error creating name card:", err); throw err; } } + + + /** + * + * @param target + * @param width + * @param height + * @returns {Buffer} + */ + scallDown(target, width, height) { + const smallCanvas = createCanvas(width, height); + const smallCtx = smallCanvas.getContext('2d'); + + smallCtx.drawImage(target, 0, 0, width, height); + return smallCanvas.toBuffer(); + } + } diff --git a/wwwroot/core/namecards/nameCardWorker.js b/wwwroot/core/namecards/nameCardWorker.js new file mode 100644 index 0000000..b9c6f60 --- /dev/null +++ b/wwwroot/core/namecards/nameCardWorker.js @@ -0,0 +1,27 @@ +import { parentPort } from 'worker_threads'; +import {NameCardCreator} from "./nameCardCreator.js"; + +parentPort.on("message", async (data) => { + try { + const { templatePath, avatarURL, username } = data; + + const creator = new NameCardCreator(templatePath); + const buffer = await creator.getWelcomeCard(avatarURL, username); + + parentPort.postMessage({ result: buffer }); + } catch (err) { + parentPort.postMessage({ error: err.message }); + } +}); + +/*(async () => { + try { + const { creator, avatarURL, userName } = workerData; + + const buffer = await creator.getWelcomeCard(avatarURL, userName); + + parentPort.postMessage(buffer); // return the buffer to main thread + } catch (err) { + parentPort.postMessage({ error: err.message }); + } +})();*/ diff --git a/wwwroot/core/namecards/workerLauncher.js b/wwwroot/core/namecards/workerLauncher.js new file mode 100644 index 0000000..33a9ceb --- /dev/null +++ b/wwwroot/core/namecards/workerLauncher.js @@ -0,0 +1,31 @@ +import path from "path"; +import { Worker } from 'worker_threads'; +import {Logger} from "../logging/logger.js"; + +function launchWorker({templatePath, avatarURL, username}) { + return new Promise((resolve, reject) => { + const workerFile = path.resolve('./wwwroot/core/namecards/nameCardWorker.js'); + const worker = new Worker(workerFile); + + worker.postMessage({templatePath, avatarURL, username}); + + worker.on('message', (result) => { + resolve(result); + worker.terminate() + .catch(err => Logger.error("Unable to terminate worker", err.message)); + }); + + worker.on('error', (err) => { + reject(err); + worker.terminate() + .catch(err => Logger.error("Unable to terminate worker", err.message)); + }); + + worker.on('exit', (code) => { + if (code !== 0) reject(new Error(`Worker stopped with code ${code}`)); + }); + }); +} + +export {launchWorker} + diff --git a/wwwroot/core/welcome/nameCardCreator.js b/wwwroot/core/welcome/nameCardCreator.js deleted file mode 100644 index 0197fcc..0000000 --- a/wwwroot/core/welcome/nameCardCreator.js +++ /dev/null @@ -1,192 +0,0 @@ -import sharp from "sharp"; -import {Logger} from "../logging/logger.js"; -import fs from "fs"; -import path from "path"; -import { createCanvas, registerFont } from 'canvas'; - -export class NameCardCreator { - - constructor(templatePath) { - this.templatePath = templatePath; - this.template = this.loadTemplate(); - } - - /** - * Loads the template file into a sharp instance - * @returns {sharp.Sharp} sharp image object - */ - loadTemplate() { - return sharp(this.templatePath); - } - - loadFont(fontPath) { - const fullPath = path.resolve(fontPath); - console.debug(fullPath); - registerFont(fullPath, { family: 'Fredoka' }); - } - - /** - * Combines a template image with a user avatar and saves it - * @param avatarPath {string} - * @param name {string} - * @returns {Promise} resulting image buffer - */ - async getWelcomeCard(avatarPath, name) { - try{ - - const avatarSize = 550; - const avatar = await this.handleAvatar(avatarPath, avatarSize); - - const messageWidth = 2000; - const messageHeight = 700; - const messageBuffer = await this.getMessageBuffer(name, messageWidth, messageHeight); - - const avatarPos = { x : 150, y : (1000-avatarSize+16)/2} - const messagePos = { x : avatarPos.x + 700, y : (1000-messageHeight)/2} - - const result = await this.template - .composite([ - { input: avatar, top: avatarPos.y, left: avatarPos.x }, - { input: messageBuffer, top: messagePos.y, left: messagePos.x } - ]) - .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); - } - } - - /** - * - * @param avatarPath - * @param outputSize - * @returns {Promise>} - */ - async handleAvatar(avatarPath, outputSize) { - const avatarSize = outputSize; - const borderSize = 8; - const totalSize = avatarSize + borderSize * 2; - const avatarRadius = avatarSize / 2; - - const maskBuffer = Buffer.from(` - - - - `); - - const avatarBuffer = await sharp(avatarPath) - .resize(avatarSize, avatarSize, { fit: "cover" }) - .png() - .toBuffer(); - - const roundedAvatarBuffer = await sharp(avatarBuffer) - .composite([{ input: maskBuffer, blend: "dest-in" }]) - .png() - .toBuffer(); - - const backgroundBuffer = Buffer.from(` - - - - `); - - return await sharp({ - create: { - width: totalSize, - height: totalSize, - channels: 4, - background: "#0000", - }}) - .composite([ - { input: backgroundBuffer, top: 0, left: 0 }, - { input: roundedAvatarBuffer, top: borderSize, left: borderSize } - ]) - .png() - .toBuffer(); - } - - /** - * - * @param message - * @param outputWidth - * @param outputHeight - * @returns {Promise>} - */ - async getMessageBuffer(message, outputWidth, outputHeight){ - return this.getMessageCanvas(message, outputWidth, outputHeight).toBuffer(); - //return Buffer.from(messageSvg, "utf-8"); - } - - /** - * - * @param message {string} - * @param outputWidth - * @param outputHeight - * @returns {string} - */ - /*getMessageSvg(message, outputWidth, outputHeight) { - const safeMessage = - message - .replace(/&/g, "&") - .replace(//g, ">"); - - return ` - - - Welcome ${safeMessage}, to the - Spicy Jail ~ - - `; - }*/ - - /** - * - * @returns {Canvas} - */ - getMessageCanvas(name, outputWidth, outputHeight){ - - const canvas = createCanvas(outputWidth, outputHeight); - const ctx = canvas.getContext('2d'); - - ctx.fillStyle = '#0000'; - ctx.fillRect(0, 0, outputWidth, outputHeight); - - ctx.fillStyle = '#ede6e6'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.font = '110px "Fredoka"'; - - const message = `Welcome ${name}, to the Spicy Jail ~`; - ctx.fillText(message, outputWidth / 2, outputHeight / 2); - - return canvas; - } - - /*loadFontData(){ - const fullPath = path.resolve(this.fontPath); - return fs.readFileSync(fullPath) - .toString("base64") - }*/ -} \ No newline at end of file