Adds Event system and automatic messages
This commit is contained in:
parent
0e10ea3cab
commit
2f826fbf36
20 changed files with 428 additions and 18 deletions
20
package-lock.json
generated
20
package-lock.json
generated
|
|
@ -18,7 +18,9 @@
|
|||
"discord.js": "^14.18.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"esbuild": "^0.25.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"log4js": "^6.9.1",
|
||||
"node-cron": "^4.0.7",
|
||||
"object-path-set": "^1.0.2",
|
||||
"svg2img": "^1.0.0-beta.2"
|
||||
}
|
||||
|
|
@ -1913,6 +1915,15 @@
|
|||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jimp": {
|
||||
"version": "0.16.13",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.13.tgz",
|
||||
|
|
@ -2086,6 +2097,15 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-cron": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.0.7.tgz",
|
||||
"integrity": "sha512-A37UUDpxRT/kWanELr/oMayCWQFk9Zx9BEUoXrAKuKwKzH4XuAX+vMixMBPkgZBkADgJwXv91w5cMRTNSVP/mA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-path-set": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/object-path-set/-/object-path-set-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@
|
|||
"discord.js": "^14.18.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"esbuild": "^0.25.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"log4js": "^6.9.1",
|
||||
"node-cron": "^4.0.7",
|
||||
"object-path-set": "^1.0.2",
|
||||
"svg2img": "^1.0.0-beta.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import {Class} from "../types/Class";
|
||||
|
||||
export class Container {
|
||||
static instance: Container;
|
||||
|
||||
private instances: Map<string, object> = new Map();
|
||||
|
||||
public set<T extends {constructor: {name: string}}>(instance: T, name: string|null = null): void
|
||||
public set<T extends Class>(instance: T, name: string|null = null): void
|
||||
{
|
||||
const settingName = name ?? instance.constructor.name;
|
||||
this.instances.set(settingName.toLowerCase(), instance);
|
||||
}
|
||||
|
||||
public get<T>(name: string): T
|
||||
public get<T extends Class>(name: string): T
|
||||
{
|
||||
return <T>this.instances.get(name.toLowerCase());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {GuildEmojiRoleManager} from "discord.js";
|
|||
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
||||
import {DiscordClient} from "../Discord/DiscordClient";
|
||||
import {IconCache} from "../Icons/IconCache";
|
||||
import {EventHandler} from "../Events/EventHandler";
|
||||
|
||||
export enum ServiceHint {
|
||||
App,
|
||||
|
|
@ -30,6 +31,8 @@ export class Services {
|
|||
const iconCache = new IconCache(discordClient);
|
||||
container.set<IconCache>(iconCache);
|
||||
|
||||
container.set<EventHandler>(new EventHandler());
|
||||
|
||||
// @ts-ignore
|
||||
configure({
|
||||
appenders: {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export class GroupSelection {
|
|||
)
|
||||
}
|
||||
|
||||
public static getGroup(interaction: CommandInteraction): GroupModel {
|
||||
public static getGroup(interaction: CommandInteraction|AutocompleteInteraction): GroupModel {
|
||||
const groupname = interaction.options.get("group", true);
|
||||
if (!groupname) {
|
||||
throw new UserError("No group name provided");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
CommandInteraction,
|
||||
AutocompleteInteraction,
|
||||
GuildMember,
|
||||
EmbedBuilder, MessageFlags, ChatInputCommandInteraction, ModalSubmitFields
|
||||
EmbedBuilder, MessageFlags, ChatInputCommandInteraction, ModalSubmitFields, time, User
|
||||
} from "discord.js";
|
||||
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
|
||||
import {Container} from "../../Container/Container";
|
||||
|
|
@ -93,17 +93,32 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
|
|||
throw new UserError("No date or invalid date format for the to parameter.");
|
||||
}
|
||||
|
||||
if (fromDate > toDate) {
|
||||
throw new UserError("The to-date can't be earlier than the from-date");
|
||||
}
|
||||
|
||||
const playdateRepo = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
||||
|
||||
const collidingTimes = playdateRepo.findPlaydatesInRange(fromDate, toDate, group);
|
||||
if (collidingTimes.length > 0) {
|
||||
throw new UserError("The playdate collides with another playdate. Please either remove the old one or choose a different time.")
|
||||
}
|
||||
|
||||
const playdate: Partial<PlaydateModel> = {
|
||||
group: group,
|
||||
from_time: new Date(fromDate),
|
||||
to_time: new Date(toDate),
|
||||
}
|
||||
|
||||
const id = Container.get<PlaydateRepository>(PlaydateRepository.name).create(playdate);
|
||||
const id = playdateRepo.create(playdate);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Created a play-date.")
|
||||
.setDescription(":white_check_mark: Your playdate has been created! You and your group get notified, when its time.")
|
||||
.setFields({
|
||||
name: "Created playdate",
|
||||
value: `${time(new Date(fromDate),'F')} - ${time(new Date(toDate), 'F')}`,
|
||||
})
|
||||
.setFooter({
|
||||
text: `Group: ${group.name}`
|
||||
})
|
||||
|
|
@ -127,12 +142,8 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
|
|||
return;
|
||||
}
|
||||
|
||||
const groupname = interaction.options.getString("group")
|
||||
const group = Container.get<GroupRepository>(GroupRepository.name).findGroupByName((groupname ?? '').toString());
|
||||
if (!group) {
|
||||
throw new UserError("No group found");
|
||||
}
|
||||
|
||||
const group = GroupSelection.getGroup(interaction);
|
||||
|
||||
const playdates = Container.get<PlaydateRepository>(PlaydateRepository.name).findFromGroup(group);
|
||||
await interaction.respond(
|
||||
playdates.map(playdate => {
|
||||
|
|
@ -153,8 +164,8 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
|
|||
playdates.map((playdate) =>
|
||||
{
|
||||
return {
|
||||
name: `${playdate.from_time.toLocaleString()} - ${playdate.to_time.toLocaleString()}`,
|
||||
value: ``
|
||||
name: `${time(playdate.from_time, 'F')} - ${time(playdate.to_time, 'F')}`,
|
||||
value: `${time(playdate.from_time, 'R')}`
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
|
|||
27
source/Events/DefaultEvents.ts
Normal file
27
source/Events/DefaultEvents.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import {EventHandler, TimedEvent} from "./EventHandler";
|
||||
import {Container} from "../Container/Container";
|
||||
import {ReminderEvent} from "./ReminderEvent";
|
||||
import {ElementCreatedEvent} from "./ElementCreatedEvent";
|
||||
import {ClassNamed} from "../types/Class";
|
||||
import {sendCreatedNotificationEventHandler} from "./Handlers/SendCreatedNotification";
|
||||
import {PlaydateModel} from "../Models/PlaydateModel";
|
||||
|
||||
export class DefaultEvents {
|
||||
public static setupTimed() {
|
||||
const events: TimedEvent[] = [
|
||||
new ReminderEvent()
|
||||
]
|
||||
|
||||
const eventHandler = Container.get<EventHandler>(EventHandler.name);
|
||||
|
||||
events.forEach((event) => {
|
||||
eventHandler.addTimed(event);
|
||||
})
|
||||
}
|
||||
|
||||
public static setupHandlers() {
|
||||
const eventHandler = Container.get<EventHandler>(EventHandler.name);
|
||||
|
||||
eventHandler.addHandler<ElementCreatedEvent<PlaydateModel>>(ElementCreatedEvent.name, sendCreatedNotificationEventHandler);
|
||||
}
|
||||
}
|
||||
10
source/Events/ElementCreatedEvent.ts
Normal file
10
source/Events/ElementCreatedEvent.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import {Model} from "../Models/Model";
|
||||
|
||||
export class ElementCreatedEvent<T extends Model = Model> {
|
||||
constructor(
|
||||
public readonly tableName: string,
|
||||
public readonly instanceValues: Partial<T>,
|
||||
public readonly instanceId: number
|
||||
) {
|
||||
}
|
||||
}
|
||||
48
source/Events/EventHandler.ts
Normal file
48
source/Events/EventHandler.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import cron from "node-cron";
|
||||
import {Nullable} from "../types/Nullable";
|
||||
import {Class, ClassNamed} from "../types/Class";
|
||||
|
||||
export type EventConfiguration = {
|
||||
name: string,
|
||||
maxExecutions?: number,
|
||||
}
|
||||
|
||||
export interface TimedEvent {
|
||||
configuration: EventConfiguration,
|
||||
cronExpression: string,
|
||||
execute: () => void
|
||||
}
|
||||
|
||||
export class EventHandler {
|
||||
private eventHandlers: Map<string, CallableFunction[]> = new Map();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public addHandler<T extends Class>(eventName: string, handler: (event: T) => void) {
|
||||
if (!this.eventHandlers.has(eventName)) {
|
||||
this.eventHandlers.set(eventName, []);
|
||||
}
|
||||
|
||||
this.eventHandlers.get(eventName)?.push(handler);
|
||||
}
|
||||
|
||||
public dispatch<T extends Class>(event: T) {
|
||||
const eventName = event.constructor.name;
|
||||
if (!this.eventHandlers.has(eventName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventHandlers.get(eventName)?.forEach((handler) => {
|
||||
handler(event);
|
||||
})
|
||||
}
|
||||
|
||||
public addTimed(event: TimedEvent) {
|
||||
if (!cron.validate(event.cronExpression)) {
|
||||
throw new Error(`Can't create event with name '${event.configuration.name}': Invalid cron expression.`)
|
||||
}
|
||||
|
||||
cron.schedule(event.cronExpression, event.execute.bind(event), event.configuration);
|
||||
}
|
||||
}
|
||||
75
source/Events/Handlers/SendCreatedNotification.ts
Normal file
75
source/Events/Handlers/SendCreatedNotification.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import {ElementCreatedEvent} from "../ElementCreatedEvent";
|
||||
import {DefaultHandler} from "../DefaultEvents";
|
||||
import {PlaydateModel} from "../../Models/PlaydateModel";
|
||||
import PlaydateTableConfiguration from "../../Database/tables/Playdate";
|
||||
import {EmbedBuilder, roleMention, time} from "discord.js";
|
||||
import {ArrayUtils} from "../../Utilities/ArrayUtils";
|
||||
import {GroupConfigurationHandler} from "../../Groups/GroupConfigurationHandler";
|
||||
import {Container} from "../../Container/Container";
|
||||
import {GroupConfigurationRenderer} from "../../Groups/GroupConfigurationRenderer";
|
||||
import {GroupConfigurationRepository} from "../../Repositories/GroupConfigurationRepository";
|
||||
import {DiscordClient} from "../../Discord/DiscordClient";
|
||||
|
||||
const NEW_PLAYDATE_MESSAGES = [
|
||||
'A new playdate was added. Lets hope, your GM has not planned to kill you. >:]',
|
||||
'Oh look. A new playdate... neat.',
|
||||
'A new playdate. Lets polish the dice.'
|
||||
];
|
||||
|
||||
export async function sendCreatedNotificationEventHandler(event: ElementCreatedEvent<PlaydateModel>) {
|
||||
if (event.tableName !== PlaydateTableConfiguration.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const playdate = event.instanceValues;
|
||||
if (!playdate.group || !playdate.from_time || !playdate.to_time) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const configurationHandler = new GroupConfigurationHandler(
|
||||
Container.get<GroupConfigurationRepository>(GroupConfigurationRepository.name),
|
||||
playdate.group
|
||||
);
|
||||
|
||||
const targetChannel = configurationHandler.getConfigurationByPath('channels.newPlaydates');
|
||||
if (!targetChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = await Container.get<DiscordClient>(DiscordClient.name).Client.channels.fetch(targetChannel)
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel.isTextBased()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel.isSendable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("New Playdate added")
|
||||
.setDescription(
|
||||
ArrayUtils.chooseRandom(NEW_PLAYDATE_MESSAGES)
|
||||
)
|
||||
.addFields({
|
||||
name: "Playdate:",
|
||||
value: `${time(playdate.from_time, "F")} - ${time(playdate.to_time, 'F')}`,
|
||||
})
|
||||
.setFooter({
|
||||
text: `Group: ${playdate.group.name}`
|
||||
});
|
||||
|
||||
channel.send({
|
||||
content: roleMention(playdate.group.role.roleid),
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
allowedMentions: {
|
||||
roles: [ playdate.group.role.roleid ]
|
||||
}
|
||||
})
|
||||
}
|
||||
122
source/Events/ReminderEvent.ts
Normal file
122
source/Events/ReminderEvent.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import {CronExpression, Event, EventConfiguration, TimedEvent} from "./EventHandler";
|
||||
import {Container} from "../Container/Container";
|
||||
import Playdate from "../Database/tables/Playdate";
|
||||
import {PlaydateRepository} from "../Repositories/PlaydateRepository";
|
||||
import {GroupConfigurationHandler} from "../Groups/GroupConfigurationHandler";
|
||||
import {GroupConfigurationRepository} from "../Repositories/GroupConfigurationRepository";
|
||||
import {PlaydateModel} from "../Models/PlaydateModel";
|
||||
import {ChannelId} from "../types/DiscordTypes";
|
||||
import {DiscordClient} from "../Discord/DiscordClient";
|
||||
import {EmbedBuilder, MessageFlags, roleMention, time} from "discord.js";
|
||||
import {ArrayUtils} from "../Utilities/ArrayUtils";
|
||||
|
||||
export class ReminderEvent implements TimedEvent {
|
||||
private static REMINDER_INTERVALS = [
|
||||
1,
|
||||
7
|
||||
];
|
||||
|
||||
private static REMINDER_NOTIFICATIONS = [
|
||||
'The darkness approaches. Get ready!',
|
||||
'Your aid is requested once again.',
|
||||
'Grab your dice and show them evil-doers how its done!',
|
||||
]
|
||||
|
||||
|
||||
configuration: EventConfiguration = {
|
||||
name: "Reminders",
|
||||
}
|
||||
|
||||
cronExpression: CronExpression = "0 9 * * *"
|
||||
|
||||
private groupConfigurationRepository: GroupConfigurationRepository
|
||||
private playdateRepository: PlaydateRepository
|
||||
private discordClient: DiscordClient
|
||||
|
||||
constructor() {
|
||||
this.playdateRepository = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
||||
this.groupConfigurationRepository = Container.get<GroupConfigurationRepository>(GroupConfigurationRepository.name);
|
||||
this.discordClient = Container.get<DiscordClient>(DiscordClient.name);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const today = new Date();
|
||||
today.setHours(0,0,0,0);
|
||||
|
||||
const playdates = ReminderEvent.REMINDER_INTERVALS.flatMap((interval) => {
|
||||
const fromDate = new Date(today.valueOf())
|
||||
fromDate.setDate(fromDate.getDate() + interval);
|
||||
|
||||
const toDate = new Date(today.valueOf())
|
||||
toDate.setDate(toDate.getDate() + interval);
|
||||
toDate.setHours(23,59,59,999);
|
||||
|
||||
return this.playdateRepository.findPlaydatesInRange(fromDate, toDate);
|
||||
}, this)
|
||||
|
||||
const promises = playdates
|
||||
.map((playdate) => {
|
||||
if (!playdate.group) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const configurationHandler = new GroupConfigurationHandler(
|
||||
this.groupConfigurationRepository,
|
||||
playdate.group
|
||||
);
|
||||
|
||||
const config = configurationHandler.getConfiguration();
|
||||
const targetChannel = config.channels?.playdateReminders;
|
||||
|
||||
if (!targetChannel) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.sendReminder(playdate, targetChannel, config.locale);
|
||||
}, this)
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
private async sendReminder(playdate: PlaydateModel, targetChannel: ChannelId, locale: Intl.Locale) {
|
||||
if (!playdate.group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = await this.discordClient.Client.channels.fetch(targetChannel)
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel.isTextBased()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel.isSendable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Playdate reminder")
|
||||
.setDescription(
|
||||
ArrayUtils.chooseRandom(ReminderEvent.REMINDER_NOTIFICATIONS)
|
||||
)
|
||||
.addFields({
|
||||
name: "Next Playdate:",
|
||||
value: `${time(playdate.from_time, "F")} - ${time(playdate.from_time, 'R')}`,
|
||||
})
|
||||
.setFooter({
|
||||
text: `Group: ${playdate.group.name}`
|
||||
});
|
||||
|
||||
channel.send({
|
||||
content: roleMention(playdate.group.role.roleid),
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
allowedMentions: {
|
||||
roles: [ playdate.group.role.roleid ]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,15 @@ import {GroupConfigurationResult, GroupConfigurationTransformers} from "./GroupC
|
|||
import setPath from 'object-path-set';
|
||||
import deepmerge from "deepmerge";
|
||||
import {Nullable} from "../types/Nullable";
|
||||
import {isPlainObject} from "is-plain-object";
|
||||
|
||||
export class GroupConfigurationHandler {
|
||||
private static DEFAULT_CONFIGURATION: RuntimeGroupConfiguration = {
|
||||
channels: null,
|
||||
locale: new Intl.Locale('en-GB'),
|
||||
permissions: {
|
||||
allowMemberManagingPlaydates: false
|
||||
}
|
||||
}
|
||||
|
||||
private readonly transformers: GroupConfigurationTransformers = new GroupConfigurationTransformers();
|
||||
|
|
@ -42,7 +46,9 @@ export class GroupConfigurationHandler {
|
|||
}
|
||||
|
||||
public getConfiguration(): RuntimeGroupConfiguration {
|
||||
return deepmerge(GroupConfigurationHandler.DEFAULT_CONFIGURATION, this.getDatabaseConfiguration());
|
||||
return deepmerge(GroupConfigurationHandler.DEFAULT_CONFIGURATION, this.getDatabaseConfiguration(), {
|
||||
isMergeableObject: isPlainObject
|
||||
});
|
||||
}
|
||||
|
||||
public getConfigurationByPath(path: string): Nullable<GroupConfigurationResult> {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {UserError} from "../Discord/UserError";
|
|||
import {RuntimeGroupConfiguration} from "./RuntimeGroupConfiguration";
|
||||
import {ChannelId} from "../types/DiscordTypes";
|
||||
import {IconCache} from "../Icons/IconCache";
|
||||
import {ifError} from "node:assert";
|
||||
|
||||
type UIElementCollection = Record<string, UIElement>;
|
||||
type UIElement = {
|
||||
|
|
@ -76,6 +77,19 @@ export class GroupConfigurationRenderer {
|
|||
key: 'locale',
|
||||
description: "Provides locale to be used for this group. This mostly sets how the dates are displayed, but this can also be later used for translations.",
|
||||
isConfiguration: true
|
||||
},
|
||||
permissions: {
|
||||
label: "Permissions",
|
||||
key: "permissions",
|
||||
description: "Allows customization, how the members are allowed to interact with the data stored in the group.",
|
||||
childrenElements: {
|
||||
allowMemberManagingPlaydates: {
|
||||
label: "Manage Playdates",
|
||||
key: "allowMemberManagingPlaydates",
|
||||
description: "Defines if the members are allowed to manage playdates like adding or deleting them.",
|
||||
isConfiguration: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +234,8 @@ export class GroupConfigurationRenderer {
|
|||
return displaynames.of((<Intl.Locale>value).baseName) ?? "Unknown";
|
||||
case TransformerType.Channel:
|
||||
return channelMention(<ChannelId>value);
|
||||
case TransformerType.PermissionBoolean:
|
||||
return value ? "Allowed" : "Disallowed"
|
||||
|
||||
default:
|
||||
return "None";
|
||||
|
|
@ -279,6 +295,21 @@ export class GroupConfigurationRenderer {
|
|||
.setCustomId(GroupConfigurationRenderer.SETVALUE_COMMAND + breadcrumbPath)
|
||||
.setChannelTypes(ChannelType.GuildText)
|
||||
.setPlaceholder("New Value");
|
||||
case TransformerType.PermissionBoolean:
|
||||
return new StringSelectMenuBuilder()
|
||||
.setCustomId(GroupConfigurationRenderer.SETVALUE_COMMAND + breadcrumbPath)
|
||||
.setOptions(
|
||||
[
|
||||
{
|
||||
label: "Allow",
|
||||
value: "1"
|
||||
},
|
||||
{
|
||||
label: "Disallow",
|
||||
value: "0"
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
default:
|
||||
return new StringSelectMenuBuilder()
|
||||
|
|
@ -309,6 +340,7 @@ export class GroupConfigurationRenderer {
|
|||
switch (transformerType) {
|
||||
case TransformerType.Locale:
|
||||
case TransformerType.Channel:
|
||||
case TransformerType.PermissionBoolean:
|
||||
return interaction.values.join('; ');
|
||||
default:
|
||||
throw new Error("Unhandled select menu");
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {ArrayUtils} from "../Utilities/ArrayUtils";
|
|||
export enum TransformerType {
|
||||
Locale,
|
||||
Channel,
|
||||
PermissionBoolean,
|
||||
}
|
||||
|
||||
type GroupConfigurationTransformer = {
|
||||
|
|
@ -16,7 +17,7 @@ type GroupConfigurationTransformer = {
|
|||
}
|
||||
|
||||
export type GroupConfigurationResult =
|
||||
ChannelId | Intl.Locale
|
||||
ChannelId | Intl.Locale | boolean
|
||||
|
||||
export class GroupConfigurationTransformers {
|
||||
static TRANSFORMERS: GroupConfigurationTransformer[] = [
|
||||
|
|
@ -31,6 +32,10 @@ export class GroupConfigurationTransformers {
|
|||
{
|
||||
path: ['locale'],
|
||||
type: TransformerType.Locale,
|
||||
},
|
||||
{
|
||||
path: ['permissions', 'allowMemberManagingPlaydates'],
|
||||
type: TransformerType.PermissionBoolean
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -45,6 +50,8 @@ export class GroupConfigurationTransformers {
|
|||
return new Intl.Locale(configValue.value)
|
||||
case TransformerType.Channel:
|
||||
return <ChannelId>configValue.value;
|
||||
case TransformerType.PermissionBoolean:
|
||||
return configValue.value === '1';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
8
source/Groups/RuntimeGroupConfiguration.d.ts
vendored
8
source/Groups/RuntimeGroupConfiguration.d.ts
vendored
|
|
@ -1,9 +1,17 @@
|
|||
import {ChannelId} from "../types/DiscordTypes";
|
||||
import {Nullable} from "../types/Nullable";
|
||||
|
||||
export type RuntimeGroupConfiguration = {
|
||||
channels: Nullable<ChannelRuntimeGroupConfiguration>,
|
||||
locale: Intl.Locale,
|
||||
permissions: PermissionRuntimeGroupConfiguration
|
||||
};
|
||||
|
||||
export type ChannelRuntimeGroupConfiguration = {
|
||||
newPlaydates: ChannelId,
|
||||
playdateReminders: ChannelId
|
||||
}
|
||||
|
||||
export type PermissionRuntimeGroupConfiguration = {
|
||||
allowMemberManagingPlaydates: boolean
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import {Routes} from "discord.js";
|
|||
import {DiscordClient} from "../Discord/DiscordClient";
|
||||
|
||||
export class IconCache {
|
||||
private existingIcons: Map<string, string>|null;
|
||||
private existingIcons: Map<string, string> | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly client: DiscordClient
|
||||
|
|
@ -11,7 +11,7 @@ export class IconCache {
|
|||
}
|
||||
|
||||
public get(iconName: string): string | null {
|
||||
if (!this.existingIcons?.has(iconName) ?? false) {
|
||||
if (!this.existingIcons?.has(iconName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,30 @@ export class PlaydateRepository extends Repository<PlaydateModel, DBPlaydate> {
|
|||
|
||||
return finds.map((playdate) => this.convertToModelType(playdate, group));
|
||||
}
|
||||
findPlaydatesInRange(fromDate: Date|number, toDate: Date|number, group: GroupModel | undefined = undefined) {
|
||||
if (fromDate instanceof Date) {
|
||||
fromDate = fromDate.getTime();
|
||||
}
|
||||
if (toDate instanceof Date) {
|
||||
toDate = toDate.getTime();
|
||||
}
|
||||
|
||||
let sql = `SELECT * FROM ${this.schema.name} WHERE time_from > ? AND time_from < ?`;
|
||||
const params = [fromDate, toDate];
|
||||
|
||||
if (group) {
|
||||
sql = `${sql} AND groupid = ?`
|
||||
params.push(group.id)
|
||||
}
|
||||
|
||||
const finds = this.database.fetchAll<number, DBPlaydate>(
|
||||
sql,
|
||||
...params
|
||||
);
|
||||
|
||||
return finds.map((playdate) => this.convertToModelType(playdate, group));
|
||||
}
|
||||
|
||||
protected convertToModelType(intermediateModel: DBPlaydate | undefined, fixedGroup: Nullable<GroupModel> = null): PlaydateModel {
|
||||
if (!intermediateModel) {
|
||||
throw new Error("Unable to convert the playdate model");
|
||||
|
|
|
|||
|
|
@ -3,8 +3,12 @@ import {Model} from "../Models/Model";
|
|||
import { Nullable } from "../types/Nullable";
|
||||
import {DatabaseDefinition} from "../Database/DatabaseDefinition";
|
||||
import {debug} from "node:util";
|
||||
import {Container} from "../Container/Container";
|
||||
import {EventHandler} from "../Events/EventHandler";
|
||||
import {ElementCreatedEvent} from "../Events/ElementCreatedEvent";
|
||||
|
||||
export class Repository<ModelType extends Model, IntermediateModelType = unknown> {
|
||||
|
||||
constructor(
|
||||
protected readonly database: DatabaseConnection,
|
||||
public readonly schema: DatabaseDefinition,
|
||||
|
|
@ -30,7 +34,11 @@ export class Repository<ModelType extends Model, IntermediateModelType = unknown
|
|||
const sql = `INSERT INTO ${this.schema.name}(${Object.keys(createObject).join(',')})
|
||||
VALUES (${Object.keys(createObject).map(() => "?").join(',')})`;
|
||||
const result = this.database.execute(sql, ...Object.values(createObject));
|
||||
return result.lastInsertRowid;
|
||||
const id = result.lastInsertRowid;
|
||||
|
||||
Container.get<EventHandler>(EventHandler.name).dispatch(new ElementCreatedEvent<ModelType>(this.schema.name, instance, id));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public update(instance: Partial<ModelType>&{id: number}): boolean {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ import {Container} from "./Container/Container";
|
|||
import {DatabaseConnection} from "./Database/DatabaseConnection";
|
||||
import {ServiceHint, Services} from "./Container/Services";
|
||||
import {IconCache} from "./Icons/IconCache";
|
||||
import {DefaultEvents} from "./Events/DefaultEvents";
|
||||
|
||||
const container = Container.getInstance();
|
||||
Services.setup(container, ServiceHint.App);
|
||||
DefaultEvents.setupTimed();
|
||||
DefaultEvents.setupHandlers();
|
||||
(async () => {
|
||||
const env = container.get<Environment>(Environment.name);
|
||||
|
||||
|
|
|
|||
2
source/types/Class.ts
Normal file
2
source/types/Class.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export type Class = {constructor: {name: string}}
|
||||
export type ClassNamed = {name: string}
|
||||
Loading…
Add table
Add a link
Reference in a new issue