cwk-gen
Version:
High-quality image generator for Discord bots with welcome images, rank cards, profile cards, and server banners
144 lines (124 loc) • 5.2 kB
JavaScript
const { createCanvas, loadImage } = require('@napi-rs/canvas');
const { loadImageBuffer, cropToCircle, wrapText } = require('../utils');
/**
* Generate a welcome image for Discord
* @param {object} options
* @param {string} options.username - User's username
* @param {string} options.avatarURL - URL to user's avatar
* @param {string|Buffer|object} [options.background] - Background config (URL/Buffer or {image, color, blur, overlay})
* @param {string} [options.title="WELCOME"] - Title text
* @param {string} [options.message="Welcome to the server!"] - Welcome message
* @param {string} [options.color="#7289DA"] - Fallback color if background fails
* @param {string} [options.textColor="#FFFFFF"] - Text color
* @param {number} [options.width=1200] - Image width (updated for modern Discord)
* @param {number} [options.height=400] - Image height
* @param {number} [options.avatarSize=200] - Avatar size
* @param {string} [options.font="sans-serif"] - Font family
* @param {boolean} [options.shadow=true] - Whether to add text shadow
* @param {string} [options.avatarBorderColor="#FFFFFF"] - Avatar border color
* @returns {Promise<Buffer>}
*/
async function generateWelcomeImage(options) {
const {
username,
avatarURL,
background,
title = "WELCOME",
message = "Welcome to the server!",
color = "#7289DA",
textColor = "#FFFFFF",
width = 1200,
height = 400,
avatarSize = 200,
font = "sans-serif",
shadow = true,
avatarBorderColor = "#FFFFFF"
} = options;
// Create canvas
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// Background handling (supports both simple and advanced config)
const bgConfig = typeof background === 'object' && !Buffer.isBuffer(background)
? background
: { image: background };
try {
if (bgConfig?.image) {
const bgBuffer = await loadImageBuffer(bgConfig.image);
const bgImage = await loadImage(bgBuffer);
// Apply background effects
ctx.save();
// Optional blur (default: 0)
if (bgConfig.blur) {
ctx.filter = `blur(${bgConfig.blur}px)`;
}
// Draw background image
ctx.globalAlpha = bgConfig.opacity || 0.85;
ctx.drawImage(bgImage, 0, 0, width, height);
ctx.restore();
// Overlay (default: dark semi-transparent)
ctx.fillStyle = bgConfig.overlay || 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, width, height);
} else {
// Solid color fallback
ctx.fillStyle = bgConfig.color || color;
ctx.fillRect(0, 0, width, height);
}
} catch (err) {
console.error('Background error:', err);
ctx.fillStyle = color;
ctx.fillRect(0, 0, width, height);
}
// Draw avatar with improved border
try {
const avatarBuffer = await loadImageBuffer(avatarURL);
const circularAvatar = await cropToCircle(avatarBuffer, avatarSize);
const avatarImage = await loadImage(circularAvatar);
const avatarX = (width - avatarSize) / 2;
const avatarY = 80; // Fixed position from top
// Border with configurable color
ctx.beginPath();
ctx.arc(avatarX + avatarSize/2, avatarY + avatarSize/2, avatarSize/2 + 8, 0, Math.PI * 2);
ctx.fillStyle = avatarBorderColor;
ctx.fill();
// Inner shadow effect
ctx.save();
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
ctx.shadowBlur = 15;
ctx.shadowOffsetY = 5;
ctx.drawImage(avatarImage, avatarX, avatarY, avatarSize, avatarSize);
ctx.restore();
} catch (err) {
throw new Error(`Avatar processing failed: ${err.message}`);
}
// Text rendering with improved layout
const centerX = width / 2;
// Draw title
ctx.font = `bold 42px ${font}`;
ctx.fillStyle = textColor;
if (shadow) {
ctx.shadowColor = 'rgba(0, 0, 0, 0.7)';
ctx.shadowBlur = 8;
ctx.shadowOffsetY = 3;
}
const titleWidth = ctx.measureText(title).width;
ctx.fillText(title, centerX - titleWidth/2, height - 120);
// Draw username
ctx.font = `bold 36px ${font}`;
ctx.shadowBlur = shadow ? 6 : 0;
const usernameWidth = ctx.measureText(username).width;
ctx.fillText(username, centerX - usernameWidth/2, height - 70);
// Draw message with improved wrapping
ctx.font = `28px ${font}`;
ctx.shadowColor = 'transparent';
const maxMessageWidth = width * 0.8;
const wrappedMessage = wrapText(ctx, message, maxMessageWidth);
const lines = wrappedMessage.split('\n');
const lineHeight = 34;
const messageY = height - 180;
lines.forEach((line, i) => {
const lineWidth = ctx.measureText(line).width;
ctx.fillText(line, centerX - lineWidth/2, messageY + i * lineHeight);
});
return canvas.toBuffer('image/png');
}
module.exports = { generateWelcomeImage };