diff --git a/apps/discord-bot/src/commands/config/badge.command.tsx b/apps/discord-bot/src/commands/config/badge.command.tsx index d04ea406a..6f00e4e86 100644 --- a/apps/discord-bot/src/commands/config/badge.command.tsx +++ b/apps/discord-bot/src/commands/config/badge.command.tsx @@ -16,6 +16,7 @@ import { IMessage, LocalizeFunction, SubCommand, + TextArgument, } from "@statsify/discord"; import { type Canvas, Image } from "skia-canvas"; import { DemoProfile } from "./demo.profile.js"; @@ -41,7 +42,10 @@ export class BadgeCommand { description: (t) => t("commands.badge-set"), tier: UserTier.GOLD, preview: "badge.png", - args: [new FileArgument("badge", true)], + args: [ + new FileArgument("badge"), + new TextArgument("emoji", (t) => t("arguments.emoji"), false), + ], }) public set(context: CommandContext) { return this.run(context, "set"); @@ -62,6 +66,7 @@ export class BadgeCommand { ): Promise { const userId = context.getInteraction().getUserId(); const file = context.option("badge"); + const emoji = context.option("emoji"); const user = context.getUser(); const t = context.t(); @@ -82,41 +87,13 @@ export class BadgeCommand { } case "set": { - if (!file) + if (!file && !emoji) throw new ErrorMessage( (t) => t("errors.unknown.title"), (t) => t("errors.unknown.description") ); - const canvas = createCanvas(32, 32); - const ctx = canvas.getContext("2d"); - ctx.imageSmoothingEnabled = false; - - if (!["image/png", "image/jpeg", "image/gif"].includes(file.content_type ?? "")) - throw new ErrorMessage( - (t) => t("errors.unsupportedFileType.title"), - (t) => t("errors.unsupportedFileType.description") - ); - - const badge = await loadImage(file.url); - - const ratio = Math.min(canvas.width / badge.width, canvas.height / badge.height); - const scaled = badge.width > 32 || badge.height > 32; - - const width = scaled ? badge.width * ratio : badge.width; - const height = scaled ? badge.height * ratio : badge.height; - - ctx.drawImage( - badge, - 0, - 0, - badge.width, - badge.height, - (canvas.width - width) / 2, - (canvas.height - height) / 2, - width, - height - ); + const canvas = file ? await this.getBadgeCanvas(file) : await this.getEmojiCanvas(emoji as string); await this.apiService.updateUserBadge(userId, await canvas.toBuffer("png")); const profile = await this.getProfile(t, user, canvas); @@ -141,6 +118,61 @@ export class BadgeCommand { } } + private async getBadgeCanvas(file: APIAttachment) { + const canvas = createCanvas(32, 32); + const ctx = canvas.getContext("2d"); + ctx.imageSmoothingEnabled = false; + + if (!["image/png", "image/jpeg", "image/gif"].includes(file.content_type ?? "")) + throw new ErrorMessage( + (t) => t("errors.unsupportedFileType.title"), + (t) => t("errors.unsupportedFileType.description") + ); + + const badge = await loadImage(file.url); + + const ratio = Math.min(canvas.width / badge.width, canvas.height / badge.height); + const scaled = badge.width > 32 || badge.height > 32; + + const width = scaled ? badge.width * ratio : badge.width; + const height = scaled ? badge.height * ratio : badge.height; + + ctx.drawImage( + badge, + 0, + 0, + badge.width, + badge.height, + (canvas.width - width) / 2, + (canvas.height - height) / 2, + width, + height + ); + + return canvas; + } + + private async getEmojiCanvas(input: string) { + const canvas = createCanvas(32, 32); + const ctx = canvas.getContext("2d"); + ctx.imageSmoothingEnabled = false; + + const customEmoji = input.trim().match(/^$/); + + if (customEmoji) { + const badge = await loadImage(`https://cdn.discordapp.com/emojis/${customEmoji[1]}.png?size=32&quality=lossless`); + ctx.drawImage(badge, 0, 0, badge.width, badge.height, 0, 0, 32, 32); + return canvas; + } + + ctx.font = "28px Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, sans-serif"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(input.trim(), 16, 17); + + return canvas; + } + private async getProfile(t: LocalizeFunction, user: User, badge?: Image | Canvas) { if (!user?.uuid) throw new ErrorMessage("errors.unknown"); diff --git a/locales/en-US/default.json b/locales/en-US/default.json index b66f3e884..007b125b2 100644 --- a/locales/en-US/default.json +++ b/locales/en-US/default.json @@ -1,6 +1,7 @@ { "arguments": { "choice": "Choose an option", + "emoji": "A Discord emoji", "file": "Upload a file", "gtbhelper": "The current hint", "guild-leaderboard": "$t(arguments.player-leaderboard)",