Adds eslint and linted & improved routing for interactions
15
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import { defineConfig } from "eslint/config";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"] },
|
||||||
|
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], languageOptions: { globals: globals.browser } },
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
1599
package-lock.json
generated
|
|
@ -28,5 +28,11 @@
|
||||||
"node-cron": "^4.0.7",
|
"node-cron": "^4.0.7",
|
||||||
"object-path-set": "^1.0.2",
|
"object-path-set": "^1.0.2",
|
||||||
"svg2img": "^1.0.0-beta.2"
|
"svg2img": "^1.0.0-beta.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.29.0",
|
||||||
|
"eslint": "^9.29.0",
|
||||||
|
"globals": "^16.2.0",
|
||||||
|
"typescript-eslint": "^8.34.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
public/icons/angle-left-solid.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
|
||||||
|
After Width: | Height: | Size: 399 B |
1
public/icons/door-open-solid-white.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="#ffffff" d="M320 32c0-9.9-4.5-19.2-12.3-25.2S289.8-1.4 280.2 1l-179.9 45C79 51.3 64 70.5 64 92.5L64 448l-32 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 192 0 32 0 0-32 0-448zM256 256c0 17.7-10.7 32-24 32s-24-14.3-24-32s10.7-32 24-32s24 14.3 24 32zm96-128l96 0 0 352c0 17.7 14.3 32 32 32l64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-32 0 0-320c0-35.3-28.7-64-64-64l-96 0 0 64z"/></svg>
|
||||||
|
After Width: | Height: | Size: 605 B |
|
|
@ -1 +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>
|
<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 fill="white" 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>
|
||||||
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 419 B |
|
|
@ -1 +0,0 @@
|
||||||
<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>
|
|
||||||
|
Before Width: | Height: | Size: 732 B |
1
public/icons/pen-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 fill="white" d="M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z"/></svg>
|
||||||
|
After Width: | Height: | Size: 504 B |
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#FFD43B" d="M72 88a56 56 0 1 1 112 0A56 56 0 1 1 72 88zM64 245.7C54 256.9 48 271.8 48 288s6 31.1 16 42.3l0-84.7zm144.4-49.3C178.7 222.7 160 261.2 160 304c0 34.3 12 65.8 32 90.5l0 21.5c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32-14.3-32-32l0-26.8C26.2 371.2 0 332.7 0 288c0-61.9 50.1-112 112-112l32 0c24 0 46.2 7.5 64.4 20.3zM448 416l0-21.5c20-24.7 32-56.2 32-90.5c0-42.8-18.7-81.3-48.4-107.7C449.8 183.5 472 176 496 176l32 0c61.9 0 112 50.1 112 112c0 44.7-26.2 83.2-64 101.2l0 26.8c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32-14.3-32-32zm8-328a56 56 0 1 1 112 0A56 56 0 1 1 456 88zM576 245.7l0 84.7c10-11.3 16-26.1 16-42.3s-6-31.1-16-42.3zM320 32a64 64 0 1 1 0 128 64 64 0 1 1 0-128zM240 304c0 16.2 6 31 16 42.3l0-84.7c-10 11.3-16 26.1-16 42.3zm144-42.3l0 84.7c10-11.3 16-26.1 16-42.3s-6-31.1-16-42.3zM448 304c0 44.7-26.2 83.2-64 101.2l0 42.8c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32-14.3-32-32l0-42.8c-37.8-18-64-56.5-64-101.2c0-61.9 50.1-112 112-112l32 0c61.9 0 112 50.1 112 112z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,15 +1,16 @@
|
||||||
import {Environment} from "../Environment";
|
import {Environment} from "../Environment";
|
||||||
import {Container} from "./Container";
|
import {Container} from "./Container";
|
||||||
import {DatabaseConnection} from "../Database/DatabaseConnection";
|
import {DatabaseConnection} from "../Database/DatabaseConnection";
|
||||||
import {getLogger, configure, Logger} from "log4js";
|
import {configure, getLogger, Logger} from "log4js";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import {GroupRepository} from "../Repositories/GroupRepository";
|
import {GroupRepository} from "../Repositories/GroupRepository";
|
||||||
import {PlaydateRepository} from "../Repositories/PlaydateRepository";
|
import {PlaydateRepository} from "../Repositories/PlaydateRepository";
|
||||||
import {GuildEmojiRoleManager} from "discord.js";
|
|
||||||
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
||||||
import {DiscordClient} from "../Discord/DiscordClient";
|
import {DiscordClient} from "../Discord/DiscordClient";
|
||||||
import {IconCache} from "../Icons/IconCache";
|
import {IconCache} from "../Icons/IconCache";
|
||||||
import {EventHandler} from "../Events/EventHandler";
|
import {EventHandler} from "../Events/EventHandler";
|
||||||
|
import {InteractionRouter} from "../Discord/InteractionRouter";
|
||||||
|
import Commands from "../Discord/Commands/Commands";
|
||||||
|
|
||||||
export enum ServiceHint {
|
export enum ServiceHint {
|
||||||
App,
|
App,
|
||||||
|
|
@ -22,18 +23,34 @@ export class Services {
|
||||||
env.setup();
|
env.setup();
|
||||||
container.set<Environment>(env);
|
container.set<Environment>(env);
|
||||||
|
|
||||||
|
const logger = this.setupLogger(hint);
|
||||||
|
container.set<Logger>(logger, 'logger');
|
||||||
|
|
||||||
const database = new DatabaseConnection(env.database);
|
const database = new DatabaseConnection(env.database);
|
||||||
container.set<DatabaseConnection>(database);
|
container.set<DatabaseConnection>(database);
|
||||||
|
|
||||||
const discordClient = new DiscordClient(env.discord.clientId);
|
const discordClient = new DiscordClient(
|
||||||
|
env.discord.clientId,
|
||||||
|
new InteractionRouter(new Commands(), logger)
|
||||||
|
);
|
||||||
container.set<DiscordClient>(discordClient);
|
container.set<DiscordClient>(discordClient);
|
||||||
|
|
||||||
const iconCache = new IconCache(discordClient);
|
const iconCache = new IconCache(discordClient);
|
||||||
container.set<IconCache>(iconCache);
|
container.set<IconCache>(iconCache);
|
||||||
|
|
||||||
container.set<EventHandler>(new EventHandler());
|
container.set<EventHandler>(new EventHandler());
|
||||||
|
this.setupRepositories(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static setupRepositories(container: Container) {
|
||||||
|
const db = container.get<DatabaseConnection>(DatabaseConnection.name);
|
||||||
|
container.set<GroupRepository>(new GroupRepository(db));
|
||||||
|
container.set<PlaydateRepository>(new PlaydateRepository(db, container.get<GroupRepository>(GroupRepository.name)))
|
||||||
|
container.set<GroupConfigurationRepository>(new GroupConfigurationRepository(db, container.get<GroupRepository>(GroupRepository.name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private static setupLogger(hint: ServiceHint): Logger {
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
configure({
|
configure({
|
||||||
appenders: {
|
appenders: {
|
||||||
out: { type: "stdout" },
|
out: { type: "stdout" },
|
||||||
|
|
@ -57,17 +74,6 @@ export class Services {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
const logger = getLogger(loggername);
|
return getLogger(loggername);
|
||||||
|
|
||||||
container.set<Logger>(logger, 'logger');
|
|
||||||
|
|
||||||
this.setupRepositories(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static setupRepositories(container: Container) {
|
|
||||||
const db = container.get<DatabaseConnection>(DatabaseConnection.name);
|
|
||||||
container.set<GroupRepository>(new GroupRepository(db));
|
|
||||||
container.set<PlaydateRepository>(new PlaydateRepository(db, container.get<GroupRepository>(GroupRepository.name)))
|
|
||||||
container.set<GroupConfigurationRepository>(new GroupConfigurationRepository(db, container.get<GroupRepository>(GroupRepository.name)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
import {DatabaseEnvironment, Environment} from "../Environment";
|
import {DatabaseEnvironment} from "../Environment";
|
||||||
import Sqlite3 from "better-sqlite3";
|
import Sqlite3 from "better-sqlite3";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import {Container} from "../Container/Container";
|
import {Container} from "../Container/Container";
|
||||||
import {Logger} from "log4js";
|
import {Logger} from "log4js";
|
||||||
|
|
||||||
export class DatabaseConnection {
|
export class DatabaseConnection {
|
||||||
private static connection: DatabaseConnection;
|
|
||||||
|
|
||||||
private database: Sqlite3.Database;
|
private database: Sqlite3.Database;
|
||||||
|
|
||||||
constructor(private readonly env: DatabaseEnvironment) {
|
constructor(env: DatabaseEnvironment) {
|
||||||
this.database = new Database(env.path, {
|
this.database = new Database(env.path, {
|
||||||
nativeBinding: "node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
nativeBinding: "node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
||||||
})
|
})
|
||||||
this.database.pragma('journal_mode = WAL');
|
this.database.pragma('journal_mode = WAL');
|
||||||
}
|
}
|
||||||
|
|
||||||
public execute(query: string, ...args: any[]): Sqlite3.RunResult {
|
public execute(query: string, ...args: unknown[]): Sqlite3.RunResult {
|
||||||
try {
|
try {
|
||||||
const preparedQuery = this.database.prepare(query);
|
const preparedQuery = this.database.prepare(query);
|
||||||
return preparedQuery.run(args);
|
return preparedQuery.run(args);
|
||||||
|
|
@ -26,11 +24,11 @@ export class DatabaseConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetch<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(query: string, ...args: any[]): Result|undefined {
|
public fetch<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(query: string, ...args: unknown[]): Result|undefined {
|
||||||
const preparedQuery = this.database.prepare<BindParameters, Result>(query);
|
const preparedQuery = this.database.prepare<BindParameters, Result>(query);
|
||||||
return preparedQuery.get(args);
|
return preparedQuery.get(args);
|
||||||
}
|
}
|
||||||
public fetchAll<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(query: string, ...args: any[]): Result[] {
|
public fetchAll<BindParameters extends unknown[] | {} = unknown[], Result = unknown>(query: string, ...args: unknown[]): Result[] {
|
||||||
const preparedQuery = this.database.prepare<BindParameters, Result>(query);
|
const preparedQuery = this.database.prepare<BindParameters, Result>(query);
|
||||||
return preparedQuery.all(args);
|
return preparedQuery.all(args);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export class DatabaseUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureTableColumns(definition: DatabaseDefinition) {
|
private ensureTableColumns(definition: DatabaseDefinition) {
|
||||||
const DBSQLColumns = this.database.fetchAll<{}, {name: string, type: string}>(
|
const DBSQLColumns = this.database.fetchAll<object, {name: string, type: string}>(
|
||||||
`PRAGMA table_info("${definition.name}")`
|
`PRAGMA table_info("${definition.name}")`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -76,6 +76,6 @@ export class DatabaseUpdater {
|
||||||
}).join(', ');
|
}).join(', ');
|
||||||
|
|
||||||
const sql = `CREATE TABLE IF NOT EXISTS ${definition.name} (${columnsSQL})`;
|
const sql = `CREATE TABLE IF NOT EXISTS ${definition.name} (${columnsSQL})`;
|
||||||
const result = this.database.execute(sql);
|
this.database.execute(sql);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ export class CommandDeployer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deployAvailableServers() {
|
public async deployAvailableServers() {
|
||||||
const commandInfos = [];
|
const commandInfos: object[] = [];
|
||||||
this.client.Commands.allCommands.forEach((command) => {
|
this.client.Commands.allCommands.forEach((command) => {
|
||||||
commandInfos.push(command.definition().toJSON())
|
commandInfos.push(command.definition().toJSON())
|
||||||
})
|
})
|
||||||
|
|
@ -27,11 +27,10 @@ export class CommandDeployer {
|
||||||
this.logger.log(`Started refreshing ${commandInfos.length} application (/) commands for ${serverId}.`);
|
this.logger.log(`Started refreshing ${commandInfos.length} application (/) commands for ${serverId}.`);
|
||||||
|
|
||||||
// The put method is used to fully refresh all commands in the guild with the current set
|
// The put method is used to fully refresh all commands in the guild with the current set
|
||||||
const data = await this.client.RESTClient.put(
|
await this.client.RESTClient.put(
|
||||||
Routes.applicationGuildCommands(this.client.ApplicationId, serverId),
|
Routes.applicationGuildCommands(this.client.ApplicationId, serverId),
|
||||||
{ body: commandInfos },
|
{ body: commandInfos },
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.log(`Successfully reloaded ${commandInfos.length} application (/) commands for ${serverId}.`);
|
this.logger.log(`Successfully reloaded ${commandInfos.length} application (/) commands for ${serverId}.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import {
|
import {
|
||||||
AutocompleteInteraction,
|
AutocompleteInteraction,
|
||||||
ChatInputCommandInteraction,
|
|
||||||
CommandInteraction,
|
CommandInteraction,
|
||||||
GuildMember, SlashCommandIntegerOption,
|
GuildMember, SlashCommandIntegerOption,
|
||||||
SlashCommandStringOption
|
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import {Container} from "../../Container/Container";
|
import {Container} from "../../Container/Container";
|
||||||
import {GroupRepository} from "../../Repositories/GroupRepository";
|
import {GroupRepository} from "../../Repositories/GroupRepository";
|
||||||
|
|
@ -22,7 +20,7 @@ export class GroupSelection {
|
||||||
public static async handleAutocomplete(interaction: AutocompleteInteraction, onlyLeaders: boolean = false): Promise<void> {
|
public static async handleAutocomplete(interaction: AutocompleteInteraction, onlyLeaders: boolean = false): Promise<void> {
|
||||||
const value = interaction.options.getFocused();
|
const value = interaction.options.getFocused();
|
||||||
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
||||||
let groups = repo.findGroupsByMember(<GuildMember>interaction.member, onlyLeaders);
|
const groups = repo.findGroupsByMember(<GuildMember>interaction.member, onlyLeaders);
|
||||||
await interaction.respond(
|
await interaction.respond(
|
||||||
groups
|
groups
|
||||||
.filter((group) => group.name.startsWith(value))
|
.filter((group) => group.name.startsWith(value))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import {ChatInputCommandInteraction, Interaction, SlashCommandBuilder} from "discord.js";
|
import {ChatInputCommandInteraction, Interaction, SlashCommandBuilder} from "discord.js";
|
||||||
import Commands from "./Commands";
|
|
||||||
|
|
||||||
export interface Command {
|
export interface Command {
|
||||||
definition(): SlashCommandBuilder;
|
definition(): SlashCommandBuilder;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
GuildMember,
|
GuildMember,
|
||||||
EmbedBuilder,
|
EmbedBuilder,
|
||||||
AutocompleteInteraction,
|
AutocompleteInteraction,
|
||||||
roleMention, time, userMention
|
roleMention, time, userMention, GuildMemberRoleManager
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
|
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
|
||||||
import {GroupModel} from "../../Models/GroupModel";
|
import {GroupModel} from "../../Models/GroupModel";
|
||||||
|
|
@ -19,8 +19,8 @@ import {GroupConfigurationRenderer} from "../../Groups/GroupConfigurationRendere
|
||||||
import {GroupConfigurationHandler} from "../../Groups/GroupConfigurationHandler";
|
import {GroupConfigurationHandler} from "../../Groups/GroupConfigurationHandler";
|
||||||
import {GroupConfigurationTransformers} from "../../Groups/GroupConfigurationTransformers";
|
import {GroupConfigurationTransformers} from "../../Groups/GroupConfigurationTransformers";
|
||||||
import {GroupConfigurationRepository} from "../../Repositories/GroupConfigurationRepository";
|
import {GroupConfigurationRepository} from "../../Repositories/GroupConfigurationRepository";
|
||||||
import {IconCache} from "../../Icons/IconCache";
|
|
||||||
import {PlaydateRepository} from "../../Repositories/PlaydateRepository";
|
import {PlaydateRepository} from "../../Repositories/PlaydateRepository";
|
||||||
|
import {Nullable} from "../../types/Nullable";
|
||||||
|
|
||||||
export class GroupCommand implements Command, ChatInteractionCommand, AutocompleteCommand {
|
export class GroupCommand implements Command, ChatInteractionCommand, AutocompleteCommand {
|
||||||
private static GOODBYE_MESSAGES: string[] = [
|
private static GOODBYE_MESSAGES: string[] = [
|
||||||
|
|
@ -36,7 +36,7 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
]
|
]
|
||||||
|
|
||||||
definition(): SlashCommandBuilder {
|
definition(): SlashCommandBuilder {
|
||||||
// @ts-ignore
|
// @ts-expect-error Slash command expects more than needed.
|
||||||
return new SlashCommandBuilder()
|
return new SlashCommandBuilder()
|
||||||
.setName('groups')
|
.setName('groups')
|
||||||
.setDescription(`Manages groups`)
|
.setDescription(`Manages groups`)
|
||||||
|
|
@ -114,7 +114,7 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
throw new UserError("Creating a group for everyone is not permitted!");
|
throw new UserError("Creating a group for everyone is not permitted!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!interaction.member?.roles.cache.has(role?.id) ?? true) {
|
if (!(<Nullable<GuildMemberRoleManager>>interaction.member?.roles)?.cache.has(role?.id)) {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
"You are not part of the role, you try to create a group for.",
|
"You are not part of the role, you try to create a group for.",
|
||||||
"Add yourself to the group or ask your admin to do so."
|
"Add yourself to the group or ask your admin to do so."
|
||||||
|
|
@ -122,6 +122,7 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
}
|
}
|
||||||
|
|
||||||
const validName = this.validateGroupName(name);
|
const validName = this.validateGroupName(name);
|
||||||
|
// @ts-expect-error Is correct, since the valid name can return either true or a string and the error should only be thrown if it's a string.
|
||||||
if (name !== true) {
|
if (name !== true) {
|
||||||
throw new UserError(`Your group name contains one or more invalid character sequences: ${validName}`)
|
throw new UserError(`Your group name contains one or more invalid character sequences: ${validName}`)
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +147,7 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
|
|
||||||
private validateGroupName(name: string): true|string{
|
private validateGroupName(name: string): true|string{
|
||||||
const lowercaseName = name.toLowerCase();
|
const lowercaseName = name.toLowerCase();
|
||||||
for (let invalidcharactersequence of GroupCommand.INVALID_CHARACTER_SEQUENCES) {
|
for (const invalidcharactersequence of GroupCommand.INVALID_CHARACTER_SEQUENCES) {
|
||||||
if (!lowercaseName.includes(invalidcharactersequence)) {
|
if (!lowercaseName.includes(invalidcharactersequence)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -162,8 +163,6 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
|
|
||||||
const playdateRepo = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
const playdateRepo = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
||||||
|
|
||||||
const iconCache = Container.get<IconCache>(IconCache.name);
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("Your groups on this server:")
|
.setTitle("Your groups on this server:")
|
||||||
.setFields(
|
.setFields(
|
||||||
|
|
@ -171,13 +170,13 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
const nextPlaydate = playdateRepo.getNextPlaydateForGroup(group);
|
const nextPlaydate = playdateRepo.getNextPlaydateForGroup(group);
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
`Role: ${iconCache.getEmoji("people_group_solid")} ${roleMention(group.role.roleid)}`,
|
`Role: ${roleMention(group.role.roleid)}`,
|
||||||
`Leader/GM: ${userMention(group.leader.memberid)}`
|
`Leader/GM: ${userMention(group.leader.memberid)}`
|
||||||
];
|
];
|
||||||
|
|
||||||
if (nextPlaydate) {
|
if (nextPlaydate) {
|
||||||
values.push(
|
values.push(
|
||||||
`Next Playdate: ${iconCache.getEmoji("calendar_days_solid")} ${time(nextPlaydate.from_time, "F")}`
|
`Next Playdate: ${time(nextPlaydate.from_time, "F")}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +254,7 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newLeader = <GuildMember>interaction.options.getMember("target", true);
|
const newLeader = <GuildMember>interaction.options.getMember("target");
|
||||||
if (!newLeader.roles.cache.has(group.role.roleid)) {
|
if (!newLeader.roles.cache.has(group.role.roleid)) {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
"Can't transfer leadership: The target member is not part of your group.",
|
"Can't transfer leadership: The target member is not part of your group.",
|
||||||
|
|
@ -268,9 +267,9 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
|
||||||
|
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("Leadership transfered")
|
.setTitle("Leadership transferred")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Leadership was successfully transfered to ${userMention(newLeader.user.id)}`
|
`Leadership was successfully transferred to ${userMention(newLeader.user.id)}`
|
||||||
)
|
)
|
||||||
|
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {SlashCommandBuilder, Interaction, CommandInteraction} from "discord.js";
|
import {SlashCommandBuilder, CommandInteraction} from "discord.js";
|
||||||
import {Command} from "./Command";
|
import {Command} from "./Command";
|
||||||
|
|
||||||
export class HelloWorldCommand implements Command {
|
export class HelloWorldCommand implements Command {
|
||||||
|
|
@ -6,7 +6,7 @@ export class HelloWorldCommand implements Command {
|
||||||
'Hello :)',
|
'Hello :)',
|
||||||
'zzzZ... ZzzzZ... huh? I am awake. I am awake!',
|
'zzzZ... ZzzzZ... huh? I am awake. I am awake!',
|
||||||
'Roll for initiative!',
|
'Roll for initiative!',
|
||||||
'I was an adventurerer like you...',
|
'I was an adventurer like you...',
|
||||||
'Hello :) How are you?',
|
'Hello :) How are you?',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,16 @@
|
||||||
import {
|
import {
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
Interaction,
|
|
||||||
CommandInteraction,
|
CommandInteraction,
|
||||||
AutocompleteInteraction,
|
AutocompleteInteraction,
|
||||||
GuildMember,
|
EmbedBuilder, MessageFlags, ChatInputCommandInteraction, time
|
||||||
EmbedBuilder, MessageFlags, ChatInputCommandInteraction, ModalSubmitFields, time, User
|
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
|
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
|
||||||
import {Container} from "../../Container/Container";
|
import {Container} from "../../Container/Container";
|
||||||
import {GroupRepository} from "../../Repositories/GroupRepository";
|
|
||||||
import {GroupSelection} from "../CommandPartials/GroupSelection";
|
import {GroupSelection} from "../CommandPartials/GroupSelection";
|
||||||
import {setFlagsFromString} from "node:v8";
|
|
||||||
import {UserError} from "../UserError";
|
import {UserError} from "../UserError";
|
||||||
import Playdate from "../../Database/tables/Playdate";
|
|
||||||
import {PlaydateModel} from "../../Models/PlaydateModel";
|
import {PlaydateModel} from "../../Models/PlaydateModel";
|
||||||
import {PlaydateRepository} from "../../Repositories/PlaydateRepository";
|
import {PlaydateRepository} from "../../Repositories/PlaydateRepository";
|
||||||
import {GroupModel} from "../../Models/GroupModel";
|
import {GroupModel} from "../../Models/GroupModel";
|
||||||
import playdate from "../../Database/tables/Playdate";
|
|
||||||
|
|
||||||
export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInteractionCommand {
|
export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInteractionCommand {
|
||||||
static REGEX = [
|
static REGEX = [
|
||||||
|
|
@ -24,7 +18,7 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
|
||||||
]
|
]
|
||||||
|
|
||||||
definition(): SlashCommandBuilder {
|
definition(): SlashCommandBuilder {
|
||||||
// @ts-ignore
|
// @ts-expect-error Command builder is improperly marked as incomplete.
|
||||||
return new SlashCommandBuilder()
|
return new SlashCommandBuilder()
|
||||||
.setName("playdates")
|
.setName("playdates")
|
||||||
.setDescription("Manage your playdates")
|
.setDescription("Manage your playdates")
|
||||||
|
|
@ -106,7 +100,7 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
|
||||||
to_time: new Date(toDate),
|
to_time: new Date(toDate),
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = playdateRepo.create(playdate);
|
playdateRepo.create(playdate);
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("Created a play-date.")
|
.setTitle("Created a play-date.")
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,22 @@ import {
|
||||||
Client,
|
Client,
|
||||||
GatewayIntentBits,
|
GatewayIntentBits,
|
||||||
Events,
|
Events,
|
||||||
Interaction,
|
ActivityType, REST
|
||||||
ChatInputCommandInteraction,
|
|
||||||
MessageFlags,
|
|
||||||
Activity,
|
|
||||||
ActivityType, REST, inlineCode
|
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import Commands from "./Commands/Commands";
|
import Commands from "./Commands/Commands";
|
||||||
import {Container} from "../Container/Container";
|
import {Container} from "../Container/Container";
|
||||||
import {Logger} from "log4js";
|
import {Logger} from "log4js";
|
||||||
import {UserError} from "./UserError";
|
import {InteractionRouter} from "./InteractionRouter";
|
||||||
|
|
||||||
export class DiscordClient {
|
export class DiscordClient {
|
||||||
private readonly client: Client;
|
private readonly client: Client;
|
||||||
private commands: Commands;
|
|
||||||
private readonly restClient: REST;
|
|
||||||
|
|
||||||
public get Client (): Client {
|
public get Client(): Client {
|
||||||
return this.client;
|
return this.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get Commands(): Commands {
|
public get Commands(): Commands {
|
||||||
return this.commands
|
return this.router.commands
|
||||||
}
|
}
|
||||||
|
|
||||||
public get RESTClient(): REST {
|
public get RESTClient(): REST {
|
||||||
|
|
@ -35,18 +29,21 @@ export class DiscordClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly applicationId: string
|
private readonly applicationId: string,
|
||||||
|
private readonly router: InteractionRouter,
|
||||||
|
private readonly restClient: REST = new REST()
|
||||||
) {
|
) {
|
||||||
this.client = new Client({
|
this.client = new Client({
|
||||||
intents: [GatewayIntentBits.Guilds]
|
intents: [GatewayIntentBits.Guilds]
|
||||||
})
|
})
|
||||||
|
|
||||||
this.commands = new Commands();
|
|
||||||
this.restClient = new REST();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyEvents() {
|
applyEvents() {
|
||||||
this.client.once(Events.ClientReady, () => {
|
this.client.once(Events.ClientReady, () => {
|
||||||
|
if (!this.client.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Container.get<Logger>("logger").info(`Ready! Logged in as ${this.client.user.tag}`);
|
Container.get<Logger>("logger").info(`Ready! Logged in as ${this.client.user.tag}`);
|
||||||
this.client.user.setActivity('your PnP playdates', {
|
this.client.user.setActivity('your PnP playdates', {
|
||||||
type: ActivityType.Watching,
|
type: ActivityType.Watching,
|
||||||
|
|
@ -57,15 +54,7 @@ export class DiscordClient {
|
||||||
Container.get<Logger>("logger").info("Joined Guild?")
|
Container.get<Logger>("logger").info("Joined Guild?")
|
||||||
})
|
})
|
||||||
|
|
||||||
this.client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
this.client.on(Events.InteractionCreate, this.router.route.bind(this.router));
|
||||||
const method = this.findCommandMethod(interaction);
|
|
||||||
if (!method) {
|
|
||||||
Container.get<Logger>("logger").error(`Could not find method for '${interaction.commandName}'`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await method();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(token: string) {
|
connect(token: string) {
|
||||||
|
|
@ -75,69 +64,4 @@ export class DiscordClient {
|
||||||
connectRESTClient(token: string) {
|
connectRESTClient(token: string) {
|
||||||
this.restClient.setToken(token);
|
this.restClient.setToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findCommandMethod(interaction: Interaction) {
|
|
||||||
if (interaction.isChatInputCommand()) {
|
|
||||||
const command = this.commands.getCommand(interaction.commandName);
|
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!('execute' in command)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return async () => {
|
|
||||||
Container.get<Logger>("logger").debug(`Found chat command ${interaction.commandName}: running...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await command.execute(interaction)
|
|
||||||
}
|
|
||||||
catch (e: any) {
|
|
||||||
Container.get<Logger>("logger").error(e)
|
|
||||||
|
|
||||||
let userMessage = ":x: There was an error while executing this command!";
|
|
||||||
if (e.constructor.name === UserError.name) {
|
|
||||||
userMessage = `:x: \`${e.message}\` - Please validate your request!`
|
|
||||||
if (e.tryInstead) {
|
|
||||||
userMessage += `
|
|
||||||
|
|
||||||
You can try the following:
|
|
||||||
${inlineCode(e.tryInstead)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (interaction.replied || interaction.deferred) {
|
|
||||||
await interaction.followUp({ content: userMessage, flags: MessageFlags.Ephemeral });
|
|
||||||
} else {
|
|
||||||
await interaction.reply({ content: userMessage, flags: MessageFlags.Ephemeral });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interaction.isAutocomplete()) {
|
|
||||||
const command = this.commands.getCommand(interaction.commandName);
|
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!('handleAutocomplete' in command)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return async () => {
|
|
||||||
Container.get<Logger>("logger").debug(`Found command ${interaction.commandName} for autocomplete: handling...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await command.handleAutocomplete(interaction);
|
|
||||||
} catch (e: any) {
|
|
||||||
Container.get<Logger>('logger').error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
109
source/Discord/InteractionRouter.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
import {
|
||||||
|
AutocompleteInteraction,
|
||||||
|
ChatInputCommandInteraction,
|
||||||
|
inlineCode,
|
||||||
|
Interaction,
|
||||||
|
MessageFlags,
|
||||||
|
} from "discord.js";
|
||||||
|
import Commands from "./Commands/Commands";
|
||||||
|
import {Logger} from "log4js";
|
||||||
|
import {UserError} from "./UserError";
|
||||||
|
import {Container} from "../Container/Container";
|
||||||
|
|
||||||
|
enum InteractionRoutingType {
|
||||||
|
Unrouted,
|
||||||
|
Command,
|
||||||
|
AutoComplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InteractionRouter {
|
||||||
|
constructor(
|
||||||
|
public readonly commands: Commands,
|
||||||
|
public readonly logger: Logger
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async route(interaction: Interaction) {
|
||||||
|
const interactionType = this.findInteractionType(interaction);
|
||||||
|
|
||||||
|
switch (interactionType) {
|
||||||
|
case InteractionRoutingType.Unrouted:
|
||||||
|
this.logger.debug("Unroutable interaction found...")
|
||||||
|
break;
|
||||||
|
case InteractionRoutingType.Command:
|
||||||
|
await this.handleCommand(<ChatInputCommandInteraction>interaction);
|
||||||
|
break;
|
||||||
|
case InteractionRoutingType.AutoComplete:
|
||||||
|
await this.handleAutocomplete(<AutocompleteInteraction>interaction)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findInteractionType(interaction: Interaction): InteractionRoutingType {
|
||||||
|
if (interaction.isChatInputCommand()) {
|
||||||
|
return InteractionRoutingType.Command;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interaction.isAutocomplete()) {
|
||||||
|
return InteractionRoutingType.AutoComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InteractionRoutingType.Unrouted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleCommand(interaction: ChatInputCommandInteraction) {
|
||||||
|
try {
|
||||||
|
const command = this.commands.getCommand(interaction.commandName);
|
||||||
|
if (!command) {
|
||||||
|
throw new UserError(`Requested command not found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('execute' in command)) {
|
||||||
|
throw new UserError(`Requested command is not setup for a chat command.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`Found chat command ${interaction.commandName}: running...`);
|
||||||
|
|
||||||
|
await command.execute?.call(command, interaction);
|
||||||
|
} catch (e: any) {
|
||||||
|
this.logger.error(e)
|
||||||
|
|
||||||
|
let userMessage = ":x: There was an error while executing this command!";
|
||||||
|
if (e.constructor.name === UserError.name) {
|
||||||
|
userMessage = `:x: \`${e.message}\` - Please validate your request!`
|
||||||
|
if (e.tryInstead) {
|
||||||
|
userMessage += `
|
||||||
|
|
||||||
|
You can try the following:
|
||||||
|
${inlineCode(e.tryInstead)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (interaction.replied || interaction.deferred) {
|
||||||
|
await interaction.followUp({ content: userMessage, flags: MessageFlags.Ephemeral });
|
||||||
|
} else {
|
||||||
|
await interaction.reply({ content: userMessage, flags: MessageFlags.Ephemeral });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleAutocomplete(interaction: AutocompleteInteraction) {
|
||||||
|
const command = this.commands.getCommand(interaction.commandName);
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('handleAutocomplete' in command)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Container.get<Logger>("logger").debug(`Found command ${interaction.commandName} for autocomplete: handling...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await command.handleAutocomplete?.call(command, interaction);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
Container.get<Logger>('logger').error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ export class ElementCreatedEvent<T extends Model = Model> {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly tableName: string,
|
public readonly tableName: string,
|
||||||
public readonly instanceValues: Partial<T>,
|
public readonly instanceValues: Partial<T>,
|
||||||
public readonly instanceId: number
|
public readonly instanceId: number|bigint
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
import {Nullable} from "../types/Nullable";
|
import {Class} from "../types/Class";
|
||||||
import {Class, ClassNamed} from "../types/Class";
|
|
||||||
|
|
||||||
export type EventConfiguration = {
|
export type EventConfiguration = {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import {ElementCreatedEvent} from "../ElementCreatedEvent";
|
import {ElementCreatedEvent} from "../ElementCreatedEvent";
|
||||||
import {DefaultHandler} from "../DefaultEvents";
|
|
||||||
import {PlaydateModel} from "../../Models/PlaydateModel";
|
import {PlaydateModel} from "../../Models/PlaydateModel";
|
||||||
import PlaydateTableConfiguration from "../../Database/tables/Playdate";
|
import PlaydateTableConfiguration from "../../Database/tables/Playdate";
|
||||||
import {EmbedBuilder, roleMention, time} from "discord.js";
|
import {EmbedBuilder, roleMention, time} from "discord.js";
|
||||||
import {ArrayUtils} from "../../Utilities/ArrayUtils";
|
import {ArrayUtils} from "../../Utilities/ArrayUtils";
|
||||||
import {GroupConfigurationHandler} from "../../Groups/GroupConfigurationHandler";
|
import {GroupConfigurationHandler} from "../../Groups/GroupConfigurationHandler";
|
||||||
import {Container} from "../../Container/Container";
|
import {Container} from "../../Container/Container";
|
||||||
import {GroupConfigurationRenderer} from "../../Groups/GroupConfigurationRenderer";
|
|
||||||
import {GroupConfigurationRepository} from "../../Repositories/GroupConfigurationRepository";
|
import {GroupConfigurationRepository} from "../../Repositories/GroupConfigurationRepository";
|
||||||
import {DiscordClient} from "../../Discord/DiscordClient";
|
import {DiscordClient} from "../../Discord/DiscordClient";
|
||||||
|
|
||||||
|
|
@ -37,7 +35,7 @@ export async function sendCreatedNotificationEventHandler(event: ElementCreatedE
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = await Container.get<DiscordClient>(DiscordClient.name).Client.channels.fetch(targetChannel)
|
const channel = await Container.get<DiscordClient>(DiscordClient.name).Client.channels.fetch(<string>targetChannel)
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import {CronExpression, Event, EventConfiguration, TimedEvent} from "./EventHandler";
|
import {EventConfiguration, TimedEvent} from "./EventHandler";
|
||||||
import {Container} from "../Container/Container";
|
import {Container} from "../Container/Container";
|
||||||
import Playdate from "../Database/tables/Playdate";
|
|
||||||
import {PlaydateRepository} from "../Repositories/PlaydateRepository";
|
import {PlaydateRepository} from "../Repositories/PlaydateRepository";
|
||||||
import {GroupConfigurationHandler} from "../Groups/GroupConfigurationHandler";
|
import {GroupConfigurationHandler} from "../Groups/GroupConfigurationHandler";
|
||||||
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
||||||
import {PlaydateModel} from "../Models/PlaydateModel";
|
import {PlaydateModel} from "../Models/PlaydateModel";
|
||||||
import {ChannelId} from "../types/DiscordTypes";
|
import {ChannelId} from "../types/DiscordTypes";
|
||||||
import {DiscordClient} from "../Discord/DiscordClient";
|
import {DiscordClient} from "../Discord/DiscordClient";
|
||||||
import {EmbedBuilder, MessageFlags, roleMention, time} from "discord.js";
|
import {EmbedBuilder, roleMention, time} from "discord.js";
|
||||||
import {ArrayUtils} from "../Utilities/ArrayUtils";
|
import {ArrayUtils} from "../Utilities/ArrayUtils";
|
||||||
|
|
||||||
export class ReminderEvent implements TimedEvent {
|
export class ReminderEvent implements TimedEvent {
|
||||||
|
|
@ -27,9 +26,9 @@ export class ReminderEvent implements TimedEvent {
|
||||||
name: "Reminders",
|
name: "Reminders",
|
||||||
}
|
}
|
||||||
|
|
||||||
cronExpression: CronExpression = "0 9 * * *"
|
cronExpression: string = "0 9 * * *"
|
||||||
|
|
||||||
private groupConfigurationRepository: GroupConfigurationRepository
|
private readonly groupConfigurationRepository: GroupConfigurationRepository
|
||||||
private playdateRepository: PlaydateRepository
|
private playdateRepository: PlaydateRepository
|
||||||
private discordClient: DiscordClient
|
private discordClient: DiscordClient
|
||||||
|
|
||||||
|
|
@ -72,13 +71,13 @@ export class ReminderEvent implements TimedEvent {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sendReminder(playdate, targetChannel, config.locale);
|
return this.sendReminder(playdate, targetChannel);
|
||||||
}, this)
|
}, this)
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendReminder(playdate: PlaydateModel, targetChannel: ChannelId, locale: Intl.Locale) {
|
private async sendReminder(playdate: PlaydateModel, targetChannel: ChannelId) {
|
||||||
if (!playdate.group) {
|
if (!playdate.group) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import {RuntimeGroupConfiguration} from "./RuntimeGroupConfiguration";
|
||||||
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
||||||
import {GroupModel} from "../Models/GroupModel";
|
import {GroupModel} from "../Models/GroupModel";
|
||||||
import {GroupConfigurationResult, GroupConfigurationTransformers} from "./GroupConfigurationTransformers";
|
import {GroupConfigurationResult, GroupConfigurationTransformers} from "./GroupConfigurationTransformers";
|
||||||
// @ts-ignore
|
// @ts-expect-error set-path is provided
|
||||||
import setPath from 'object-path-set';
|
import setPath from 'object-path-set';
|
||||||
import deepmerge from "deepmerge";
|
import deepmerge from "deepmerge";
|
||||||
import {Nullable} from "../types/Nullable";
|
import {Nullable} from "../types/Nullable";
|
||||||
|
// @ts-expect-error Any is fine
|
||||||
import {isPlainObject} from "is-plain-object";
|
import {isPlainObject} from "is-plain-object";
|
||||||
|
|
||||||
export class GroupConfigurationHandler {
|
export class GroupConfigurationHandler {
|
||||||
|
|
|
||||||
|
|
@ -2,41 +2,26 @@ import {GroupConfigurationTransformers, TransformerType} from "./GroupConfigurat
|
||||||
import {GroupConfigurationHandler} from "./GroupConfigurationHandler";
|
import {GroupConfigurationHandler} from "./GroupConfigurationHandler";
|
||||||
import {
|
import {
|
||||||
ActionRowBuilder,
|
ActionRowBuilder,
|
||||||
AnyComponentBuilder, AnySelectMenuInteraction,
|
AnySelectMenuInteraction,
|
||||||
APISelectMenuComponent,
|
|
||||||
ButtonBuilder,
|
ButtonBuilder,
|
||||||
ButtonStyle, channelMention,
|
ButtonStyle, channelMention,
|
||||||
ChannelSelectMenuBuilder, ChannelSelectMenuInteraction,
|
ChannelSelectMenuBuilder, ChannelType,
|
||||||
ChannelType,
|
ChatInputCommandInteraction, EmbedBuilder, inlineCode, Interaction,
|
||||||
ChatInputCommandInteraction, codeBlock,
|
|
||||||
EmbedBuilder, inlineCode,
|
|
||||||
InteractionCallbackResponse,
|
|
||||||
InteractionEditReplyOptions,
|
|
||||||
InteractionReplyOptions,
|
InteractionReplyOptions,
|
||||||
InteractionUpdateOptions, italic, MessageFlags,
|
InteractionUpdateOptions, italic, MessageFlags,
|
||||||
SelectMenuBuilder,
|
|
||||||
StringSelectMenuBuilder,
|
StringSelectMenuBuilder,
|
||||||
StringSelectMenuOptionBuilder, TextBasedChannel,
|
StringSelectMenuOptionBuilder, UserSelectMenuBuilder
|
||||||
UserSelectMenuBuilder
|
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import {Logger} from "log4js";
|
import {Logger} from "log4js";
|
||||||
import {Container} from "../Container/Container";
|
import {Container} from "../Container/Container";
|
||||||
import {Nullable} from "../types/Nullable";
|
import {Nullable} from "../types/Nullable";
|
||||||
import GroupConfiguration from "../Database/tables/GroupConfiguration";
|
|
||||||
import {
|
import {
|
||||||
BaseSelectMenuBuilder,
|
|
||||||
MentionableSelectMenuBuilder,
|
MentionableSelectMenuBuilder,
|
||||||
MessageActionRowComponentBuilder,
|
MessageActionRowComponentBuilder,
|
||||||
RoleSelectMenuBuilder
|
RoleSelectMenuBuilder
|
||||||
} from "@discordjs/builders";
|
} from "@discordjs/builders";
|
||||||
import {unwatchFile} from "node:fs";
|
|
||||||
import {UserError} from "../Discord/UserError";
|
|
||||||
import {RuntimeGroupConfiguration} from "./RuntimeGroupConfiguration";
|
|
||||||
import {ChannelId} from "../types/DiscordTypes";
|
import {ChannelId} from "../types/DiscordTypes";
|
||||||
import {IconCache} from "../Icons/IconCache";
|
import {IconCache} from "../Icons/IconCache";
|
||||||
import {ifError} from "node:assert";
|
|
||||||
import {DiscordClient} from "../Discord/DiscordClient";
|
|
||||||
import {channel} from "node:diagnostics_channel";
|
|
||||||
|
|
||||||
type UIElementCollection = Record<string, UIElement>;
|
type UIElementCollection = Record<string, UIElement>;
|
||||||
type UIElement = {
|
type UIElement = {
|
||||||
|
|
@ -104,7 +89,7 @@ export class GroupConfigurationRenderer {
|
||||||
let response = await interaction.reply(this.getReplyOptions());
|
let response = await interaction.reply(this.getReplyOptions());
|
||||||
let exit = false;
|
let exit = false;
|
||||||
let eventResponse;
|
let eventResponse;
|
||||||
const filter = i => i.user.id === interaction.user.id;
|
const filter = (i: Interaction) => i.user.id === interaction.user.id;
|
||||||
do {
|
do {
|
||||||
|
|
||||||
if (eventResponse) {
|
if (eventResponse) {
|
||||||
|
|
@ -117,7 +102,7 @@ export class GroupConfigurationRenderer {
|
||||||
filter: filter,
|
filter: filter,
|
||||||
time: 60_000
|
time: 60_000
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (_: unknown) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +124,7 @@ export class GroupConfigurationRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventResponse.customId.startsWith(GroupConfigurationRenderer.SETVALUE_COMMAND)) {
|
if (eventResponse.customId.startsWith(GroupConfigurationRenderer.SETVALUE_COMMAND)) {
|
||||||
this.handleSelection(eventResponse);
|
this.handleSelection(<AnySelectMenuInteraction>eventResponse);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +135,7 @@ export class GroupConfigurationRenderer {
|
||||||
await eventResponse.update(
|
await eventResponse.update(
|
||||||
this.getReplyOptions()
|
this.getReplyOptions()
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
|
|
||||||
}
|
}
|
||||||
await eventResponse.deleteReply();
|
await eventResponse.deleteReply();
|
||||||
|
|
@ -170,6 +155,7 @@ export class GroupConfigurationRenderer {
|
||||||
|
|
||||||
private getReplyOptions(): InteractionUpdateOptions & InteractionReplyOptions & { withResponse: true } {
|
private getReplyOptions(): InteractionUpdateOptions & InteractionReplyOptions & { withResponse: true } {
|
||||||
const embed = this.createEmbed();
|
const embed = this.createEmbed();
|
||||||
|
const icons = Container.get<IconCache>(IconCache.name);
|
||||||
embed.setAuthor({
|
embed.setAuthor({
|
||||||
name: "/ " + this.breadcrumbs.join(" / ")
|
name: "/ " + this.breadcrumbs.join(" / ")
|
||||||
});
|
});
|
||||||
|
|
@ -177,7 +163,8 @@ export class GroupConfigurationRenderer {
|
||||||
const exitButton = new ButtonBuilder()
|
const exitButton = new ButtonBuilder()
|
||||||
.setLabel("Exit")
|
.setLabel("Exit")
|
||||||
.setStyle(ButtonStyle.Danger)
|
.setStyle(ButtonStyle.Danger)
|
||||||
.setCustomId("exit");
|
.setCustomId("exit")
|
||||||
|
.setEmoji(icons.get("door_open_solid_white") ?? '');
|
||||||
|
|
||||||
const actionrow = new ActionRowBuilder<ButtonBuilder>()
|
const actionrow = new ActionRowBuilder<ButtonBuilder>()
|
||||||
|
|
||||||
|
|
@ -185,7 +172,8 @@ export class GroupConfigurationRenderer {
|
||||||
const backButton = new ButtonBuilder()
|
const backButton = new ButtonBuilder()
|
||||||
.setLabel("Back")
|
.setLabel("Back")
|
||||||
.setStyle(ButtonStyle.Secondary)
|
.setStyle(ButtonStyle.Secondary)
|
||||||
.setCustomId(GroupConfigurationRenderer.MOVEBACK_COMMAND);
|
.setCustomId(GroupConfigurationRenderer.MOVEBACK_COMMAND)
|
||||||
|
.setEmoji(icons.get("angle_left_solid") ?? '');
|
||||||
|
|
||||||
actionrow.addComponents(backButton)
|
actionrow.addComponents(backButton)
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +189,7 @@ export class GroupConfigurationRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createEmbed(): EmbedBuilder {
|
private createEmbed(): EmbedBuilder {
|
||||||
const {currentCollection, currentElement} = this.findCurrentUI();
|
const {currentElement} = this.findCurrentUI();
|
||||||
|
|
||||||
if (currentElement === null) {
|
if (currentElement === null) {
|
||||||
return new EmbedBuilder()
|
return new EmbedBuilder()
|
||||||
|
|
@ -224,7 +212,7 @@ export class GroupConfigurationRenderer {
|
||||||
|
|
||||||
private getCurrentValueAsUI(): string {
|
private getCurrentValueAsUI(): string {
|
||||||
const path = this.breadcrumbs.join(".");
|
const path = this.breadcrumbs.join(".");
|
||||||
let value = this.configurationHandler.getConfigurationByPath(path);
|
const value = this.configurationHandler.getConfigurationByPath(path);
|
||||||
|
|
||||||
if (value === undefined) return italic("None");
|
if (value === undefined) return italic("None");
|
||||||
|
|
||||||
|
|
@ -263,7 +251,7 @@ export class GroupConfigurationRenderer {
|
||||||
if (currentElement?.isConfiguration ?? false) {
|
if (currentElement?.isConfiguration ?? false) {
|
||||||
return [
|
return [
|
||||||
new ActionRowBuilder<ChannelSelectMenuBuilder | MentionableSelectMenuBuilder | RoleSelectMenuBuilder | StringSelectMenuBuilder | UserSelectMenuBuilder>()
|
new ActionRowBuilder<ChannelSelectMenuBuilder | MentionableSelectMenuBuilder | RoleSelectMenuBuilder | StringSelectMenuBuilder | UserSelectMenuBuilder>()
|
||||||
.addComponents(this.getSelectForBreadcrumbs(<UIElement>currentElement))
|
.addComponents(this.getSelectForBreadcrumbs())
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,13 +262,13 @@ export class GroupConfigurationRenderer {
|
||||||
.setLabel(` ${elem.label}`)
|
.setLabel(` ${elem.label}`)
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setCustomId(GroupConfigurationRenderer.MOVETO_COMMAND + elem.key)
|
.setCustomId(GroupConfigurationRenderer.MOVETO_COMMAND + elem.key)
|
||||||
.setEmoji(icons.get("folder_tree_solid") ?? '')
|
.setEmoji(icons.get(elem.isConfiguration ? 'pen_solid' : "folder_solid") ?? '')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSelectForBreadcrumbs(currentElement: UIElement): ChannelSelectMenuBuilder | MentionableSelectMenuBuilder | RoleSelectMenuBuilder | StringSelectMenuBuilder | UserSelectMenuBuilder {
|
private getSelectForBreadcrumbs(): ChannelSelectMenuBuilder | MentionableSelectMenuBuilder | RoleSelectMenuBuilder | StringSelectMenuBuilder | UserSelectMenuBuilder {
|
||||||
const breadcrumbPath = this.breadcrumbs.join('.')
|
const breadcrumbPath = this.breadcrumbs.join('.')
|
||||||
const transformerType = this.transformers.getTransformerType(breadcrumbPath);
|
const transformerType = this.transformers.getTransformerType(breadcrumbPath);
|
||||||
if (transformerType === undefined) {
|
if (transformerType === undefined) {
|
||||||
|
|
@ -300,7 +288,7 @@ export class GroupConfigurationRenderer {
|
||||||
.setCustomId(GroupConfigurationRenderer.SETVALUE_COMMAND + breadcrumbPath)
|
.setCustomId(GroupConfigurationRenderer.SETVALUE_COMMAND + breadcrumbPath)
|
||||||
.setOptions(
|
.setOptions(
|
||||||
options.map(intl => new StringSelectMenuOptionBuilder()
|
options.map(intl => new StringSelectMenuOptionBuilder()
|
||||||
.setLabel(displaynames.of(intl))
|
.setLabel(displaynames.of(intl) ?? '')
|
||||||
.setValue(intl)
|
.setValue(intl)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import {ChannelId} from "../types/DiscordTypes";
|
import {ChannelId} from "../types/DiscordTypes";
|
||||||
import {GroupConfigurationModel} from "../Models/GroupConfigurationModel";
|
import {GroupConfigurationModel} from "../Models/GroupConfigurationModel";
|
||||||
import {config} from "dotenv";
|
|
||||||
import {transform} from "esbuild";
|
|
||||||
import {Nullable} from "../types/Nullable";
|
import {Nullable} from "../types/Nullable";
|
||||||
import {ArrayUtils} from "../Utilities/ArrayUtils";
|
import {ArrayUtils} from "../Utilities/ArrayUtils";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {formatEmoji, Routes, Snowflake} from "discord.js";
|
import {formatEmoji, Routes, Snowflake} from "discord.js";
|
||||||
import {DiscordClient} from "../Discord/DiscordClient";
|
import {DiscordClient} from "../Discord/DiscordClient";
|
||||||
|
import {Nullable} from "../types/Nullable";
|
||||||
|
|
||||||
export class IconCache {
|
export class IconCache {
|
||||||
private existingIcons: Map<string, string> | undefined;
|
private existingIcons: Map<string, string> | undefined;
|
||||||
|
|
@ -22,7 +23,7 @@ export class IconCache {
|
||||||
const id = this.get(iconName);
|
const id = this.get(iconName);
|
||||||
|
|
||||||
return formatEmoji({
|
return formatEmoji({
|
||||||
id: id,
|
id,
|
||||||
name: iconName
|
name: iconName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -47,10 +48,14 @@ export class IconCache {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingEmojis: DiscordIconRequest = await this.client.RESTClient.get(
|
const existingEmojis: Nullable<DiscordIconRequest> = await this.client.RESTClient.get(
|
||||||
Routes.applicationEmojis(this.client.ApplicationId)
|
Routes.applicationEmojis(this.client.ApplicationId)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!existingEmojis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.existingIcons = new Map<string, string>(
|
this.existingIcons = new Map<string, string>(
|
||||||
existingEmojis.items.map((item) => {
|
existingEmojis.items.map((item) => {
|
||||||
return [ item.name, item.id ]
|
return [ item.name, item.id ]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import {REST, Routes} from "discord.js";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import svg2img from "svg2img";
|
import svg2img from "svg2img";
|
||||||
|
|
@ -14,7 +13,7 @@ export class IconDeployer {
|
||||||
public async ensureExistance() {
|
public async ensureExistance() {
|
||||||
const directory = await fs.promises.opendir(IconDeployer.ICON_PATH);
|
const directory = await fs.promises.opendir(IconDeployer.ICON_PATH);
|
||||||
const addIconPromises: Promise<void>[] = [];
|
const addIconPromises: Promise<void>[] = [];
|
||||||
for await (let dirname of directory) {
|
for await (const dirname of directory) {
|
||||||
const iconName = path.basename(dirname.name, '.svg').replaceAll('-','_');
|
const iconName = path.basename(dirname.name, '.svg').replaceAll('-','_');
|
||||||
|
|
||||||
if (this.iconCache.get(iconName) !== null) {
|
if (this.iconCache.get(iconName) !== null) {
|
||||||
|
|
@ -43,7 +42,7 @@ export class IconDeployer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function (err, buffer) {
|
function (_err, buffer) {
|
||||||
resolve(buffer);
|
resolve(buffer);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import {Repository} from "./Repository";
|
||||||
import {GroupModel} from "../Models/GroupModel";
|
import {GroupModel} from "../Models/GroupModel";
|
||||||
import Groups, {DBGroup} from "../Database/tables/Groups";
|
import Groups, {DBGroup} from "../Database/tables/Groups";
|
||||||
import {DatabaseConnection} from "../Database/DatabaseConnection";
|
import {DatabaseConnection} from "../Database/DatabaseConnection";
|
||||||
import {CacheType, CacheTypeReducer, Guild, GuildMember, GuildMemberRoleManager} from "discord.js";
|
import {GuildMember} from "discord.js";
|
||||||
import {Nullable} from "../types/Nullable";
|
import {Nullable} from "../types/Nullable";
|
||||||
import {PlaydateRepository} from "./PlaydateRepository";
|
import {PlaydateRepository} from "./PlaydateRepository";
|
||||||
import {Container} from "../Container/Container";
|
import {Container} from "../Container/Container";
|
||||||
|
|
@ -33,7 +33,7 @@ export class GroupRepository extends Repository<GroupModel, DBGroup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public findGroupsByRoles(server: string, roleIds: string[]): GroupModel[] {
|
public findGroupsByRoles(server: string, roleIds: string[]): GroupModel[] {
|
||||||
const template = roleIds.map(roleId => '?').join(',');
|
const template = roleIds.map(_roleId => '?').join(',');
|
||||||
|
|
||||||
const dbResult = this.database.fetchAll<number[], DBGroup>(`
|
const dbResult = this.database.fetchAll<number[], DBGroup>(`
|
||||||
SELECT * FROM groups WHERE server = ? AND role IN (${template})
|
SELECT * FROM groups WHERE server = ? AND role IN (${template})
|
||||||
|
|
@ -64,7 +64,6 @@ export class GroupRepository extends Repository<GroupModel, DBGroup> {
|
||||||
public deleteGroup(group: GroupModel): void {
|
public deleteGroup(group: GroupModel): void {
|
||||||
this.delete(group.id);
|
this.delete(group.id);
|
||||||
|
|
||||||
debugger
|
|
||||||
const repo = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
const repo = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
||||||
const playdates = repo.findFromGroup(group, true)
|
const playdates = repo.findFromGroup(group, true)
|
||||||
playdates.forEach((playdate) => {
|
playdates.forEach((playdate) => {
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ export class PlaydateRepository extends Repository<PlaydateModel, DBPlaydate> {
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextPlaydateForGroup(group: GroupModel): PlaydateModel | null {
|
getNextPlaydateForGroup(group: GroupModel): PlaydateModel | null {
|
||||||
let sql = `SELECT * FROM ${this.schema.name} WHERE groupid = ? AND time_from > ? ORDER BY time_from ASC LIMIT 1`;
|
const sql = `SELECT * FROM ${this.schema.name} WHERE groupid = ? AND time_from > ? ORDER BY time_from LIMIT 1`;
|
||||||
|
|
||||||
const find = this.database.fetch<number, DBPlaydate>(
|
const find = this.database.fetch<number, DBPlaydate>(
|
||||||
sql,
|
sql,
|
||||||
|
|
@ -78,14 +78,12 @@ export class PlaydateRepository extends Repository<PlaydateModel, DBPlaydate> {
|
||||||
if (!intermediateModel) {
|
if (!intermediateModel) {
|
||||||
throw new Error("Unable to convert the playdate model");
|
throw new Error("Unable to convert the playdate model");
|
||||||
}
|
}
|
||||||
const result: PlaydateModel = {
|
return {
|
||||||
id: intermediateModel.id,
|
id: intermediateModel.id,
|
||||||
group: fixedGroup ?? this.groupRepository.getById(intermediateModel.groupid),
|
group: fixedGroup ?? this.groupRepository.getById(intermediateModel.groupid),
|
||||||
from_time: new Date(intermediateModel.time_from),
|
from_time: new Date(intermediateModel.time_from),
|
||||||
to_time: new Date(intermediateModel.time_to),
|
to_time: new Date(intermediateModel.time_to),
|
||||||
}
|
};
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected convertToCreateObject(instance: Partial<PlaydateModel>): object {
|
protected convertToCreateObject(instance: Partial<PlaydateModel>): object {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import {DatabaseConnection} from "../Database/DatabaseConnection";
|
||||||
import {Model} from "../Models/Model";
|
import {Model} from "../Models/Model";
|
||||||
import { Nullable } from "../types/Nullable";
|
import { Nullable } from "../types/Nullable";
|
||||||
import {DatabaseDefinition} from "../Database/DatabaseDefinition";
|
import {DatabaseDefinition} from "../Database/DatabaseDefinition";
|
||||||
import {debug} from "node:util";
|
|
||||||
import {Container} from "../Container/Container";
|
import {Container} from "../Container/Container";
|
||||||
import {EventHandler} from "../Events/EventHandler";
|
import {EventHandler} from "../Events/EventHandler";
|
||||||
import {ElementCreatedEvent} from "../Events/ElementCreatedEvent";
|
import {ElementCreatedEvent} from "../Events/ElementCreatedEvent";
|
||||||
|
|
@ -62,7 +61,7 @@ export class Repository<ModelType extends Model, IntermediateModelType = unknown
|
||||||
SET ${Object.keys(createObject).map((key) => `${key} = ?`).join(',')}
|
SET ${Object.keys(createObject).map((key) => `${key} = ?`).join(',')}
|
||||||
WHERE id = ?`;
|
WHERE id = ?`;
|
||||||
const result = this.database.execute(sql, ...Object.values(createObject), instance.id);
|
const result = this.database.execute(sql, ...Object.values(createObject), instance.id);
|
||||||
return result.lastInsertRowid;
|
return result.changes > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getById(id: number): Nullable<ModelType> {
|
public getById(id: number): Nullable<ModelType> {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export class ArrayUtils {
|
||||||
if (a == null || b == null) return false;
|
if (a == null || b == null) return false;
|
||||||
if (a.length !== b.length) return false;
|
if (a.length !== b.length) return false;
|
||||||
|
|
||||||
for (var i = 0; i < a.length; ++i) {
|
for (let i = 0; i < a.length; ++i) {
|
||||||
if (a[i] !== b[i]) return false;
|
if (a[i] !== b[i]) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import Commands from "./Discord/Commands/Commands";
|
|
||||||
import {Environment} from "./Environment";
|
import {Environment} from "./Environment";
|
||||||
import {DatabaseConnection} from "./Database/DatabaseConnection";
|
import {DatabaseConnection} from "./Database/DatabaseConnection";
|
||||||
import {DatabaseUpdater} from "./Database/DatabaseUpdater";
|
import {DatabaseUpdater} from "./Database/DatabaseUpdater";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import {DiscordClient} from "./Discord/DiscordClient";
|
import {DiscordClient} from "./Discord/DiscordClient";
|
||||||
import {Environment} from "./Environment";
|
import {Environment} from "./Environment";
|
||||||
import {Container} from "./Container/Container";
|
import {Container} from "./Container/Container";
|
||||||
import {DatabaseConnection} from "./Database/DatabaseConnection";
|
|
||||||
import {ServiceHint, Services} from "./Container/Services";
|
import {ServiceHint, Services} from "./Container/Services";
|
||||||
import {IconCache} from "./Icons/IconCache";
|
import {IconCache} from "./Icons/IconCache";
|
||||||
import {DefaultEvents} from "./Events/DefaultEvents";
|
import {DefaultEvents} from "./Events/DefaultEvents";
|
||||||
|
|
|
||||||