Add name card generation
This commit is contained in:
3
bot.js
3
bot.js
@@ -1,6 +1,7 @@
|
|||||||
import {data} from "./wwwroot/core/appData.js";
|
import {data} from "./wwwroot/core/appData.js";
|
||||||
import JsonManager from "./wwwroot/core/utils/jsonManager.js";
|
import JsonManager from "./wwwroot/core/utils/jsonManager.js";
|
||||||
import {Logger} from "./wwwroot/core/logging/logger.js";
|
import {Logger} from "./wwwroot/core/logging/logger.js";
|
||||||
|
import {NameCardCreator} from "./wwwroot/core/welcome/nameCardCreator.js";
|
||||||
|
|
||||||
const launch = async () => {
|
const launch = async () => {
|
||||||
try{
|
try{
|
||||||
@@ -30,7 +31,7 @@ const launch = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
data.client.on('guildMemberAdd', member => {
|
data.client.on('guildMemberAdd', member => {
|
||||||
const avatar = member.user.avatarURL();
|
NameCardCreator.getWelcomeCard("", member.user.avatarURL(), member.user.displayName)
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGINT', async () => {
|
process.on('SIGINT', async () => {
|
||||||
|
|||||||
BIN
namecard.png
BIN
namecard.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 764 KiB After Width: | Height: | Size: 764 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import {NameCardCreator} from "../wwwroot/core/welcome/nameCardCreator.js";
|
import {NameCardCreator} from "../wwwroot/core/welcome/nameCardCreator.js";
|
||||||
|
|
||||||
const templatePath = "./assets/name-card-template.png";
|
const templatePath = "./wwwroot/assets/name-card-template.png";
|
||||||
const avatarPath = "./assets/avatar-test.png";
|
const avatarPath = "./tests/assets/avatar-test.png";
|
||||||
|
const name = "Aude Vaiselle";
|
||||||
const res = await NameCardCreator.getWelcomeCard(templatePath, avatarPath);
|
const creator = new NameCardCreator(templatePath);
|
||||||
|
await creator.getWelcomeCard(avatarPath, name)
|
||||||
@@ -1,56 +1,58 @@
|
|||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import {Logger} from "../logging/logger.js";
|
import {Logger} from "../logging/logger.js";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
export class NameCardCreator {
|
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
|
* Combines a template image with a user avatar and saves it
|
||||||
* @param {string} templatePath - path to the template image
|
* @param avatarPath {string}
|
||||||
* @param {string} avatarPath - path to the avatar
|
* @param name {string}
|
||||||
* @returns {Promise<sharp.OutputInfo>} resulting image buffer
|
* @returns {Promise<sharp.OutputInfo>} resulting image buffer
|
||||||
*/
|
*/
|
||||||
static async getWelcomeCard(templatePath, avatarPath) {
|
async getWelcomeCard(avatarPath, name) {
|
||||||
try{
|
try{
|
||||||
|
|
||||||
const template = await this.loadTemplate(templatePath);
|
const template = this.loadTemplate();
|
||||||
|
|
||||||
const avatar = await this.handleAvatar(avatarPath);
|
const avatar = await this.handleAvatar(avatarPath);
|
||||||
|
const messageBuffer = await this.getMessageBuffer(`Hello ${name}!`);
|
||||||
|
|
||||||
const result = await template
|
const result = await template
|
||||||
.composite([{
|
.composite([
|
||||||
input: avatar,
|
{ input: avatar, top: 215, left: 275 },
|
||||||
top: 215,
|
{ input: messageBuffer, top: 400, left: 1300 }
|
||||||
left: 200,
|
])
|
||||||
}])
|
|
||||||
.toFile("namecard.png")
|
.toFile("namecard.png")
|
||||||
|
|
||||||
console.log("✅ Welcome card created: welcome-card.png");
|
console.log("✅ Welcome card created: welcome-card.png");
|
||||||
return result;
|
return result;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
await Logger.error("Unable to create name card", err);
|
await Logger.error("Unable to create name card", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the template file into a sharp instance
|
*
|
||||||
* @param {string} path - file path
|
* @param avatarPath
|
||||||
* @returns {sharp.Sharp} sharp image object
|
* @returns {Promise<Buffer<ArrayBufferLike>>}
|
||||||
*/
|
*/
|
||||||
static async loadTemplate(path) {
|
async handleAvatar(avatarPath) {
|
||||||
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<sharp.Sharp>} combined image
|
|
||||||
*/
|
|
||||||
static async mergeBitmaps(base, { item, x, y }) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async handleAvatar(avatarPath) {
|
|
||||||
const avatarSize = 670;
|
const avatarSize = 670;
|
||||||
const borderSize = 8;
|
const borderSize = 8;
|
||||||
const radius = avatarSize / 2;
|
const radius = avatarSize / 2;
|
||||||
@@ -61,6 +63,30 @@ export class NameCardCreator {
|
|||||||
.png()
|
.png()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
|
const maskSvg = Buffer.from(`
|
||||||
|
<svg width="${avatarSize}" height="${avatarSize}" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="${radius}" cy="${radius}" r="${radius}" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const roundedAvatar = await sharp(avatarBuffer)
|
||||||
|
.composite([{ input: maskSvg, blend: "dest-in" }])
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const roundedBorder = Buffer.from(`
|
||||||
|
<svg width="${totalSize}" height="${totalSize}" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle
|
||||||
|
cx="${totalSize / 2}"
|
||||||
|
cy="${totalSize / 2}"
|
||||||
|
r="${radius + borderSize / 2}"
|
||||||
|
stroke="#ffffff"
|
||||||
|
stroke-width="${borderSize}"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`)
|
||||||
|
|
||||||
return await sharp({
|
return await sharp({
|
||||||
create: {
|
create: {
|
||||||
width: totalSize,
|
width: totalSize,
|
||||||
@@ -68,11 +94,53 @@ export class NameCardCreator {
|
|||||||
channels: 4,
|
channels: 4,
|
||||||
background: "#0000"
|
background: "#0000"
|
||||||
}
|
}
|
||||||
})
|
}).composite([
|
||||||
.composite([
|
{ input: roundedAvatar, top: 0, left: 0 },
|
||||||
{ input: avatarBuffer, top: borderSize, left: borderSize }
|
{ input: roundedBorder, top: borderSize, left: borderSize }
|
||||||
])
|
])
|
||||||
.png()
|
.png()
|
||||||
.toBuffer();
|
.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, "<")
|
||||||
|
.replace(/>/g, ">");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<svg width="1500" height="200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Fredoka';
|
||||||
|
src: url(data:font/truetype;charset=utf-8;base64,${this.fontData}) format('truetype');
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-family: 'Fredoka', sans-serif;
|
||||||
|
fill: #ede6e6;
|
||||||
|
font-size: 80px;
|
||||||
|
font-weight: bold;
|
||||||
|
dominant-baseline: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<text x="50%" y="50%" text-anchor="middle" class="title">${safeMessage}</text>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFontData(){
|
||||||
|
return fs.readFileSync(this.fontPath).toString("base64");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user