enable/disable commands

This commit is contained in:
nolan 2020-07-27 20:00:24 -07:00
parent 435ec0dd1a
commit 6c1338558f
19 changed files with 426 additions and 51 deletions

View File

@ -23,7 +23,7 @@ in channel{plural} {channels}
[C_REVOKE_DESCRIPTION]
Revoke permissions granted to roles or users.
NOTE: If a user left the guild, you must use the ID to revoke the permissions.
NOTE: If a user left the server, you must use the ID to revoke the permissions.
[C_REVOKE_MISSINGRESOLVEABLES]
Unable to find any roles or users, view `{prefix}cmd revoke` for more information.
@ -56,7 +56,7 @@ switch({user}) {
}
[C_PERMISSIONS_SHOWDESCRIPTION]
An overview of all {resolve}'s permissions in the guild. If you would like to see an in-depth view of the role or user's permissions, use `{prefix}perms [user|role]`.
An overview of all {resolve}'s permissions in the server. If you would like to see an in-depth view of the role or user's permissions, use `{prefix}perms [user|role]`.
[C_PERMISSIONS_MAXFIELDS]
:warning: **You have met the max amount of fields and you will need to use `{prefix}perms --json` to view them.** :warning:
@ -81,3 +81,52 @@ Channel-specific permissions are listed below.
[C_PERMISSIONS_PERMISSIONSNOTFOUND]
Found {type} **{resolveable}** but {they}had no permissions.
//Disable Command
[C_DISABLE_DESCRIPTION]
Disable commands in your server to prevent usage.
[C_DISABLE_LIST]
The command{plural} currently disabled in your server are: {commands}
[C_DISABLE_LISTFAIL]
There are no disabled commands in your server.
[C_DISABLE_MISSINGCOMMANDS]
You must provide commands to disable.
[C_DISABLE_SUCCESS]
Successfully **disabled** command{plural} {commands} in your server.
[C_DISABLE_FAIL]
Failed to disable command{plural} {commands}. You are unable to disable the **command:enable** and **command:disable** commands.
[C_DISABLE_WARNING]
You disabled a command inside of the **Administration** module. It is dangerous to do so, as it may disable configurability of your server. Note you are unable to disable the `command:enable` and `command:disable` commands.
[C_DISABLE_SUCCESSGLOBAL]
Successfully **disabled** command{plural} {commands} globally. **`[{amount}/{total}]`**
These changes will be reverted if a shard restarts; edit the file{plural} for sustainability.
[C_DISABLE_FAILGLOBAL]
Failed to globally disable command{plural} {commands}. **`[0/{total}]`**
//Enable Command
[C_ENABLE_DESCRIPTION]
Enable commands in your server.
[C_ENABLE_MISSINGCOMMANDS]
You must provide commands to enable.
[C_ENABLE_SUCCESS]
Successfully **enabled** command{plural} {commands} in your server.
[C_ENABLE_FAIL]
Failed to enable command{plural} {commands}, they likely were not disabled.
[C_ENABLE_SUCCESSGLOBAL]
Successfully **enabled** command{plural} {commands} globally. **`[{amount}/{total}]`**
These changes will be reverted if a shard restarts; edit the file{plural} for sustainability.
[C_ENABLE_FAILGLOBAL]
Failed to globally enable command{plural} {commands}. **`[0/{total}]`**

View File

@ -151,7 +151,17 @@ The command **{command}** requires the bot to have permissions to use.
*Missing: {missing}.*
[I_DISABLED_ERROR]
The command **{command}** is currently {globally}disabled.
The command **{command}** is disabled {modifier}.
[I_DISABLED_ERRORMODIFIER]
switch("{globally}") {
case true:
'globally';
break;
case false:
'in this server';
break;
}
[I_GUILDONLY_ERROR]
The command **{command}** can only be run in servers.

View File

@ -85,6 +85,7 @@ class DiscordClient extends Client {
}
this._defaultConfig = def;
return def;
}
}

View File

@ -15,6 +15,18 @@ class Resolver {
* @returns
* @memberof Resolver
*/
resolveComponent(arg, strict = true, type) {
const string = arg.toLowerCase();
const components = this.client.registry.components
.filter((c) => c.type === type)
.filter(strict ? filterExact(string) : filterInexact(string)); //eslint-disable-line no-use-before-define
return components.first();
}
components(str = '', type, exact = true) {
const string = str.toLowerCase();

View File

@ -0,0 +1,131 @@
const { Command } = require('../../../../interfaces/');
const { Emojis } = require('../../../../../util/');
class DisableCommand extends Command {
constructor(client) {
super(client, {
name: 'disable',
module: 'administration',
usage: "<command..|'list'>",
examples: [
"snipe",
"command:snipe command:note"
],
memberPermissions: ['ADMINISTRATOR', 'MANAGE_GUILD'],
showUsage: true,
guildOnly: true,
arguments: [
{
name: 'global',
type: 'BOOLEAN',
types: [
'FLAG'
],
default: true,
archivable: false
}
]
});
}
async execute(message, { params, args }) {
if(params.join(' ').toLowerCase() === 'list') {
const disabled = message.guild._settings.disabledCommands;
if(disabled.length > 0) {
message.respond(message.format('C_DISABLE_LIST', {
plural: disabled.length === 1 ? '' : 's',
commands: disabled.map((d) => `\`${d}\``).join(', ')
}), { emoji: 'success' });
} else {
message.respond(message.format('C_DISABLE_LISTFAIL'), { emoji: 'success' });
}
return undefined;
}
const { parsed } = await this.client.resolver.infinite(params, [
this.client.resolver.resolveComponent.bind(this.client.resolver)
], true, 'command');
if(parsed.length === 0) {
return message.respond(message.format('C_DISABLE_MISSINGCOMMANDS'), {
emoji: 'failure'
});
}
if(args.global) {
if(!message.author.developer) return message.respond(message.format('C_DISABLE_PERMISSIONSREQUIRED'), {
emoji: 'failure'
});
const results = await this.client.shard.broadcastEval(`this.registry.components
.filter((c) => [${parsed.map((p) => `"${p.resolveable}"`).join(', ')}].includes(c.resolveable))
.map((c) => c.disable())`);
const succeeded = results.filter((r) => r.some((r) => !r.error));
if(succeeded.length > 0) {
return message.respond(message.format('C_DISABLE_SUCCESSGLOBAL', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', '),
amount: succeeded.length,
total: results.length
}), { emoji: 'success' });
}
return message.respond(message.format('C_DISABLE_FAILGLOBAL', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', '),
total: results.length
}), { emoji: 'failure' });
}
const { disabledCommands } = message.guild._settings;
let warning = false,
commands = []; //eslint-disable-line prefer-const
for(const command of parsed) {
if(command.module.id === 'administration') {
if(['command:enable', 'command:disable'].includes(command.resolveable)) continue;
warning = true;
}
commands.push(command.resolveable);
if(disabledCommands.includes(command.resolveable)) continue;
else disabledCommands.push(command.resolveable);
}
await message.guild._updateSettings({
disabledCommands
});
if(commands.length === 0) {
message.respond(message.format('C_DISABLE_FAIL', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', ')
}), { emoji: 'failure' });
} else {
message.respond(message.format('C_DISABLE_SUCCESS', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', ')
}), {
emoji: 'success',
embed: warning ? {
author: {
name: `${Emojis.warning} Warning`
},
description: message.format('C_DISABLE_WARNING'),
color: 0xffe15c
} : null
});
}
return undefined;
}
}
module.exports = DisableCommand;

View File

@ -0,0 +1,107 @@
const { Command } = require('../../../../interfaces/');
class DisableCommand extends Command {
constructor(client) {
super(client, {
name: 'enable',
module: 'administration',
usage: "<command..>",
examples: [
"strike",
"command:strike command:note"
],
memberPermissions: ['ADMINISTRATOR', 'MANAGE_GUILD'],
showUsage: true,
guildOnly: true,
arguments: [
{
name: 'global',
type: 'BOOLEAN',
types: [
'FLAG'
],
default: true,
archivable: false
}
]
});
}
async execute(message, { params, args }) {
const { parsed } = await this.client.resolver.infinite(params, [
this.client.resolver.resolveComponent.bind(this.client.resolver)
], true, 'command');
if(parsed.length === 0) {
return message.respond(message.format('C_ENABLE_MISSINGCOMMANDS'), {
emoji: 'failure'
});
}
if(args.global) {
if(!message.author.developer) return message.respond(message.format('C_ENABLE_PERMISSIONSREQUIRED'), {
emoji: 'failure'
});
const results = await this.client.shard.broadcastEval(`this.registry.components
.filter((c) => [${parsed.map((p) => `"${p.resolveable}"`).join(', ')}].includes(c.resolveable))
.map((c) => c.enable())`);
const succeeded = results.filter((r) => r.some((r) => !r.error));
if(succeeded.length > 0) {
return message.respond(message.format('C_ENABLE_SUCCESSGLOBAL', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', '),
amount: succeeded.length,
total: results.length
}), { emoji: 'success' });
}
return message.respond(message.format('C_ENABLE_FAILGLOBAL', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', '),
total: results.length
}), { emoji: 'failure' });
}
const { disabledCommands } = message.guild._settings;
const removed = [];
for(const command of parsed) {
if(disabledCommands.includes(command.resolveable)) {
const index = disabledCommands.indexOf(command.resolveable);
if(index > -1) {
removed.push(command.resolveable);
disabledCommands.splice(index, 1);
}
}
}
await message.guild._updateSettings({
disabledCommands
});
if(removed.length === 0) {
message.respond(message.format('C_ENABLE_FAIL', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', ')
}), { emoji: 'failure' });
} else {
message.respond(message.format('C_ENABLE_SUCCESS', {
plural: parsed.length === 1 ? '' : 's',
commands: parsed.map((p) => `\`${p.resolveable}\``).join(', ')
}), { emoji: 'success' });
}
return undefined;
}
}
module.exports = DisableCommand;

View File

@ -9,7 +9,7 @@ class SettingsCommand extends Command {
super(client, {
name: 'settings',
module: 'utility',
module: 'administration',
aliases: [
'setting',
'set'

View File

@ -33,7 +33,8 @@ class Evaluate extends Command {
}
],
showUsage: true,
keepQuotes: true
keepQuotes: true,
guarded: true
});
}

View File

@ -16,7 +16,11 @@ class Disabled extends Inhibitor {
execute(message, command) {
if(command.disabled) return super._fail({ globally: '' });
if(command.disabled) return super._fail({ modifier: message.format('I_DISABLED_ERRORMODIFIER', { globally: true }, true) });
if(message.guild._settings.disabledCommands.includes(command.resolveable)) {
return super._fail({ modifier: message.format('I_DISABLED_ERRORMODIFIER', { globally: false }, true) });
}
return super._succeed();
}

View File

@ -77,8 +77,6 @@ class MessageCache extends Observer {
async _grabMessageData(message) {
const beforeTime = Date.now();
const metadata = {
guild: message.guild.id,
message: message.id,
@ -122,18 +120,18 @@ class MessageCache extends Observer {
};
const fsize = data.size/CONSTANTS.IMAGES.MB_DIVIDER; // File size in MB
if(fsize > CONSTANTS.IMAGES.UPLOAD_LIMIT[message.guild.premium]) {
if(fsize > CONSTANTS.IMAGES.UPLOAD_LIMIT[message.guild.premiumTier]) {
metadata.attachments.push(data);
continue; //Cannot upload images larger than the guild's upload limit (Users w/ nitro can upload more than that)
continue; //Cannot upload images larger than the guild's upload limit (Users w/ nitro can upload more than that, but the bot will be unable to post them.)
}
const buffer = await Util.downloadAsBuffer(attachment.proxyURL || attachment.url).catch((err) => {
this.client.logger.error(`Failed to save buffer with image data: ${data}\n${err.stack || err}`);
this.client.logger.error(`Failed to download buffer for "${chalk.bold(data.name)}".\n${err.stack || err}`);
return null;
});
if(buffer && fsize < 15) { //Mongodb will not save images larger than 16mb, but I'm checking for 15 just incase.
try {
if(buffer) {
if(fsize < 15) {
const result = await this.client.transactionHandler.send({
provider: 'mongodb',
request: {
@ -146,9 +144,9 @@ class MessageCache extends Observer {
}
});
data.index = result?.insertedId;
// this.client.logger.debug(`Saved file ${data.name} (${fsize.toFixed(2)}mb), took ${Date.now()-beforeTime}ms.`);
} catch (err) {
this.client.logger.error('Something went wrong with storing image to database: \n' + err.stack || err);
} else {
//Upload using GridFS, not a priority right now.
this.client.logger.error(`Temporary logging; attachment "${chalk.bold(data.name)}" exceeds 15mb.`);
}
}
@ -157,9 +155,7 @@ class MessageCache extends Observer {
}
const afterTime = Date.now();
this.client.logger.debug(`${chalk.bold('[IMAGE]')} User ${message.author.tag} in guild ${message.guild.name} (#${message.channel.name}) uploaded ${message.attachments.size} attachment${message.attachments.size === 1 ? '' : 's'} (${size.toFixed(2)}mb); took ${afterTime-beforeTime}ms to save ${message.attachments.size} attachment${message.attachments.size === 1 ? '' : 's'}.`);
this.client.logger.debug(`${chalk.bold('[IMAGE]')} User ${message.author.tag} in guild ${message.guild.name} (#${message.channel.name}) uploaded ${message.attachments.size} attachment${message.attachments.size === 1 ? '' : 's'} (${size.toFixed(2)}mb).`);
await this.client.transactionHandler.send({
provider: 'mongodb',
request: {
@ -203,8 +199,6 @@ class MessageCache extends Observer {
}
});
this.client.logger.log(`${chalk.bold('[IMAGE]')} Trashed ${deleteAttachments.length} items from the attachment database.`);
const msgIds = messages.map((m) => m._id);
const deleteMessages = await this.client.transactionHandler.send({
provider: 'mongodb',
@ -216,8 +210,11 @@ class MessageCache extends Observer {
}
}
});
this.client.emit(`${chalk.bold('[IMAGE]')} Trashed ${deleteMessages.length} items from the attachment database.`);
const messageCount = deleteMessages.deletedCount;
const attachmentCount = deleteAttachments.deletedCount;
this.client.logger.info(`${chalk.bold('[IMAGE]')} Trashed ${messageCount} messages${messageCount === 1 ? '' : 's'} and ${attachmentCount} attachment${attachmentCount === 1 ? '' : 's'}.`);
}
}

View File

@ -0,0 +1,62 @@
const { Setting } = require('../../../../interfaces/');
class DisabledCommandsSetting extends Setting {
constructor(client) {
super(client, {
name: 'disabledCommands',
module: 'administration',
aliases: ['disabled', 'disableCommands', 'disableCommand', 'disableCmd', 'disableCmds', 'disabledCmds', 'disable'],
usage: "<+|-|list> [command..]",
resolve: 'GUILD',
default: {
disabledCommands: [
'command:strike'
]
},
examples: [
'list',
'add command:strike',
'remove command:strike'
]
});
}
async handle(message, params) {
const parameters = params.join(' ').toLowerCase();
let type = null;
for(const [ key, value ] of Object.entries(Constants.Types)) {
if(value.includes(parameters)) type = key;
}
if(!type) return {
msg: message.format('S_PERMISSIONTYPE_INVALIDTYPE'),
error: true
};
await message.guild._updateSettings({ [this.index]: type });
const description = message.format('S_PERMISSIONTYPE_DESCRIPTIONS', { type }, true);
return {
msg: `${message.format('S_PERMISSIONTYPE_SUCCESS', { type, description })}`,
error: false
};
}
fields(guild) {
return [
{
name: '》Disabled Commands',
value: `\`${guild._settings.permissionType}\``
}
];
}
}
module.exports = DisabledCommandsSetting;

View File

@ -14,7 +14,7 @@ class PermissionTypeSetting extends Setting {
super(client, {
name: 'permissionType',
module: 'utility',
module: 'administration',
aliases: ['permissionTypes', 'permTypes', 'permType'],
usage: "[permission-type]",
resolve: 'GUILD',

View File

@ -111,7 +111,7 @@ class ProtectionSetting extends Setting {
inline: true
}, {
name: '》Roles',
value: roles?.map(r => r.name).join(', ') || 'N/A',
value: roles?.map((r) => r.name).join(', ') || 'N/A',
inline: false
}
];

View File

@ -53,7 +53,7 @@ class IgnoreSetting extends Setting {
setting.enabled = true;
index = 'S_IGNORE_ADD';
const changed = channels.filter(c => response.changed.includes(c.id)).map(c => c.name).join('`, `');
const changed = channels.filter((c) => response.changed.includes(c.id)).map((c) => c.name).join('`, `');
langParams.changes = changed.length ? changed : 'N/A';
} else if (method === 'remove') {
@ -63,7 +63,7 @@ class IgnoreSetting extends Setting {
setting.enabled = setting.channels.length && true;
index = 'S_IGNORE_REMOVE';
const changed = channels.filter(c => response.changed.includes(c.id)).map(c => c.name).join('`, `');
const changed = channels.filter((c) => response.changed.includes(c.id)).map((c) => c.name).join('`, `');
langParams.changes = changed.length ? changed : 'N/A';
} else if (method === 'list') {
@ -71,19 +71,19 @@ class IgnoreSetting extends Setting {
const roles = await resolver.resolveRoles(setting.roleBypass, false, guild);
index = 'S_IGNORE_LIST';
const ch = response.resolved ? response.resolved?.map(c => c.name).join('`, `') : '';
const r = roles.length ? roles.map(r => r.name).join('`, `') : '';
const ch = response.resolved ? response.resolved?.map((c) => c.name).join('`, `') : '';
const r = roles.length ? roles.map((r) => r.name).join('`, `') : '';
langParams.channels = ch.length ? ch : 'N/A';
langParams.roles = r.length ? r : 'N/A';
} else if (method === 'set') {
const channels = response.resolved;
setting.channels = channels.map(c => c.id);
setting.channels = channels.map((c) => c.id);
setting.enabled = channels.length && true;
index = 'S_IGNORE_SET';
const ch = channels.map(c => c.name).join('`, `');
const ch = channels.map((c) => c.name).join('`, `');
langParams.changes = ch.length ? ch : 'N/A';
} else if (method === 'bypass') {
@ -96,7 +96,7 @@ class IgnoreSetting extends Setting {
setting.roleBypass = response.result;
index = 'S_IGNORE_BYPASS_ADD';
const changed = roles.filter(r => response.changed.includes(r.id)).map(r => r.name).join('`, `');
const changed = roles.filter((r) => response.changed.includes(r.id)).map((r) => r.name).join('`, `');
langParams.changes = changed.length ? changed : 'N/A';
} else if (response.method === 'remove') {
@ -105,10 +105,10 @@ class IgnoreSetting extends Setting {
setting.roleBypass = response.result;
index = 'S_IGNORE_BYPASS_REMOVE';
const changed = roles.filter(r => response.changed.includes(r.id)).map(r => r.name).join('`, `');
const changed = roles.filter((r) => response.changed.includes(r.id)).map((r) => r.name).join('`, `');
langParams.changes = changed.length ? changed : 'N/A';
} else return { error: true, msg: message.format('ERR_INVALID_SUBMETHOD', { submethod: params[0] }) }
} else return { error: true, msg: message.format('ERR_INVALID_SUBMETHOD', { submethod: params[0] }) };
} else return { error: true, msg: message.format('ERR_INVALID_METHOD', { method }) };
@ -128,10 +128,10 @@ class IgnoreSetting extends Setting {
value: setting?.enabled || 'false'
}, {
name: '》Channels',
value: channels?.map(c => c.name).join(', ') || 'N/A'
value: channels?.map((c) => c.name).join(', ') || 'N/A'
}, {
name: '》Roles',
value: roles?.map(r => r.name).join(', ') || 'N/A'
value: roles?.map((r) => r.name).join(', ') || 'N/A'
}
];

View File

@ -22,8 +22,9 @@ const Guild = Structures.extend('Guild', (Guild) => {
async settings() {
if(!this._settings) this._settings = this.client.transactionHandler.send({ provider: 'mongodb', request: { collection: 'guilds', type: 'findOne', query: { guildId: this.id } } });
if(this._settings instanceof Promise) this._settings = await this._settings || null;
if(!this._settings) this._settings = { guildId: this.id, ...this.client.defaultConfig };
else this._settings = { ...this.client.defaultConfig, ...this._settings };
if(!this._settings) this._settings = { guildId: this.id, ...this.defaultConfig };
// else this._settings = Object.assign({}, { ...this.defaultConfig, ...this._settings }); //eslint-disable-line prefer-object-spread
else this._settings = { ...this.defaultConfig, ...this._settings }; //eslint-disable-line prefer-object-spread
return this._settings;
}
@ -54,7 +55,7 @@ const Guild = Structures.extend('Guild', (Guild) => {
}
}
});
this._settings = { ...{}, ...this.client.defaultConfig }; //Create a new object so settings that change the _settings value won't replicate it towards the defaultConfig.
this._settings = { ...{}, ...this.defaultConfig }; //Create a new object so settings that change the _settings value won't replicate it towards the defaultConfig.
this._storageLog(`Database Delete (guild:${this.id}).`);
} catch(error) {
this._storageError(error);
@ -81,8 +82,8 @@ const Guild = Structures.extend('Guild', (Guild) => {
}
});
this._settings = {
...this.client.defaultConfig,
caseId: this._settings.caseId
...this.defaultConfig,
...{ caseId: this._settings.caseId }
};
this._storageLog(`Database Reset (guild:${this.id}).`);
} catch(error) {
@ -117,8 +118,8 @@ const Guild = Structures.extend('Guild', (Guild) => {
async _removeSettings(value) { //Remove property
if(!this._settings) await this.settings();
if(this.client.defaultConfig[value]) {
await this._updateSettings({ [value]: this.client.defaultConfig[value] });
if(this.defaultConfig[value]) {
await this._updateSettings({ [value]: this.defaultConfig[value] });
return undefined;
}
try {
@ -232,6 +233,10 @@ const Guild = Structures.extend('Guild', (Guild) => {
/* Lazy Developer Getters */
get defaultConfig() {
return JSON.parse(JSON.stringify(this.client.defaultConfig));
}
get prefix() {
return this._settings.prefix
|| this.client._options.bot.prefix;

View File

@ -94,7 +94,7 @@ const Message = Structures.extend('Message', (Message) => {
}
async respond(str, opts = { attachments: [] }) {
async respond(str, opts = { attachments: [], embed: null }) {
if(typeof str === 'string') {
if(opts.emoji) {
@ -106,7 +106,7 @@ const Message = Structures.extend('Message', (Message) => {
}
//console.log(str)
this._pending = await this.channel.send(str, { files: opts.attachments });
this._pending = await this.channel.send(str, { files: opts.attachments, embed: opts.embed });
return this._pending;
}

View File

@ -34,6 +34,7 @@ class Argument {
this.type = options.type && Constants.Types.includes(options.type) ? options.type : 'BOOLEAN'; //What type the argument is ['STRING', 'INTEGER', 'FLOAT', 'BOOLEAN'].
this.types = options.types || ['FLAG', 'VERBAL']; //['FLAG'], ['VERBAL'], or ['FLAG', 'VERBAL']. Declares if argument can be used verbally-only, flag-only, or both.
this.archivable = options.archivable === undefined ? true : Boolean(options.archivable); //Displays argument in showUsage or not.
//NOTE: Instead of telling the person the argument is missing and is required, ask them and continue the command execution afterwards. More work to do.

View File

@ -33,11 +33,6 @@ class Command extends Component {
this.arguments = opts.arguments || [];
this.keepQuotes = Boolean(opts.keepQuotes);
//Moderation
this.infractionType = opts.infractionType;
this.customArguments = opts.customArguments || []; //Arguments that will be attempted to be parsed in the command.
this.forcedArguments = opts.forcedArguments || {}; //Arguments that will be forcefully added (with defaults) if an "alias" exists.
this.clientPermissions = opts.clientPermissions || [];
this.memberPermissions = opts.memberPermissions || [];

View File

@ -2,7 +2,7 @@
"success": "<:success:723595130808041512>",
"failure": "<:failure:723595130912637018>",
"loading": "<a:loading:723589386356129832>",
"warning": "⚠",
"warning": "⚠",
"enabled": "<:enabled:723589993733423215>",
"disabled": "<:disabled:723589993506799618>",
"text-channel": "<:textchannel:716414423094525952>",