Adds initial progress

This commit is contained in:
Michel Fedde 2025-03-28 23:19:54 +01:00
commit a0b668cb90
34 changed files with 2680 additions and 0 deletions

View file

@ -0,0 +1,17 @@
import {ChatInputCommandInteraction, Interaction, SlashCommandBuilder} from "discord.js";
import Commands from "./Commands";
export interface Command {
definition(): SlashCommandBuilder;
}
export interface ChatInteractionCommand {
execute(interaction: ChatInputCommandInteraction): Promise<void>;
}
export interface AutocompleteCommand {
handleAutocomplete(interaction: Interaction): Promise<void>;
}
export type CommandUnion =
Command | Partial<ChatInteractionCommand> | Partial<AutocompleteCommand>;

View file

@ -0,0 +1,41 @@
import {HelloWorldCommand} from "./HelloWorldCommand";
import {Command, CommandUnion} from "./Command";
import {GroupCommand} from "./Groups";
import {PlaydatesCommand} from "./Playdates";
const commands: Set<Command> = new Set<Command>([
new HelloWorldCommand(),
new GroupCommand(),
new PlaydatesCommand()
]);
export default class Commands {
private mappedCommands: Map<string, Command> = new Map<string, Command>();
constructor() {
this.mappedCommands = this.getMap();
}
public getCommand(commandName: string): CommandUnion|undefined {
if (!this.mappedCommands.has(commandName)) {
throw new Error(`Unknown command: ${commandName}`);
}
return this.mappedCommands.get(commandName);
}
public get allCommands(): Set<Command> {
return commands;
}
private getMap(): Map<string, Command>
{
const map = new Map<string, Command>();
for (const command of commands) {
const definition = command.definition()
map.set(definition.name, command);
}
return map;
}
}

View file

@ -0,0 +1,100 @@
import {
SlashCommandBuilder,
Interaction,
CommandInteraction,
ChatInputCommandInteraction,
MessageFlags, GuildMemberRoleManager, InteractionReplyOptions, GuildMember
} from "discord.js";
import {ChatInteractionCommand, Command} from "./Command";
import {GroupModel} from "../../Models/GroupModel";
import {GroupRepository} from "../../Repositories/GroupRepository";
import {DatabaseConnection} from "../../Database/DatabaseConnection";
import {Container} from "../../Container/Container";
export class GroupCommand implements Command, ChatInteractionCommand {
definition(): SlashCommandBuilder {
// @ts-ignore
return new SlashCommandBuilder()
.setName('groups')
.setDescription(`Manages groups`)
.addSubcommand(create =>
create.setName("create")
.setDescription("Creates a new group, with executing user being the leader")
.addStringOption((option) =>
option.setName("name")
.setDescription("Defines the name for the group.")
.setRequired(true)
)
.addRoleOption((builder) =>
builder.setName("role")
.setDescription("Defines the role, where all the members are located in.")
.setRequired(true)
)
)
.addSubcommand(listCommand =>
listCommand
.setName("list")
.setDescription("Displays the groups you are apart of.")
);
}
execute(interaction: ChatInputCommandInteraction): Promise<void> {
switch (interaction.options.getSubcommand()) {
case "create":
this.create(interaction);
break;
case "list":
this.list(interaction);
break;
default:
throw new Error("Unsupported command");
}
return Promise.resolve();
}
private create(interaction: ChatInputCommandInteraction): void {
const name = interaction.options.getString("name") ?? '';
const role = interaction.options.getRole("role");
const group: GroupModel = {
id: -1,
name: name,
leader: {
server: interaction.guildId ?? '',
memberid: interaction.member?.user.id ?? ''
},
role: {
server: interaction.guildId ?? '',
roleid: role?.id ?? ''
}
}
Container.get<GroupRepository>(GroupRepository.name).create(group);
interaction.reply({content: `:white_check_mark: Created group \`${name}\``, flags: MessageFlags.Ephemeral })
}
private list(interaction: ChatInputCommandInteraction) {
const repo = Container.get<GroupRepository>(GroupRepository.name);
const groups = repo.findGroupsByMember(<GuildMember>interaction.member);
const reply: InteractionReplyOptions = {
embeds: [
{
title: "Your groups on this server:",
fields: groups.map((group) => {
return {
name: group.name,
value: ""
}
})
}
],
flags: MessageFlags.Ephemeral
}
interaction.reply(reply);
}
}

View file

@ -0,0 +1,25 @@
import {SlashCommandBuilder, Interaction, CommandInteraction} from "discord.js";
import {Command} from "./Command";
export class HelloWorldCommand implements Command {
private static RESPONSES: string[] = [
'Hello :)',
'zzzZ... ZzzzZ... huh? I am awake. I am awake!',
'Roll for initiative!',
'I was an adventurerer like you...',
'Hello :) How are you?',
]
definition(): SlashCommandBuilder
{
return new SlashCommandBuilder()
.setName("hello")
.setDescription("Displays a random response. (commonly used to test if the bot is online)")
}
async execute(interaction: CommandInteraction): Promise<void> {
const random = Math.floor(Math.random() * HelloWorldCommand.RESPONSES.length);
await interaction.reply(HelloWorldCommand.RESPONSES[random]);
return Promise.resolve();
}
}

View file

@ -0,0 +1,206 @@
import {
SlashCommandBuilder,
Interaction,
CommandInteraction,
AutocompleteInteraction,
GuildMember,
EmbedBuilder, MessageFlags, ChatInputCommandInteraction, ModalSubmitFields
} from "discord.js";
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
import {Container} from "../../Container/Container";
import {GroupRepository} from "../../Repositories/GroupRepository";
import {GroupSelection} from "../CommandPartials/GroupSelection";
import {setFlagsFromString} from "node:v8";
import {UserError} from "../UserError";
import Playdate from "../../Database/tables/Playdate";
import {PlaydateModel} from "../../Models/PlaydateModel";
import {PlaydateRepository} from "../../Repositories/PlaydateRepository";
import {GroupModel} from "../../Models/GroupModel";
import playdate from "../../Database/tables/Playdate";
export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInteractionCommand {
static REGEX = [
]
definition(): SlashCommandBuilder {
// @ts-ignore
return new SlashCommandBuilder()
.setName("playdates")
.setDescription("Manage your playdates")
.addSubcommand((subcommand) => subcommand
.setName("create")
.setDescription("Creates a new playdate")
.addStringOption(GroupSelection.createOptionSetup())
.addStringOption((option) => option
.setName("from")
.setDescription("Defines the start date & time. Format: YYYY-MM-DD HH:mm")
)
.addStringOption((option) => option
.setName("to")
.setDescription("Defines the end date & time. Format: YYYY-MM-DD HH:mm")
)
.addAttachmentOption((option) => option
.setName("calendar-entry")
.setDescription("Optional, you can upload a iCal file and the from and to-values are read from it.")
)
)
.addSubcommand((subcommand) => subcommand
.setName("list")
.setDescription("Lists all playdates")
.addStringOption(GroupSelection.createOptionSetup())
)
.addSubcommand((subcommand) => subcommand
.setName("remove")
.setDescription("Removes a playdate")
.addStringOption(GroupSelection.createOptionSetup())
.addIntegerOption((option) => option
.setName("playdate")
.setDescription("Selects a playdate")
.setRequired(true)
.setAutocomplete(true)
)
);
}
async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const group = GroupSelection.getGroup(interaction);
switch (interaction.options.getSubcommand()) {
case "create":
await this.create(interaction, group);
break;
case "remove":
await this.delete(interaction, group);
break;
case "list":
await this.list(interaction, group);
break;
default:
throw new UserError("This subcommand is not yet implemented.");
}
}
async create(interaction: CommandInteraction, group: GroupModel): Promise<void> {
const fromDate = Date.parse(<string>interaction.options.get("from")?.value ?? '');
const toDate = Date.parse(<string>interaction.options.get("to")?.value ?? '');
if (isNaN(fromDate)) {
throw new UserError("No date or invalid date format for the from parameter.");
}
if (isNaN(toDate)) {
throw new UserError("No date or invalid date format for the to parameter.");
}
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 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.")
.setFooter({
text: `Group: ${group.name}`
})
await interaction.reply({
embeds: [
embed
],
flags: MessageFlags.Ephemeral,
})
}
async handleAutocomplete(interaction: AutocompleteInteraction): Promise<void> {
const option = interaction.options.getFocused(true);
if (option.name == "group") {
await GroupSelection.handleAutocomplete(interaction);
return;
}
if (option.name != 'playdate') {
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 playdates = Container.get<PlaydateRepository>(PlaydateRepository.name).findFromGroup(group);
await interaction.respond(
playdates.map(playdate => {
return {
name: `${playdate.from_time.toLocaleString()} - ${playdate.to_time.toLocaleString()}`,
value: playdate.id
}
})
)
}
private async list(interaction: ChatInputCommandInteraction, group: GroupModel) {
const playdates = Container.get<PlaydateRepository>(PlaydateRepository.name).findFromGroup(group);
const embed = new EmbedBuilder()
.setTitle("The next playdates:")
.setFields(
playdates.map((playdate) =>
{
return {
name: `${playdate.from_time.toLocaleString()} - ${playdate.to_time.toLocaleString()}`,
value: ``
}
})
)
.setFooter({
text: `Group: ${group.name}`
})
await interaction.reply({
embeds: [
embed
],
flags: MessageFlags.Ephemeral,
})
}
private async delete(interaction: ChatInputCommandInteraction, group: GroupModel): Promise<void> {
const playdateId = interaction.options.getInteger("playdate", true)
const repo = Container.get<PlaydateRepository>(PlaydateRepository.name);
const selected = repo.getById(playdateId);
if (!selected) {
throw new UserError("No playdate found");
}
console.log(selected, group);
if (selected.group?.id != group.id) {
throw new UserError("No playdate found");
}
repo.delete(playdateId);
const embed = new EmbedBuilder()
.setTitle("Playdate deleted")
.setDescription(
`:x: Deleted \`${selected.from_time.toLocaleString()} - ${selected.to_time.toLocaleString()}\``
)
.setFooter({
text: `Group: ${group.name}`
})
await interaction.reply({
embeds: [
embed
],
flags: MessageFlags.Ephemeral,
})
}
}