Command and setting translations

This commit is contained in:
Erik 2023-12-04 13:09:51 +02:00
parent e44eb8435e
commit 8142312546
47 changed files with 539 additions and 452 deletions

View File

@ -20,16 +20,16 @@ import {
ThreadChannel,
APIEmbedField,
VoiceState,
Invite,
Interaction
Invite
} from 'discord.js';
import { InvokerWrapper, MemberWrapper, UserWrapper } from '../client/components/wrappers/index.js';
import GuildWrapper from '../client/components/wrappers/GuildWrapper.js';
import DiscordClient from '../client/DiscordClient.js';
import { CommandOption, Inhibitor } from '../client/interfaces/index.js';
import { InvokerWrapper, MemberWrapper, UserWrapper } from '../client/components/wrappers/index.ts';
import GuildWrapper from '../client/components/wrappers/GuildWrapper.ts';
import DiscordClient from '../client/DiscordClient.ts';
import { CommandOption, Inhibitor } from '../client/interfaces/index.ts';
import { MuteType } from './Settings.js';
import { ClientEvents } from './Events.js';
import { FilterResult } from './Utils.js';
import { GuildSettingTypes } from './Guild.js';
export type ManagerEvalOptions = {
context?: object,
@ -276,7 +276,7 @@ export type SettingOptions = {
description?: string,
display?: string,
emoji?: SettingEmojiOption,
default?: BaseSetting
default: GuildSettingTypes
definitions?: SettingApiDefinitions,
commandOptions?: CommandOptionParams[],
commandType?: CommandOptionType,

16
src/@types/Commands/Settings.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
import { SlashCommandOptions } from '../Client.js';
import { ReplyOptions } from '../Wrappers.js';
export type SettingsCommandOptions = {
//
} & SlashCommandOptions
export type SettingResult = {
error?: boolean,
} & ReplyOptions
export type SettingModifyResult<T> = {
list: T[],
modified: T[],
skipped?: T[],
}

View File

@ -1,5 +0,0 @@
import { SlashCommandOptions } from '../Client.js';
export type SettingsCommandOptions = {
//
} & SlashCommandOptions

View File

@ -1,5 +1,5 @@
import { LoggerMasterOptions, LoggerClientOptions } from '@navy.gif/logger';
import { ClientOptions } from './Client.js';
import { ClientOptions } from './Client.ts';
// export type ClientOptions = {
// prefix: string,

View File

@ -1,6 +1,6 @@
import { Component } from '../client/interfaces/index.js';
import { Component } from '../client/interfaces/index.ts';
import { ClientEvents as CE, InvalidRequestWarningData, RateLimitData, ResponseLike } from 'discord.js';
import { ExtendedGuildBan, ExtendedMessage } from './Client.js';
import { ExtendedGuildBan, ExtendedMessage } from './Client.ts';
import { APIRequest } from '@discordjs/rest';
import { AutomodErrorProps, LinkFilterWarnProps, LogErrorProps, MissingPermsProps, UtilityErrorProps, WordWatcherErrorProps } from './Moderation.js';

View File

@ -10,6 +10,7 @@ import {
IndexingSettings,
InviteFilterSettings,
LinkfilterSettings,
LocaleSettings,
MemberLogSettings,
MentionFilterSettings,
MessagesSettings,
@ -31,14 +32,45 @@ import {
} from './Settings.js';
import { ObjectId } from 'mongodb';
export type GuildSettingTypes =
| AutomodSettings
| AutoroleSettings
| CommandSettings
| DMInfractionSettings
| ErrorLogSettings
| GrantableSettings
| IgnoreSettings
| IndexingSettings
| InviteFilterSettings
| LinkfilterSettings
| MemberLogSettings
| MentionFilterSettings
| MessagesSettings
| ModerationPoints
| ModerationSettings
| MuteSettings
| NicknameLogSettings
| PermissionSettings
| ProtectionSettings
| SelfroleSettings
| SilenceSettings
| StaffSettings
| StickyRoleSettings
| TextCommands
| VoiceSettings
| WelcomerSettings
| WordFilter
| WordWatcherSettings
| LocaleSettings
export type PartialGuildSettings = Partial<GuildSettings>
// {
// [key: string]: unknown
// }
export type GuildSettings = {
[key: string]: object,
locale: {language: string},
[key: string]: GuildSettingTypes,
locale: LocaleSettings,
textcommands: TextCommands,
wordfilter: WordFilter
moderation: ModerationSettings,

View File

@ -1,7 +1,7 @@
import { GuildTextBasedChannel } from 'discord.js';
import { MemberWrapper } from '../client/components/wrappers/index.js';
import { BaseInfractionData } from './Client.js';
import { Infraction } from '../client/interfaces/index.js';
import { MemberWrapper } from '../client/components/wrappers/index.ts';
import { BaseInfractionData } from './Client.ts';
import { Infraction } from '../client/interfaces/index.ts';
export type ResolveResult = {
result?: void,

View File

@ -1,7 +1,7 @@
import { GuildBasedChannel, Message, TextChannel } from 'discord.js';
import { GuildWrapper, InvokerWrapper, MemberWrapper, UserWrapper } from '../client/components/wrappers/index.js';
import { CommandOption, Infraction } from '../client/interfaces/index.js';
import { FormatParams, InfractionType } from './Client.js';
import { GuildWrapper, InvokerWrapper, MemberWrapper, UserWrapper } from '../client/components/wrappers/index.ts';
import { CommandOption, Infraction } from '../client/interfaces/index.ts';
import { FormatParams, InfractionType } from './Client.ts';
export type ModerationTarget = UserWrapper | MemberWrapper | TextChannel
export type ModerationTargets = ModerationTarget[]

View File

@ -1,6 +1,6 @@
import { Snowflake } from 'discord.js';
import { InfractionType } from './Client.js';
import { AutomodAction } from './Moderation.js';
import { InfractionType } from './Client.ts';
import { AutomodAction } from './Moderation.ts';
export type UserSettings = {
prefix?: string,
@ -30,9 +30,10 @@ export type WordFilter = {
} & FilterSetting
export type ModerationSettings = {
channel: Snowflake,
channel: Snowflake | null,
infractions: InfractionType[],
anonymous: boolean,
enabled: boolean
}
export type DMInfractionSettings = {
@ -65,8 +66,9 @@ export type AutomodSettings = {
},
} & Setting
export type PermissionType = 'discord' | 'grant' | 'both';
export type PermissionSettings = {
type: 'discord' | 'grant' | 'both'
type: PermissionType
}
export type GrantableSettings = {
@ -92,12 +94,14 @@ export type SilenceSettings = {
} & Setting
export type IgnoreSettings = {
[key: string]
channels: Snowflake[],
bypass: Snowflake[]
}
export type CommandSettings = {
disabled: string[]
disabled: string[],
custom: object
}
export type IndexingSettings = {
@ -110,10 +114,12 @@ export type ErrorLogSettings = {
}
export type MessagesSettings = {
[key: string]
channel: string | null,
bypass: string[],
ignore: string[],
attachments: boolean
attachments: boolean,
webhook: string | null,
} & Setting
export type StaffSettings = {
@ -126,6 +132,7 @@ export type VoiceSettings = {
} & Setting
export type SelfroleSettings = {
text: string | null,
roles: string[],
message: string | null,
channel: string | null,
@ -175,4 +182,8 @@ export type InviteFilterSettings = {
export type MentionFilterSettings = {
unique: boolean,
limit: number
} & FilterSetting
} & FilterSetting
export type LocaleSettings = {
language: string
}

View File

@ -1,4 +1,4 @@
import { ClientOptions } from './Client.js';
import { ClientOptions } from './Client.ts';
export type ShardingOptions = {
shardList?: 'auto' | number[],

View File

@ -1,5 +1,5 @@
import { WriteOptions, LoggerClient } from '@navy.gif/logger';
import { ClientOptions } from './Client.js';
import { ClientOptions } from './Client.ts';
export type CommandOption = {
[key: string]: unknown

View File

@ -1,5 +1,5 @@
import { APIEmbed, Attachment, AttachmentBuilder, AttachmentPayload, ChannelSelectMenuInteraction, EmbedBuilder, MentionableSelectMenuInteraction, MessageComponent, MessageComponentType, RoleSelectMenuInteraction, StringSelectMenuInteraction, UserSelectMenuInteraction } from 'discord.js';
import { FormatOpts, FormatParams } from './Client.js';
import { FormatOpts, FormatParams } from './Client.ts';
export type MessageOptions = {
index?: string,

View File

@ -36,15 +36,14 @@ class Registry
* @return {Array<Component>} The loaded components
* @memberof Registry
*/
async loadComponents (dir: string, classToHandle: typeof Component)
async loadComponents (dir: string, classToHandle: typeof Component)
{
const directory = path.join(process.cwd(), 'src/structure', dir); // Finds directory of component folder relative to current working directory.
const files = Util.readdirRecursive(directory); // Loops through all folders in the directory and returns the files.
const loaded = [];
for (const filePath of files)
for (const filePath of files)
{
const func = await import(`file://${filePath}`);
if (typeof func !== 'function')
@ -63,11 +62,8 @@ class Registry
}
loaded.push(await this.loadComponent(component, filePath));
}
return loaded;
}
/**
@ -79,9 +75,9 @@ class Registry
* @return {Component}
* @memberof Registry
*/
async loadComponent (component: Component, directory?: string, silent = false)
async loadComponent<T extends Component> (component: T, directory?: string, silent = false): Promise<T>
{
if (!(component instanceof Component))
if (!(component instanceof Component))
throw new Error('Attempted to load an invalid component.');
if (this.#components.has(component.resolveable))
@ -89,14 +85,14 @@ class Registry
if (directory)
component.directory = directory;
if (component.module && typeof component.module === 'string')
if (component.moduleName)
{ // Sets modules or "groups" for each component, specified by their properties.
let module = this.#components.get(`module:${component.module}`) as Module;
if (!module)
module = await this.loadComponent(new Module(this.#client, { name: component.module })) as Module;
let module = this.#components.get(`module:${component.moduleName}`) as Module;
if (!module)
module = await this.loadComponent(new Module(this.#client, { name: component.moduleName }));
this.#components.set(module.resolveable, module);
component.module = module as Module;
component.module = module;
module.components.set(component.resolveable, component);
}
@ -112,14 +108,12 @@ class Registry
async unloadComponent (component: Component)
{
if (component.module instanceof Module)
{
if (component.module instanceof Module)
component.module.components.delete(component.resolveable);
}
this.#components.delete(component.resolveable);
}
filter<T extends Component> (f: (c: T) => boolean)
filter<T extends Component> (f: (c: T) => boolean)
{
const coll = new Collection<string, T>();
for (const component of this.#components.values())

View File

@ -489,7 +489,7 @@ class Resolver
}
async resolveChannel<T extends Channel> (
resolveable: ChannelResolveable, strict?: boolean,
resolveable?: ChannelResolveable | null, strict?: boolean,
guild?: GuildWrapper | null, filter?: (channel: Channel) => boolean
)
{

View File

@ -157,14 +157,14 @@ class PermissionsCommand extends SlashCommand
for (const component of module.components.values())
if (component.type === 'command')
{
if ((component.module as Module).id === 'administration')
if (component.module.id === 'administration')
adminWarning = true;
parsed.push(component.resolveable);
}
}
else
{
if ((result.module as Module).id === 'administration')
if (result.module.id === 'administration')
adminWarning = true;
parsed.push(result.resolveable);
}

View File

@ -1,7 +1,6 @@
import { APIEmbed, APIEmbedField } from 'discord.js';
import { CommandOptionType } from '../../../../@types/Client.js';
import DiscordClient from '../../../DiscordClient.js';
import Module from '../../../interfaces/Module.js';
import SlashCommand from '../../../interfaces/commands/SlashCommand.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import Util from '../../../../utilities/Util.js';
@ -45,7 +44,7 @@ class SettingsCommand extends SlashCommand
const { settings } = this.client.registry;
const modules = settings.reduce((acc, setting) =>
{
const { name } = setting.module as Module;
const { name } = setting.module;
if (!acc[name])
acc[name] = [];
acc[name].push(setting.display);

View File

@ -46,7 +46,7 @@ class Commands extends SlashCommand
{
if (command.restricted)
continue;
const _module = (command.module as Module).name;
const _module = command.module.name;
if (!modules[_module])
modules[_module] = [];
const emoji = disabled?.includes(command.resolveable) ? Emojis.failure : Emojis.success;

View File

@ -848,7 +848,6 @@ class CommandHandler extends Observer
_generateError (invoker: InvokerWrapper<true, true>, info: ErrorParams)
{
const messages: {[key: string]: () => object} = {
command: () =>
{

View File

@ -1,7 +1,12 @@
class CommandsSetting extends Setting
{
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { CommandSettings } from '../../../../@types/Settings.js';
import DiscordClient from '../../../DiscordClient.js';
import Setting from '../../../interfaces/Setting.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
constructor (client)
class CommandsSetting extends Setting
{
constructor (client: DiscordClient)
{
super(client, {
name: 'commands',
@ -16,69 +21,66 @@ class CommandsSetting extends Setting
disabled: { ARRAY: 'COMMAND' },
custom: { OBJECT: 'CUSTOM_COMMAND' }
},
commandType: 'SUB_COMMAND_GROUP',
commandType: CommandOptionType.SUB_COMMAND_GROUP,
commandOptions: [{
name: [ 'enable', 'disable' ],
description: [ 'Enable commands', 'Disable commands' ],
type: 'SUB_COMMAND',
type: CommandOptionType.SUB_COMMAND,
options: [{
name: 'commands',
description: 'The command to enable/disable',
type: 'COMMANDS',
type: CommandOptionType.COMMANDS,
required: true
}]
}, {
name: 'list',
description: 'List disabled commands',
type: 'SUB_COMMAND'
type: CommandOptionType.SUB_COMMAND
}]
});
}
async execute (invoker, { commands }, setting)
async execute (invoker: InvokerWrapper<true>, { commands }: CommandParams, setting: CommandSettings)
{
const { subcommand, guild } = invoker;
let warning = null;
if (subcommand.name === 'enable')
if (subcommand!.name === 'enable')
{
if (!setting.disabled.length)
if (!setting.disabled.length)
return { error: true, index: 'SETTING_COMMANDS_NONE' };
for (const command of commands.value)
for (const command of commands!.asCommands)
{
const index = setting.disabled.indexOf(command.resolveable);
if (index >= 0)
if (index >= 0)
setting.disabled.splice(index, 1);
}
}
else if (subcommand.name === 'disable')
else if (subcommand!.name === 'disable')
{
for (const command of commands.value)
for (const command of commands!.asCommands)
{
if (command.module.name === 'administration')
if (command.module.name === 'administration')
{
warning = guild.format('SETTING_COMMAND_WARNING');
continue;
}
if (!setting.disabled.includes(command.resolveable))
if (!setting.disabled.includes(command.resolveable))
setting.disabled.push(command.resolveable);
}
if (warning)
if (warning)
return { content: warning };
}
else if (subcommand.name === 'list')
else if (subcommand!.name === 'list')
{
let content = null;
if (setting.disabled.length)
if (setting.disabled.length)
content = setting.disabled.join(', ');
else
else
content = guild.format('SETTING_COMMANDS_NONE');
return { content };
}
return { index: 'SETTING_SUCCESS_ALT' };
}
}
export default CommandsSetting;

View File

@ -1,12 +1,15 @@
const { Util } = require('../../../../utilities');
const { Setting } = require('../../../interfaces');
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { IgnoreSettings } from '../../../../@types/Settings.js';
import Util from '../../../../utilities/Util.js';
import DiscordClient from '../../../DiscordClient.js';
import Setting from '../../../interfaces/Setting.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
class IgnoreSetting extends Setting
class IgnoreSetting extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'ignore',
module: 'administration',
@ -24,7 +27,7 @@ class IgnoreSetting extends Setting
{
name: 'list',
description: 'List to act on',
type: 'STRING',
type: CommandOptionType.STRING,
choices: [
{ name: 'channels', value: 'channels' },
{ name: 'bypass', value: 'bypass' }
@ -34,7 +37,7 @@ class IgnoreSetting extends Setting
{
name: 'method',
description: 'Method of modifying',
type: 'STRING',
type: CommandOptionType.STRING,
choices: [
{ name: 'add', value: 'add' },
{ name: 'remove', value: 'remove' },
@ -46,48 +49,42 @@ class IgnoreSetting extends Setting
}
]
});
}
async execute (interaction, opts, setting)
async execute (invoker: InvokerWrapper<true>, opts: CommandParams, setting: IgnoreSettings)
{
const { list, method } = opts;
if (method.value === 'list')
return this.list(setting[list.value], interaction, list.value);
if (method?.value === 'list')
return this.list(setting[list!.asString], invoker, list?.asString);
const response = await this._prompt(interaction, {
index: `SETTING_PROMPT_${method.value.toUpperCase()}`,
params: { list: list.value }
const promptResponse = await this._prompt(invoker, {
index: `SETTING_PROMPT_${method!.asString.toUpperCase()}`,
params: { list: list?.asString }
});
if (response.error)
return response;
if (method.value === 'reset' && !this.client.resolver.resolveBoolean(response))
{
if (promptResponse.error)
return promptResponse;
const content = promptResponse.content!;
if (method?.asString === 'reset' && !this.client.resolver.resolveBoolean(content))
return { index: 'SETTING_NO_CHANGE' };
}
const params = Util.parseQuotes(response).map(([ param ]) => param);
const params = Util.parseQuotes(content).map(([ param ]) => param);
let values = [];
const { guild } = interaction;
if (list.value === 'bypass')
const { guild } = invoker;
if (list?.asString === 'bypass')
values = await guild.resolveRoles(params);
else
else
values = await guild.resolveChannels(params);
const { modified } = this[method.value](setting[list.value], values.map((o) => o.id));
if (!modified.length)
const { modified } = this[method!.asString](setting[list!.asString], values.map((o) => o.id));
if (!modified.length)
return { index: 'SETTING_NO_CHANGE' };
return { index: `SETTING_SUCCESS_${method.value.toUpperCase()}`, params: { updated: list.value, modified: values.filter((o) => modified.includes(o.id)).map((o) => o.name).join('__, __') } };
return { index: `SETTING_SUCCESS_${method?.asString.toUpperCase()}`, params: { updated: list!.asString, modified: values.filter((o) => modified.includes(o.id)).map((o) => o.name).join('__, __') } };
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as IgnoreSettings;
return [
{
name: 'GENERAL_CHANNELS',

View File

@ -1,12 +1,15 @@
const { Setting } = require('../../../interfaces');
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { IndexingSettings } from '../../../../@types/Settings.js';
import DiscordClient from '../../../DiscordClient.js';
import Setting from '../../../interfaces/Setting.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
// setting for guild indexing and description
class IndexSetting extends Setting
class IndexSetting extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'indexing',
module: 'administration',
@ -20,7 +23,7 @@ class IndexSetting extends Setting
{
name: 'enabled',
description: 'Toggle enable state',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
@ -31,23 +34,20 @@ class IndexSetting extends Setting
}
]
});
}
async execute (invoker, { enabled, description }, setting)
async execute (_invoker: InvokerWrapper, { enabled, description }: CommandParams, setting: IndexingSettings)
{
if (enabled)
setting.enabled = enabled.value;
setting.enabled = enabled.asBool;
if (description)
setting.description = description.value;
setting.description = description.asString;
return { index: 'SETTING_SUCCESS_ALT' };
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as IndexingSettings;
return [
{
name: 'GENERAL_STATUS',
@ -62,7 +62,6 @@ class IndexSetting extends Setting
}
];
}
}
export default IndexSetting;

View File

@ -1,11 +1,14 @@
const { Setting } = require('../../../interfaces/');
import { CommandParams } from '../../../../@types/Client.js';
import { PermissionSettings, PermissionType } from '../../../../@types/Settings.js';
import DiscordClient from '../../../DiscordClient.js';
import Setting from '../../../interfaces/Setting.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
class PermissionType extends Setting
class Permissions extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'permissions',
description: 'Change between discord and bot based permissions',
@ -19,7 +22,6 @@ class PermissionType extends Setting
},
commandOptions: [
{
type: 'STRING',
name: 'type',
description: 'Where to read permissions from',
choices: [
@ -30,21 +32,18 @@ class PermissionType extends Setting
}
]
});
}
async execute (interaction, opts, setting)
async execute (_invoker: InvokerWrapper, opts: CommandParams, setting: PermissionSettings)
{
const { type } = opts;
setting.type = type.value;
setting.type = type!.asString as PermissionType;
return { index: 'SETTING_SUCCESS_ALT' };
}
async fields (guild)
async fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as PermissionSettings;
return [
{
name: 'SETTING_PERMISSION_TYPE_FIELD',
@ -52,7 +51,6 @@ class PermissionType extends Setting
}
];
}
}
export default PermissionType;
export default Permissions;

View File

@ -1,12 +1,10 @@
const { Util } = require('../../../../utilities');
const { Setting } = require('../../../interfaces');
import DiscordClient from '../../../DiscordClient.js';
import Setting from '../../../interfaces/Setting.js';
class ProtectionSetting extends Setting
class ProtectionSetting extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'protection',
description: 'Select which roles are immune from moderation',
@ -24,7 +22,6 @@ class ProtectionSetting extends Setting
},
commandOptions: [
{
type: 'STRING',
name: 'type',
description: 'Select protection type',
choices: [
@ -34,7 +31,6 @@ class ProtectionSetting extends Setting
dependsOn: []
},
{
type: 'STRING',
name: 'roles',
description: 'Method of modifying',
choices: [
@ -47,7 +43,6 @@ class ProtectionSetting extends Setting
dependsOn: []
},
{
type: 'BOOLEAN',
name: 'enabled',
description: 'Whether setting is active or not'
}

View File

@ -1,6 +1,3 @@
const { Setting } = require('../../../interfaces');
class SilentSetting extends Setting
{

View File

@ -1,5 +1,3 @@
const { Setting } = require('../../../interfaces');
class TextCommands extends Setting
{

View File

@ -1,4 +1,10 @@
const { Setting } = require('../../../interfaces');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams, FormatParams, InfractionType } from '../../../../@types/Client.js';
import { DMInfractionSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
const Infractions = [
'NOTE',
'WARN',
@ -16,12 +22,11 @@ const Infractions = [
'REMOVEROLE',
'ADDROLE',
'NICKNAME'
];
] satisfies InfractionType[];
class DmInfraction extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'dminfraction',
@ -43,7 +48,7 @@ class DmInfraction extends Setting
'VCKICK',
'VCBAN',
'VCUNBAN'
],
] satisfies InfractionType[],
messages: {
default: 'You were **{infraction}** {from|on} the server `{server}`, your infraction details are below.'
},
@ -62,13 +67,13 @@ class DmInfraction extends Setting
{
name: 'message',
description: 'Set the message for an infraction type',
type: 'STRING',
type: CommandOptionType.STRING,
dependsOn: [ 'infraction' ]
},
{
name: 'infraction',
description: 'Choose the infraction for which to modify the message',
type: 'STRING',
type: CommandOptionType.STRING,
choices: Infractions.map((inf) =>
{
return { name: inf, value: inf };
@ -78,7 +83,7 @@ class DmInfraction extends Setting
{
name: 'infractions',
description: 'Modify the list of infractions that are sent',
type: 'STRING',
type: CommandOptionType.STRING,
choices: [
{ name: 'add', value: 'add' },
{ name: 'remove', value: 'remove' },
@ -89,14 +94,14 @@ class DmInfraction extends Setting
{
name: 'enabled',
description: 'Enable or disable the sending of infractions in DMs',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
},
{
name: 'anonymous',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true,
@ -106,31 +111,32 @@ class DmInfraction extends Setting
});
}
async execute (interaction, opts, setting)
async execute (invoker: InvokerWrapper, opts: CommandParams, setting: DMInfractionSettings)
{
const { enabled, infractions, infraction, message, anonymous } = opts,
langParams = {};
langParams: FormatParams = {};
let index = 'SETTING_SUCCESS_ALT';
if (enabled)
setting.enabled = enabled.value;
setting.enabled = enabled.asBool;
if (anonymous)
setting.anonymous = anonymous.value;
setting.anonymous = anonymous.asBool;
if (infractions)
{
const extra = `\n\n${interaction.format('SETTING_DMINFRACTION_VALID', {
const extra = `\n\n${invoker.format('SETTING_DMINFRACTION_VALID', {
valid: Infractions.join(', ')
})}`;
const reset = infractions.value === 'reset';
const response = await this._prompt(interaction, {
message: `${interaction.format(`SETTING_PROMPT_${infractions.value.toUpperCase()}`, {
const reset = infractions.asString === 'reset';
const promptResponse = await this._prompt(invoker, {
message: `${invoker.format(`SETTING_PROMPT_${infractions.asString.toUpperCase()}`, {
list: 'infractions'
})}${reset ? '' : extra}`
});
if (response.error)
return response;
if (promptResponse.error)
return promptResponse;
const response = promptResponse.content!;
if (reset)
{
if (this.client.resolver.resolveBoolean(response))
@ -144,10 +150,10 @@ class DmInfraction extends Setting
const infs = response.split(' ')
.map((inf) => this.client.resolver.resolveInfraction(inf))
.filter((inf) => inf !== null);
const { modified } = this[infractions.value](setting.infractions, infs);
const { modified } = this[infractions.asString](setting.infractions, infs);
if (modified.length)
{
index = `SETTING_SUCCESS_${infractions.value.toUpperCase()}`;
index = `SETTING_SUCCESS_${infractions.asString.toUpperCase()}`;
langParams.modified = modified.join('__, __');
langParams.updated = 'infractions';
}
@ -156,17 +162,17 @@ class DmInfraction extends Setting
if (message && infraction)
{
setting.messages[infraction.value] = message.value;
setting.messages[infraction.asString] = message.asString;
}
return { index, params: langParams };
}
async fields (guild)
async fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as DMInfractionSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,9 +1,14 @@
const { Setting } = require('../../../interfaces');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { ErrorLogSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
class MessageLog extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
// Guild based logging of errors, typically configuration errors.
@ -27,12 +32,12 @@ class MessageLog extends Setting
{
name: 'channel',
description: 'Channel in which to output logs',
type: 'TEXT_CHANNEL'
type: CommandOptionType.TEXT_CHANNEL
},
{
name: 'enabled',
description: 'Toggle logging on or off',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
@ -42,23 +47,23 @@ class MessageLog extends Setting
}
async execute (interaction, opts, setting)
async execute (_invoker: InvokerWrapper, opts: CommandParams, setting: ErrorLogSettings)
{
if (opts.enabled?.value === false)
if (opts.enabled?.asBool === false)
setting.channel = null;
if (opts.channel)
{
const channel = opts.channel.value;
const perms = channel.permissionsFor(this.client.user);
const missingPerms = perms.missing([ 'ViewChannel', 'EmbedLinks', 'SendMessages' ]);
if (missingPerms.length)
const channel = opts.channel.asChannel;
const perms = channel.permissionsFor(this.client.user!);
const missingPerms = perms?.missing([ 'ViewChannel', 'EmbedLinks', 'SendMessages' ]);
if (!missingPerms || missingPerms.length)
return {
error: true,
index: 'ERR_CHANNEL_PERMS',
params: { channel: channel.name, perms: missingPerms.join(', ') }
params: { channel: channel.name, perms: missingPerms?.join(', ') }
};
setting.channel = channel.id;
@ -69,9 +74,9 @@ class MessageLog extends Setting
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as ErrorLogSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,9 +1,14 @@
const { Setting } = require('../../../interfaces');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { MemberLogSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
class MemberLog extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
@ -26,7 +31,7 @@ class MemberLog extends Setting
{
name: 'enabled',
description: 'Enable/disable member logs',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
@ -34,18 +39,18 @@ class MemberLog extends Setting
{
name: 'channel',
description: 'Select the log output channel',
type: 'TEXT_CHANNEL'
type: CommandOptionType.TEXT_CHANNEL
},
{
name: 'join',
description: 'Set the join message',
type: 'STRING',
type: CommandOptionType.STRING,
flag: true
},
{
name: 'leave',
description: 'Set the leave message',
type: 'STRING',
type: CommandOptionType.STRING,
flag: true
}
]
@ -53,26 +58,26 @@ class MemberLog extends Setting
}
async execute (interaction, opts, setting)
async execute (_invoker: InvokerWrapper, opts: CommandParams, setting: MemberLogSettings)
{
if (opts.join)
setting.join = opts.join.value;
setting.join = opts.join.asString;
if (opts.leave)
setting.leave = opts.leave.value;
setting.leave = opts.leave.asString;
if (opts.channel)
setting.channel = opts.channel.value.id;
setting.channel = opts.channel.asChannel.id;
if (opts.enabled)
setting.enabled = opts.enabled.value;
setting.enabled = opts.enabled.asBool;
return { index: 'SETTING_SUCCESS_ALT' };
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as MemberLogSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,12 +1,17 @@
const { Setting } = require('../../../interfaces');
const { Util } = require('../../../../utilities');
import { CommandError, Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams, FormatParams } from '../../../../@types/Client.js';
import { MessagesSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
import Util from '../../../../utilities/Util.js';
import { Role, TextChannel } from 'discord.js';
import { SettingModifyResult } from '../../../../@types/Commands/Settings.js';
class MessageLog extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'messages',
module: 'logging',
@ -31,12 +36,12 @@ class MessageLog extends Setting
{
name: 'channel',
description: 'Channel in which to output logs',
type: 'TEXT_CHANNEL'
type: CommandOptionType.TEXT_CHANNEL
},
{
name: 'enabled',
description: 'Toggle logging on or off',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
@ -44,7 +49,7 @@ class MessageLog extends Setting
{
name: 'attachments',
description: 'Whether to log attachments. PREMIUM TIER 1',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
@ -52,7 +57,7 @@ class MessageLog extends Setting
{
name: 'list',
description: 'Select which list to modify',
type: 'STRING',
type: CommandOptionType.STRING,
choices: [
{ name: 'bypass', value: 'bypass' },
{ name: 'ignore', value: 'ignore' },
@ -62,7 +67,7 @@ class MessageLog extends Setting
{
name: 'method',
description: 'Select which modification method to use',
type: 'STRING',
type: CommandOptionType.STRING,
choices: [
{ name: 'add', value: 'add' },
{ name: 'remove', value: 'remove' },
@ -77,30 +82,30 @@ class MessageLog extends Setting
}
async execute (interaction, opts, setting)
async execute (invoker: InvokerWrapper, opts: CommandParams, setting: MessagesSettings)
{
const method = opts.method?.value;
const list = opts.list?.value;
const { guild } = interaction;
const method = opts.method?.asString;
const list = opts.list?.asString;
const guild = invoker.guild!;
let index = 'SETTING_SUCCESS_ALT';
const langParams = {};
const langParams: FormatParams = {};
if (opts.enabled)
setting.enabled = opts.enabled.value;
setting.enabled = opts.enabled.asBool;
if (opts.channel)
if (opts.channel)
{
const channel = opts.channel.value;
const perms = channel.permissionsFor(this.client.user);
const missingPerms = perms.missing([ 'ViewChannel', 'EmbedLinks', 'SendMessages', 'ManageWebhooks' ]);
if (missingPerms.length)
const channel = opts.channel.asChannel;
if (!(channel instanceof TextChannel))
throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' });
const perms = channel.permissionsFor(this.client.user!);
const missingPerms = perms?.missing([ 'ViewChannel', 'EmbedLinks', 'SendMessages', 'ManageWebhooks' ]);
if (!missingPerms || missingPerms.length)
return {
error: true,
index: 'ERR_CHANNEL_PERMS',
params: { channel: channel.name, perms: missingPerms.join(', ') }
params: { channel: channel.name, perms: missingPerms?.join(', ') ?? 'ALL' }
};
let hook = await guild.getWebhook(this.name);
@ -108,9 +113,11 @@ class MessageLog extends Setting
if (hook)
hook = await this.client.fetchWebhook(hook.id).catch(() => null);
if (hook)
await hook.edit({ channel });
else
if (hook)
{
await hook.edit({ channel });
}
else
{
hook = await channel.createWebhook({
name: 'Galactic Bot message logs',
@ -119,59 +126,53 @@ class MessageLog extends Setting
await guild.updateWebhook(this.name, hook);
setting.webhook = hook.id;
}
setting.channel = channel.id;
}
if (method && list)
if (method && list)
{
if (method === 'list')
return this.list(setting[list], interaction, list);
if (method === 'list')
return this.list(setting[list], invoker, list);
const time = 120;
const content = await this._prompt(interaction, {
const promptResponse = await this._prompt(invoker, {
message: guild.format(
`SETTING_PROMPT_${method.toUpperCase()}`,
{ method, list }
) + '\n' + guild.format('TIMEOUT_IN', { time }),
time
});
if (content.error)
return content;
if (promptResponse.error)
return promptResponse;
const content = promptResponse.content!;
if (method === 'reset' && !this.client.resolver.resolveBoolean(content))
{
return { index: 'SETTING_NO_CHANGE' };
}
const words = Util.parseQuotes(content).map(([ word ]) => word);
let values = [];
if (list === 'bypass')
let values: TextChannel[] | Role[] = [];
if (list === 'bypass')
values = await guild.resolveRoles(words);
else if (list === 'ignore')
values = await guild.resolveChannels(words);
else if (list === 'ignore')
values = await guild.resolveChannels<TextChannel>(words);
if (!values.length)
if (!values.length)
return { error: true, index: 'RESOLVE_FAIL', params: { type: list === 'bypass' ? 'roles' : 'channels' } };
const { modified } = this[method](setting[list], values.map((o) => o.id));
if (modified.length)
const { modified } = this[method](setting[list], values.map((o) => o.id)) as SettingModifyResult<string>;
if (modified.length)
{
index = `SETTING_SUCCESS_${method.toUpperCase()}`;
langParams.updated = list;
langParams.modified = modified.map((o) => o.name).join('__, __');
langParams.modified = values.filter(o => modified.some(e => e === o.id)).map((o) => o.name).join('__, __');
}
}
return { index, params: langParams };
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as MessagesSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,5 +1,10 @@
const { Infractions } = require('../../../../constants/Constants');
const { Setting } = require('../../../interfaces');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams, FormatParams } from '../../../../@types/Client.js';
import { ModerationSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
import { Infractions } from '../../../../constants/Constants.js';
// [
// 'NOTE',
@ -26,8 +31,7 @@ const { Setting } = require('../../../interfaces');
class ModerationLog extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'moderation',
@ -37,7 +41,8 @@ class ModerationLog extends Setting
default: {
channel: null,
infractions: Infractions,
anonymous: false
anonymous: false,
enabled: false
},
definitions: {
channel: 'GUILD_TEXT',
@ -49,7 +54,7 @@ class ModerationLog extends Setting
{
name: 'enabled',
description: 'Enable/disable member logs',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
@ -57,12 +62,12 @@ class ModerationLog extends Setting
{
name: 'channel',
description: 'Logging channel',
type: 'TEXT_CHANNEL'
type: CommandOptionType.TEXT_CHANNEL
},
{
name: 'infractions',
description: 'Modify the list of infractions that are sent',
type: 'STRING',
type: CommandOptionType.STRING,
choices: [
{ name: 'add', value: 'add' },
{ name: 'remove', value: 'remove' },
@ -72,7 +77,7 @@ class ModerationLog extends Setting
},
{
name: 'anonymous',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true,
@ -82,33 +87,34 @@ class ModerationLog extends Setting
});
}
async execute (interaction, opts, setting)
async execute (invoker: InvokerWrapper, opts: CommandParams, setting: ModerationSettings)
{
const { channel, infractions, anonymous, enabled } = opts,
langParams = {};
langParams: FormatParams = {};
let index = 'SETTING_SUCCESS_ALT';
if (anonymous)
setting.anonymous = anonymous.value;
setting.anonymous = anonymous.asBool;
if (channel)
setting.channel = channel.value.id;
setting.channel = channel.asChannel.id;
if (enabled)
setting.enabled = enabled.value;
setting.enabled = enabled.asBool;
if (infractions)
{
const extra = `\n\n${interaction.format('SETTING_DMINFRACTION_VALID', {
const extra = `\n\n${invoker.format('SETTING_DMINFRACTION_VALID', {
valid: Infractions.join(', ')
})}`;
const reset = infractions.value === 'reset';
const response = await this._prompt(interaction, {
message: `${interaction.format(`SETTING_PROMPT_${infractions.value.toUpperCase()}`, {
const reset = infractions.asString === 'reset';
const promptResponse = await this._prompt(invoker, {
message: `${invoker.format(`SETTING_PROMPT_${infractions.asString.toUpperCase()}`, {
list: 'infractions'
})}${reset ? '' : extra}`
});
if (response.error)
return response;
if (promptResponse.error)
return promptResponse;
const response = promptResponse.content!;
if (reset)
{
if (this.client.resolver.resolveBoolean(response))
@ -122,10 +128,10 @@ class ModerationLog extends Setting
const infs = response.split(' ')
.map((inf) => this.client.resolver.resolveInfraction(inf))
.filter((inf) => inf !== null);
const { modified } = this[infractions.value](setting.infractions, infs);
const { modified } = this[infractions.asString](setting.infractions, infs);
if (modified.length)
{
index = `SETTING_SUCCESS_${infractions.value.toUpperCase()}`;
index = `SETTING_SUCCESS_${infractions.asString.toUpperCase()}`;
langParams.modified = modified.join('__, __');
langParams.updated = 'infractions';
}
@ -135,9 +141,9 @@ class ModerationLog extends Setting
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as ModerationSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,9 +1,13 @@
const { Setting } = require('../../../interfaces');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { NicknameLogSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
class Nicknames extends Setting
class Nicknames extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'nicknames',
@ -21,35 +25,32 @@ class Nicknames extends Setting
{
name: 'enabled',
description: 'Toggle logging on or off',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
},
{
name: 'channel',
type: 'TEXT_CHANNEL',
type: CommandOptionType.TEXT_CHANNEL,
description: 'Set the channel for nickname logging'
}
]
});
}
async execute (interaction, opts, setting)
async execute (_invoker: InvokerWrapper, opts: CommandParams, setting: NicknameLogSettings)
{
if (opts.enabled)
setting.enabled = opts.enabled.value;
setting.enabled = opts.enabled.asBool;
if (opts.channel)
setting.channel = opts.channel.value.id;
setting.channel = opts.channel.asChannel.id;
return { index: 'SETTING_SUCCESS_ALT' };
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as NicknameLogSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,9 +1,13 @@
const { Setting } = require('../../../interfaces');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { VoiceSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
class Voice extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'voice',
@ -11,7 +15,8 @@ class Voice extends Setting
display: 'Voice Channel Logging',
module: 'logging',
default: {
channel: null
channel: null,
enabled: false
},
definitions: {
channel: 'GUILD_TEXT'
@ -20,35 +25,32 @@ class Voice extends Setting
{
name: 'enabled',
description: 'Toggle logging on or off',
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true
},
{
name: 'channel',
type: 'TEXT_CHANNEL',
type: CommandOptionType.TEXT_CHANNEL,
description: 'Set the channel for voice join/leave logging'
}
]
});
}
async execute (interaction, opts, setting)
async execute (_invoker: InvokerWrapper, opts: CommandParams, setting: VoiceSettings)
{
if (opts.enabled)
setting.enabled = opts.enabled.value;
setting.enabled = opts.enabled.asBool;
if (opts.channel)
setting.channel = opts.channel.value.id;
setting.channel = opts.channel.asChannel.id;
return { index: 'SETTING_SUCCESS_ALT' };
}
fields (guild)
fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as VoiceSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -64,23 +64,22 @@ class Autorole extends Setting
{
const guild = invoker.guild!;
const time = 120;
const content = await this._prompt(invoker, {
const promptResponse = await this._prompt(invoker, {
message: guild.format(
`SETTING_PROMPT_${method.toUpperCase()}`,
{ method, list: 'roles' }
) + '\n' + guild.format('TIMEOUT_IN', { time }),
time
});
if (content.error)
return content;
if (method === 'reset' && !this.client.resolver.resolveBoolean(content))
{
if (promptResponse.error)
return promptResponse;
const content = promptResponse.content!;
if (method === 'reset' && !this.client.resolver.resolveBoolean(content))
return { index: 'SETTING_NO_CHANGE' };
}
const words = Util.parseQuotes(content).map(([ word ]) => word);
const params = (await guild.resolveRoles(words)).filter((r) => !r.managed);
if (!params.length)
if (!params.length)
return { error: true, index: 'RESOLVE_FAIL', params: { type: 'roles' } };
const { modified } = this[method](setting.roles, params.map((o) => o.id));
@ -92,16 +91,13 @@ class Autorole extends Setting
modified: params.filter((o) => modified.includes(o.id)).map((o) => o.name).join('__, __')
}
};
}
return { index: 'SETTING_SUCCESS_ALT' };
}
async fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as AutoroleSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,10 +1,11 @@
import { Setting } from '../../../interfaces/index.js';
import { CommandError, Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import Util from '../../../../utilities/Util.js';
import { SelfroleSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
import { ButtonComponentData, ButtonStyle, ComponentType, MessageCreateOptions, MessageEditOptions, StringSelectMenuComponentData, TextChannel } from 'discord.js';
import Emojis from '../../../../constants/Emojis.js';
class SelfroleSetting extends Setting
{
@ -48,20 +49,20 @@ class SelfroleSetting extends Setting
if (!roles && !channel && !text)
return { error: true, index: 'SETTING_MISSING_ARG' };
const guild = invoker.guild!;
const invokerChannel = invoker.channel!;
if (roles?.asString === 'list')
return this.list(setting.roles, invoker, 'roles');
if (roles)
{
const response = await this._prompt(invoker, {
const promptResponse = await this._prompt(invoker, {
index: `SETTING_PROMPT_${roles.asString.toUpperCase()}`,
params: { list: 'roles' }
});
if (response.error)
return response;
if (roles.asString === 'reset' && !this.client.resolver.resolveBoolean(response))
if (promptResponse.error)
return promptResponse;
const response = promptResponse.content!;
if (roles.value === 'reset' && !this.client.resolver.resolveBoolean(response))
{
return { index: 'SETTING_NO_CHANGE' };
}
@ -70,15 +71,17 @@ class SelfroleSetting extends Setting
const values = (await guild.resolveRoles(params)).filter((r) => !r.managed).map((r) => r.id);
this[roles.asString](setting.roles, values);
if (setting.roles.length >= 25 && (setting.channel || channel))
await channel.send(guild.format('SETTING_SELFROLE_WARNING'));
await (invoker.channel!).send(guild.format('SETTING_SELFROLE_WARNING'));
}
// old channel for deleting old message if one exists
const oldChannel = await guild.resolveChannel(setting.channel);
const newChannel = channel?.asString || oldChannel;
if (channel)
setting.channel = channel.asString.id; // Set the new channel if one is given
if (text)
const oldChannel = await guild.resolveChannel<TextChannel>(setting.channel);
const newChannel = channel?.asChannel || oldChannel;
if (!(newChannel instanceof TextChannel))
throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' });
if (channel)
setting.channel = channel.asChannel.id; // Set the new channel if one is given
if (text)
setting.text = text.asString;
// If an old channel exists, and a message exists, and either all roles were removed or the channel changed, delete the old message
@ -94,34 +97,36 @@ class SelfroleSetting extends Setting
if (setting.roles.length && setting.channel && setting.roles.length <= 25)
{
const roles = await guild.resolveRoles(setting.roles);
const resolvedRoles = await guild.resolveRoles(setting.roles);
const selectMenu: StringSelectMenuComponentData = {
type: ComponentType.SelectMenu,
customId: 'selfrole-select',
maxValues: resolvedRoles.length,
options: resolvedRoles.map((r) =>
{
return { label: r.name, value: r.id };
})
};
const button: ButtonComponentData = {
type: ComponentType.Button,
customId: 'selfrole-clear',
label: 'Clear',
style: ButtonStyle.Primary,
emoji: Emojis.failure
};
const components = [{
type: ComponentType.ActionRow,
components: [{
type: ComponentType.SelectMenu,
customId: 'selfrole-select',
maxValues: roles.length,
options: roles.map((r) =>
{
return { label: r.name, value: r.id };
})
}]
components: [ selectMenu ]
}, {
type: ComponentType.ActionRow,
components: [{
type: ComponentType.Button,
customId: 'selfrole-clear',
label: 'Clear',
style: ButtonStyle.Primary,
emoji: Emojis.failure
}]
components: [ button ]
}];
const payload = {
const payload: MessageCreateOptions & MessageEditOptions = {
content: setting.text || guild.format('SETTING_SELFROLE_SELECT'),
components
};
if (newChannel === oldChannel)
if (newChannel === oldChannel && oldChannel && setting.message)
{
const msg = await oldChannel.messages.fetch(setting.message).catch(() => null);
if (msg)

View File

@ -1,10 +1,15 @@
const { Setting } = require('../../../interfaces');
const { Util } = require('../../../../utilities');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import Util from '../../../../utilities/Util.js';
import { StickyRoleSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
class Autorole extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'stickyrole',
@ -21,7 +26,7 @@ class Autorole extends Setting
},
commandOptions: [
{
type: 'STRING',
type: CommandOptionType.STRING,
name: 'roles',
description: 'Modification method for roles',
choices: [
@ -34,7 +39,7 @@ class Autorole extends Setting
]
},
{
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true,
@ -46,31 +51,32 @@ class Autorole extends Setting
});
}
async execute (interaction, opts, setting)
async execute (invoker: InvokerWrapper, opts: CommandParams, setting: StickyRoleSettings)
{
const enabled = opts.enabled?.value;
const method = opts.roles?.value;
const enabled = opts.enabled?.asBool;
const method = opts.roles?.asString;
if (typeof enabled === 'boolean')
setting.enabled = enabled;
if (method === 'list')
return this.list(setting.roles, interaction, 'roles');
return this.list(setting.roles, invoker, 'roles');
if (method)
{
const { guild } = interaction;
const guild = invoker.guild!;
const time = 120;
const content = await this._prompt(interaction, {
const promptResponse = await this._prompt(invoker, {
message: guild.format(
`SETTING_PROMPT_${method.toUpperCase()}`,
{ method, list: 'roles' }
) + '\n' + guild.format('TIMEOUT_IN', { time }),
time
});
if (content.error)
return content;
if (method.value === 'reset' && !this.client.resolver.resolveBoolean(content))
if (promptResponse.error)
return promptResponse;
const content = promptResponse.content!;
if (method === 'reset' && !this.client.resolver.resolveBoolean(content))
{
return { index: 'SETTING_NO_CHANGE' };
}
@ -96,9 +102,9 @@ class Autorole extends Setting
}
async fields (guild)
async fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as StickyRoleSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -1,9 +1,14 @@
const { Setting } = require('../../../interfaces');
import { Setting } from '../../../interfaces/index.js';
import DiscordClient from '../../../DiscordClient.js';
import InvokerWrapper from '../../wrappers/InvokerWrapper.js';
import { CommandOptionType, CommandParams } from '../../../../@types/Client.js';
import { WelcomerSettings } from '../../../../@types/Settings.js';
import GuildWrapper from '../../wrappers/GuildWrapper.js';
class Autorole extends Setting
{
constructor (client)
constructor (client: DiscordClient)
{
super(client, {
name: 'welcomer',
@ -20,12 +25,12 @@ class Autorole extends Setting
},
commandOptions: [
{
type: 'STRING',
type: CommandOptionType.STRING,
name: 'message',
description: 'Set the welcome message'
},
{
type: 'BOOLEAN',
type: CommandOptionType.BOOLEAN,
flag: true,
valueOptional: true,
defaultValue: true,
@ -36,23 +41,21 @@ class Autorole extends Setting
});
}
async execute (interaction, opts, setting)
async execute (_invoker: InvokerWrapper, opts: CommandParams, setting: WelcomerSettings)
{
const enabled = opts.enabled?.value;
const message = opts.message?.value;
const enabled = opts.enabled?.asBool;
const message = opts.message?.asString;
if (typeof enabled === 'boolean')
setting.enabled = enabled;
if (typeof message === 'string')
setting.message = message;
return { index: 'SETTING_SUCCESS_ALT' };
}
async fields (guild)
async fields (guild: GuildWrapper)
{
const setting = guild._settings[this.name];
const setting = guild._settings[this.name] as WelcomerSettings;
return [
{
name: 'GENERAL_STATUS',

View File

@ -542,7 +542,7 @@ class GuildWrapper
return this.#client.resolver.resolveChannels<T>(resolveables, strict, this, filter);
}
resolveChannel<T extends Channel> (resolveable: ChannelResolveable, strict = false, filter?: (channel: Channel) => boolean)
resolveChannel<T extends Channel> (resolveable?: ChannelResolveable | null, strict = false, filter?: (channel: Channel) => boolean)
{
return this.#client.resolver.resolveChannel<T>(resolveable, strict, this, filter);
}

View File

@ -913,6 +913,11 @@ class CommandOption
return this.value as GuildBasedChannel;
}
get asCommands ()
{
return this.value as Command[];
}
get asCommand ()
{
return this.value as Command;

View File

@ -10,7 +10,8 @@ abstract class Component
#client: DiscordClient;
#id: string;
#type: ComponentType;
#module: Module | string | null;
#module!: Module;
#moduleName: string | null;
#directory: string | null;
#guarded: boolean;
#disabled: boolean;
@ -36,7 +37,8 @@ abstract class Component
this.#id = options.id; // Name of the component
this.#type = options.type; // Type of component (command, observer, etc.)
this.#module = options.module ?? null; // Group/module the component belongs to.
// this.#module = null;
this.#moduleName = options.module ?? null; // Group/module the component belongs to.
this.#directory = null; // File directory to component.
@ -60,12 +62,17 @@ abstract class Component
return this.#type;
}
get module (): Module | string
get moduleName ()
{
return this.#module ?? 'MODULE';
return this.#moduleName;
}
set module (mod: Module)
get module ()
{
return this.#module;
}
set module (mod: Module)
{
this.#module = mod;
}

View File

@ -12,7 +12,7 @@ import { FilterSettingHelperResponse, SettingAction, SettingActionType, SettingM
const validInfractions = [ 'WARN', 'MUTE', 'KICK', 'BAN', 'SOFTBAN' ];
abstract class FilterSetting extends Setting
abstract class FilterSetting extends Setting
{
protected async _action (
invoker: InvokerWrapper, method: SettingMethod, actions: SettingAction[],
@ -138,9 +138,10 @@ abstract class FilterSetting extends Setting
if (settings!.modpoints?.enabled)
{
// Points
const content = await this._prompt(invoker, { index: 'SETTING_FILTER_ACTION_ADD_POINTS' });
if (typeof content !== 'string')
return content;
const promptResponse = await this._prompt(invoker, { index: 'SETTING_FILTER_ACTION_ADD_POINTS' });
if (promptResponse.error)
return promptResponse;
const content = promptResponse.content!;
this.client.logger.debug(`Points: ${content}`);
if (![ 'no', 'n' ].includes(content))
@ -372,10 +373,11 @@ abstract class FilterSetting extends Setting
content: invoker.format('SETTING_FILTER_ACTION_EDIT_POINTS_DISABLED')
};
const content = await this._prompt(invoker, { index: 'SETTING_FILTER_ACTION_EDIT_POINTS', time: 60 });
if (typeof content !== 'string')
return content;
const promptResponse = await this._prompt(invoker, { index: 'SETTING_FILTER_ACTION_EDIT_POINTS', time: 60 });
if (promptResponse.error)
return promptResponse;
const content = promptResponse.content!;
const reg = /(\d{1,3})\s?(points?|pts?|p)?/iu;
if (!reg.test(content))
return {
@ -401,15 +403,16 @@ abstract class FilterSetting extends Setting
async _editType (invoker: InvokerWrapper, actions: SettingAction[], action: SettingAction)
: Promise<SettingAction | FilterSettingHelperResponse>
{
const content = await this._prompt(invoker, {
const promptResponse = await this._prompt(invoker, {
index: 'SETTING_FILTER_ACTION_EDIT_TYPE',
params: { valid: validInfractions.join('`, `') },
time: 120
});
if (typeof content !== 'string')
return content;
if (promptResponse.error)
return promptResponse;
const content = promptResponse.content!;
if (!validInfractions.includes(content.toUpperCase()))
return {
error: true,

View File

@ -19,6 +19,8 @@ import {
} from '../../@types/Client.js';
import CommandOption from './CommandOption.js';
import Module from './Module.js';
import { GuildSettingTypes as GuildSettingType } from '../../@types/Guild.js';
import { SettingModifyResult, SettingResult } from '../../@types/Commands/Settings.js';
// const EMOJIS = {
// 'GUILD_TEXT': {
@ -43,11 +45,21 @@ import Module from './Module.js';
// }
// };
type HelperResult = {
modified: string[]
}
// declare interface Setting extends Component {
// add: () => HelperResult;
// remove: () => HelperResult;
// set: () => HelperResult;
// }
/**
* @class Setting
* @extends {Component}
*/
abstract class Setting extends Component
abstract class Setting extends Component
{
#name: string;
#resolve: SettingTypeResolve;
@ -55,7 +67,7 @@ abstract class Setting extends Component
#description: string;
#display: string;
#emoji: SettingEmojiOption;
#default: { [key: string]: any; };
#default: { [key: string]: GuildSettingType; };
#definitions: SettingApiDefinitions;
#commandOptions: CommandOptionParams[] | CommandOption[];
#commandType: CommandOptionType;
@ -64,7 +76,9 @@ abstract class Setting extends Component
#memberPermissions: PermissionsString[];
#premium: number;
// onCollect: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: (() => HelperResult) | any;
/**
* Creates an instance of Setting.
@ -97,7 +111,7 @@ abstract class Setting extends Component
this.#display = options.display || options.name;
this.#emoji = options.emoji || {};
this.#default = { [this.#name]: options.default || {} };
this.#default = { [this.#name]: options.default };
this.#definitions = options.definitions || {}; // Used for the API for field definitions
this.#commandOptions = options.commandOptions || [];
@ -181,7 +195,7 @@ abstract class Setting extends Component
* @param {object} hook
* @memberof Setting
*/
async execute (invoker: InvokerWrapper, params: CommandParams) // wrapper: InteractionWrapper, selectMenu, hook
async execute (_invoker: InvokerWrapper, _params: CommandParams, _setting: GuildSettingType): Promise<SettingResult | null> // wrapper: InteractionWrapper, selectMenu, hook
{
throw new Error('Setting missing implementation');
// hook.collector = await this.collector(wrapper, selectMenu);
@ -189,9 +203,8 @@ abstract class Setting extends Component
// , verbose = false -- unsure if this is a necessary argument anymore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
usageEmbed (guild: GuildWrapper, _verbose = false, subcommand: CommandOption | null = null)
usageEmbed (guild: GuildWrapper, _verbose: boolean | null = false, subcommand: CommandOption | null = null)
{
const fields = [];
if (this.#commandOptions.some(opt => !(opt instanceof CommandOption)))
throw new Error('Command options not initialised');
@ -227,7 +240,7 @@ abstract class Setting extends Component
return new EmbedBuilder({
author: {
name: `${this.#display} [module:${(this.module as Module).name}]`
name: `${this.#display} [module:${this.module.name}]`
},
description: guild.format(`SETTING_${this.#name.toUpperCase()}_HELP`),
fields
@ -246,7 +259,7 @@ abstract class Setting extends Component
* @return {Array<void>}
* @memberof Setting
*/
fields (_guild: GuildWrapper): Promise<APIEmbedField[]>
fields (_guild: GuildWrapper): Promise<APIEmbedField[]> | APIEmbedField[]
{
return Promise.resolve([]);
}
@ -286,9 +299,8 @@ abstract class Setting extends Component
// Helper function for prompting for user response by editing the interaction response
async _prompt (invoker: InvokerWrapper, { message = '', params = {}, embed, index, time = 120 }:
{ message?: string, params?: FormatParams, embed?: APIEmbed | null, index?: string, time?: number })
: Promise<string | FilterSettingHelperResponse>
: Promise<FilterSettingHelperResponse>
{
if (!message.length && !index && !embed)
throw new Error('Must declare either message, index or embeds');
const response = await invoker.promptMessage(
@ -310,23 +322,22 @@ abstract class Setting extends Component
else if (!content.length)
return { error: true, index: 'SETTING_NOCONTENT' };
return content;
return { content };
}
// Manipulator functions
add<T extends {toLowerCase: () => T}> (list: T[] = [], params: T[] = [], caseSensitive = false)
add<T extends {toLowerCase: () => T}> (list: T[] = [], params: T[] = [], caseSensitive = false): SettingModifyResult<T>
{
const modified = [],
skipped = [];
for (const param of params)
for (const param of params)
{
if (list.includes(param))
if (list.includes(param))
{
skipped.push(param);
}
else
else
{
list.push(caseSensitive ? param.toLowerCase() : param);
modified.push(param);
@ -336,12 +347,12 @@ abstract class Setting extends Component
return { list, modified, skipped };
}
remove<T extends {toLowerCase: () => T}> (list: T[] = [], params: T[] = [], caseSensitive = false)
remove<T extends {toLowerCase: () => T}> (list: T[] = [], params: T[] = [], caseSensitive = false): SettingModifyResult<T>
{
const modified = [],
skipped = [];
for (const param of params)
for (const param of params)
{
if (list.includes(param))
{
@ -355,7 +366,7 @@ abstract class Setting extends Component
return { list, modified, skipped };
}
set<T> (list: T[], params: T[] = [])
set<T> (list: T[], params: T[] = []): SettingModifyResult<T>
{
const modified = [ ...new Set(params) ];
list.splice(0, list.length);
@ -363,7 +374,7 @@ abstract class Setting extends Component
return { list, modified };
}
reset (list: unknown[])
reset (list: unknown[]): SettingModifyResult<unknown>
{
const modified = list.splice(0, list.length);
return { list, modified };
@ -371,7 +382,6 @@ abstract class Setting extends Component
async list (stuff: (object | string)[], invoker: InvokerWrapper, type = 'text')
{
const items = stuff.map((i) => i.toString());
const strings = [];
let string = '';
@ -390,41 +400,37 @@ abstract class Setting extends Component
channels: resolver.resolveChannel.bind(resolver)
};
for (let item of items)
for (let item of items)
{
if (resolvers[type])
if (resolvers[type])
{
const resolved = await resolvers[type](item, true, invoker.guild);
if (resolved)
item = resolved.toString();
}
if (string.length + item.length > 2000 && string.length)
if (string.length + item.length > 2000 && string.length)
{
strings.push(string);
string = '';
}
string += `${item}, `;
}
if (string.length)
if (string.length)
strings.push(string.substring(0, string.length -2));
// await invoker.reply({ content: `Ok.`, emoji: 'success' });
for (const str of strings)
for (const str of strings)
{
if (invoker.replied)
if (invoker.replied)
await invoker.channel?.send(str);
else
else
await invoker.reply(str);
}
return null;
}
// Functions for message component based settings

View File

@ -287,7 +287,7 @@ abstract class Command extends Component
return new EmbedBuilder({
author: {
name: `${this.name} [module:${(this.module as Module)?.name}]`
name: `${this.name} [module:${this.module?.name}]`
},
description: format(`COMMAND_${this.name.toUpperCase()}_HELP`),
fields

View File

@ -9,6 +9,7 @@ import Module from '../Module.js';
import { SettingsCommandOptions } from '../../../@types/Commands/Settings.js';
import { CommandOptionType, CommandParams } from '../../../@types/Client.js';
import InvokerWrapper from '../../components/wrappers/InvokerWrapper.js';
import { APIEmbed } from 'discord.js';
class SettingsCommand extends SlashCommand
{
@ -34,7 +35,7 @@ class SettingsCommand extends SlashCommand
build ()
{
const settings = this.client.registry
.filter<Setting>((c) => c.type === 'setting' && (c.module as Module).name === this.name);
.filter<Setting>((c) => c.type === 'setting' && c.module.name === this.name);
// const allSettings = this.client.registry.components.filter((c) => c._type ==='setting');
// Organise modules into subcommand groups
@ -119,27 +120,28 @@ class SettingsCommand extends SlashCommand
// Pass setting values copy so the changes don't persist unless successful and actually saved
const _setting = { ...settings[setting.name] };
const result = await setting.execute(invoker, opts, _setting);
if (result)
if (result)
{
const obj = { components: [], params: {}, ...result };
obj.params.setting = setting.name;
if (!result.error)
if (result.error)
{
await invoker.reply({ ...obj, emoji: 'failure', edit: invoker.replied });
}
else
{
settings[setting.name] = _setting;
await guild.updateSettings(settings);
const emoji = result.emoji ? result.emoji : 'success';
await invoker.reply({ ...obj, emoji, _edit: invoker.replied });
}
else
{
await invoker.reply({ ...obj, emoji: 'failure', _edit: invoker.replied });
await invoker.reply({ ...obj, emoji, edit: invoker.replied });
}
}
}
catch (err)
catch (err)
{
this.client.logger.error(`Error during setting execution:\n${err.stack || err}`);
const error = err as Error;
this.client.logger.error(`Error during setting execution:\n${error.stack || err}`);
await invoker.editReply({
index: 'SETTINGS_ERROR',
params: { resolveable: setting.resolveable },
@ -147,17 +149,15 @@ class SettingsCommand extends SlashCommand
});
}
if (!invoker.replied)
if (!invoker.replied)
await invoker.reply({
index: 'SETTINGS_NO_REPLY',
params: { resolveable: setting.resolveable }
});
}
async _listSettings (invoker)
async _listSettings (invoker: InvokerWrapper<true>)
{
const { settings } = this.client.registry;
const modules = settings.filter((setting) => setting.module.name === this.name)
.reduce((acc, setting) =>
@ -167,31 +167,29 @@ class SettingsCommand extends SlashCommand
acc[name] = [];
acc[name].push(setting.display);
return acc;
}, {});
}, {} as {[key: string]: string[]});
const { guild } = invoker;
const embed = {
const embed: APIEmbed = {
title: 'Settings',
description: guild.format('SETTINGS_LIST'),
fields: []
};
const entries = Object.entries(modules);
for (const [ module, values ] of entries)
embed.fields.push({
embed.fields!.push({
name: Util.capitalise(module),
value: `\`${values.join('`\n`')}\``,
inline: true
});
return invoker.reply({ embeds: [ embed ] });
}
async _showSetting (invoker, setting)
async _showSetting (invoker: InvokerWrapper<true>, setting: Setting)
{
const { guild, subcommand } = invoker;
const embed = setting.usageEmbed(guild, null, this.subcommand(subcommand.name));
const embed = setting.usageEmbed(guild, null, this.subcommand(subcommand!.name));
const dataFields = await setting.fields(guild);
// eslint-disable-next-line no-return-assign
dataFields.forEach((field, index) =>

View File

@ -80,7 +80,7 @@ const InfractionResolves: {[key in InfractionType]: string[]} = {
DELETE: ['delete', 'del']
};
const Infractions = Object.keys(InfractionResolves);
const Infractions = Object.keys(InfractionResolves) as InfractionType[];
const InfractionTargetTypes = [
'USER',