Files
AuroraBot-discord/bot/graphics/studentID.ts
2026-01-08 16:09:26 +01:00

114 lines
3.8 KiB
TypeScript

import { GlobalFonts, createCanvas, loadImage } from '@napi-rs/canvas';
import { levelingService } from '@shared/modules/leveling/leveling.service';
import path from 'path';
// Register Fonts
const fontDir = path.join(process.cwd(), 'src', 'assets', 'fonts');
GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexSansCondensed-SemiBold.ttf'), 'IBMPlexSansCondensed-SemiBold');
GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexMono-Bold.ttf'), 'IBMPlexMono-Bold');
interface StudentCardData {
username: string;
avatarUrl: string;
id: string;
level: number;
au: bigint; // Astral Units
xp: bigint;
className?: string | null;
}
export async function generateStudentIdCard(data: StudentCardData): Promise<Buffer> {
const templatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'studentID', 'template.png');
const classTemplatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'studentID', `Constellation-${data.className}.png`);
const template = await loadImage(templatePath);
const classTemplate = await loadImage(classTemplatePath);
const canvas = createCanvas(template.width, template.height);
const ctx = canvas.getContext('2d');
// Draw Background Gradient with random hue
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
const saturation = 40 + Math.random() * 20; // 40-60%
const lightness = 20 + Math.random() * 20; // 20-40%
const color2 = `hsl(${Math.random() * 360}, ${saturation}%, ${lightness}%)`;
const color = `hsl(${Math.random() * 360}, ${saturation}%, ${lightness - 20}%)`;
gradient.addColorStop(0, color);
gradient.addColorStop(1, color2);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw Template
ctx.drawImage(template, 0, 0);
// Draw Class Template
ctx.drawImage(classTemplate, 0, 0);
// Draw Avatar
const avatarSize = 140;
const avatarX = 19;
const avatarY = 76;
try {
const avatar = await loadImage(data.avatarUrl);
ctx.save();
ctx.beginPath();
// Square avatar
ctx.rect(avatarX, avatarY, avatarSize, avatarSize);
ctx.clip();
ctx.drawImage(avatar, avatarX, avatarY, avatarSize, avatarSize);
ctx.restore();
} catch (e) {
console.error("Failed to load avatar", e);
}
// Draw ID
ctx.save();
ctx.font = '12px IBMPlexMono-Bold';
ctx.fillStyle = '#DAC7A1';
ctx.textAlign = 'left';
ctx.fillText(`ID: ${data.id}`, 314, 30);
ctx.restore();
// Draw Username
ctx.save();
ctx.font = '24px IBMPlexSansCondensed-SemiBold';
ctx.fillStyle = '#FFFFFF';
ctx.textAlign = 'left';
ctx.fillText(data.username, 181, 122);
ctx.restore();
// Draw AU
ctx.save();
ctx.font = '24px IBMPlexMono-Bold';
ctx.fillStyle = '#FFFFFF';
ctx.textAlign = 'right';
ctx.fillText(`${data.au}`, 270, 183);
ctx.restore();
// Draw Level
ctx.save();
ctx.font = '24px IBMPlexMono-Bold';
ctx.fillStyle = '#FFFFFF';
ctx.textAlign = 'center';
ctx.fillText(`${data.level}`, 445, 255);
ctx.restore();
// Draw XP Bar
const xpForThisLevel = levelingService.getXpForNextLevel(data.level); // The total size of the current level bucket
const xpAtStartOfLevel = levelingService.getXpToReachLevel(data.level); // The accumulated XP when this level started
const currentLevelProgress = Number(data.xp) - xpAtStartOfLevel; // How much XP into this level
const xpBarMaxWidth = 382;
const xpBarWidth = Math.max(0, Math.min(xpBarMaxWidth, xpBarMaxWidth * currentLevelProgress / xpForThisLevel));
const xpBarHeight = 3;
ctx.save();
ctx.fillStyle = '#B3AD93';
ctx.fillRect(32, 244, xpBarWidth, xpBarHeight);
ctx.restore();
return canvas.toBuffer('image/png');
}