Bugfixes to eventhooker and infraction dms
This commit is contained in:
parent
f65604ec2b
commit
9d88266f41
@ -76,7 +76,7 @@ class EventHooker
|
||||
const eventArgs = [];
|
||||
for (const arg of args)
|
||||
{
|
||||
if (arg && typeof arg === 'object' && 'guild' in arg)
|
||||
if (arg && typeof arg === 'object' && 'guild' in arg && arg.guild)
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
arg.guildWrapper = this.#target.getGuildWrapper(arg.guild!.id);
|
||||
|
@ -1,51 +1,51 @@
|
||||
import DiscordClient from '../DiscordClient.js';
|
||||
import SlashCommand from '../interfaces/commands/SlashCommand.js';
|
||||
|
||||
class Intercom
|
||||
{
|
||||
#client: DiscordClient;
|
||||
constructor (client: DiscordClient)
|
||||
{
|
||||
this.#client = client;
|
||||
|
||||
if (client.singleton || client!.shard?.ids[0] === 0)
|
||||
{
|
||||
this.#client.eventHooker.hook('built', () =>
|
||||
{
|
||||
this._transportCommands();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
send (type: string, message = {})
|
||||
{
|
||||
if (typeof message !== 'object')
|
||||
throw new Error('Invalid message object');
|
||||
if (!process.send)
|
||||
return; // Nowhere to send, the client was not spawned as a shard
|
||||
return process.send({
|
||||
[`_${type}`]: true,
|
||||
...message
|
||||
});
|
||||
}
|
||||
|
||||
_transportCommands ()
|
||||
{
|
||||
if (!this.#client.application)
|
||||
throw new Error('Missing client application');
|
||||
const clientId = this.#client.application.id;
|
||||
const commands = this.#client.registry
|
||||
.filter((c: SlashCommand) => c.type === 'command' && c.slash)
|
||||
.map((c) => c.shape);
|
||||
|
||||
// console.log(inspect(commands, { depth: 25 }));
|
||||
if (process.env.NODE_ENV === 'development')
|
||||
return this.send('commands', { type: 'guild', commands, clientId });
|
||||
|
||||
this.send('commands', { type: 'global', commands, clientId });
|
||||
// this.send('commands', { type: 'guild', commands, clientId });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
import DiscordClient from '../DiscordClient.js';
|
||||
import SlashCommand from '../interfaces/commands/SlashCommand.js';
|
||||
|
||||
class Intercom
|
||||
{
|
||||
#client: DiscordClient;
|
||||
constructor (client: DiscordClient)
|
||||
{
|
||||
this.#client = client;
|
||||
|
||||
if (client.singleton || client!.shard?.ids[0] === 0)
|
||||
{
|
||||
this.#client.eventHooker.hook('built', () =>
|
||||
{
|
||||
this._transportCommands();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
send (type: string, message = {})
|
||||
{
|
||||
if (typeof message !== 'object')
|
||||
throw new Error('Invalid message object');
|
||||
if (!process.send)
|
||||
return; // Nowhere to send, the client was not spawned as a shard
|
||||
return process.send({
|
||||
[`_${type}`]: true,
|
||||
...message
|
||||
});
|
||||
}
|
||||
|
||||
_transportCommands ()
|
||||
{
|
||||
if (!this.#client.application)
|
||||
throw new Error('Missing client application');
|
||||
const clientId = this.#client.application.id;
|
||||
const commands = this.#client.registry
|
||||
.filter((c: SlashCommand) => c.type === 'command' && c.slash)
|
||||
.map((c) => c.shape);
|
||||
|
||||
// console.log(inspect(commands, { depth: 25 }));
|
||||
if (process.env.NODE_ENV === 'development')
|
||||
return this.send('commands', { type: 'guild', commands, clientId });
|
||||
|
||||
this.send('commands', { type: 'global', commands, clientId });
|
||||
// this.send('commands', { type: 'guild', commands, clientId });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Intercom;
|
@ -626,7 +626,7 @@ class ModerationManager implements Initialisable
|
||||
if (updateCase)
|
||||
await this.#client.storage.mongodb.infractions.updateOne(
|
||||
{ id: infraction.id },
|
||||
{ _callbacked: true }
|
||||
{ $set: { _callbacked: true } }
|
||||
).catch((e) =>
|
||||
{
|
||||
this.#logger.error(`Error during update of infraction:\n${e.stack || e}`);
|
||||
|
@ -132,7 +132,7 @@ class ImportCommand extends SlashCommand
|
||||
for (const log of existingLogs)
|
||||
{
|
||||
log.case += highestOldId;
|
||||
await this.client.mongodb.infractions.updateOne({ _id: log._id }, { case: log.case });
|
||||
await this.client.mongodb.infractions.updateOne({ _id: log._id }, { $set: { case: log.case } });
|
||||
}
|
||||
await this.client.mongodb.infractions.insertMany(imported);
|
||||
if (!guild.data.caseId)
|
||||
@ -193,12 +193,12 @@ class ImportCommand extends SlashCommand
|
||||
else if (version === '3')
|
||||
{
|
||||
delete webhook.feature;
|
||||
await this.client.storageManager.mongodb.webhooks.updateOne({ feature: 'messages', guild: guild.id }, webhook);
|
||||
await this.client.storageManager.mongodb.webhooks.updateOne({ feature: 'messages', guild: guild.id }, { $set: webhook });
|
||||
}
|
||||
}
|
||||
|
||||
if (permissions)
|
||||
await this.client.storageManager.mongodb.permissions.updateOne({ guildId: guild.id }, permissions);
|
||||
await this.client.storageManager.mongodb.permissions.updateOne({ guildId: guild.id }, { $set: permissions });
|
||||
|
||||
const { premium } = imported.settings;
|
||||
delete imported.settings.premium;
|
||||
|
@ -48,9 +48,9 @@ class UtilityHook extends Observer
|
||||
|
||||
await this.client.storageManager.mongodb.roleCache.updateOne({
|
||||
member: member.id, guild: guild.id
|
||||
}, {
|
||||
}, { $set: {
|
||||
roles: storeThese, timestamp: Date.now()
|
||||
});
|
||||
} });
|
||||
}
|
||||
|
||||
async automute (member: ExtendedGuildMember)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CommandParams, FormatParams } from '../../../../../@types/Client.js';
|
||||
import { CommandOptionType, CommandParams, FormatParams } from '../../../../../@types/Client.js';
|
||||
import { ProtectionSettings, ProtectionType } from '../../../../../@types/Settings.js';
|
||||
import Util from '../../../../utilities/Util.js';
|
||||
import DiscordClient from '../../../DiscordClient.js';
|
||||
@ -49,11 +49,11 @@ class ProtectionSetting extends Setting
|
||||
},
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Whether setting is active or not'
|
||||
description: 'Whether setting is active or not',
|
||||
type: CommandOptionType.BOOLEAN
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async execute (invoker: InvokerWrapper<true>, opts: CommandParams, setting: ProtectionSettings)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -152,7 +152,7 @@ class UserWrapper
|
||||
{
|
||||
await this.#client.mongodb.users.updateOne(
|
||||
{ guildId: this.id },
|
||||
data
|
||||
{ $set: data }
|
||||
);
|
||||
this.#settings = {
|
||||
...this.#settings,
|
||||
|
@ -112,7 +112,7 @@ class UnlockdownInfraction extends Infraction
|
||||
if (callback)
|
||||
await this.client.moderation.removeCallback(callback.infraction, true);
|
||||
else
|
||||
await this.client.mongodb.infractions.updateOne({ id: latest.id }, { _callbacked: true });
|
||||
await this.client.mongodb.infractions.updateOne({ id: latest.id }, { $set: { _callbacked: true } });
|
||||
}
|
||||
|
||||
await this.handle();
|
||||
|
@ -166,7 +166,6 @@ class Infraction
|
||||
|
||||
async handle ()
|
||||
{
|
||||
|
||||
// Infraction was fetched from database, i.e. was already executed previously
|
||||
if (this.#fetched)
|
||||
throw new Error('Cannot handle a fetched Infraction');
|
||||
@ -186,7 +185,7 @@ class Infraction
|
||||
});
|
||||
|
||||
/* Logging */
|
||||
if (moderation.channel)
|
||||
if (moderation.channel)
|
||||
{
|
||||
if (moderation.infractions.includes(this.#type!))
|
||||
{
|
||||
@ -197,7 +196,6 @@ class Infraction
|
||||
this.#dmLogMessage = await this.#moderationLog.send({ embeds: [ await this.#embed() ] }).catch(null);
|
||||
this.#modLogMessageId = this.#dmLogMessage?.id || null;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -205,7 +203,7 @@ class Infraction
|
||||
}
|
||||
}
|
||||
|
||||
if (dminfraction.enabled && !this.#silent)
|
||||
if (dminfraction.enabled && !this.#silent)
|
||||
{
|
||||
if (this.#targetType === 'USER' && dminfraction.infractions.includes(this.#type!))
|
||||
{
|
||||
@ -218,30 +216,25 @@ class Infraction
|
||||
.replace(/\{infraction\}/ugim, this.dictionary.past)
|
||||
.replace(/\{from\|on\}/ugim, Constants.RemovedInfractions.includes(this.#type!) ? 'from' : 'on'); // add more if you want i should probably add a better system for this...
|
||||
|
||||
|
||||
if (Util.isSendable(this.#target))
|
||||
if (Util.isSendable(this.#target))
|
||||
{
|
||||
const logMessage = await this.#target.send({
|
||||
content: message,
|
||||
embeds: [ await this.#embed(true) ]
|
||||
}).catch(null);
|
||||
this.#dmLogMessageId = logMessage.id;
|
||||
}).catch(() => null);
|
||||
this.#dmLogMessageId = logMessage?.id ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#duration)
|
||||
{
|
||||
if (this.#duration)
|
||||
await this.#client.moderation.handleCallbacks([ this.json ]);
|
||||
}
|
||||
|
||||
/* LMAOOOO PLEASE DONT JUDGE ME */
|
||||
if (this.#data.roles)
|
||||
delete this.#data.roles;
|
||||
|
||||
|
||||
return this.save();
|
||||
|
||||
}
|
||||
|
||||
execute (): Promise<InfractionSuccess | InfractionFail>
|
||||
@ -254,7 +247,7 @@ class Infraction
|
||||
const filter: {id: string, _id?: ObjectId} = { id: this.id };
|
||||
if (this.#mongoId)
|
||||
filter._id = this.#mongoId;
|
||||
return this.#client.mongodb.infractions.updateOne(filter, this.json)
|
||||
return this.#client.mongodb.infractions.updateOne(filter, { $set: this.json })
|
||||
.catch((error: Error) =>
|
||||
{
|
||||
this.#logger.error(`There was an issue saving infraction data to the database.\n${error.stack || error}\nInfraction data:\n${inspect(this.json)}`);
|
||||
@ -321,7 +314,6 @@ class Infraction
|
||||
// Function implemented in subclasses for additional case data
|
||||
if (this.description && this.description instanceof Function)
|
||||
description += this.description(dm);
|
||||
|
||||
|
||||
if (this.#resolved)
|
||||
{
|
||||
@ -345,14 +337,12 @@ class Infraction
|
||||
}
|
||||
|
||||
embed.description = description;
|
||||
|
||||
return embed;
|
||||
|
||||
}
|
||||
|
||||
description (_dm: boolean): string
|
||||
{
|
||||
throw new Error('Description is to be implemented by a subclass');
|
||||
return '';
|
||||
}
|
||||
|
||||
protected get client ()
|
||||
|
@ -1,402 +1,402 @@
|
||||
import { LoggerClient } from '@navy.gif/logger';
|
||||
import { EmbedBuilder, Message, PermissionsString, Snowflake } from 'discord.js';
|
||||
import Component from '../Component.js';
|
||||
import CommandOption from '../CommandOption.js';
|
||||
import { Util } from '../../../utilities/index.js';
|
||||
import DiscordClient from '../../DiscordClient.js';
|
||||
import { InvokerWrapper } from '../../components/wrappers/index.js';
|
||||
import { CommandOptionParams, CommandOptionType, CommandOptions, CommandParams } from '../../../../@types/Client.js';
|
||||
import { ReplyOptions } from '../../../../@types/Wrappers.js';
|
||||
|
||||
type CommandUsageLimits = {
|
||||
usages: number,
|
||||
duration: number
|
||||
}
|
||||
type CommandThrottle = {
|
||||
usages: number,
|
||||
start: number,
|
||||
timeout: NodeJS.Timeout
|
||||
}
|
||||
|
||||
// declare interface Command extends Component
|
||||
// {
|
||||
// get type(): 'command'
|
||||
// }
|
||||
|
||||
abstract class Command extends Component
|
||||
{
|
||||
#logger: LoggerClient;
|
||||
|
||||
#name: string;
|
||||
#description: string;
|
||||
#tags: string[];
|
||||
#aliases: string[];
|
||||
|
||||
#restricted: boolean;
|
||||
#showUsage: boolean;
|
||||
#guildOnly: boolean;
|
||||
#archivable: boolean;
|
||||
#slash: boolean;
|
||||
|
||||
#clientPermissions: PermissionsString[];
|
||||
#memberPermissions: PermissionsString[];
|
||||
|
||||
#invokes: {
|
||||
success: number,
|
||||
successTime: number,
|
||||
fail: number,
|
||||
failTime: number
|
||||
};
|
||||
|
||||
#options: CommandOption[];
|
||||
|
||||
#throttling?: CommandUsageLimits;
|
||||
#throttles: Map<Snowflake, CommandThrottle>;
|
||||
|
||||
/**
|
||||
* Creates an instance of Command.
|
||||
* @param {DiscordClient} client
|
||||
* @param {Object} [options={}]
|
||||
* @memberof Command
|
||||
*/
|
||||
constructor (client: DiscordClient, options: CommandOptions)
|
||||
{
|
||||
if (!options)
|
||||
throw Util.fatal(new Error('Missing command options'));
|
||||
if (!options.name)
|
||||
throw Util.fatal(new Error('Missing name'));
|
||||
|
||||
super(client, {
|
||||
id: options.name,
|
||||
type: 'command',
|
||||
disabled: options.disabled,
|
||||
guarded: options.guarded,
|
||||
moduleName: options.moduleName
|
||||
});
|
||||
|
||||
this.#name = options.name;
|
||||
this.#logger = client.createLogger(this);
|
||||
if (!options.moduleName)
|
||||
this.logger.warn(`Command ${this.#name} is missing module information.`);
|
||||
|
||||
this.#description = options.description || '';
|
||||
this.#tags = options.tags || [];
|
||||
this.#aliases = options.aliases || [];
|
||||
|
||||
this.#restricted = Boolean(options?.restricted);
|
||||
this.#showUsage = Boolean(options.showUsage);
|
||||
this.#guildOnly = Boolean(options?.guildOnly);
|
||||
|
||||
this.#archivable = typeof options.archivable === 'undefined' ? true : Boolean(options.archivable);
|
||||
|
||||
this.#slash = Boolean(options.slash);
|
||||
// Convers permissions to PascalCase from snake case bc for some reason d.js decided it was a good change
|
||||
this.#clientPermissions = [ ...new Set<PermissionsString>([ 'SendMessages', ...options.clientPermissions || [] ]) ]; // .map(Util.pascalConverter);
|
||||
this.#memberPermissions = options.memberPermissions || []; // .map(Util.pascalConverter);
|
||||
|
||||
this.#invokes = {
|
||||
success: 0,
|
||||
successTime: 0,
|
||||
fail: 0,
|
||||
failTime: 0
|
||||
};
|
||||
|
||||
this.#options = [];
|
||||
if (options.options)
|
||||
this.#parseOptions(options.options);
|
||||
|
||||
this.#options.sort((a, b) =>
|
||||
{
|
||||
if (a.required)
|
||||
return -1;
|
||||
if (b.required)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.#throttles = new Map();
|
||||
}
|
||||
|
||||
get name ()
|
||||
{
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
get aliases ()
|
||||
{
|
||||
return this.#aliases;
|
||||
}
|
||||
|
||||
get description ()
|
||||
{
|
||||
return this.#description;
|
||||
}
|
||||
|
||||
get tags ()
|
||||
{
|
||||
return this.#tags;
|
||||
}
|
||||
|
||||
get restricted ()
|
||||
{
|
||||
return this.#restricted;
|
||||
}
|
||||
|
||||
get showUsage ()
|
||||
{
|
||||
return this.#showUsage;
|
||||
}
|
||||
|
||||
get archivable ()
|
||||
{
|
||||
return this.#archivable;
|
||||
}
|
||||
|
||||
get slash ()
|
||||
{
|
||||
return this.#slash;
|
||||
}
|
||||
|
||||
get memberPermissions ()
|
||||
{
|
||||
return this.#memberPermissions;
|
||||
}
|
||||
|
||||
get clientPermissions ()
|
||||
{
|
||||
return this.#clientPermissions;
|
||||
}
|
||||
|
||||
get options ()
|
||||
{
|
||||
return this.#options;
|
||||
}
|
||||
|
||||
get guildOnly ()
|
||||
{
|
||||
return this.#guildOnly;
|
||||
}
|
||||
|
||||
protected get logger ()
|
||||
{
|
||||
return this.#logger;
|
||||
}
|
||||
|
||||
get throttling ()
|
||||
{
|
||||
return this.#throttling;
|
||||
}
|
||||
|
||||
get throttles ()
|
||||
{
|
||||
return this.#throttles;
|
||||
}
|
||||
|
||||
get invokes ()
|
||||
{
|
||||
return this.#invokes;
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
const now = Date.now();
|
||||
const execTime = now - when;
|
||||
// Calculate new average
|
||||
if (this.#invokes.successTime)
|
||||
{
|
||||
this.#invokes.successTime = (this.#invokes.successTime * this.#invokes.success + execTime) / ++this.#invokes.success;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#invokes.successTime = execTime;
|
||||
this.#invokes.success++;
|
||||
}
|
||||
}
|
||||
|
||||
error (when: number)
|
||||
{
|
||||
const now = Date.now();
|
||||
const execTime = now - when;
|
||||
// Calculate new average
|
||||
if (this.#invokes.failTime)
|
||||
{
|
||||
this.#invokes.failTime = (this.#invokes.failTime * this.#invokes.fail + execTime) / ++this.#invokes.fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#invokes.failTime = execTime;
|
||||
this.#invokes.fail++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async usageEmbed (invoker: InvokerWrapper, verbose = false)
|
||||
{
|
||||
const fields = [];
|
||||
const { guild, subcommand, subcommandGroup } = invoker;
|
||||
|
||||
let type = null;
|
||||
const format = (index: string) => guild
|
||||
? guild.format(index)
|
||||
: this.client.format(index);
|
||||
if (guild)
|
||||
({ permissions: { type } } = await guild.settings());
|
||||
|
||||
if (this.#options.length)
|
||||
{
|
||||
if (verbose)
|
||||
fields.push(...this.#options.map((opt) => opt.usage(guild)));
|
||||
else if (subcommand)
|
||||
{
|
||||
const opt = this.subcommand(subcommand.name) as CommandOption;
|
||||
fields.push(opt.usage(guild));
|
||||
}
|
||||
else if (subcommandGroup)
|
||||
{
|
||||
const opt = this.subcommandGroup(subcommandGroup.name) as CommandOption;
|
||||
fields.push(opt.usage(guild));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.memberPermissions.length)
|
||||
{
|
||||
let required = [];
|
||||
if (type === 'discord')
|
||||
required = this.memberPermissions;
|
||||
else if (type === 'grant')
|
||||
required = [ this.resolveable ];
|
||||
else
|
||||
required = [ this.resolveable, ...this.memberPermissions ];
|
||||
fields.push({
|
||||
name: `》 ${format('GENERAL_PERMISSIONS')}`,
|
||||
value: `\`${required.join('`, `')}\``
|
||||
});
|
||||
}
|
||||
|
||||
return new EmbedBuilder({
|
||||
author: {
|
||||
name: `${this.name} [module:${this.module?.name}]`
|
||||
},
|
||||
description: format(`COMMAND_${this.name.toUpperCase()}_HELP`),
|
||||
fields
|
||||
});
|
||||
}
|
||||
|
||||
subcommandGroup (name: string)
|
||||
{
|
||||
if (!name)
|
||||
return null;
|
||||
name = name.toLowerCase();
|
||||
return this.subcommandGroups.find((group) => group.name === name) ?? null;
|
||||
}
|
||||
|
||||
get subcommandGroups ()
|
||||
{
|
||||
return this.#options.filter((opt) => opt.type === CommandOptionType.SUB_COMMAND_GROUP);
|
||||
}
|
||||
|
||||
subcommand (name?: string)
|
||||
{
|
||||
if (!name)
|
||||
return null;
|
||||
name = name.toLowerCase();
|
||||
return this.subcommands.find((cmd) => cmd.name === name) ?? null;
|
||||
}
|
||||
|
||||
get subcommands ()
|
||||
{
|
||||
return this.#subcommands(this.#options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
#subcommands (opts: CommandOption[]): CommandOption[]
|
||||
{
|
||||
const subcommands = [];
|
||||
for (const opt of opts)
|
||||
{
|
||||
if (opt.type === CommandOptionType.SUB_COMMAND)
|
||||
subcommands.push(opt);
|
||||
else if (opt.type === CommandOptionType.SUB_COMMAND_GROUP)
|
||||
subcommands.push(...this.#subcommands(opt.options));
|
||||
}
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
// probably not a final name -- flattenedOptions maybe?
|
||||
get actualOptions ()
|
||||
{
|
||||
return this.#actualOptions(this.#options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
#actualOptions (opts: CommandOption[]): CommandOption[]
|
||||
{
|
||||
const options: CommandOption[] = [];
|
||||
for (const opt of opts)
|
||||
{
|
||||
if ([ CommandOptionType.SUB_COMMAND_GROUP, CommandOptionType.SUB_COMMAND ].includes(opt.type))
|
||||
options.push(...this.#actualOptions(opt.options));
|
||||
else
|
||||
options.push(opt);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
#parseOptions (options: CommandOptionParams[])
|
||||
{
|
||||
for (const opt of options)
|
||||
{
|
||||
if (opt instanceof CommandOption)
|
||||
{
|
||||
opt.client = this.client;
|
||||
this.#options.push(opt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(opt.name instanceof Array))
|
||||
{
|
||||
this.#options.push(new CommandOption({ ...opt, client: this.client }));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allows easy templating of subcommands that share arguments
|
||||
const { name: names, description, type, ...opts } = opt;
|
||||
for (const name of names)
|
||||
{
|
||||
const index = names.indexOf(name);
|
||||
let desc = description,
|
||||
_type = type;
|
||||
if (description instanceof Array)
|
||||
desc = description[index] || 'Missing description';
|
||||
if (type instanceof Array)
|
||||
_type = type[index];
|
||||
if (!_type)
|
||||
{
|
||||
_type = CommandOptionType.STRING;
|
||||
this.logger.warn(`Missing option type for ${this.resolveable}.${name}, defaulting to string`);
|
||||
}
|
||||
// throw new Error(`Missing type for option ${name} in command ${this.name}`);
|
||||
this.#options.push(new CommandOption({
|
||||
...opts,
|
||||
name,
|
||||
type: _type,
|
||||
description: desc,
|
||||
client: this.client
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import { LoggerClient } from '@navy.gif/logger';
|
||||
import { EmbedBuilder, Message, PermissionsString, Snowflake } from 'discord.js';
|
||||
import Component from '../Component.js';
|
||||
import CommandOption from '../CommandOption.js';
|
||||
import { Util } from '../../../utilities/index.js';
|
||||
import DiscordClient from '../../DiscordClient.js';
|
||||
import { InvokerWrapper } from '../../components/wrappers/index.js';
|
||||
import { CommandOptionParams, CommandOptionType, CommandOptions, CommandParams } from '../../../../@types/Client.js';
|
||||
import { ReplyOptions } from '../../../../@types/Wrappers.js';
|
||||
|
||||
type CommandUsageLimits = {
|
||||
usages: number,
|
||||
duration: number
|
||||
}
|
||||
type CommandThrottle = {
|
||||
usages: number,
|
||||
start: number,
|
||||
timeout: NodeJS.Timeout
|
||||
}
|
||||
|
||||
// declare interface Command extends Component
|
||||
// {
|
||||
// get type(): 'command'
|
||||
// }
|
||||
|
||||
abstract class Command extends Component
|
||||
{
|
||||
#logger: LoggerClient;
|
||||
|
||||
#name: string;
|
||||
#description: string;
|
||||
#tags: string[];
|
||||
#aliases: string[];
|
||||
|
||||
#restricted: boolean;
|
||||
#showUsage: boolean;
|
||||
#guildOnly: boolean;
|
||||
#archivable: boolean;
|
||||
#slash: boolean;
|
||||
|
||||
#clientPermissions: PermissionsString[];
|
||||
#memberPermissions: PermissionsString[];
|
||||
|
||||
#invokes: {
|
||||
success: number,
|
||||
successTime: number,
|
||||
fail: number,
|
||||
failTime: number
|
||||
};
|
||||
|
||||
#options: CommandOption[];
|
||||
|
||||
#throttling?: CommandUsageLimits;
|
||||
#throttles: Map<Snowflake, CommandThrottle>;
|
||||
|
||||
/**
|
||||
* Creates an instance of Command.
|
||||
* @param {DiscordClient} client
|
||||
* @param {Object} [options={}]
|
||||
* @memberof Command
|
||||
*/
|
||||
constructor (client: DiscordClient, options: CommandOptions)
|
||||
{
|
||||
if (!options)
|
||||
throw Util.fatal(new Error('Missing command options'));
|
||||
if (!options.name)
|
||||
throw Util.fatal(new Error('Missing name'));
|
||||
|
||||
super(client, {
|
||||
id: options.name,
|
||||
type: 'command',
|
||||
disabled: options.disabled,
|
||||
guarded: options.guarded,
|
||||
moduleName: options.moduleName
|
||||
});
|
||||
|
||||
this.#name = options.name;
|
||||
this.#logger = client.createLogger(this);
|
||||
if (!options.moduleName)
|
||||
this.logger.warn(`Command ${this.#name} is missing module information.`);
|
||||
|
||||
this.#description = options.description || '';
|
||||
this.#tags = options.tags || [];
|
||||
this.#aliases = options.aliases || [];
|
||||
|
||||
this.#restricted = Boolean(options?.restricted);
|
||||
this.#showUsage = Boolean(options.showUsage);
|
||||
this.#guildOnly = Boolean(options?.guildOnly);
|
||||
|
||||
this.#archivable = typeof options.archivable === 'undefined' ? true : Boolean(options.archivable);
|
||||
|
||||
this.#slash = Boolean(options.slash);
|
||||
// Convers permissions to PascalCase from snake case bc for some reason d.js decided it was a good change
|
||||
this.#clientPermissions = [ ...new Set<PermissionsString>([ 'SendMessages', ...options.clientPermissions || [] ]) ]; // .map(Util.pascalConverter);
|
||||
this.#memberPermissions = options.memberPermissions || []; // .map(Util.pascalConverter);
|
||||
|
||||
this.#invokes = {
|
||||
success: 0,
|
||||
successTime: 0,
|
||||
fail: 0,
|
||||
failTime: 0
|
||||
};
|
||||
|
||||
this.#options = [];
|
||||
if (options.options)
|
||||
this.#parseOptions(options.options);
|
||||
|
||||
this.#options.sort((a, b) =>
|
||||
{
|
||||
if (a.required)
|
||||
return -1;
|
||||
if (b.required)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.#throttles = new Map();
|
||||
}
|
||||
|
||||
get name ()
|
||||
{
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
get aliases ()
|
||||
{
|
||||
return this.#aliases;
|
||||
}
|
||||
|
||||
get description ()
|
||||
{
|
||||
return this.#description;
|
||||
}
|
||||
|
||||
get tags ()
|
||||
{
|
||||
return this.#tags;
|
||||
}
|
||||
|
||||
get restricted ()
|
||||
{
|
||||
return this.#restricted;
|
||||
}
|
||||
|
||||
get showUsage ()
|
||||
{
|
||||
return this.#showUsage;
|
||||
}
|
||||
|
||||
get archivable ()
|
||||
{
|
||||
return this.#archivable;
|
||||
}
|
||||
|
||||
get slash ()
|
||||
{
|
||||
return this.#slash;
|
||||
}
|
||||
|
||||
get memberPermissions ()
|
||||
{
|
||||
return this.#memberPermissions;
|
||||
}
|
||||
|
||||
get clientPermissions ()
|
||||
{
|
||||
return this.#clientPermissions;
|
||||
}
|
||||
|
||||
get options ()
|
||||
{
|
||||
return this.#options;
|
||||
}
|
||||
|
||||
get guildOnly ()
|
||||
{
|
||||
return this.#guildOnly;
|
||||
}
|
||||
|
||||
protected get logger ()
|
||||
{
|
||||
return this.#logger;
|
||||
}
|
||||
|
||||
get throttling ()
|
||||
{
|
||||
return this.#throttling;
|
||||
}
|
||||
|
||||
get throttles ()
|
||||
{
|
||||
return this.#throttles;
|
||||
}
|
||||
|
||||
get invokes ()
|
||||
{
|
||||
return this.#invokes;
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
const now = Date.now();
|
||||
const execTime = now - when;
|
||||
// Calculate new average
|
||||
if (this.#invokes.successTime)
|
||||
{
|
||||
this.#invokes.successTime = (this.#invokes.successTime * this.#invokes.success + execTime) / ++this.#invokes.success;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#invokes.successTime = execTime;
|
||||
this.#invokes.success++;
|
||||
}
|
||||
}
|
||||
|
||||
error (when: number)
|
||||
{
|
||||
const now = Date.now();
|
||||
const execTime = now - when;
|
||||
// Calculate new average
|
||||
if (this.#invokes.failTime)
|
||||
{
|
||||
this.#invokes.failTime = (this.#invokes.failTime * this.#invokes.fail + execTime) / ++this.#invokes.fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#invokes.failTime = execTime;
|
||||
this.#invokes.fail++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async usageEmbed (invoker: InvokerWrapper, verbose = false)
|
||||
{
|
||||
const fields = [];
|
||||
const { guild, subcommand, subcommandGroup } = invoker;
|
||||
|
||||
let type = null;
|
||||
const format = (index: string) => guild
|
||||
? guild.format(index)
|
||||
: this.client.format(index);
|
||||
if (guild)
|
||||
({ permissions: { type } } = await guild.settings());
|
||||
|
||||
if (this.#options.length)
|
||||
{
|
||||
if (verbose)
|
||||
fields.push(...this.#options.map((opt) => opt.usage(guild)));
|
||||
else if (subcommand)
|
||||
{
|
||||
const opt = this.subcommand(subcommand.name) as CommandOption;
|
||||
fields.push(opt.usage(guild));
|
||||
}
|
||||
else if (subcommandGroup)
|
||||
{
|
||||
const opt = this.subcommandGroup(subcommandGroup.name) as CommandOption;
|
||||
fields.push(opt.usage(guild));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.memberPermissions.length)
|
||||
{
|
||||
let required = [];
|
||||
if (type === 'discord')
|
||||
required = this.memberPermissions;
|
||||
else if (type === 'grant')
|
||||
required = [ this.resolveable ];
|
||||
else
|
||||
required = [ this.resolveable, ...this.memberPermissions ];
|
||||
fields.push({
|
||||
name: `》 ${format('GENERAL_PERMISSIONS')}`,
|
||||
value: `\`${required.join('`, `')}\``
|
||||
});
|
||||
}
|
||||
|
||||
return new EmbedBuilder({
|
||||
author: {
|
||||
name: `${this.name} [module:${this.module?.name}]`
|
||||
},
|
||||
description: format(`COMMAND_${this.name.toUpperCase()}_HELP`),
|
||||
fields
|
||||
});
|
||||
}
|
||||
|
||||
subcommandGroup (name: string)
|
||||
{
|
||||
if (!name)
|
||||
return null;
|
||||
name = name.toLowerCase();
|
||||
return this.subcommandGroups.find((group) => group.name === name) ?? null;
|
||||
}
|
||||
|
||||
get subcommandGroups ()
|
||||
{
|
||||
return this.#options.filter((opt) => opt.type === CommandOptionType.SUB_COMMAND_GROUP);
|
||||
}
|
||||
|
||||
subcommand (name?: string)
|
||||
{
|
||||
if (!name)
|
||||
return null;
|
||||
name = name.toLowerCase();
|
||||
return this.subcommands.find((cmd) => cmd.name === name) ?? null;
|
||||
}
|
||||
|
||||
get subcommands ()
|
||||
{
|
||||
return this.#subcommands(this.#options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
#subcommands (opts: CommandOption[]): CommandOption[]
|
||||
{
|
||||
const subcommands = [];
|
||||
for (const opt of opts)
|
||||
{
|
||||
if (opt.type === CommandOptionType.SUB_COMMAND)
|
||||
subcommands.push(opt);
|
||||
else if (opt.type === CommandOptionType.SUB_COMMAND_GROUP)
|
||||
subcommands.push(...this.#subcommands(opt.options));
|
||||
}
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
// probably not a final name -- flattenedOptions maybe?
|
||||
get actualOptions ()
|
||||
{
|
||||
return this.#actualOptions(this.#options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
#actualOptions (opts: CommandOption[]): CommandOption[]
|
||||
{
|
||||
const options: CommandOption[] = [];
|
||||
for (const opt of opts)
|
||||
{
|
||||
if ([ CommandOptionType.SUB_COMMAND_GROUP, CommandOptionType.SUB_COMMAND ].includes(opt.type))
|
||||
options.push(...this.#actualOptions(opt.options));
|
||||
else
|
||||
options.push(opt);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
#parseOptions (options: CommandOptionParams[])
|
||||
{
|
||||
for (const opt of options)
|
||||
{
|
||||
if (opt instanceof CommandOption)
|
||||
{
|
||||
opt.client = this.client;
|
||||
this.#options.push(opt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(opt.name instanceof Array))
|
||||
{
|
||||
this.#options.push(new CommandOption({ ...opt, client: this.client }));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allows easy templating of subcommands that share arguments
|
||||
const { name: names, description, type, ...opts } = opt;
|
||||
for (const name of names)
|
||||
{
|
||||
const index = names.indexOf(name);
|
||||
let desc = description,
|
||||
_type = type;
|
||||
if (description instanceof Array)
|
||||
desc = description[index] || 'Missing description';
|
||||
if (type instanceof Array)
|
||||
_type = type[index];
|
||||
if (!_type)
|
||||
{
|
||||
_type = CommandOptionType.STRING;
|
||||
this.logger.warn(`Missing option type for ${this.resolveable}.${name}, defaulting to string`);
|
||||
}
|
||||
// throw new Error(`Missing type for option ${name} in command ${this.name}`);
|
||||
this.#options.push(new CommandOption({
|
||||
...opts,
|
||||
name,
|
||||
type: _type,
|
||||
description: desc,
|
||||
client: this.client
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Command;
|
@ -72,7 +72,7 @@ class Util
|
||||
|
||||
static has (o: unknown, k: string)
|
||||
{
|
||||
return Object.prototype.hasOwnProperty.call(o, k);
|
||||
return Object.prototype.hasOwnProperty.call(o, k);
|
||||
}
|
||||
|
||||
static hasId (obj: unknown): obj is DiscordStruct
|
||||
@ -124,7 +124,7 @@ class Util
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
static isSendable (obj: any): obj is { send: () => Promise<Message>}
|
||||
{
|
||||
return Util.has(obj, 'send') && typeof obj.send === 'function';
|
||||
return typeof obj.send === 'function';
|
||||
}
|
||||
|
||||
// static hasProperty<T> (obj: any, name: string): obj is { [key in typeof name]: T }
|
||||
|
Loading…
Reference in New Issue
Block a user