This commit is contained in:
Neko Life 2021-06-28 18:04:54 +09:00
parent 7eb0f91e20
commit 013860d988
13 changed files with 682 additions and 3 deletions

View file

@ -23,6 +23,7 @@ const { timestampAt } = require("./resources/debug");
const requireAll = require("require-all");
const lgr = requireAll({ dirname: join(__dirname, "resources/eventsLogger"), recursive: true });
client.functions = requireAll({ dirname: join(__dirname, "../../resources"), recursive: true });
client.registry
.registerGroups([
@ -34,7 +35,6 @@ client.registry
"profile",
"owner"
])
.registerDefaults()
.registerCommandsIn(join(__dirname, 'cmds'));
client.setProvider(

49
cmds/owner/disable.js Normal file
View file

@ -0,0 +1,49 @@
const { oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class DisableCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'disable',
aliases: ['disable-command', 'cmd-off', 'command-off'],
group: 'commands',
memberName: 'disable',
description: 'Disables a command or command group.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command or command group.
Only administrators may use this command.
`,
examples: ['disable util', 'disable Utility', 'disable prefix'],
guarded: true,
args: [
{
key: 'cmdOrGrp',
label: 'command/group',
prompt: 'Which command or group would you like to disable?',
type: 'group|command'
}
]
});
}
hasPermission(msg) {
if (!msg.guild) return this.client.isOwner(msg.author);
return msg.member.permissions.has('ADMINISTRATOR') || this.client.isOwner(msg.author);
}
run(msg, args) {
if (!args.cmdOrGrp.isEnabledIn(msg.guild, true)) {
return msg.reply(
`The \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'} is already disabled.`
);
}
if (args.cmdOrGrp.guarded) {
return msg.reply(
`You cannot disable the \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'}.`
);
}
args.cmdOrGrp.setEnabledIn(msg.guild, false);
return msg.reply(`Disabled the \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'}.`);
}
};

55
cmds/owner/enable.js Normal file
View file

@ -0,0 +1,55 @@
const { oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class EnableCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'enable',
aliases: ['enable-command', 'cmd-on', 'command-on'],
group: 'commands',
memberName: 'enable',
description: 'Enables a command or command group.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command or command group.
Only administrators may use this command.
`,
examples: ['enable util', 'enable Utility', 'enable prefix'],
guarded: true,
args: [
{
key: 'cmdOrGrp',
label: 'command/group',
prompt: 'Which command or group would you like to enable?',
type: 'group|command'
}
]
});
}
hasPermission(msg) {
if(!msg.guild) return this.client.isOwner(msg.author);
return msg.member.permissions.has('ADMINISTRATOR') || this.client.isOwner(msg.author);
}
run(msg, args) {
const group = args.cmdOrGrp.group;
if(args.cmdOrGrp.isEnabledIn(msg.guild, true)) {
return msg.reply(
`The \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'} is already enabled${
group && !group.isEnabledIn(msg.guild) ?
`, but the \`${group.name}\` group is disabled, so it still can't be used` :
''
}.`
);
}
args.cmdOrGrp.setEnabledIn(msg.guild, true);
return msg.reply(
`Enabled the \`${args.cmdOrGrp.name}\` ${group ? 'command' : 'group'}${
group && !group.isEnabledIn(msg.guild) ?
`, but the \`${group.name}\` group is disabled, so it still can't be used` :
''
}.`
);
}
};

116
cmds/owner/eval.js Normal file
View file

@ -0,0 +1,116 @@
const util = require('util');
const discord = require('discord.js');
const tags = require('common-tags');
const { escapeRegex } = require('../../node_modules/@iceprod/discord.js-commando/src/util');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
const nl = '!!NL!!';
const nlPattern = new RegExp(nl, 'g');
module.exports = class EvalCommand extends Command {
constructor(client) {
super(client, {
name: 'eval',
group: 'util',
memberName: 'eval',
description: 'Executes JavaScript code.',
details: 'Only the bot owner(s) may use this command.',
ownerOnly: true,
args: [
{
key: 'script',
prompt: 'What code would you like to evaluate?',
type: 'string'
}
]
});
this.lastResult = null;
Object.defineProperty(this, '_sensitivePattern', { value: null, configurable: true });
}
run(msg, args) {
// Make a bunch of helpers
/* eslint-disable no-unused-vars */
const message = msg;
const client = msg.client;
const lastResult = this.lastResult;
const doReply = val => {
if (val instanceof Error) {
msg.reply(`Callback error: \`${val}\``);
} else {
const result = this.makeResultMessages(val, process.hrtime(this.hrStart));
if (Array.isArray(result)) {
for (const item of result) msg.reply(item);
} else {
msg.reply(result);
}
}
};
/* eslint-enable no-unused-vars */
// Remove any surrounding code blocks before evaluation
if (args.script.startsWith('```') && args.script.endsWith('```')) {
args.script = args.script.replace(/(^.*?\s)|(\n.*$)/g, '');
}
// Run the code and measure its execution time
let hrDiff;
try {
const hrStart = process.hrtime();
this.lastResult = eval(args.script);
hrDiff = process.hrtime(hrStart);
} catch (err) {
return msg.reply(`Error while evaluating: \`${err}\``);
}
// Prepare for callback time and respond
this.hrStart = process.hrtime();
const result = this.makeResultMessages(this.lastResult, hrDiff, args.script);
if (Array.isArray(result)) {
return result.map(item => msg.reply(item));
} else {
return msg.reply(result);
}
}
makeResultMessages(result, hrDiff, input = null) {
const inspected = util.inspect(result, { depth: 0 })
.replace(nlPattern, '\n')
.replace(this.sensitivePattern, '--snip--');
const split = inspected.split('\n');
const last = inspected.length - 1;
const prependPart = inspected[0] !== '{' && inspected[0] !== '[' && inspected[0] !== "'" ? split[0] : inspected[0];
const appendPart = inspected[last] !== '}' && inspected[last] !== ']' && inspected[last] !== "'" ?
split[split.length - 1] :
inspected[last];
const prepend = `\`\`\`javascript\n${prependPart}\n`;
const append = `\n${appendPart}\n\`\`\``;
if (input) {
return discord.splitMessage(tags.stripIndents`
*Executed in ${hrDiff[0] > 0 ? `${hrDiff[0]}s ` : ''}${hrDiff[1] / 1000000}ms.*
\`\`\`javascript
${inspected}
\`\`\`
`, { maxLength: 2000, prepend, append });
} else {
return discord.splitMessage(tags.stripIndents`
*Callback executed after ${hrDiff[0] > 0 ? `${hrDiff[0]}s ` : ''}${hrDiff[1] / 1000000}ms.*
\`\`\`javascript
${inspected}
\`\`\`
`, { maxLength: 2000, prepend, append });
}
}
get sensitivePattern() {
if (!this._sensitivePattern) {
const client = this.client;
let pattern = '';
if (client.token) pattern += escapeRegex(client.token);
Object.defineProperty(this, '_sensitivePattern', { value: new RegExp(pattern, 'gi'), configurable: false });
}
return this._sensitivePattern;
}
};

30
cmds/owner/groups.js Normal file
View file

@ -0,0 +1,30 @@
const { stripIndents } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class ListGroupsCommand extends Command {
constructor(client) {
super(client, {
name: 'groups',
aliases: ['list-groups', 'show-groups'],
group: 'commands',
memberName: 'groups',
description: 'Lists all command groups.',
details: 'Only administrators may use this command.',
guarded: true
});
}
hasPermission(msg) {
if(!msg.guild) return this.client.isOwner(msg.author);
return msg.member.permissions.has('ADMINISTRATOR') || this.client.isOwner(msg.author);
}
run(msg) {
return msg.reply(stripIndents`
__**Groups**__
${this.client.registry.groups.map(grp =>
`**${grp.name}:** ${grp.isEnabledIn(msg.guild) ? 'Enabled' : 'Disabled'}`
).join('\n')}
`);
}
};

72
cmds/owner/load.js Normal file
View file

@ -0,0 +1,72 @@
const fs = require('fs');
const { oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class LoadCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'load',
aliases: ['load-command'],
group: 'commands',
memberName: 'load',
description: 'Loads a new command.',
details: oneLine`
The argument must be full name of the command in the format of \`group:memberName\`.
Only the bot owner(s) may use this command.
`,
examples: ['load some-command'],
ownerOnly: true,
guarded: true,
args: [
{
key: 'command',
prompt: 'Which command would you like to load?',
validate: val => new Promise(resolve => {
if(!val) return resolve(false);
const split = val.split(':');
if(split.length !== 2) return resolve(false);
if(this.client.registry.findCommands(val).length > 0) {
return resolve('That command is already registered.');
}
const cmdPath = this.client.registry.resolveCommandPath(split[0], split[1]);
fs.access(cmdPath, fs.constants.R_OK, err => err ? resolve(false) : resolve(true));
return null;
}),
parse: val => {
const split = val.split(':');
const cmdPath = this.client.registry.resolveCommandPath(split[0], split[1]);
delete require.cache[cmdPath];
return require(cmdPath);
}
}
]
});
}
async run(msg, args) {
this.client.registry.registerCommand(args.command);
const command = this.client.registry.commands.last();
if(this.client.shard) {
try {
await this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id))) {
const cmdPath = this.registry.resolveCommandPath('${command.groupID}', '${command.name}');
delete require.cache[cmdPath];
this.registry.registerCommand(require(cmdPath));
}
`);
} catch(err) {
this.client.emit('warn', `Error when broadcasting command load to other shards`);
this.client.emit('error', err);
await msg.reply(`Loaded \`${command.name}\` command, but failed to load on other shards.`);
return null;
}
}
await msg.reply(`Loaded \`${command.name}\` command${this.client.shard ? ' on all shards' : ''}.`);
return null;
}
};

68
cmds/owner/reload.js Normal file
View file

@ -0,0 +1,68 @@
const { oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class ReloadCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'reload',
aliases: ['reload-command'],
group: 'commands',
memberName: 'reload',
description: 'Reloads a command or command group.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command or command group.
Providing a command group will reload all of the commands in that group.
Only the bot owner(s) may use this command.
`,
examples: ['reload some-command'],
ownerOnly: true,
guarded: true,
args: [
{
key: 'cmdOrGrp',
label: 'command/group',
prompt: 'Which command or group would you like to reload?',
type: 'group|command'
}
]
});
}
async run(msg, args) {
const { cmdOrGrp } = args;
const isCmd = Boolean(cmdOrGrp.groupID);
cmdOrGrp.reload();
if(this.client.shard) {
try {
await this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id))) {
this.registry.${isCmd ? 'commands' : 'groups'}.get('${isCmd ? cmdOrGrp.name : cmdOrGrp.id}').reload();
}
`);
} catch(err) {
this.client.emit('warn', `Error when broadcasting command reload to other shards`);
this.client.emit('error', err);
if(isCmd) {
await msg.reply(`Reloaded \`${cmdOrGrp.name}\` command, but failed to reload on other shards.`);
} else {
await msg.reply(
`Reloaded all of the commands in the \`${cmdOrGrp.name}\` group, but failed to reload on other shards.`
);
}
return null;
}
}
if(isCmd) {
await msg.reply(`Reloaded \`${cmdOrGrp.name}\` command${this.client.shard ? ' on all shards' : ''}.`);
} else {
await msg.reply(
`Reloaded all of the commands in the \`${cmdOrGrp.name}\` group${this.client.shard ? ' on all shards' : ''}.`
);
}
return null;
}
};

View file

@ -0,0 +1,36 @@
const { trySend } = require('../../resources/functions');
const { chatAnswer } = require('../../resources/shaChat');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class UnknownCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'unknown-command',
group: 'util',
memberName: 'unknown-command',
description: 'Displays help information for when an unknown command is used.',
examples: ['unknown-command kickeverybodyever'],
unknown: true,
hidden: true
});
}
// eslint-disable-next-line
async run(msg) {
if (/^<@\!?\d{17,19}>\s.+/.test(msg.content)) {
const s = msg.cleanContent.slice((msg.guild ? msg.guild.member(msg.client.user).displayName.length : msg.client.user.username.length) + 2).trim();
return msg.channel.startTyping().then(trySend(msg.client, msg, await chatAnswer(s)).then(r => r)).catch(() => { }).finally(msg.channel.stopTyping());
}
if (!msg.guild && !msg.content.toLowerCase().startsWith(msg.client.commandPrefix)) return msg.channel.startTyping().then(trySend(msg.client, msg, await chatAnswer(msg.cleanContent)).then(r => r)).catch(() => { }).finally(msg.channel.stopTyping());
try {
return await msg.channel.send(
`Unknown command \`${msg.content}\`. Use ${msg.anyUsage(
'help',
msg.guild ? undefined : null,
msg.guild ? undefined : null
)} to view the command list.`
);
// eslint-disable-next-line
} catch (exception) { }
}
};

52
cmds/owner/unload.js Normal file
View file

@ -0,0 +1,52 @@
const { oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class UnloadCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'unload',
aliases: ['unload-command'],
group: 'commands',
memberName: 'unload',
description: 'Unloads a command.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command.
Only the bot owner(s) may use this command.
`,
examples: ['unload some-command'],
ownerOnly: true,
guarded: true,
args: [
{
key: 'command',
prompt: 'Which command would you like to unload?',
type: 'command'
}
]
});
}
async run(msg, args) {
args.command.unload();
if(this.client.shard) {
try {
await this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id))) {
this.registry.commands.get('${args.command.name}').unload();
}
`);
} catch(err) {
this.client.emit('warn', `Error when broadcasting command unload to other shards`);
this.client.emit('error', err);
await msg.reply(`Unloaded \`${args.command.name}\` command, but failed to unload on other shards.`);
return null;
}
}
await msg.reply(`Unloaded \`${args.command.name}\` command${this.client.shard ? ' on all shards' : ''}.`);
return null;
}
};

View file

@ -9,10 +9,11 @@ module.exports = class update extends commando.Command {
memberName: "update",
group: "owner",
description: "Update Shasha.",
ownerOnly: true
ownerOnly: true,
guarded: true
});
}
run(msg) {
}
};

105
cmds/utility/help.js Normal file
View file

@ -0,0 +1,105 @@
const { stripIndents, oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
const { disambiguation } = require('../../node_modules/@iceprod/discord.js-commando/src/util');
module.exports = class HelpCommand extends Command {
constructor(client) {
super(client, {
name: 'help',
group: 'util',
memberName: 'help',
aliases: ['commands'],
description: 'Displays a list of available commands, or detailed information for a specified command.',
details: oneLine`
The command may be part of a command name or a whole command name.
If it isn't specified, all available commands will be listed.
`,
examples: ['help', 'help prefix'],
guarded: true,
args: [
{
key: 'command',
prompt: 'Which command would you like to view the help for?',
type: 'string',
default: ''
}
]
});
}
async run(msg, args) { // eslint-disable-line complexity
const groups = this.client.registry.groups;
const commands = this.client.registry.findCommands(args.command, false, msg);
const showAll = args.command && args.command.toLowerCase() === 'all';
if (args.command && !showAll) {
if (commands.length === 1) {
let help = stripIndents`
${oneLine`
__Command **${commands[0].name}**:__ ${commands[0].description}
${commands[0].guildOnly ? ' (Usable only in servers)' : ''}
${commands[0].nsfw ? ' (NSFW)' : ''}
`}
**Format:** ${msg.anyUsage(`${commands[0].name}${commands[0].format ? ` ${commands[0].format}` : ''}`)}
`;
if (commands[0].aliases.length > 0) help += `\n**Aliases:** ${commands[0].aliases.join(', ')}`;
help += `\n${oneLine`
**Group:** ${commands[0].group.name}
(\`${commands[0].groupID}:${commands[0].memberName}\`)
`}`;
if (commands[0].details) help += `\n**Details:** ${commands[0].details}`;
if (commands[0].examples) help += `\n**Examples:**\n${commands[0].examples.join('\n')}`;
const messages = [];
try {
messages.push(await msg.direct(help));
if (msg.channel.type !== 'dm') messages.push(await msg.reply('Sent you a DM with information.'));
} catch (err) {
messages.push(await msg.reply('Unable to send you the help DM. You probably have DMs disabled.'));
}
return messages;
} else if (commands.length > 15) {
return msg.reply('Multiple commands found. Please be more specific.');
} else if (commands.length > 1) {
return msg.reply(disambiguation(commands, 'commands'));
} else {
return msg.reply(
`Unable to identify command. Use ${msg.usage(
null, msg.channel.type === 'dm' ? null : undefined, msg.channel.type === 'dm' ? null : undefined
)} to view the list of all commands.`
);
}
} else {
const messages = [];
try {
messages.push(await msg.direct(stripIndents`
${oneLine`
To run a command in ${msg.guild ? msg.guild.name : 'any server'},
use ${Command.usage('command', msg.guild ? msg.guild.commandPrefix : null, this.client.user)}.
For example, ${Command.usage('prefix', msg.guild ? msg.guild.commandPrefix : null, this.client.user)}.
`}
To run a command in this DM, simply use ${Command.usage('command', null, null)} with no prefix.
Use ${this.usage('<command>', null, null)} to view detailed information about a specific command.
Use ${this.usage('all', null, null)} to view a list of *all* commands, not just available ones.
__**${showAll ? 'All commands' : `Available commands in ${msg.guild || 'this DM'}`}**__
${groups.filter(grp => grp.commands.some(cmd => !cmd.hidden && (showAll || cmd.isUsable(msg))))
.map(grp => stripIndents`
__${grp.name}__
${grp.commands.filter(cmd => !cmd.hidden && (showAll || cmd.isUsable(msg)))
.map(cmd => `**${cmd.name}:** ${cmd.description}${cmd.nsfw ? ' (NSFW)' : ''}`).join('\n')
}
`).join('\n\n')
}
`, { split: true }));
if (msg.channel.type !== 'dm') messages.push(await msg.reply('Sent you a DM with information.'));
} catch (err) {
messages.push(await msg.reply('Unable to send you the help DM. You probably have DMs disabled.'));
}
return messages;
}
}
};

28
cmds/utility/ping.js Normal file
View file

@ -0,0 +1,28 @@
const { oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class PingCommand extends Command {
constructor(client) {
super(client, {
name: 'ping',
group: 'util',
memberName: 'ping',
description: 'Checks the bot\'s ping to the Discord server.',
throttling: {
usages: 5,
duration: 10
}
});
}
async run(msg) {
const pingMsg = await msg.reply('Pinging...');
return pingMsg.edit(oneLine`
${msg.channel.type !== 'dm' ? `${msg.author},` : ''}
Pong! The message round-trip took ${
(pingMsg.editedTimestamp || pingMsg.createdTimestamp) - (msg.editedTimestamp || msg.createdTimestamp)
}ms.
${this.client.ws.ping ? `The heartbeat ping is ${Math.round(this.client.ws.ping)}ms.` : ''}
`);
}
};

67
cmds/utility/prefix.js Normal file
View file

@ -0,0 +1,67 @@
const { stripIndents, oneLine } = require('common-tags');
const Command = require('../../node_modules/@iceprod/discord.js-commando/src/commands/base');
module.exports = class PrefixCommand extends Command {
constructor(client) {
super(client, {
name: 'prefix',
group: 'util',
memberName: 'prefix',
description: 'Shows or sets the command prefix.',
format: '[prefix/"default"/"none"]',
details: oneLine`
If no prefix is provided, the current prefix will be shown.
If the prefix is "default", the prefix will be reset to the bot's default prefix.
If the prefix is "none", the prefix will be removed entirely, only allowing mentions to run commands.
Only administrators may change the prefix.
`,
examples: ['prefix', 'prefix -', 'prefix omg!', 'prefix default', 'prefix none'],
args: [
{
key: 'prefix',
prompt: 'What would you like to set the bot\'s prefix to?',
type: 'string',
max: 15,
default: ''
}
]
});
}
async run(msg, args) {
// Just output the prefix
if(!args.prefix) {
const prefix = msg.guild ? msg.guild.commandPrefix : this.client.commandPrefix;
return msg.reply(stripIndents`
${prefix ? `The command prefix is \`${prefix}\`.` : 'There is no command prefix.'}
To run commands, use ${msg.anyUsage('command')}.
`);
}
// Check the user's permission before changing anything
if(msg.guild) {
if(!msg.member.permissions.has('ADMINISTRATOR') && !this.client.isOwner(msg.author)) {
return msg.reply('Only administrators may change the command prefix.');
}
} else if(!this.client.isOwner(msg.author)) {
return msg.reply('Only the bot owner(s) may change the global command prefix.');
}
// Save the prefix
const lowercase = args.prefix.toLowerCase();
const prefix = lowercase === 'none' ? '' : args.prefix;
let response;
if(lowercase === 'default') {
if(msg.guild) msg.guild.commandPrefix = null; else this.client.commandPrefix = null;
const current = this.client.commandPrefix ? `\`${this.client.commandPrefix}\`` : 'no prefix';
response = `Reset the command prefix to the default (currently ${current}).`;
} else {
if(msg.guild) msg.guild.commandPrefix = prefix; else this.client.commandPrefix = prefix;
response = prefix ? `Set the command prefix to \`${args.prefix}\`.` : 'Removed the command prefix entirely.';
}
await msg.reply(`${response} To run commands, use ${msg.anyUsage('command')}.`);
return null;
}
};