Adds deployment for icons
This commit is contained in:
parent
154002f6f3
commit
0e10ea3cab
14 changed files with 1449 additions and 53 deletions
|
|
@ -11,6 +11,9 @@ const context = await esbuild.context({
|
|||
platform: 'node',
|
||||
target: 'node10.4',
|
||||
sourcemap: 'linked',
|
||||
loader: {
|
||||
'.node': 'copy',
|
||||
}
|
||||
})
|
||||
|
||||
export default context
|
||||
1295
package-lock.json
generated
1295
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -24,6 +24,7 @@
|
|||
"dotenv": "^16.4.7",
|
||||
"esbuild": "^0.25.0",
|
||||
"log4js": "^6.9.1",
|
||||
"object-path-set": "^1.0.2"
|
||||
"object-path-set": "^1.0.2",
|
||||
"svg2img": "^1.0.0-beta.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
public/icons/calendar-days-solid.svg
Normal file
1
public/icons/calendar-days-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#3d3846" d="M128 0c17.7 0 32 14.3 32 32l0 32 128 0 0-32c0-17.7 14.3-32 32-32s32 14.3 32 32l0 32 48 0c26.5 0 48 21.5 48 48l0 48L0 160l0-48C0 85.5 21.5 64 48 64l48 0 0-32c0-17.7 14.3-32 32-32zM0 192l448 0 0 272c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 192zm64 80l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm128 0l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zM64 400l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zm112 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
public/icons/folder-solid.svg
Normal file
1
public/icons/folder-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z"/></svg>
|
||||
|
After Width: | Height: | Size: 406 B |
1
public/icons/folder-tree-solid.svg
Normal file
1
public/icons/folder-tree-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#9a9996" d="M64 32C64 14.3 49.7 0 32 0S0 14.3 0 32l0 96L0 384c0 35.3 28.7 64 64 64l192 0 0-64L64 384l0-224 192 0 0-64L64 96l0-64zM288 192c0 17.7 14.3 32 32 32l224 0c17.7 0 32-14.3 32-32l0-128c0-17.7-14.3-32-32-32l-98.7 0c-8.5 0-16.6-3.4-22.6-9.4L409.4 9.4c-6-6-14.1-9.4-22.6-9.4L320 0c-17.7 0-32 14.3-32 32l0 160zm0 288c0 17.7 14.3 32 32 32l224 0c17.7 0 32-14.3 32-32l0-128c0-17.7-14.3-32-32-32l-98.7 0c-8.5 0-16.6-3.4-22.6-9.4l-13.3-13.3c-6-6-14.1-9.4-22.6-9.4L320 288c-17.7 0-32 14.3-32 32l0 160z"/></svg>
|
||||
|
After Width: | Height: | Size: 732 B |
|
|
@ -7,6 +7,8 @@ import {GroupRepository} from "../Repositories/GroupRepository";
|
|||
import {PlaydateRepository} from "../Repositories/PlaydateRepository";
|
||||
import {GuildEmojiRoleManager} from "discord.js";
|
||||
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
||||
import {DiscordClient} from "../Discord/DiscordClient";
|
||||
import {IconCache} from "../Icons/IconCache";
|
||||
|
||||
export enum ServiceHint {
|
||||
App,
|
||||
|
|
@ -22,6 +24,12 @@ export class Services {
|
|||
const database = new DatabaseConnection(env.database);
|
||||
container.set<DatabaseConnection>(database);
|
||||
|
||||
const discordClient = new DiscordClient(env.discord.clientId);
|
||||
container.set<DiscordClient>(discordClient);
|
||||
|
||||
const iconCache = new IconCache(discordClient);
|
||||
container.set<IconCache>(iconCache);
|
||||
|
||||
// @ts-ignore
|
||||
configure({
|
||||
appenders: {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
ChatInputCommandInteraction,
|
||||
MessageFlags,
|
||||
Activity,
|
||||
ActivityType
|
||||
ActivityType, REST
|
||||
} from "discord.js";
|
||||
import Commands from "./Commands/Commands";
|
||||
import {Container} from "../Container/Container";
|
||||
|
|
@ -16,17 +16,33 @@ import {UserError} from "./UserError";
|
|||
export class DiscordClient {
|
||||
private readonly client: Client;
|
||||
private commands: Commands;
|
||||
private readonly restClient: REST;
|
||||
|
||||
public get Client (): Client {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
public get Commands(): Commands {
|
||||
return this.commands
|
||||
}
|
||||
|
||||
public get RESTClient(): REST {
|
||||
return this.restClient;
|
||||
}
|
||||
|
||||
public get ApplicationId(): string {
|
||||
return this.applicationId;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly applicationId: string
|
||||
) {
|
||||
this.client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds]
|
||||
})
|
||||
|
||||
this.commands = new Commands();
|
||||
this.restClient = new REST();
|
||||
}
|
||||
|
||||
applyEvents() {
|
||||
|
|
@ -52,6 +68,10 @@ export class DiscordClient {
|
|||
this.client.login(token);
|
||||
}
|
||||
|
||||
connectRESTClient(token: string) {
|
||||
this.restClient.setToken(token);
|
||||
}
|
||||
|
||||
private findCommandMethod(interaction: Interaction) {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const command = this.commands.getCommand(interaction.commandName);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {unwatchFile} from "node:fs";
|
|||
import {UserError} from "../Discord/UserError";
|
||||
import {RuntimeGroupConfiguration} from "./RuntimeGroupConfiguration";
|
||||
import {ChannelId} from "../types/DiscordTypes";
|
||||
import {IconCache} from "../Icons/IconCache";
|
||||
|
||||
type UIElementCollection = Record<string, UIElement>;
|
||||
type UIElement = {
|
||||
|
|
@ -227,6 +228,7 @@ export class GroupConfigurationRenderer {
|
|||
|
||||
private createActionRowBuildersForMenu() : ActionRowBuilder<MessageActionRowComponentBuilder>[] {
|
||||
const {currentCollection, currentElement} = this.findCurrentUI();
|
||||
const icons = Container.get<IconCache>(IconCache.name);
|
||||
|
||||
if (currentElement?.isConfiguration ?? false) {
|
||||
return [
|
||||
|
|
@ -239,9 +241,10 @@ export class GroupConfigurationRenderer {
|
|||
new ActionRowBuilder<ButtonBuilder>()
|
||||
.setComponents(
|
||||
...Object.values(currentCollection).map(elem => new ButtonBuilder()
|
||||
.setLabel(elem.label)
|
||||
.setLabel(` ${elem.label}`)
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setCustomId(GroupConfigurationRenderer.MOVETO_COMMAND + elem.key)
|
||||
.setEmoji(icons.get("folder_tree_solid") ?? '')
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
|
|||
8
source/Icons/DiscordIcons.d.ts
vendored
Normal file
8
source/Icons/DiscordIcons.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
type DiscordIcon = {
|
||||
id: string,
|
||||
name: string,
|
||||
}
|
||||
|
||||
type DiscordIconRequest = {
|
||||
items: DiscordIcon[]
|
||||
}
|
||||
52
source/Icons/IconCache.ts
Normal file
52
source/Icons/IconCache.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import {Routes} from "discord.js";
|
||||
import {DiscordClient} from "../Discord/DiscordClient";
|
||||
|
||||
export class IconCache {
|
||||
private existingIcons: Map<string, string>|null;
|
||||
|
||||
constructor(
|
||||
private readonly client: DiscordClient
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public get(iconName: string): string | null {
|
||||
if (!this.existingIcons?.has(iconName) ?? false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.existingIcons?.get(iconName) ?? null;
|
||||
}
|
||||
|
||||
public async set(iconName: string, pngBuffer: Buffer) {
|
||||
const pngBase64 = pngBuffer.toString("base64");
|
||||
const iconDataUrl = `data:image/png;base64,${pngBase64}`;
|
||||
|
||||
await this.client.RESTClient.post(
|
||||
Routes.applicationEmojis(this.client.ApplicationId),
|
||||
{
|
||||
body: {
|
||||
name: iconName,
|
||||
image: iconDataUrl
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public async populate() {
|
||||
if (this.existingIcons != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingEmojis: DiscordIconRequest = await this.client.RESTClient.get(
|
||||
Routes.applicationEmojis(this.client.ApplicationId)
|
||||
)
|
||||
|
||||
this.existingIcons = new Map<string, string>(
|
||||
existingEmojis.items.map((item) => {
|
||||
return [ item.name, item.id ]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
56
source/Icons/IconDeployer.ts
Normal file
56
source/Icons/IconDeployer.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import {REST, Routes} from "discord.js";
|
||||
import path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import svg2img from "svg2img";
|
||||
import {IconCache} from "./IconCache";
|
||||
|
||||
export class IconDeployer {
|
||||
static ICON_PATH = path.resolve('public/icons')
|
||||
|
||||
constructor(
|
||||
private readonly iconCache: IconCache
|
||||
) {}
|
||||
|
||||
public async ensureExistance() {
|
||||
const directory = await fs.promises.opendir(IconDeployer.ICON_PATH);
|
||||
const addIconPromises: Promise<void>[] = [];
|
||||
for await (let dirname of directory) {
|
||||
const iconName = path.basename(dirname.name, '.svg').replaceAll('-','_');
|
||||
|
||||
if (this.iconCache.get(iconName) !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addIconPromises.push(
|
||||
this.addIcon(path.resolve(dirname.parentPath, dirname.name), iconName)
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(addIconPromises);
|
||||
}
|
||||
|
||||
private async addIcon(iconPath: string, iconName: string) {
|
||||
const svgBuffer = await fs.promises.readFile(iconPath, 'utf-8');
|
||||
const pngBuffer = await new Promise<Buffer>(resolve => {
|
||||
svg2img(
|
||||
svgBuffer,
|
||||
{
|
||||
format: "png",
|
||||
resvg: {
|
||||
fitTo: {
|
||||
mode: "width",
|
||||
value: 128
|
||||
}
|
||||
}
|
||||
},
|
||||
function (err, buffer) {
|
||||
resolve(buffer);
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
await this.iconCache.set(iconName, pngBuffer);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -7,16 +7,23 @@ import {Container} from "./Container/Container";
|
|||
import {ServiceHint, Services} from "./Container/Services";
|
||||
import {Logger} from "log4js";
|
||||
|
||||
const { REST, Routes } = require('discord.js');
|
||||
import {REST, Routes} from 'discord.js';
|
||||
import {IconDeployer} from "./Icons/IconDeployer";
|
||||
import {DiscordClient} from "./Discord/DiscordClient";
|
||||
import {IconCache} from "./Icons/IconCache";
|
||||
|
||||
const container = Container.getInstance();
|
||||
Services.setup(container, ServiceHint.Deploy)
|
||||
|
||||
const commands = new Commands().allCommands;
|
||||
|
||||
const environment = container.get<Environment>(Environment.name);
|
||||
const logger = container.get<Logger>("logger");
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST().setToken(environment.discord.token);
|
||||
|
||||
const client = container.get<DiscordClient>(DiscordClient.name);
|
||||
client.connectRESTClient(environment.discord.token)
|
||||
|
||||
const commands = client.Commands.allCommands;
|
||||
|
||||
// and deploy your commands!
|
||||
(async () => {
|
||||
|
|
@ -29,7 +36,7 @@ const rest = new REST().setToken(environment.discord.token);
|
|||
logger.log(`Started refreshing ${commandInfos.length} application (/) commands.`);
|
||||
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(
|
||||
const data = await client.RESTClient.put(
|
||||
Routes.applicationGuildCommands(environment.discord.clientId, environment.discord.guildId),
|
||||
{ body: commandInfos },
|
||||
);
|
||||
|
|
@ -44,3 +51,15 @@ const rest = new REST().setToken(environment.discord.token);
|
|||
logger.log("Ensuring Database...");
|
||||
const updater = new DatabaseUpdater(container.get<DatabaseConnection>(DatabaseConnection.name));
|
||||
updater.ensureAvaliablity(Definitions);
|
||||
|
||||
logger.log("Ensuring icons...");
|
||||
(async () => {
|
||||
const iconCache = container.get<IconCache>(IconCache.name);
|
||||
await iconCache.populate();
|
||||
|
||||
const deployer = new IconDeployer(
|
||||
iconCache
|
||||
);
|
||||
|
||||
deployer.ensureExistance()
|
||||
})()
|
||||
|
|
@ -3,10 +3,18 @@ import {Environment} from "./Environment";
|
|||
import {Container} from "./Container/Container";
|
||||
import {DatabaseConnection} from "./Database/DatabaseConnection";
|
||||
import {ServiceHint, Services} from "./Container/Services";
|
||||
import {IconCache} from "./Icons/IconCache";
|
||||
|
||||
const container = Container.getInstance();
|
||||
Services.setup(container, ServiceHint.App);
|
||||
(async () => {
|
||||
const env = container.get<Environment>(Environment.name);
|
||||
|
||||
const client = container.get<DiscordClient>(DiscordClient.name);
|
||||
client.connectRESTClient(env.discord.token);
|
||||
|
||||
await container.get<IconCache>(IconCache.name).populate()
|
||||
|
||||
const client = new DiscordClient()
|
||||
client.applyEvents()
|
||||
client.connect(container.get<Environment>(Environment.name).discord.token)
|
||||
client.connect(env.discord.token)
|
||||
})()
|
||||
Loading…
Add table
Add a link
Reference in a new issue