Update main branch #15
@ -116,6 +116,15 @@ declare interface DiscordClient extends Client {
|
||||
on<K extends keyof ClientEvents>(event: K, listener: EventHook<K>): this
|
||||
}
|
||||
|
||||
/**
|
||||
* Client class
|
||||
* Should not house much functionality itself,
|
||||
* rather it should mostly house components that implement behaviour.
|
||||
*
|
||||
* @class DiscordClient
|
||||
* @typedef {DiscordClient}
|
||||
* @extends {Client}
|
||||
*/
|
||||
class DiscordClient extends Client
|
||||
{
|
||||
#logger: Logger;
|
||||
@ -204,13 +213,6 @@ class DiscordClient extends Client
|
||||
this.#resolver = new Resolver(this);
|
||||
this.#rateLimiter = new RateLimiter(this);
|
||||
|
||||
|
||||
// As of d.js v14 these events are emitted from the rest manager, rebinding them to the client
|
||||
// this.rest.on('request', (...args) =>
|
||||
// {
|
||||
// this.emit('apiRequest', ...args);
|
||||
// });
|
||||
|
||||
this.rest.on('response', (...args) =>
|
||||
{
|
||||
this.emit('apiResponse', ...args);
|
||||
@ -228,10 +230,6 @@ class DiscordClient extends Client
|
||||
|
||||
this.#loadEevents();
|
||||
|
||||
// process.on('uncaughtException', (err) => {
|
||||
// this.logger.error(`Uncaught exception:\n${err.stack || err}`);
|
||||
// });
|
||||
|
||||
process.on('unhandledRejection', (err: Error) =>
|
||||
{
|
||||
this.#logger.error(`Unhandled rejection:\n${err?.stack || err}`);
|
||||
@ -314,7 +312,6 @@ class DiscordClient extends Client
|
||||
this.shutdown();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
async managerEval<Result> (script: ((controller: Controller) => Promise<Result> | Result) | string, options: ManagerEvalOptions = {})
|
||||
: Promise<Result>
|
||||
{
|
||||
@ -384,6 +381,7 @@ class DiscordClient extends Client
|
||||
}
|
||||
|
||||
// Helper function to pass options to the logger in a unified way
|
||||
// also avoids having to import the logger everywhere
|
||||
createLogger (comp: object, options: LoggerClientOptions = {})
|
||||
{
|
||||
return new Logger({ name: comp.constructor.name, ...this.#options.logger, ...options });
|
||||
@ -445,17 +443,6 @@ class DiscordClient extends Client
|
||||
return this.shard.ids[0];
|
||||
}
|
||||
|
||||
// on<K extends keyof ClientEvents>(event: K, listener: (...args: unknown[]) => Awaitable<void>): this;
|
||||
// on<K extends keyof ClientEvents> (event: K, listener: (...args: unknown[]) => Awaitable<void>)
|
||||
// {
|
||||
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// // @ts-ignore
|
||||
// return super.on(event, listener);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
#loadEevents ()
|
||||
{
|
||||
this.#eventHooker.hook('ready', () =>
|
||||
@ -518,9 +505,6 @@ class DiscordClient extends Client
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
#createWrappers ()
|
||||
{
|
||||
this.guilds.cache.forEach((guild) =>
|
||||
@ -567,7 +551,6 @@ class DiscordClient extends Client
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// Signatures for typescript inferral
|
||||
getUserWrapper(resolveable: UserResolveable, fetch?: false): UserWrapper | null;
|
||||
getUserWrapper(resolveable: UserResolveable, fetch?: true): Promise<UserWrapper | null>;
|
||||
getUserWrapper (resolveable: UserResolveable, fetch = true)
|
||||
@ -631,13 +614,6 @@ class DiscordClient extends Client
|
||||
return this.#built;
|
||||
}
|
||||
|
||||
// override get user ()
|
||||
// {
|
||||
// if (!super.user)
|
||||
// throw new Error('User not set');
|
||||
// return super.user;
|
||||
// }
|
||||
|
||||
get prefix ()
|
||||
{
|
||||
return this.#options.prefix;
|
||||
@ -745,6 +721,8 @@ class DiscordClient extends Client
|
||||
|
||||
}
|
||||
|
||||
// This is what actually starts the client.
|
||||
// Just running this file directly will not start the client and the process will just hang.
|
||||
process.once('message', (msg: IPCMessage) =>
|
||||
{
|
||||
if (msg._start)
|
||||
@ -754,8 +732,4 @@ process.once('message', (msg: IPCMessage) =>
|
||||
}
|
||||
});
|
||||
|
||||
export default DiscordClient;
|
||||
|
||||
// process.on("unhandledRejection", (error) => {
|
||||
// console.error("[DiscordClient.js] Unhandled promise rejection:", error); //eslint-disable-line no-console
|
||||
// });
|
||||
export default DiscordClient;
|
@ -22,6 +22,15 @@ import { EventHook } from '../../../@types/Client.js';
|
||||
import { ClientEvents } from '../../../@types/Events.js';
|
||||
import { Interaction } from 'discord.js';
|
||||
|
||||
/**
|
||||
* Hooks events to their handlers.
|
||||
* This is done in order to dispatch events to their handlers sequentially.
|
||||
* The order of handler execution matters due to priority and to avoid
|
||||
* race conditions and simultaneous execution in filter hooks.
|
||||
*
|
||||
* @class EventHooker
|
||||
* @typedef {EventHooker}
|
||||
*/
|
||||
class EventHooker
|
||||
{
|
||||
#target: DiscordClient;
|
||||
@ -84,9 +93,8 @@ class EventHooker
|
||||
}
|
||||
|
||||
let guild = null;
|
||||
// this.logger.debug(`Handler ${eventName}`);
|
||||
// Should probably move this elsewhere, but testing this out -- or maybe not, might be the most appropriate place for this
|
||||
const eventArgs = [];
|
||||
// Add guildWrapper property to d.js structures
|
||||
for (const arg of args)
|
||||
{
|
||||
if (arg && typeof arg === 'object' && 'guild' in arg && arg.guild)
|
||||
@ -102,19 +110,9 @@ class EventHooker
|
||||
|
||||
if (eventName === 'interactionCreate')
|
||||
{
|
||||
// const idx = eventArgs.findIndex(val =>
|
||||
// {
|
||||
// console.log(val!.constructor.name, val instanceof BaseInteraction, val instanceof ChatInputCommandInteraction);
|
||||
// return val instanceof BaseInteraction;
|
||||
// });
|
||||
// // ChatInputCommandInteraction;
|
||||
// console.log(idx, args);
|
||||
eventArgs[0] = new InteractionWrapper(this.#target, args[0] as Interaction);
|
||||
}
|
||||
|
||||
// async-await does nothing in a .forEach loop,
|
||||
// the forEach implementation does not await the results of the function before moving onto the next iteration
|
||||
// which is a problem if we don't want functionality to be overlapping, i.e. different filters trying to delete the same message
|
||||
for (const handler of this.#events.get(eventName) || [])
|
||||
{
|
||||
if (process.env.NODE_ENV === 'development')
|
||||
|
@ -17,6 +17,14 @@
|
||||
import DiscordClient from '../DiscordClient.js';
|
||||
import SlashCommand from '../interfaces/commands/SlashCommand.js';
|
||||
|
||||
/**
|
||||
* IPC module.
|
||||
* Currently primarily only used to tell the controller to update slash commands.
|
||||
* Wraps sending messages to the main process.
|
||||
*
|
||||
* @class Intercom
|
||||
* @typedef {Intercom}
|
||||
*/
|
||||
class Intercom
|
||||
{
|
||||
#client: DiscordClient;
|
||||
|
@ -31,6 +31,16 @@ type Languages = {
|
||||
[key: string]: Language
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of localisation.
|
||||
* Loads languages from files and takes care of formatting the strings.
|
||||
*
|
||||
* This class should rarely be called directly from most code,
|
||||
* rather used through wrappers that fill in options, e.g. GuildWrapper.format
|
||||
*
|
||||
* @class LocaleLoader
|
||||
* @typedef {LocaleLoader}
|
||||
*/
|
||||
class LocaleLoader
|
||||
{
|
||||
#logger: LoggerClient;
|
||||
|
@ -30,6 +30,17 @@ type DeleteQueueEntry = {
|
||||
message: Message,
|
||||
} & QueueEntry
|
||||
|
||||
/**
|
||||
* Utility class used for limiting the amount of messages the bot sends.
|
||||
* Most useful when filtering and sending certain log messages.
|
||||
*
|
||||
* Filters usually notify the user about their message being deleted, but that notice should be sent out infrequently.
|
||||
* Logs like member logs may sometimes have to send out multiple messages in a short span, and can be batched easily.
|
||||
* Also used when batching deletions.
|
||||
*
|
||||
* @class RateLimiter
|
||||
* @typedef {RateLimiter}
|
||||
*/
|
||||
class RateLimiter
|
||||
{
|
||||
#client: DiscordClient;
|
||||
|
@ -24,6 +24,13 @@ import { Command, Component, Module, Setting } from '../interfaces/index.js';
|
||||
import { Util } from '../../utilities/index.js';
|
||||
import { isInitialisable } from '../interfaces/Initialisable.js';
|
||||
|
||||
/**
|
||||
* Component registry.
|
||||
* Takes care of instantiating and storing most components.
|
||||
*
|
||||
* @class Registry
|
||||
* @typedef {Registry}
|
||||
*/
|
||||
class Registry
|
||||
{
|
||||
#client: DiscordClient;
|
||||
|
@ -38,17 +38,19 @@ const filterInexact = <T extends Component>(search: string) => (comp: T) => comp
|
||||
|| ((comp instanceof Command || comp instanceof Setting) && comp.aliases && (comp.aliases.some((ali) => `${comp.type}:${ali}`.toLowerCase().includes(search)))
|
||||
|| ((comp instanceof Command || comp instanceof Setting) && comp.aliases.some((ali) => ali.toLowerCase().includes(search))));
|
||||
|
||||
/**
|
||||
* Resolves various structures.
|
||||
* E.g. string -> discord user
|
||||
*
|
||||
* @class Resolver
|
||||
* @typedef {Resolver}
|
||||
*/
|
||||
class Resolver
|
||||
{
|
||||
#client: DiscordClient;
|
||||
#dnsresolver: DNSResolver;
|
||||
#logger: LoggerClient;
|
||||
|
||||
/**
|
||||
* Creates an instance of Resolver.
|
||||
* @param {DiscordClient} client
|
||||
* @memberof Resolver
|
||||
*/
|
||||
constructor (client: DiscordClient)
|
||||
{
|
||||
this.#client = client;
|
||||
|
@ -52,6 +52,14 @@ const Constants = {
|
||||
RemovedInfractions: [ 'BAN', 'SOFTBAN', 'KICK' ]
|
||||
};
|
||||
|
||||
/**
|
||||
* Base Infraction class.
|
||||
* This class is responsible for logging and storing the infraction.
|
||||
* Subclasses are responsible for implementing the behaviour of the infraction.
|
||||
*
|
||||
* @class Infraction
|
||||
* @typedef {Infraction}
|
||||
*/
|
||||
class Infraction
|
||||
{
|
||||
static get Type (): InfractionType
|
||||
|
@ -21,6 +21,16 @@ import InvokerWrapper from '../components/wrappers/InvokerWrapper.js';
|
||||
import Component from './Component.js';
|
||||
import { Command } from './index.js';
|
||||
|
||||
/**
|
||||
* Base inhibitor class.
|
||||
* Inhibitors are what stop commands from executing in the wrong conditions,
|
||||
* e.g. if a user is missing permissions or the channel is ignored.
|
||||
*
|
||||
* @abstract
|
||||
* @class Inhibitor
|
||||
* @typedef {Inhibitor}
|
||||
* @extends {Component}
|
||||
*/
|
||||
abstract class Inhibitor extends Component
|
||||
{
|
||||
#name: string;
|
||||
|
@ -20,6 +20,15 @@ import DiscordClient from '../DiscordClient.js';
|
||||
import Component from './Component.js';
|
||||
import { LoggerClient } from '@navy.gif/logger';
|
||||
|
||||
/**
|
||||
* Base Observer class.
|
||||
* Observers are what drives the bot's functionality. They listen to the events emitted by Discord.
|
||||
* Most important ones being the CommandHandler and Automoderation classes.
|
||||
*
|
||||
* @class Observer
|
||||
* @typedef {Observer}
|
||||
* @extends {Component}
|
||||
*/
|
||||
class Observer extends Component
|
||||
{
|
||||
#logger: LoggerClient;
|
||||
|
@ -37,40 +37,13 @@ import CommandOption from './CommandOption.js';
|
||||
import { GuildSettingTypes as GuildSettingType } from '../../../@types/Guild.js';
|
||||
import { SettingModifyResult, SettingResult } from '../../../@types/Commands/Settings.js';
|
||||
|
||||
// const EMOJIS = {
|
||||
// 'GUILD_TEXT': {
|
||||
// name: 'textchannel',
|
||||
// id: '716414423094525952'
|
||||
// },
|
||||
// 'GUILD_VOICE': {
|
||||
// name: 'voicechannel',
|
||||
// id: '716414422662512762'
|
||||
// },
|
||||
// 'GUILD_NEWS': {
|
||||
// name: 'news',
|
||||
// id: '741725913171099810'
|
||||
// },
|
||||
// 'GUILD_CATEGORY': {
|
||||
// name: 'category',
|
||||
// id: '741731053818871901'
|
||||
// },
|
||||
// 'GUILD_ROLE': {
|
||||
// name: 'role',
|
||||
// id: '743563678292639794'
|
||||
// }
|
||||
// };
|
||||
|
||||
type HelperResult = {
|
||||
modified: string[]
|
||||
}
|
||||
|
||||
// declare interface Setting extends Component {
|
||||
// add: () => HelperResult;
|
||||
// remove: () => HelperResult;
|
||||
// set: () => HelperResult;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Base setting class.
|
||||
*
|
||||
* @class Setting
|
||||
* @extends {Component}
|
||||
*/
|
||||
@ -243,22 +216,6 @@ abstract class Setting<IsGuildSetting extends boolean = true> extends Component
|
||||
fields.push({
|
||||
name: `》 ${guild.format('GENERAL_OPTIONS')}`,
|
||||
value: options.map((opt) => opt.usage(guild, true)).map((f) => f.value).join('\n\n')
|
||||
// value: options.map(
|
||||
// (opt) => {
|
||||
// let msg = `**${opt.name} [${opt.type}]:** ${opt.description}`;
|
||||
// if (opt.choices.length)
|
||||
// msg += `\n__${guild.format('GENERAL_CHOICES')}__: ${opt.choices.map((choice) => choice.name).join(', ')}`;
|
||||
// if (opt.dependsOn.length)
|
||||
// msg += `\n${guild.format('GENERAL_DEPENDSON', { dependencies: opt.dependsOn.join('`, `') })}`;
|
||||
// if (opt.minimum !== undefined)
|
||||
// msg += `\nMIN: \`${opt.minimum}\``;
|
||||
// if (opt.maximum !== undefined) {
|
||||
// const newline = opt.minimum !== undefined ? ', ' : '\n';
|
||||
// msg += `${newline}MAX: \`${opt.maximum}\``;
|
||||
// }
|
||||
// return msg;
|
||||
// }
|
||||
// ).join('\n\n')
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,19 @@ type CommandThrottle = {
|
||||
timeout: NodeJS.Timeout
|
||||
}
|
||||
|
||||
// declare interface Command extends Component
|
||||
// {
|
||||
// get type(): 'command'
|
||||
// }
|
||||
|
||||
/**
|
||||
* Base class for commands. These can be invoked as text commands (as opposed to slash commands).
|
||||
* Most commands should not inherit this class directly, rather inherit the SlashCommand class, as most commands
|
||||
* should work as slash commands.
|
||||
*
|
||||
* Note that even though most commands are slash commands, the SlashCommand class inherits this one,
|
||||
* meaning they can also be invoked as text commands.
|
||||
*
|
||||
* @abstract
|
||||
* @class Command
|
||||
* @typedef {Command}
|
||||
* @extends {Component}
|
||||
*/
|
||||
abstract class Command extends Component
|
||||
{
|
||||
#logger: LoggerClient;
|
||||
@ -58,6 +66,7 @@ abstract class Command extends Component
|
||||
#clientPermissions: PermissionsString[];
|
||||
#memberPermissions: PermissionsString[];
|
||||
|
||||
// For statistics purposes
|
||||
#invokes: {
|
||||
success: number,
|
||||
successTime: number,
|
||||
@ -217,9 +226,6 @@ abstract class Command extends Component
|
||||
|
||||
abstract execute(invoker: InvokerWrapper, options: CommandParams):
|
||||
Promise<string | Message | null | ReplyOptions | void | EmbedBuilder>;
|
||||
// {
|
||||
// throw new Error(`${this.resolveable} is missing an execute function.`);
|
||||
// }
|
||||
|
||||
success (when: number)
|
||||
{
|
||||
|
@ -20,6 +20,17 @@ import DiscordClient from '../../DiscordClient.js';
|
||||
import { ModerationCommandOptions } from '../../../../@types/Commands/Moderation.js';
|
||||
import { CommandOptionParams, CommandOptionType } from '../../../../@types/Client.js';
|
||||
|
||||
/**
|
||||
* Base class for moderation commands.
|
||||
* Mostly a convenience class as most moderation commands share a lot of the options.
|
||||
* Meaning that strictly speaking a moderation command does not need to inherit this,
|
||||
* but for consistentcy probably should as long as it doesn't prove inconvenient.
|
||||
*
|
||||
* @abstract
|
||||
* @class ModerationCommand
|
||||
* @typedef {ModerationCommand}
|
||||
* @extends {SlashCommand}
|
||||
*/
|
||||
abstract class ModerationCommand extends SlashCommand
|
||||
{
|
||||
constructor (client: DiscordClient, opts: ModerationCommandOptions)
|
||||
|
@ -25,6 +25,13 @@ import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
|
||||
import InvokerWrapper from '../../components/wrappers/InvokerWrapper.js';
|
||||
import { APIEmbed } from 'discord.js';
|
||||
|
||||
/**
|
||||
* Superclass for any settings commands.
|
||||
*
|
||||
* @class SettingsCommand
|
||||
* @typedef {SettingsCommand}
|
||||
* @extends {SlashCommand}
|
||||
*/
|
||||
class SettingsCommand extends SlashCommand
|
||||
{
|
||||
constructor (client: DiscordClient, opts: SettingsCommandOptions)
|
||||
@ -51,10 +58,6 @@ class SettingsCommand extends SlashCommand
|
||||
const settings = this.client.registry
|
||||
.filter((c: Setting) => c.type === 'setting' && c.module.name === this.name);
|
||||
|
||||
// const allSettings = this.client.registry.components.filter((c) => c._type ==='setting');
|
||||
// Organise modules into subcommand groups
|
||||
// const modules = new Set(allSettings.map((set) => set.module.name));
|
||||
|
||||
for (const setting of settings.values())
|
||||
{
|
||||
try
|
||||
@ -77,28 +80,6 @@ class SettingsCommand extends SlashCommand
|
||||
this.client.logger.error(`Setting ${setting.name} errored during options build:\n${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
// for (const module of modules) {
|
||||
// const settingsModule = allSettings.filter((s) => s.module.name === module);
|
||||
// // /settings moderation
|
||||
// const moduleSubcommand = new CommandOption({
|
||||
// name: module,
|
||||
// description: `Configure ${module} settings`,
|
||||
// type: 'SUB_COMMAND_GROUP'
|
||||
// });
|
||||
// for (const setting of settingsModule.values()) {
|
||||
// // Each setting becomes its own subcommand with options defined in the settings
|
||||
// // /settings moderation mute role:@muted permanent:false default:1h type:1
|
||||
// const settingSubcommand = new CommandOption({
|
||||
// name: setting.name,
|
||||
// description: setting.description,
|
||||
// type: 'SUB_COMMAND',
|
||||
// options: setting.commandOptions
|
||||
// });
|
||||
// moduleSubcommand.options.push(settingSubcommand);
|
||||
// }
|
||||
// this.options.push(moduleSubcommand);
|
||||
// }
|
||||
}
|
||||
|
||||
async execute (invoker: InvokerWrapper<true>, opts: CommandParams)
|
||||
@ -125,7 +106,6 @@ class SettingsCommand extends SlashCommand
|
||||
});
|
||||
}
|
||||
|
||||
// await invoker.deferReply();
|
||||
const settings = await guild.settings();
|
||||
if (!Object.keys(opts).length && this.subcommand(subcommand!.name)!.options.length)
|
||||
return this._showSetting(invoker, setting);
|
||||
|
@ -20,6 +20,16 @@ import Command from './Command.js';
|
||||
|
||||
import { ApplicationCommandType, PermissionsBitField } from 'discord.js';
|
||||
|
||||
/**
|
||||
* Base class for most of the commands on the bot.
|
||||
* Only commands that SHOULD NOT inherit this are ones that shouldn't appear as slash commands,
|
||||
* e.g. certain developer only commands (ex. eval)
|
||||
*
|
||||
* @abstract
|
||||
* @class SlashCommand
|
||||
* @typedef {SlashCommand}
|
||||
* @extends {Command}
|
||||
*/
|
||||
abstract class SlashCommand extends Command
|
||||
{
|
||||
#type: ApplicationCommandType;
|
||||
|
Loading…
Reference in New Issue
Block a user