Skip to content

Instantly share code, notes, and snippets.

@svemat01
Created May 17, 2023 17:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save svemat01/b9af4b186eb14b2b8955783d39f2f042 to your computer and use it in GitHub Desktop.
Save svemat01/b9af4b186eb14b2b8955783d39f2f042 to your computer and use it in GitHub Desktop.
Typescript bot types/handlers
import { Interaction } from 'discord.js';
import { slashCommands } from './SlashHandler';
export const autoCompletes = slashCommands.filter(
(command) => command.autoComplete
); // Auto load commands from SlashHandler but only include them if they have an autocomplete function
export const handleAutoComplete = async (
interaction: Interaction
): Promise<void> => {
if (!interaction.isAutocomplete()) return;
for (const cmd of autoCompletes) {
if (!cmd.autoComplete) return; // Type guard
if (interaction.commandName == cmd.data.name) {
await cmd.autoComplete(interaction);
}
}
};
import { Interaction } from 'discord.js';
import { ButtonComponent } from '../types/ButtonComponent';
const slashButtons = slashCommands
.filter((command) => command.buttons)
.flatMap((command) => command.buttons);
const textButtons = textCommands
.filter((command) => command.buttons)
.flatMap((command) => command.buttons);
export const Buttons: (ButtonComponent | undefined)[] = [
...slashButtons,
...textButtons,
];
export const handleButton = async (interaction: Interaction): Promise<void> => {
// eslint-disable-next-line sonarjs/no-empty-collection
for (const button of Buttons) {
if (!button) continue;
if (typeof button.customId === 'string') {
if (interaction.customId == button.customId) {
await button.run(interaction);
}
continue
}
if (interaction.customId.match(button.customId)) {
await button.run(interaction);
}
}
};
import { Client } from 'discord.js';
import {DiscordEvent} from '$types/DiscordEvent'
const events: DiscordEvent[] = []
export const RegisterEvents = (client: Client): void => {
for (const event of events) {
client.on(event.event, event.handler);
}
};
import { Interaction } from 'discord.js';
import { slashCommands } from './SlashHandler';
import { ModalComponent } from '$types/ModalComponent';
const slashModals = slashCommands
.filter((command) => command.modals)
.flatMap((command) => command.modals);
export const Modals: (ModalComponent | undefined)[] = [
...slashModals,
];
export const handleModalSubmit = async (interaction: Interaction): Promise<void> => {
if (!interaction.isModalSubmit()) return;
// eslint-disable-next-line sonarjs/no-empty-collection
for (const modal of Modals) {
if (modal && interaction.customId == modal.customId) {
await modal.run(interaction);
}
}
};
import { Interaction } from 'discord.js';
export const slashCommands: SlashCommand[] = [
];
export const handleSlashCommand = async (
interaction: Interaction
): Promise<void> => {
if (!interaction.isChatInputCommand()) return;
for (const cmd of slashCommands) {
if (interaction.commandName == cmd.data.name) {
await cmd.run(interaction);
}
}
};
import {
channelLink,
Colors,
EmbedBuilder,
GuildMember,
Message,
} from 'discord.js';
import { parseArguments } from '$lib/ArgumentParser';
import { getPermissionLevel } from '$lib/Permissions';
import { CmdContext } from '$types/CmdContext';
import { PermissionLevel } from '$types/PermissionLevel';
import { Prefix } from '..';
import { TextCommand } from '$types/TextCommand';
export const textCommands: TextCommand[] = [
];
// eslint-disable-next-line sonarjs/cognitive-complexity
export const handleTextCommand = async (message: Message): Promise<void> => {
// if (!message.channel || !message.member) {
// return;
// }
if (message.author.bot) {
return;
}
for (const cmd of textCommands) {
// Check either if message starts with command handle or alias if any
const arguments_ = parseArguments(message.content);
if (
arguments_[0] === `${Prefix}${cmd.handle}` ||
cmd.aliases?.some(
(alias: string) => arguments_[0] === `${Prefix}${alias}`
)
) {
arguments_.shift();
let context_member: GuildMember | undefined;
let context_member2: GuildMember | undefined;
let memberPermissionLevel: PermissionLevel;
if (message.member) {
// Run when ran in guild
context_member = await message.member.fetch();
memberPermissionLevel = getPermissionLevel(context_member);
} else {
// Run when in DM
context_member = undefined;
memberPermissionLevel = PermissionLevel.REGULAR;
}
if (!Prefix) return;
const context: CmdContext = {
message: message,
command: cmd,
arguments: arguments_,
author: message.author,
member: context_member,
channel: message.channel,
prefix: Prefix,
};
if (!context.member && !cmd.works_dm) {
// eslint-disable-next-line prettier/prettier
await context.channel.send("This doesn't work in DM");
break;
}
if (memberPermissionLevel < cmd.required_permission) {
console.log('No permission');
break;
}
if (
cmd.channels &&
!cmd.channels.includes(context.channel.id) &&
context.member &&
memberPermissionLevel < PermissionLevel.ADMINISTRATOR
) {
break;
}
if (await cmd.run(context)) {
break;
}
}
}
};
/* Copyright (C) 2021 Luc van Kampen - All Rights Reserved
* You may use, distribute and modify this code up to your
* own discretion. As long as you acknowledge the existance
* of Luc. Feel free to become my friend.
* Please write to: luc@lucemans.nl, or visit https://lvk.sh/:
*/
export const parseArguments = (input: string): string[] => {
const sub = '▄';
const quotedStrings = [...input.matchAll(/"(.*?)"/gi)];
let result = input;
for (const quoted of quotedStrings) {
if (quoted.index == undefined) continue;
result =
result.slice(0, Math.max(0, quoted.index)) +
quoted.at(1)?.replace(/ /g, sub) +
result.slice(
quoted.index + (quoted.at(1) ? quoted.at(1)!.length : 0) + 2
);
}
return result.split(' ').map((entry) => entry.split(sub).join(' '));
};
import { ButtonInteraction } from 'discord.js';
export type ButtonComponent = {
customId: string | RegExp;
run: (interaction: ButtonInteraction) => boolean | Promise<boolean>;
};
import { ClientEvents } from 'discord.js';
export type DiscordEvent = {
handler: (...arguments_: any[]) => Promise<void>;
event: keyof ClientEvents;
name: string;
};
import { ModalSubmitInteraction } from 'discord.js';
export type ModalComponent = {
customId: string;
run: (interaction: ModalSubmitInteraction) => boolean | Promise<boolean>;
};
import {
AutocompleteInteraction,
ChatInputApplicationCommandData,
ChatInputCommandInteraction,
Snowflake,
} from 'discord.js';
import { ButtonComponent } from './ButtonComponent';
import { ModalComponent } from './ModalComponent';
export type SlashCommandRun = (interaction: ChatInputCommandInteraction) => Promise<boolean>;
export type SlashCommand = {
data: ChatInputApplicationCommandData;
guilds?: Snowflake[];
run: SlashCommandRun;
autoComplete?: (
interaction: AutocompleteInteraction
) => boolean | Promise<boolean>;
buttons?: ButtonComponent[];
modals?: ModalComponent[];
};
import { Snowflake } from 'discord.js';
import { ButtonComponent } from './ButtonComponent';
import { CmdCategory } from './CmdCategory';
import { CmdContext } from './CmdContext';
import { PermissionLevel } from './PermissionLevel';
export type TextCommandRun = (context: CmdContext) => boolean | Promise<boolean>;
export type TextCommand = {
handle: string;
description: string;
category: CmdCategory;
aliases?: string[];
required_permission: PermissionLevel;
works_dm: boolean;
run: TextCommandRun;
buttons?: ButtonComponent[];
channels?: Snowflake[];
};
import { GuildMember, Message, TextBasedChannel, User } from 'discord.js';
import { TextCommand } from './TextCommand';
export type CmdContext = {
message: Message;
command: TextCommand;
arguments: string[];
author: User;
member: GuildMember | undefined;
channel: TextBasedChannel;
prefix: string;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment