Adds eslint and linted & improved routing for interactions
This commit is contained in:
parent
83209f642c
commit
441715675c
35 changed files with 2091 additions and 463 deletions
|
|
@ -10,7 +10,7 @@ export class CommandDeployer {
|
|||
}
|
||||
|
||||
public async deployAvailableServers() {
|
||||
const commandInfos = [];
|
||||
const commandInfos: object[] = [];
|
||||
this.client.Commands.allCommands.forEach((command) => {
|
||||
commandInfos.push(command.definition().toJSON())
|
||||
})
|
||||
|
|
@ -27,11 +27,10 @@ export class CommandDeployer {
|
|||
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
|
||||
const data = await this.client.RESTClient.put(
|
||||
Routes.applicationGuildCommands(this.client.ApplicationId, serverId),
|
||||
{ body: commandInfos },
|
||||
await this.client.RESTClient.put(
|
||||
Routes.applicationGuildCommands(this.client.ApplicationId, serverId),
|
||||
{ body: commandInfos },
|
||||
);
|
||||
|
||||
this.logger.log(`Successfully reloaded ${commandInfos.length} application (/) commands for ${serverId}.`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
import {
|
||||
AutocompleteInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
CommandInteraction,
|
||||
GuildMember, SlashCommandIntegerOption,
|
||||
SlashCommandStringOption
|
||||
} from "discord.js";
|
||||
import {Container} from "../../Container/Container";
|
||||
import {GroupRepository} from "../../Repositories/GroupRepository";
|
||||
|
|
@ -22,7 +20,7 @@ export class GroupSelection {
|
|||
public static async handleAutocomplete(interaction: AutocompleteInteraction, onlyLeaders: boolean = false): Promise<void> {
|
||||
const value = interaction.options.getFocused();
|
||||
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(
|
||||
groups
|
||||
.filter((group) => group.name.startsWith(value))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import {ChatInputCommandInteraction, Interaction, SlashCommandBuilder} from "discord.js";
|
||||
import Commands from "./Commands";
|
||||
|
||||
export interface Command {
|
||||
definition(): SlashCommandBuilder;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
ChatInputCommandInteraction,
|
||||
SlashCommandBuilder,
|
||||
ChatInputCommandInteraction,
|
||||
MessageFlags,
|
||||
InteractionReplyOptions,
|
||||
GuildMember,
|
||||
EmbedBuilder,
|
||||
AutocompleteInteraction,
|
||||
roleMention, time, userMention
|
||||
roleMention, time, userMention, GuildMemberRoleManager
|
||||
} from "discord.js";
|
||||
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
|
||||
import {GroupModel} from "../../Models/GroupModel";
|
||||
|
|
@ -19,265 +19,264 @@ import {GroupConfigurationRenderer} from "../../Groups/GroupConfigurationRendere
|
|||
import {GroupConfigurationHandler} from "../../Groups/GroupConfigurationHandler";
|
||||
import {GroupConfigurationTransformers} from "../../Groups/GroupConfigurationTransformers";
|
||||
import {GroupConfigurationRepository} from "../../Repositories/GroupConfigurationRepository";
|
||||
import {IconCache} from "../../Icons/IconCache";
|
||||
import {PlaydateRepository} from "../../Repositories/PlaydateRepository";
|
||||
import {Nullable} from "../../types/Nullable";
|
||||
|
||||
export class GroupCommand implements Command, ChatInteractionCommand, AutocompleteCommand {
|
||||
private static GOODBYE_MESSAGES: string[] = [
|
||||
'Sad to see you go.',
|
||||
'May your next adventure be fruitful.',
|
||||
'I hope, I served you well.',
|
||||
'I wish you and your group good luck on your next adventures.',
|
||||
]
|
||||
|
||||
private static INVALID_CHARACTER_SEQUENCES: string[] = [
|
||||
"http://",
|
||||
"https://"
|
||||
]
|
||||
|
||||
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)
|
||||
.setMaxLength(64)
|
||||
)
|
||||
.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.")
|
||||
)
|
||||
.addSubcommand(command => command
|
||||
.setName('config')
|
||||
.setDescription("Starts the config manager for the group.")
|
||||
.addIntegerOption(GroupSelection.createOptionSetup())
|
||||
)
|
||||
.addSubcommand(command => command
|
||||
.setName("remove")
|
||||
.setDescription("Deletes a group you are the leader for.")
|
||||
.addIntegerOption(GroupSelection.createOptionSetup())
|
||||
)
|
||||
.addSubcommand(command => command
|
||||
.setName("transfer")
|
||||
.setDescription("Transfers leadership of a group to a different person")
|
||||
.addIntegerOption(GroupSelection.createOptionSetup)
|
||||
.addUserOption(option => option
|
||||
.setName("target")
|
||||
.setDescription("The member, that is the new leader")
|
||||
.setRequired(true)
|
||||
)
|
||||
);
|
||||
}
|
||||
private static GOODBYE_MESSAGES: string[] = [
|
||||
'Sad to see you go.',
|
||||
'May your next adventure be fruitful.',
|
||||
'I hope, I served you well.',
|
||||
'I wish you and your group good luck on your next adventures.',
|
||||
]
|
||||
|
||||
async execute(interaction: ChatInputCommandInteraction): Promise<void> {
|
||||
switch (interaction.options.getSubcommand()) {
|
||||
case "create":
|
||||
this.create(interaction);
|
||||
break;
|
||||
case "list":
|
||||
this.list(interaction);
|
||||
break;
|
||||
case "remove":
|
||||
await this.remove(interaction);
|
||||
break;
|
||||
case "config":
|
||||
await this.runConfigurator(interaction);
|
||||
break;
|
||||
case "transfer":
|
||||
await this.transferLeadership(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", true);
|
||||
|
||||
if (role.id === interaction.guildId) {
|
||||
throw new UserError("Creating a group for everyone is not permitted!");
|
||||
}
|
||||
|
||||
if (!interaction.member?.roles.cache.has(role?.id) ?? true) {
|
||||
throw new UserError(
|
||||
"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."
|
||||
);
|
||||
}
|
||||
|
||||
const validName = this.validateGroupName(name);
|
||||
if (name !== true) {
|
||||
throw new UserError(`Your group name contains one or more invalid character sequences: ${validName}`)
|
||||
}
|
||||
|
||||
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 validateGroupName(name: string): true|string{
|
||||
const lowercaseName = name.toLowerCase();
|
||||
for (let invalidcharactersequence of GroupCommand.INVALID_CHARACTER_SEQUENCES) {
|
||||
if (!lowercaseName.includes(invalidcharactersequence)) {
|
||||
continue
|
||||
}
|
||||
|
||||
return invalidcharactersequence
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static INVALID_CHARACTER_SEQUENCES: string[] = [
|
||||
"http://",
|
||||
"https://"
|
||||
]
|
||||
|
||||
private list(interaction: ChatInputCommandInteraction) {
|
||||
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
||||
const groups = repo.findGroupsByMember(<GuildMember>interaction.member);
|
||||
|
||||
const playdateRepo = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
||||
|
||||
const iconCache = Container.get<IconCache>(IconCache.name);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Your groups on this server:")
|
||||
.setFields(
|
||||
groups.map(group => {
|
||||
const nextPlaydate = playdateRepo.getNextPlaydateForGroup(group);
|
||||
|
||||
const values = [
|
||||
`Role: ${iconCache.getEmoji("people_group_solid")} ${roleMention(group.role.roleid)}`,
|
||||
`Leader/GM: ${userMention(group.leader.memberid)}`
|
||||
];
|
||||
|
||||
if (nextPlaydate) {
|
||||
values.push(
|
||||
`Next Playdate: ${iconCache.getEmoji("calendar_days_solid")} ${time(nextPlaydate.from_time, "F")}`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
name: group.name,
|
||||
value: values.join("\n")
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const reply: InteractionReplyOptions = {
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
allowedMentions: { roles: [] },
|
||||
flags: MessageFlags.Ephemeral
|
||||
}
|
||||
|
||||
interaction.reply(reply);
|
||||
}
|
||||
|
||||
private async remove(interaction: ChatInputCommandInteraction) {
|
||||
const group = GroupSelection.getGroup(interaction);
|
||||
|
||||
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
||||
if (group.leader.memberid != interaction.member?.user.id) {
|
||||
throw new UserError("Can't remove group. You are not the leader.");
|
||||
}
|
||||
|
||||
repo.deleteGroup(group);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Group deleted.")
|
||||
.setDescription(
|
||||
`:x: Deleted \`${group.name}\`. ${ArrayUtils.chooseRandom(GroupCommand.GOODBYE_MESSAGES)}`
|
||||
)
|
||||
|
||||
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, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async runConfigurator(interaction: ChatInputCommandInteraction) {
|
||||
const group = GroupSelection.getGroup(interaction);
|
||||
|
||||
const configurationRenderer = new GroupConfigurationRenderer(
|
||||
new GroupConfigurationHandler(
|
||||
Container.get<GroupConfigurationRepository>(GroupConfigurationRepository.name),
|
||||
group
|
||||
),
|
||||
new GroupConfigurationTransformers(),
|
||||
definition(): SlashCommandBuilder {
|
||||
// @ts-expect-error Slash command expects more than needed.
|
||||
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)
|
||||
.setMaxLength(64)
|
||||
)
|
||||
.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.")
|
||||
)
|
||||
.addSubcommand(command => command
|
||||
.setName('config')
|
||||
.setDescription("Starts the config manager for the group.")
|
||||
.addIntegerOption(GroupSelection.createOptionSetup())
|
||||
)
|
||||
.addSubcommand(command => command
|
||||
.setName("remove")
|
||||
.setDescription("Deletes a group you are the leader for.")
|
||||
.addIntegerOption(GroupSelection.createOptionSetup())
|
||||
)
|
||||
.addSubcommand(command => command
|
||||
.setName("transfer")
|
||||
.setDescription("Transfers leadership of a group to a different person")
|
||||
.addIntegerOption(GroupSelection.createOptionSetup)
|
||||
.addUserOption(option => option
|
||||
.setName("target")
|
||||
.setDescription("The member, that is the new leader")
|
||||
.setRequired(true)
|
||||
)
|
||||
|
||||
await configurationRenderer.setup(interaction);
|
||||
);
|
||||
}
|
||||
|
||||
async execute(interaction: ChatInputCommandInteraction): Promise<void> {
|
||||
switch (interaction.options.getSubcommand()) {
|
||||
case "create":
|
||||
this.create(interaction);
|
||||
break;
|
||||
case "list":
|
||||
this.list(interaction);
|
||||
break;
|
||||
case "remove":
|
||||
await this.remove(interaction);
|
||||
break;
|
||||
case "config":
|
||||
await this.runConfigurator(interaction);
|
||||
break;
|
||||
case "transfer":
|
||||
await this.transferLeadership(interaction);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unsupported command");
|
||||
}
|
||||
|
||||
private async transferLeadership(interaction: ChatInputCommandInteraction) {
|
||||
const group = GroupSelection.getGroup(interaction);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
||||
if (group.leader.memberid != interaction.member?.user.id) {
|
||||
throw new UserError(
|
||||
"Can't transfer leadership. You are not the leader."
|
||||
);
|
||||
}
|
||||
|
||||
const newLeader = <GuildMember>interaction.options.getMember("target", true);
|
||||
if (!newLeader.roles.cache.has(group.role.roleid)) {
|
||||
throw new UserError(
|
||||
"Can't transfer leadership: The target member is not part of your group.",
|
||||
"Add the user to the role this group is part in or ask your server admin to do it."
|
||||
);
|
||||
}
|
||||
|
||||
group.leader.memberid = newLeader.id
|
||||
repo.update(group);
|
||||
private create(interaction: ChatInputCommandInteraction): void {
|
||||
const name = interaction.options.getString("name") ?? '';
|
||||
const role = interaction.options.getRole("role", true);
|
||||
|
||||
if (role.id === interaction.guildId) {
|
||||
throw new UserError("Creating a group for everyone is not permitted!");
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Leadership transfered")
|
||||
.setDescription(
|
||||
`Leadership was successfully transfered to ${userMention(newLeader.user.id)}`
|
||||
if (!(<Nullable<GuildMemberRoleManager>>interaction.member?.roles)?.cache.has(role?.id)) {
|
||||
throw new UserError(
|
||||
"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."
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new UserError(`Your group name contains one or more invalid character sequences: ${validName}`)
|
||||
}
|
||||
|
||||
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 validateGroupName(name: string): true|string{
|
||||
const lowercaseName = name.toLowerCase();
|
||||
for (const invalidcharactersequence of GroupCommand.INVALID_CHARACTER_SEQUENCES) {
|
||||
if (!lowercaseName.includes(invalidcharactersequence)) {
|
||||
continue
|
||||
}
|
||||
|
||||
return invalidcharactersequence
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private list(interaction: ChatInputCommandInteraction) {
|
||||
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
||||
const groups = repo.findGroupsByMember(<GuildMember>interaction.member);
|
||||
|
||||
const playdateRepo = Container.get<PlaydateRepository>(PlaydateRepository.name);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Your groups on this server:")
|
||||
.setFields(
|
||||
groups.map(group => {
|
||||
const nextPlaydate = playdateRepo.getNextPlaydateForGroup(group);
|
||||
|
||||
const values = [
|
||||
`Role: ${roleMention(group.role.roleid)}`,
|
||||
`Leader/GM: ${userMention(group.leader.memberid)}`
|
||||
];
|
||||
|
||||
if (nextPlaydate) {
|
||||
values.push(
|
||||
`Next Playdate: ${time(nextPlaydate.from_time, "F")}`
|
||||
)
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
flags: MessageFlags.Ephemeral,
|
||||
return {
|
||||
name: group.name,
|
||||
value: values.join("\n")
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const reply: InteractionReplyOptions = {
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
allowedMentions: { roles: [] },
|
||||
flags: MessageFlags.Ephemeral
|
||||
}
|
||||
|
||||
interaction.reply(reply);
|
||||
}
|
||||
|
||||
private async remove(interaction: ChatInputCommandInteraction) {
|
||||
const group = GroupSelection.getGroup(interaction);
|
||||
|
||||
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
||||
if (group.leader.memberid != interaction.member?.user.id) {
|
||||
throw new UserError("Can't remove group. You are not the leader.");
|
||||
}
|
||||
|
||||
repo.deleteGroup(group);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Group deleted.")
|
||||
.setDescription(
|
||||
`:x: Deleted \`${group.name}\`. ${ArrayUtils.chooseRandom(GroupCommand.GOODBYE_MESSAGES)}`
|
||||
)
|
||||
|
||||
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, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async runConfigurator(interaction: ChatInputCommandInteraction) {
|
||||
const group = GroupSelection.getGroup(interaction);
|
||||
|
||||
const configurationRenderer = new GroupConfigurationRenderer(
|
||||
new GroupConfigurationHandler(
|
||||
Container.get<GroupConfigurationRepository>(GroupConfigurationRepository.name),
|
||||
group
|
||||
),
|
||||
new GroupConfigurationTransformers(),
|
||||
)
|
||||
|
||||
await configurationRenderer.setup(interaction);
|
||||
}
|
||||
|
||||
private async transferLeadership(interaction: ChatInputCommandInteraction) {
|
||||
const group = GroupSelection.getGroup(interaction);
|
||||
|
||||
const repo = Container.get<GroupRepository>(GroupRepository.name);
|
||||
if (group.leader.memberid != interaction.member?.user.id) {
|
||||
throw new UserError(
|
||||
"Can't transfer leadership. You are not the leader."
|
||||
);
|
||||
}
|
||||
|
||||
const newLeader = <GuildMember>interaction.options.getMember("target");
|
||||
if (!newLeader.roles.cache.has(group.role.roleid)) {
|
||||
throw new UserError(
|
||||
"Can't transfer leadership: The target member is not part of your group.",
|
||||
"Add the user to the role this group is part in or ask your server admin to do it."
|
||||
);
|
||||
}
|
||||
|
||||
group.leader.memberid = newLeader.id
|
||||
repo.update(group);
|
||||
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Leadership transferred")
|
||||
.setDescription(
|
||||
`Leadership was successfully transferred to ${userMention(newLeader.user.id)}`
|
||||
)
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
flags: MessageFlags.Ephemeral,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {SlashCommandBuilder, Interaction, CommandInteraction} from "discord.js";
|
||||
import {SlashCommandBuilder, CommandInteraction} from "discord.js";
|
||||
import {Command} from "./Command";
|
||||
|
||||
export class HelloWorldCommand implements Command {
|
||||
|
|
@ -6,7 +6,7 @@ export class HelloWorldCommand implements Command {
|
|||
'Hello :)',
|
||||
'zzzZ... ZzzzZ... huh? I am awake. I am awake!',
|
||||
'Roll for initiative!',
|
||||
'I was an adventurerer like you...',
|
||||
'I was an adventurer like you...',
|
||||
'Hello :) How are you?',
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,16 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
Interaction,
|
||||
CommandInteraction,
|
||||
AutocompleteInteraction,
|
||||
GuildMember,
|
||||
EmbedBuilder, MessageFlags, ChatInputCommandInteraction, ModalSubmitFields, time, User
|
||||
EmbedBuilder, MessageFlags, ChatInputCommandInteraction, time
|
||||
} 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 = [
|
||||
|
|
@ -24,7 +18,7 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
|
|||
]
|
||||
|
||||
definition(): SlashCommandBuilder {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error Command builder is improperly marked as incomplete.
|
||||
return new SlashCommandBuilder()
|
||||
.setName("playdates")
|
||||
.setDescription("Manage your playdates")
|
||||
|
|
@ -106,7 +100,7 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
|
|||
to_time: new Date(toDate),
|
||||
}
|
||||
|
||||
const id = playdateRepo.create(playdate);
|
||||
playdateRepo.create(playdate);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Created a play-date.")
|
||||
|
|
|
|||
|
|
@ -2,142 +2,66 @@ import {
|
|||
Client,
|
||||
GatewayIntentBits,
|
||||
Events,
|
||||
Interaction,
|
||||
ChatInputCommandInteraction,
|
||||
MessageFlags,
|
||||
Activity,
|
||||
ActivityType, REST, inlineCode
|
||||
ActivityType, REST
|
||||
} from "discord.js";
|
||||
import Commands from "./Commands/Commands";
|
||||
import {Container} from "../Container/Container";
|
||||
import {Logger} from "log4js";
|
||||
import {UserError} from "./UserError";
|
||||
import {InteractionRouter} from "./InteractionRouter";
|
||||
|
||||
export class DiscordClient {
|
||||
private readonly client: Client;
|
||||
private commands: Commands;
|
||||
private readonly restClient: REST;
|
||||
|
||||
public get Client (): Client {
|
||||
|
||||
public get Client(): Client {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
public get Commands(): Commands {
|
||||
return this.commands
|
||||
|
||||
public get Commands(): Commands {
|
||||
return this.router.commands
|
||||
}
|
||||
|
||||
|
||||
public get RESTClient(): REST {
|
||||
return this.restClient;
|
||||
}
|
||||
|
||||
|
||||
public get ApplicationId(): string {
|
||||
return this.applicationId;
|
||||
}
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly applicationId: string
|
||||
private readonly applicationId: string,
|
||||
private readonly router: InteractionRouter,
|
||||
private readonly restClient: REST = new REST()
|
||||
) {
|
||||
this.client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds]
|
||||
})
|
||||
|
||||
this.commands = new Commands();
|
||||
this.restClient = new REST();
|
||||
}
|
||||
|
||||
|
||||
applyEvents() {
|
||||
this.client.once(Events.ClientReady, () => {
|
||||
if (!this.client.user) {
|
||||
return;
|
||||
}
|
||||
|
||||
Container.get<Logger>("logger").info(`Ready! Logged in as ${this.client.user.tag}`);
|
||||
this.client.user.setActivity('your PnP playdates', {
|
||||
type: ActivityType.Watching,
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
this.client.on(Events.GuildAvailable, () => {
|
||||
Container.get<Logger>("logger").info("Joined Guild?")
|
||||
})
|
||||
|
||||
this.client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
||||
const method = this.findCommandMethod(interaction);
|
||||
if (!method) {
|
||||
Container.get<Logger>("logger").error(`Could not find method for '${interaction.commandName}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
await method();
|
||||
})
|
||||
|
||||
this.client.on(Events.InteractionCreate, this.router.route.bind(this.router));
|
||||
}
|
||||
|
||||
|
||||
connect(token: string) {
|
||||
this.client.login(token);
|
||||
}
|
||||
|
||||
|
||||
connectRESTClient(token: string) {
|
||||
this.restClient.setToken(token);
|
||||
}
|
||||
|
||||
private findCommandMethod(interaction: Interaction) {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const command = this.commands.getCommand(interaction.commandName);
|
||||
|
||||
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
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue