Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
"homepage": "https://github.com/jafrilli/acm-bot#readme",
"dependencies": {
"@discordjs/builders": "^0.12.0",
"@discordjs/rest": "^0.1.0-canary.0",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was using an alpha version of this earlier, but stable version works now

"@discordjs/rest": "^0.4.1",
"@google-cloud/firestore": "^4.13.0",
"axios": "^0.21.4",
"body-parser": "^1.19.0",
"discord-api-types": "^0.23.1",
"discord.js": "^13.6.0",
"discord.js": "^13.7.0",
"express": "^4.17.1",
"is-url": "^1.2.4",
"leeks.js": "^0.0.8",
Expand Down
14 changes: 8 additions & 6 deletions src/api/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface Config {
slashCommandPath: string;
cmCommandPath: string;
buttonPath: string;
modalPath: string;
eventPath: string;
endpointPath: string;
sentryDNS: string;
Expand Down Expand Up @@ -87,17 +88,18 @@ export default class Bot extends Client {
this,
config.slashCommandPath,
config.cmCommandPath,
config.buttonPath
config.buttonPath,
config.modalPath,
),
error: new ErrorManager(this),
database: new DatabaseManager(this, config),
firestore: new FirestoreManager(this),
verification: new VerificationManager(this),
event: new EventManager(this, config.eventPath),
indicator: new IndicatorManager(this),
database: new DatabaseManager(this, config),
scheduler: new ScheduleManager(this),
circle: new CircleManager(this),
rero: new ReactionRoleManager(this),
firestore: new FirestoreManager(this),
resolve: new ResolveManager(this),
express: new ExpressManager(this, config.endpointPath),
points: new PointsManager(this),
Expand All @@ -110,9 +112,9 @@ export default class Bot extends Client {
async start(): Promise<void> {
await this.login(this.config.token);
this.logger.info("Initializing managers...");
Object.entries(this.managers).forEach(([k, v]) => {
v.init();
});
for (const manager of Object.values(this.managers)) {
await manager.init();
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some managers depend on others to start up first (error handling, databases, etc. should be first)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably leave a comment here

this.logger.info("Bot started.");
}
}
37 changes: 37 additions & 0 deletions src/api/interaction/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
ApplicationCommandPermissionData,
ModalSubmitInteraction,
Interaction,
} from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import CustomInteraction, {
InteractionConfig,
InteractionContext,
} from "./interaction";

export interface ModalInteractionConfig extends InteractionConfig {}

export interface ModalInteractionContext extends InteractionContext {
interaction: ModalSubmitInteraction;
}

/**
* No need to register modals, but we do need a way to handle the callbacks
*/
export default abstract class CustomModalInteraction extends CustomInteraction {
protected constructor(config: ModalInteractionConfig) {
super(config);
}

/**
* Match to customId, either by direct comparison or regex or something else.
* If it matches, expect handleInteraction to be called afterwards.
* @param customId
*/
public abstract matchCustomId(customId: string): boolean;

/**
* Perform actions for handling the interaction
*/
public abstract handleInteraction(context: ModalInteractionContext): any;
}
3 changes: 3 additions & 0 deletions src/event/guildmemberadd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export default class GuildMemberAddEvent extends Event {
}

public async emit(bot: Bot, member: GuildMember) {
// If member previously verified, restore their member role
await bot.managers.verification.handleMemberJoin(member);

const embed = new MessageEmbed({
title: `**Welcome to the ACM Discord Server!** 🎉`,
author: {
Expand Down
1 change: 0 additions & 1 deletion src/event/messagecreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ export default class MessageCreateEvent extends Event {

public async emit(bot: Bot, msg: Message): Promise<void> {
await bot.managers.command.handle(msg);
await bot.managers.verification.handle(msg);
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const bot: Bot = new Bot({
slashCommandPath: path.join(process.cwd(), "dist", "interaction", "command"),
cmCommandPath: path.join(process.cwd(), "dist", "interaction", "contextmenu"),
buttonPath: path.join(process.cwd(), "dist", "interaction", "button"),
modalPath: path.join(process.cwd(), "dist", "interaction", "modal"),
eventPath: path.join(process.cwd(), "dist", "event"),
endpointPath: path.join(process.cwd(), "dist", "endpoint"),
responseFormat: settings.responseFormat,
Expand Down
22 changes: 22 additions & 0 deletions src/interaction/button/verification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import CustomButtonInteraction, {
ButtonInteractionContext
} from "../../api/interaction/button";

export default class VerificationButton extends CustomButtonInteraction {
public constructor() {
super({
name: "verification-button",
});
}

public matchCustomId(customId: string) {
return customId === "verification-button";
}

public async handleInteraction({
bot,
interaction,
}: ButtonInteractionContext): Promise<void> {
await bot.managers.verification.handleVerificationRequest(interaction);
}
}
23 changes: 23 additions & 0 deletions src/interaction/modal/verification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import CustomModalInteraction, {
ModalInteractionContext,
} from "../../api/interaction/modal";

export default class VerificationModal extends CustomModalInteraction {
public constructor() {
super({
name: "verification-modal",
});
}

public matchCustomId(customId: string) {
return customId === "verification-modal";
}

public async handleInteraction({
bot,
interaction,
}: ModalInteractionContext): Promise<void> {
// Forward to circle handler
await bot.managers.verification.handleVerificationSubmit(interaction);
}
}
36 changes: 23 additions & 13 deletions src/util/manager/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import {
ButtonInteraction,
Collection,
CommandInteraction,
ContextMenuInteraction,
GuildApplicationCommandPermissionData,
Interaction,
} from "discord.js";
import path from "path";
import Bot from "../../api/bot";
import Manager from "../../api/manager";
import BaseInteraction from "../../api/interaction/interaction";
Expand All @@ -15,28 +10,32 @@ import { Routes } from "discord-api-types/v9";
import SlashCommand from "../../api/interaction/slashcommand";
import CustomButtonInteraction from "../../api/interaction/button";
import ContextMenuCommand from "../../api/interaction/contextmenucommand";
import { ApplicationCommandType } from "discord-api-types";
import CustomModalInteraction from "../../api/interaction/modal";

export default class InteractionManager extends Manager {
// private readonly interactionPath = process.cwd() + "/dist/interaction/";
private slashCommandPath: string;
private cmCommandPath: string;
private buttonPath: string;
private modalPath: string;

private slashCommands: Map<string, SlashCommand> = new Map();
private cmCommands: Map<string, ContextMenuCommand> = new Map();
private buttons: Map<string, CustomButtonInteraction> = new Map();
private modals: Map<string, CustomModalInteraction> = new Map();

constructor(
bot: Bot,
slashCommandPath: string,
cmCommandPath: string,
buttonPath: string
buttonPath: string,
modalPath: string,
) {
super(bot);
this.slashCommandPath = slashCommandPath;
this.cmCommandPath = cmCommandPath;
this.buttonPath = buttonPath;
this.modalPath = modalPath;
}

/**
Expand All @@ -45,7 +44,7 @@ export default class InteractionManager extends Manager {
public init() {
// this.loadInteractionHandlers();
this.registerSlashAndContextMenuCommands();
this.registerButtons();
this.registerButtonsAndModals();
}

/**
Expand All @@ -65,6 +64,10 @@ export default class InteractionManager extends Manager {
handler = [...this.buttons.values()].find((x) =>
x.matchCustomId(interaction.customId)
) as BaseInteraction;
} else if (interaction.isModalSubmit()) {
handler = [...this.modals.values()].find((x) =>
x.matchCustomId(interaction.customId)
) as BaseInteraction;
} else return;

// Return if not found
Expand All @@ -74,9 +77,10 @@ export default class InteractionManager extends Manager {
try {
await handler.handleInteraction({ bot: this.bot, interaction });
} catch (e: any) {
await interaction.reply(
"Command execution failed. Please contact a bot maintainer..."
);
await interaction.reply({
content: "Command execution failed. Please contact a bot maintainer...",
ephemeral: true
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this ephemeral to hide failed executions from public, in case someones trying to use an anon slash command or smth.

});
// Don't throw and let the bot handle this as an unhandled rejection. Instead,
// take initiative to handle it as an error so we can see the trace.
await this.bot.managers.error.handleErr(e);
Expand Down Expand Up @@ -161,16 +165,22 @@ export default class InteractionManager extends Manager {
}
}

private async registerButtons() {
private async registerButtonsAndModals() {
try {
// Dynamically load source files
this.buttons = new Map(
DynamicLoader.loadClasses(this.buttonPath).map((sc) => [sc.name, sc])
);

for (const btn of this.buttons.keys()) {
this.bot.logger.info(`Loaded button '${btn}'`);
}

this.modals = new Map(
DynamicLoader.loadClasses(this.modalPath).map((sc) => [sc.name, sc])
);
for (const mdl of this.modals.keys()) {
this.bot.logger.info(`Loaded modal '${mdl}'`);
}
} catch (error: any) {
await this.bot.managers.error.handleErr(error);
}
Expand Down
Loading