Skip to content

Commit

Permalink
chore(profile): html has been replaced by canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
talkincheap committed Oct 3, 2024
1 parent 29157db commit 05754d0
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 7 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
## production runner
FROM node:20-alpine as prod-runner

ENV CHROME_BIN="/usr/bin/chromium-browser" \
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD="true"
# ENV CHROME_BIN="/usr/bin/chromium-browser" \
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD="true"

RUN npm install -g pnpm

Expand All @@ -13,7 +13,7 @@ RUN set -x \
udev \
ttf-freefont \
chromium \
&& pnpm install puppeteer
## && pnpm install puppeteer


WORKDIR /bot
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@discordjs/voice": "^0.16.1",
"@discordx/importer": "^1.2.3",
"@discordx/pagination": "^3.4.1",
"@napi-rs/canvas": "^0.1.56",
"@types/node-schedule": "^2.1.1",
"discord.js": "^14.13.0",
"discordx": "^11.7.6",
Expand All @@ -28,6 +29,7 @@
"node-schedule": "^2.1.1",
"pg": "^8.11.3",
"reflect-metadata": "^0.1.13",
"sharp": "^0.33.5",
"tslog": "^4.9.2",
"tsyringe": "^4.8.0",
"typeorm": "^0.3.17",
Expand Down
Binary file added src/canvas/eventsmode-profile/profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 159 additions & 0 deletions src/canvas/eventsmode-profile/profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { createCanvas, loadImage, SKRSContext2D } from '@napi-rs/canvas';
import sharp from 'sharp';
import { dirname } from '@discordx/importer';

interface EventsmodeProfileProps {
user: {
avatar: string;
nickname: string;
staffRole: string;
};
stats: {
totalTime: string;
totalSalary: number;
weeklyTime: string;
weeklySalary: number;
warns: number;
date: string;

favoriteEvent: string;
longestEvent: string;

hearts: number;
top: number;

percentage: number;
};
}

export async function generateCanvasProfile(props: EventsmodeProfileProps) {
const canvas = createCanvas(1420, 785);
const content = canvas.getContext('2d');

const profile = await loadImage(dirname(import.meta.url) + '/profile.png');
content.drawImage(profile, 0, 0, canvas.width, canvas.height);

const roundedAvatar = await createRoundedImage(props.user.avatar, 190);

content.drawImage(roundedAvatar, 153, 177);

content.fillStyle = '#FFFFFF';
content.font = '700 32 Montserrat';
content.textAlign = 'center';

content.fillText(
`${
props.user.nickname.length > 15
? `${props.user.nickname.slice(0, 15)}...`
: props.user.nickname
}`,
245,
440,
);

content.fillStyle = '#B0BCFF';
content.font = '600 16 Montserrat';
content.textAlign = 'center';

content.fillText(props.user.staffRole, 245, 490);

// Statistics
content.font = '600 22 Montserrat';

// The longest event
content.fillText(props.stats.longestEvent, 1175, 325);

// Favorite event
content.fillText(props.stats.favoriteEvent, 1175, 460);

content.textAlign = 'right';

// Total time
content.fillText(props.stats.totalTime, 970, 262);

// Weekly time
content.fillText(props.stats.weeklyTime, 970, 312);

// Total salary
content.fillText(props.stats.totalSalary.toString(), 940, 362);

// Weekly salary
content.fillText(props.stats.weeklySalary.toString(), 940, 412);

// Warns
content.fillText(`${props.stats.warns} / 5`, 970, 462);

// Date
content.fillText(props.stats.date, 970, 512);

content.font = '600 20 Montserrat';

// Candy
content.fillText(props.stats.hearts.toString(), 215, 676);

// Top
content.fillText(props.stats.top.toString(), 390, 676);

content.font = '600 22 Montserrat';
content.textAlign = 'left';

// Progress
content.fillText(`${props.stats.percentage}%`, 520, 680);

await drawRoundedProgressBar(content, 520, 612, 700, 25, props.stats.percentage, 30);

const bufferCanvas = canvas.toBuffer('image/png');

return await sharp(bufferCanvas).resize(1420, 785).toBuffer();
}

async function drawRoundedProgressBar(
ctx: SKRSContext2D,
x: number,
y: number,
width: number,
height: number,
progress: number, // progress от 0 до 100
borderRadius: number,
) {
const normalizedProgress = Math.max(0, Math.min(progress, 100)) / 100;
const progressWidth = width * normalizedProgress;
const barRadius = Math.min(borderRadius, height / 2);

function drawBar(startX: number, endX: number, fillColor: string) {
ctx.fillStyle = fillColor;
ctx.beginPath();
ctx.moveTo(startX + barRadius, y);
ctx.lineTo(endX - barRadius, y);
ctx.arcTo(endX, y, endX, y + barRadius, barRadius);
ctx.lineTo(endX, y + height - barRadius);
ctx.arcTo(endX, y + height, endX - barRadius, y + height, barRadius);
ctx.lineTo(startX + barRadius, y + height);
ctx.arcTo(startX, y + height, startX, y + height - barRadius, barRadius);
ctx.lineTo(startX, y + barRadius);
ctx.arcTo(startX, y, startX + barRadius, y, barRadius);
ctx.closePath();
ctx.fill();
}

drawBar(x, x + width, '#262732');

if (progress > 0) {
drawBar(x, x + progressWidth, '#B3BFFF');
}
}

async function createRoundedImage(imageURL: string, size: number) {
const image = await loadImage(imageURL);
const canvas = createCanvas(size, size);
const ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();

ctx.drawImage(image, 0, 0, size, size);

return canvas;
}
13 changes: 9 additions & 4 deletions src/commands/eventsmode/profile.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { ApplicationCommandOptionType, CommandInteraction, GuildMember, AttachmentBuilder } from 'discord.js';
import {
ApplicationCommandOptionType,
CommandInteraction,
GuildMember,
AttachmentBuilder,
} from 'discord.js';
import { Discord, Guard, Slash, SlashOption } from 'discordx';
import { Eventsmode } from '../../feature/eventsmode/eventsmode.entity.js';
import { EventsmodeGuard } from '../../guards/eventsmode.guard.js';
import { generateEventsmodeProfile } from '../../html/eventsmode-profile/profile.js';
import { BotMessages, Colors } from '../../lib/constants.js';
import { embedResponse } from '../../lib/embed-response.js';
import { CommandError } from '../../lib/errors/command.error.js';
import { humanizeMinutes } from '../../lib/humanize-duration.js';
import { getStuffRole } from '../../lib/log-formatter.js';
import { generateCanvasProfile } from '../../canvas/eventsmode-profile/profile.js';

@Discord()
@Guard(EventsmodeGuard)
Expand Down Expand Up @@ -59,10 +64,10 @@ export class Command {

const percentageBar = ~~((eventsmode.weeklyTime / minimumWeeklyQuota) * 100);

const buffer = await generateEventsmodeProfile({
const buffer = await generateCanvasProfile({
user: {
nickname: author.user.username,
avatar: author.user.avatarURL({ forceStatic: true }) ?? '',
avatar: author.user.avatarURL({ forceStatic: true, size: 2048 }) ?? '',
staffRole: getStuffRole(eventsmode.staffRole),
},
stats: {
Expand Down

0 comments on commit 05754d0

Please sign in to comment.