Framework Rewrite

This commit is contained in:
Dragon Fire
2021-06-05 12:17:33 -04:00
parent 5c9f237321
commit 7917766ce3
48 changed files with 1101 additions and 294 deletions
+5 -32
View File
@@ -1,8 +1,8 @@
require('dotenv').config();
const { XIAO_TOKEN, OWNERS, XIAO_PREFIX, INVITE, APRIL_FOOLS } = process.env;
const { XIAO_TOKEN, OWNERS, XIAO_PREFIX, INVITE } = process.env;
const { mkdir } = require('fs/promises');
const path = require('path');
const { Intents, Permissions, SystemChannelFlags, MessageEmbed } = require('discord.js');
const { Intents, MessageEmbed } = require('discord.js');
const Client = require('./structures/Client');
const client = new Client({
commandPrefix: XIAO_PREFIX,
@@ -16,7 +16,6 @@ const client = new Client({
intents: [Intents.NON_PRIVILEGED, Intents.FLAGS.GUILD_MEMBERS]
});
const { formatNumber, checkFileExists } = require('./util/Util');
const aprilFoolsMsgs = require('./assets/json/april-fools');
client.registry
.registerDefaultTypes()
@@ -52,13 +51,6 @@ client.registry
['roleplay', 'Roleplay'],
['other', 'Other']
])
.registerDefaultCommands({
help: false,
ping: false,
prefix: false,
commandState: false,
unknownCommand: false
})
.registerCommandsIn(path.join(__dirname, 'commands'));
client.on('ready', async () => {
@@ -248,7 +240,7 @@ client.on('message', async msg => {
const hasEmbed = msg.embeds.length !== 0;
if (msg.author.bot || (!hasText && !hasImage && !hasEmbed)) return;
if (client.blacklist.user.includes(msg.author.id)) return;
if (msg.isCommand && msg.channel.type !== 'dm') return;
if (client.dispatcher.isCommand(msg) && msg.channel.type !== 'dm') return;
if (client.games.has(msg.channel.id)) return;
// Cleverbot handler
@@ -340,8 +332,8 @@ client.on('guildDelete', async guild => {
client.on('guildMemberRemove', async member => {
if (member.id === client.user.id) return null;
const channel = member.guild.systemChannel;
if (!channel || !channel.permissionsFor(client.user).has(Permissions.FLAGS.SEND_MESSAGES)) return null;
if (member.guild.systemChannelFlags.has(SystemChannelFlags.FLAGS.SUPPRESS_JOIN_NOTIFICATIONS)) return null;
if (!channel || !channel.permissionsFor(client.user).has('SEND_MESSAGES')) return null;
if (member.guild.systemChannelFlags.has('SUPPRESS_JOIN_NOTIFICATIONS')) return null;
if (channel.topic && channel.topic.includes('<xiao:disable-leave>')) return null;
try {
const leaveMessage = client.leaveMessages[Math.floor(Math.random() * client.leaveMessages.length)];
@@ -387,30 +379,11 @@ client.on('warn', warn => client.logger.warn(warn));
client.on('commandRun', async command => {
if (command.unknown) return;
client.logger.info(`[COMMAND] ${command.name} was used.`);
if (command.uses === undefined) return;
command.uses++;
if (command.lastRun === undefined) return;
command.lastRun = new Date();
const channel = await client.fetchCommandChannel();
channel.send(`\`${command.name}\` was used! It has now been used **${formatNumber(command.uses)}** times!`)
.catch(() => null);
});
client.dispatcher.addInhibitor(msg => {
if (client.blacklist.user.includes(msg.author.id)) return 'blacklisted';
if (msg.guild && client.blacklist.guild.includes(msg.guild.id)) return 'blacklisted';
return false;
});
if (APRIL_FOOLS) {
client.dispatcher.addInhibitor(msg => {
if (client.isOwner(msg.author)) return false;
const random = Math.floor(Math.random() * 2);
if (random === 1) return msg.reply(aprilFoolsMsgs[Math.floor(Math.random() * aprilFoolsMsgs.length)]);
return false;
});
}
client.on('commandError', (command, err) => client.logger.error(`[COMMAND:${command.name}]\n${err.stack}`));
client.login(XIAO_TOKEN);
-17
View File
@@ -1,17 +0,0 @@
[
"You don't command me!",
"Sorry loser, I don't listen to idiots.",
"I take orders from no one!",
"Hahaha look at you, trying to use a command.",
"You wish you could use a bot like me.",
"I'm too cute to take orders from you.",
"You? _You_? As if I'd do anything for _you_.",
"I've got better things to do.",
"Go away.",
"Keep trying. Maybe one day it'll work.",
"Nope, sorry, I'm not listening.",
"La la la la la, I'm not listening!",
"Eat pant.",
"Command? What's that? Is it tasty?",
"Go do something more productive with your life."
]
+35
View File
@@ -0,0 +1,35 @@
{
"ADMINISTRATOR": "",
"CREATE_INSTANT_INVITE": "",
"KICK_MEMBERS": "",
"BAN_MEMBERS": "",
"MANAGE_CHANNELS": "",
"MANAGE_GUILD": "",
"ADD_REACTIONS": "",
"VIEW_AUDIT_LOG": "",
"PRIORITY_SPEAKER": "",
"STREAM": "",
"VIEW_CHANNEL": "",
"SEND_MESSAGES": "",
"SEND_TTS_MESSAGES": "",
"MANAGE_MESSAGES": "",
"EMBED_LINKS": "",
"ATTACH_FILES": "",
"READ_MESSAGE_HISTORY": "",
"MENTION_EVERYONE": "",
"USE_EXTERNAL_EMOJIS": "",
"VIEW_GUILD_INSIGHTS": "",
"CONNECT": "",
"SPEAK": "",
"MUTE_MEMBERS": "",
"DEAFEN_MEMBERS": "",
"MOVE_MEMBERS": "",
"USE_VAD": "",
"CHANGE_NICKNAME": "",
"MANAGE_NICKNAMES": "",
"MANAGE_ROLES": "",
"MANAGE_WEBHOOKS": "",
"MANAGE_EMOJIS": "",
"USE_APPLICATION_COMMANDS": "",
"REQUEST_TO_SPEAK": ""
}
-21
View File
@@ -1,21 +0,0 @@
const Command = require('../../structures/commands/AutoReply');
module.exports = class NoUCommand extends Command {
constructor(client) {
super(client, {
name: 'no-u',
aliases: ['no-you'],
group: 'auto',
memberName: 'no-u',
description: 'no u',
patterns: [/^n+o+ u+$/i]
});
}
generateText(fromPattern) {
if (!fromPattern) return 'no u';
const chance = Boolean(Math.floor(Math.random() * 2));
if (chance) return null;
return 'no u';
}
};
-17
View File
@@ -1,17 +0,0 @@
const Command = require('../../structures/commands/AutoReply');
module.exports = class UnflipCommand extends Command {
constructor(client) {
super(client, {
name: 'unflip',
group: 'auto',
memberName: 'unflip',
description: 'Unflips a flipped table.',
patterns: [/\(╯°□°)╯︵ ┻━┻/i]
});
}
generateText() {
return '┬─┬ ( ゜-゜ノ)';
}
};
+1 -1
View File
@@ -1,7 +1,7 @@
const Command = require('../../structures/Command');
const moment = require('moment');
const { MessageEmbed } = require('discord.js');
const { util: { permissions } } = require('discord.js-commando');
const permissions = require('../../assets/json/permission-names');
module.exports = class RoleCommand extends Command {
constructor(client) {
+2 -2
View File
@@ -1,6 +1,6 @@
const Command = require('../../structures/Command');
const { MessageEmbed } = require('discord.js');
const { util: { permissions } } = require('discord.js-commando');
const permissions = require('../../assets/json/permission-names');
const { stripIndents } = require('common-tags');
module.exports = class HelpCommand extends Command {
@@ -31,7 +31,7 @@ module.exports = class HelpCommand extends Command {
const embed = new MessageEmbed()
.setTitle(`Command List (Page ${i + 1})`)
.setDescription(stripIndents`
To run a command, use ${msg.anyUsage('<command>')}.
To run a command, use ${this.usage()}.
${nsfw ? '' : 'Use in an NSFW channel to see NSFW commands.'}
`)
.setColor(0x00AE86);
+1 -2
View File
@@ -1,6 +1,5 @@
const Command = require('../../structures/Command');
const { MessageEmbed, version: djsVersion } = require('discord.js');
const { version: commandoVersion } = require('discord.js-commando');
const moment = require('moment');
require('moment-duration-format');
const { formatNumber, embedURL } = require('../../util/Util');
@@ -39,7 +38,7 @@ module.exports = class InfoCommand extends Command {
.addField(' Version', `v${version}`, true)
.addField(' Node.js', process.version, true)
.addField(' Discord.js', `v${djsVersion}`, true)
.addField(' Commando', `v${commandoVersion}`, true)
.addField(' Framework', 'Custom', true)
.addField(' Dependencies', Object.keys(deps).sort().join(', '));
return msg.embed(embed);
}
+1 -5
View File
@@ -1,5 +1,4 @@
const Command = require('../../structures/Command');
const { stripIndents } = require('common-tags');
module.exports = class PrefixCommand extends Command {
constructor(client) {
@@ -14,9 +13,6 @@ module.exports = class PrefixCommand extends Command {
run(msg) {
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 a command, use ${msg.anyUsage('<command>')}.
`);
return msg.reply(prefix ? `The command prefix is \`${prefix}\`.` : 'There is no command prefix.');
}
};
+1 -1
View File
@@ -18,7 +18,7 @@ module.exports = class UnknownCommandCommand extends Command {
run(msg) {
if (!msg.guild) return null;
const commands = this.makeCommandArray(this.client.isOwner(msg.author), msg.channel.nsfw);
const command = msg.content.match(this.client.dispatcher._commandPatterns[this.client.commandPrefix]);
const command = msg.content.match(this.client.dispatcher.commandPattern);
const str = command ? command[2] : msg.content.split(' ')[0];
const results = didYouMean(str, commands, { returnType: ReturnTypeEnums.ALL_SORTED_MATCHES });
return msg.reply(stripIndents`
+116
View File
@@ -0,0 +1,116 @@
// Credit: https://github.com/discordjs/Commando/blob/master/src/commands/util/eval.js
const util = require('util');
const discord = require('discord.js');
const tags = require('common-tags');
const { escapeRegex } = require('../../util/Util');
const Command = require('../../structures/Command');
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: 1900, prepend, append });
} else {
return discord.splitMessage(tags.stripIndents`
*Callback executed after ${hrDiff[0] > 0 ? `${hrDiff[0]}s ` : ''}${hrDiff[1] / 1000000}ms.*
\`\`\`javascript
${inspected}
\`\`\`
`, { maxLength: 1900, 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;
}
};
+20
View File
@@ -0,0 +1,20 @@
const UnionType = require('./UnionType');
module.exports = class Argument {
constructor(client, options) {
Object.defineProperty(this, 'client', { value: client });
this.key = options.key.toLowerCase();
this.typeID = options.type.toLowerCase();
this.min = options.min;
this.max = options.max;
this.oneOf = options.oneOf;
this.default = options.default;
this.avatarSize = options.avatarSize || 2048;
}
get type() {
if (this.typeID.includes('|')) return new UnionType(this.client, this.typeID);
return this.client.registry.types.get(this.typeID);
}
};
+17
View File
@@ -0,0 +1,17 @@
module.exports = class ArgumentType {
constructor(id) {
this.id = id.toLowerCase();
}
validate(val) {
return Boolean(val);
}
parse(val) {
return val;
}
isEmpty(val) {
return !val;
}
};
+228
View File
@@ -0,0 +1,228 @@
const { Client } = require('discord.js');
const fs = require('fs');
const { stripIndents } = require('common-tags');
const Registry = require('./Registry');
const Dispatcher = require('./Dispatcher');
const Patreon = require('../structures/Patreon');
require('./Extensions');
module.exports = class CommandClient extends Client {
constructor(options) {
super(options);
this.commandPrefix = options.commandPrefix;
this.owner = typeof options.owner === 'string' ? [options.owner] : options.owner;
this.invite = options.invite || null;
this.registry = new Registry(this);
this.dispatcher = new Dispatcher(this);
this.patreon = new Patreon();
this.blacklist = { user: [], guild: [] };
this._throttlingTimeouts = new Map();
this.once('ready', this.onceReady);
this.on('message', this.onMessage);
}
isOwner(user) {
return this.owners.includes(user.id);
}
async onceReady() {
for (const owner of this.owner) {
await this.users.fetch(owner);
}
}
async onMessage(msg) {
if (!msg.author) return;
if (msg.channel.partial) msg.channel = await this.channels.fetch(msg.channel.id);
if (msg.partial) msg = await msg.channel.messages.fetch(msg.id);
if (msg.member.partial || !msg.guild.members.cache.has(msg.author.id)) {
await msg.guild.members.fetch(msg.author.id);
}
if (msg.author.bot) return;
if (this.blacklist.user.includes(msg.author.id)) return;
if (msg.guild && this.blacklist.guild.includes(msg.guild.id)) return;
if (!msg.channel.permissionsFor(this.user).has('SEND_MESSAGES')) return;
if (!this.dispatcher.isCommand(msg)) return;
const parsed = await this.dispatcher.parseMessage(msg);
if (typeof parsed === 'string') {
const helpUsage = this.registry.commands.get('help').usage();
await msg.reply(`${parsed} Use ${helpUsage} for more information.`);
return;
}
const { command, args } = parsed;
if (!command._enabled) {
await msg.reply(`The \`${command.name}\` command is disabled.`);
return;
}
if (command.ownerOnly && !this.isOwner(msg.author)) {
await msg.reply(`The \`${command.name}\` command can only be used by the bot owner.`);
return;
}
if (command.guildOnly && !msg.guild) {
await msg.reply(`The \`${command.name}\` command can only be used in a server channel.`);
return;
}
if (command.nsfw && !msg.channel.nsfw) {
await msg.reply(`The \`${command.name}\` command can only be used in NSFW channels.`);
return;
}
if (command.patronOnly && !this.patreon.isPatron(msg.author.id)) {
await msg.reply(stripIndents`
The \`${command.name}\` command can only be used by Patrons.
Visit <https://www.patreon.com/xiaodiscord> to sign-up!
`);
return;
}
if (command.clientPermissions.length) {
for (const permission of command.clientPermissions) {
if (msg.channel.permissionsFor(this.user).has(permission)) continue;
await msg.reply(`The \`${command.name}\` command requires me to have the "${permission}" permission.`);
return;
}
}
if (command.userPermissions.length) {
for (const permission of command.userPermissions) {
if (msg.channel.permissionsFor(msg.author).has(permission)) continue;
await msg.reply(`You need the "${permission}" permission to use the \`${command.name}\` command.`);
return;
}
}
const throttleAmount = command.throttles.get(msg.author.id) || 0;
if (throttleAmount >= command.throttling.uses) {
const timeout = command._timeouts.get(msg.author.id);
await msg.reply(`Please wait ${getTimeLeft(timeout)} seconds before using the \`${command.name}\` command again.`);
return;
}
command.throttles.set(msg.author.id, throttleAmount + 1);
if (!throttleAmount) {
const timeout = setTimeout(() => command.throttles.delete(msg.author.id), command.throttling.duration);
command._timeouts.set(msg.author.id, timeout);
}
try {
const result = await command.run(msg, args);
command.uses++;
command.lastRun = new Date();
this.emit('commandRun', command, result, msg, args);
} catch (err) {
this.emit('commandError', command, err, msg, args);
await msg.reply(stripIndents`
An error occurred while running this command: \`${err.message}\`.
You shouldn't ever recieve an error like this.
${this.invite ? `Please visit ${this.invite} for support.` : ''}
`);
}
}
importBlacklist() {
const read = fs.readFileSync(path.join(__dirname, '..', 'blacklist.json'), { encoding: 'utf8' });
const file = JSON.parse(read);
if (typeof file !== 'object' || Array.isArray(file)) return null;
if (!file.guild || !file.user) return null;
for (const id of file.guild) {
if (typeof id !== 'string') continue;
if (this.blacklist.guild.includes(id)) continue;
this.blacklist.guild.push(id);
}
for (const id of file.user) {
if (typeof id !== 'string') continue;
if (this.blacklist.user.includes(id)) continue;
this.blacklist.user.push(id);
}
return file;
}
exportBlacklist() {
let text = '{\n "guild": [\n ';
if (this.blacklist.guild.length) {
for (const id of this.blacklist.guild) {
text += `"${id}",\n `;
}
text = text.slice(0, -4);
}
text += '\n ],\n "user": [\n ';
if (this.blacklist.user.length) {
for (const id of this.blacklist.user) {
text += `"${id}",\n `;
}
text = text.slice(0, -4);
}
text += '\n ]\n}\n';
const buf = Buffer.from(text);
fs.writeFileSync(path.join(__dirname, '..', 'blacklist.json'), buf, { encoding: 'utf8' });
return buf;
}
importCommandLeaderboard(add = false) {
const read = fs.readFileSync(path.join(__dirname, '..', 'command-leaderboard.json'), {
encoding: 'utf8'
});
const file = JSON.parse(read);
if (typeof file !== 'object' || Array.isArray(file)) return null;
for (const [id, value] of Object.entries(file)) {
if (typeof value !== 'number') continue;
const found = this.registry.commands.get(id);
if (!found || found.uses === undefined) continue;
if (add) found.uses += value;
else found.uses = value;
}
return file;
}
exportCommandLeaderboard() {
let text = '{';
for (const command of this.registry.commands.values()) {
if (command.unknown) continue;
if (command.uses === undefined) continue;
text += `\n "${command.name}": ${command.uses},`;
}
text = text.slice(0, -1);
text += '\n}\n';
const buf = Buffer.from(text);
fs.writeFileSync(path.join(__dirname, '..', 'command-leaderboard.json'), buf, {
encoding: 'utf8'
});
return buf;
}
importLastRun() {
const read = fs.readFileSync(path.join(__dirname, '..', 'command-last-run.json'), {
encoding: 'utf8'
});
const file = JSON.parse(read);
if (typeof file !== 'object' || Array.isArray(file)) return null;
for (const [id, value] of Object.entries(file)) {
if (!value) continue;
const date = new Date(value);
if (date.toString() === 'Invalid Date') continue;
const found = this.registry.commands.get(id);
if (!found || found.lastRun === undefined) continue;
found.lastRun = date;
}
return file;
}
exportLastRun() {
let text = '{';
for (const command of this.registry.commands.values()) {
if (command.unknown) continue;
if (command.lastRun === undefined) continue;
text += `\n "${command.name}": ${command.lastRun ? `"${command.lastRun.toISOString()}"` : null},`;
}
text = text.slice(0, -1);
text += '\n}\n';
const buf = Buffer.from(text);
fs.writeFileSync(path.join(__dirname, '..', 'command-last-run.json'), buf, {
encoding: 'utf8'
});
return buf;
}
};
function getTimeLeft(timeout) {
return Math.ceil((timeout._idleStart + timeout._idleTimeout - Date.now()) / 1000);
}
+52
View File
@@ -0,0 +1,52 @@
const Argument = require('./Argument');
module.exports = class Command {
constructor(client, options) {
Object.defineProperty(this, 'client', { value: client });
this.name = options.name.toLowerCase();
this.aliases = options.aliases ? options.aliases.map(alias => alias.toLowerCase()) : [];
this.groupID = options.group.toLowerCase();
this.memberName = options.memberName.toLowerCase();
this.description = options.description;
this.details = options.details || null;
this.args = options.args ? options.args.map(arg => new Argument(arg)) : [];
this.clientPermissions = options.clientPermissions || [];
this.userPermissions = options.userPermissions || [];
this.ownerOnly = options.ownerOnly || false;
this.nsfw = options.nsfw || false;
this.guildOnly = options.guildOnly || false;
this.patronOnly = options.patronOnly || false;
this.guarded = options.guarded || false;
this.throttling = options.throttling || { usages: 2, duration: 5 };
this.credit = options.credit || [];
this.credit.push({
name: 'Dragon Fire',
url: 'https://github.com/dragonfire535',
reason: 'Code'
});
this.uses = 0;
this.lastRun = null;
this.throttles = new Map();
this._timeouts = new Map();
this._enabled = true;
}
get group() {
return this.client.registry.groups.get(this.groupID);
}
usage() {
const args = this.args
.map(arg => `${arg.default ? '[' : '<'}${arg.label || arg.name}${arg.default ? ']' : '>'}`).join(' ');
return `\`${this.client.commandPrefix}${this.name} ${args}\` or \`@${this.client.user.tag} ${this.name} ${args}\``;
}
disable() {
this._enabled = false;
}
enable() {
this._enabled = true;
}
};
+58
View File
@@ -0,0 +1,58 @@
const minimist = require('minimist');
const argRegex = /"([^"]*)"|(\S+)/g;
module.exports = class CommandDispatcher {
constructor(client) {
Object.defineProperty(this, 'client', { value: client });
this._commandPattern = null;
}
get commandPattern() {
if (this._commandPattern) return this._commandPattern;
const prefix = this.client.commandPrefix;
this._commandPattern = new RegExp(
`^(<@!?${this.client.user.id}>\\s+(?:${prefix}}\\s*)?|${prefix}\\s*)([^\\s]+)`, 'i'
);
return this._commandPattern;
}
isCommand(msg) {
const command = msg.content.match(this.commandPattern);
return Boolean(command);
}
async parseMessage(msg) {
const command = this.resolveCommand(command[2].toLowerCase());
if (!command) {
return {
command: this.registry.commands.find(cmd => cmd.unknown),
args: { command: command[2].toLowerCase() }
};
}
const content = msg.content.replace(this.commandPattern, '').trim();
const result = (content.match(argRegex) || []).map(m => m.replace(argRegex, '$1$2'));
const parsed = minimist(result);
const result = { flags: [...parsed] };
for (let i = 0; i > command.args.length; i++) {
const arg = command.args[i];
const parsedArg = result._[i];
if (arg.isEmpty(parsedArg, msg, arg)) {
if (arg.default) {
result[arg.name] = typeof arg.default === 'function' ? arg.default(msg) : arg.default;
continue;
} else {
return `The "${arg.label || arg.name}" argument is required.`;
}
}
const valid = await arg.validate(parsedArg, msg, arg);
if (!valid) return `An invalid value was provided for the "${arg.label || arg.name}" argument.`;
result[arg.name] = await arg.parse(parsedArg, msg, arg);
}
return { command, args: result };
}
resolveCommand(command) {
return this.registry.commands.find(cmd => cmd.name === command || cmd.aliases.includes(command));
}
};
+21
View File
@@ -0,0 +1,21 @@
const { Structures } = require('discord.js');
module.exports = Structures.extend('Message', Message => {
return class CommandMessage extends Message {
constructor(...args) {
super(...args);
}
say(content, options) {
return this.channel.send(content, options);
}
embed(embed, options) {
return this.channel.send('', { embed, ...options });
}
code(lang, content, options) {
return this.channel.send(content, { code: lang, ...options });
}
}
});
+12
View File
@@ -0,0 +1,12 @@
module.exports = class Group {
constructor(client, id, name) {
Object.defineProperty(this, 'client', { value: client });
this.id = id.toLowerCase();
this.name = name;
}
get commands() {
return this.client.registry.commands.filter(command => command.groupID === this.id);
}
};
+74
View File
@@ -0,0 +1,74 @@
const Collection = require('@discordjs/collection');
const fs = require('fs');
const path = require('path');
const Group = require('./Group');
module.exports = class Registry {
constructor(client) {
Object.defineProperty(this, 'client', { value: client });
this.commands = new Collection();
this.groups = new Collection();
this.types = new Collection();
}
findCommands(query) {
query = query.toLowerCase();
return this.commands.filter(command => command.name === query || command.aliases.includes(query));
}
registerCommand(command) {
this.commands.set(command.name, command);
return this;
}
registerCommandsIn(dir) {
const groups = fs.readdirSync(dir);
for (const group of groups) {
const commands = fs.readdirSync(path.join(dir, group));
for (const command of commands) {
if (!command.endsWith('.js')) continue;
const required = require(path.join(dir, group, command));
this.registerCommand(new required(this.client));
}
}
return this;
}
findGroups(query) {
query = query.toLowerCase();
return this.groups.filter(group => group.id === query || group.name.toLowerCase() === query);
}
registerGroup(group) {
this.groups.set(group.id, group);
return this;
}
registerGroups(groups) {
for (const [id, name] of groups) {
const group = new Group(this.client, id, name);
this.registerGroup(group);
}
return this;
}
registerType(type) {
this.types.set(type.id, type);
return this;
}
registerTypesIn(dir) {
const types = fs.readdirSync(dir);
for (const type of types) {
if (!type.endsWith('.js')) continue;
const required = require(path.join(dir, type));
this.registerType(new required(this.client));
}
return this;
}
registerDefaultTypes() {
return this.registerTypesIn(path.join(__dirname, 'types'));
}
};
+37
View File
@@ -0,0 +1,37 @@
const ArgumentType = require('./ArgumentType');
module.exports = class ArgumentUnionType extends ArgumentType {
constructor(client, id) {
super(client, id);
this.types = [];
const typeIDs = id.split('|');
for (const typeID of typeIDs) {
const type = client.registry.types.get(typeID);
if (!type) throw new Error(`Argument type "${typeID}" is not registered.`);
this.types.push(type);
}
}
async validate(val, msg, arg) {
let results = this.types.map(type => !type.isEmpty(val, msg, arg) && type.validate(val, msg, arg));
results = await Promise.all(results);
if (results.some(valid => valid && typeof valid !== 'string')) return true;
const errors = results.filter(valid => typeof valid === 'string');
if (errors.length > 0) return errors.join('\n');
return false;
}
async parse(val, msg, arg) {
let results = this.types.map(type => !type.isEmpty(val, msg, arg) && type.validate(val, msg, arg));
results = await Promise.all(results);
for (let i = 0; i < results.length; i++) {
if (results[i] && typeof results[i] !== 'string') return this.types[i].parse(val, msg, arg);
}
throw new Error(`Couldn't parse value "${val}" with union type ${this.id}.`);
}
isEmpty(val, msg, arg) {
return !this.types.some(type => !type.isEmpty(val, msg, arg));
}
};
+21
View File
@@ -0,0 +1,21 @@
const ArgumentType = require('../ArgumentType');
module.exports = class BooleanArgumentType extends ArgumentType {
constructor(client) {
super(client, 'boolean');
this.truthy = new Set(['true', 't', 'yes', 'y', 'on', 'enable', 'enabled', '1', '+']);
this.falsy = new Set(['false', 'f', 'no', 'n', 'off', 'disable', 'disabled', '0', '-']);
}
validate(val) {
const lc = val.toLowerCase();
return this.truthy.has(lc) || this.falsy.has(lc);
}
parse(val) {
const lc = val.toLowerCase();
if (this.truthy.has(lc)) return true;
if (this.falsy.has(lc)) return false;
throw new RangeError('Unknown boolean value.');
}
}
+39
View File
@@ -0,0 +1,39 @@
const ArgumentType = require('../ArgumentType');
module.exports = class ChannelArgumentType extends ArgumentType {
constructor(client) {
super(client, 'channel');
}
validate(val, msg) {
const matches = val.match(/^(?:<#)?([0-9]+)>?$/);
if (matches) return msg.guild.channels.cache.has(matches[1]);
const search = val.toLowerCase();
const channels = msg.guild.channels.cache.filter(nameFilterInexact(search));
if (channels.size === 0) return false;
if (channels.size === 1) return true;
const exactChannels = channels.filter(nameFilterExact(search));
if (exactChannels.size === 1) return true;
return false;
}
parse(val, msg) {
const matches = val.match(/^(?:<#)?([0-9]+)>?$/);
if (matches) return msg.guild.channels.cache.get(matches[1]) || null;
const search = val.toLowerCase();
const channels = msg.guild.channels.cache.filter(nameFilterInexact(search));
if (channels.size === 0) return null;
if (channels.size === 1) return channels.first();
const exactChannels = channels.filter(nameFilterExact(search));
if (exactChannels.size === 1) return exactChannels.first();
return null;
}
}
function nameFilterExact(search) {
return thing => thing.name.toLowerCase() === search;
}
function nameFilterInexact(search) {
return thing => thing.name.toLowerCase().includes(search);
}
+17
View File
@@ -0,0 +1,17 @@
const ArgumentType = require('../ArgumentType');
module.exports = class CommandArgumentType extends ArgumentType {
constructor(client) {
super(client, 'command');
}
validate(val) {
const commands = this.client.registry.findCommands(val);
if (commands.size === 1) return true;
return false;
}
parse(val) {
return this.client.registry.findCommands(val).first();
}
}
+40
View File
@@ -0,0 +1,40 @@
const ArgumentType = require('../ArgumentType');
module.exports = class CustomEmojiArgumentType extends ArgumentType {
constructor(client) {
super(client, 'custom-emoji');
}
validate(value, msg) {
const matches = value.match(/^(?:<a?:([a-zA-Z0-9_]+):)?([0-9]+)>?$/);
if (matches && msg.client.emojis.cache.has(matches[2])) return true;
if (!msg.guild) return false;
const search = value.toLowerCase();
const emojis = msg.guild.emojis.cache.filter(nameFilterInexact(search));
if (!emojis.size) return false;
if (emojis.size === 1) return true;
const exactEmojis = emojis.filter(nameFilterExact(search));
if (exactEmojis.size === 1) return true;
return false;
}
parse(value, msg) {
const matches = value.match(/^(?:<a?:([a-zA-Z0-9_]+):)?([0-9]+)>?$/);
if (matches) return msg.client.emojis.cache.get(matches[2]) || null;
const search = value.toLowerCase();
const emojis = msg.guild.emojis.cache.filter(nameFilterInexact(search));
if (!emojis.size) return null;
if (emojis.size === 1) return emojis.first();
const exactEmojis = emojis.filter(nameFilterExact(search));
if (exactEmojis.size === 1) return exactEmojis.first();
return null;
}
}
function nameFilterExact(search) {
return emoji => emoji.name.toLowerCase() === search;
}
function nameFilterInexact(search) {
return emoji => emoji.name.toLowerCase().includes(search);
}
+18
View File
@@ -0,0 +1,18 @@
const ArgumentType = require('../ArgumentType');
const emojiRegex = require('emoji-regex/RGI_Emoji.js');
module.exports = class DefaultEmojiArgumentType extends ArgumentType {
constructor(client) {
super(client, 'default-emoji');
this.regex = new RegExp(`^(?:${emojiRegex().source})$`);
}
validate(value) {
if (!this.regex.test(value)) return false;
return true;
}
parse(value) {
return value;
}
}
+20
View File
@@ -0,0 +1,20 @@
const ArgumentType = require('../ArgumentType');
module.exports = class FloatArgumentType extends ArgumentType {
constructor(client) {
super(client, 'float');
}
validate(val, msg, arg) {
const float = Number.parseFloat(val);
if (Number.isNaN(float)) return false;
if (arg.oneOf && !arg.oneOf.includes(float)) return false;
if (arg.min !== null && typeof arg.min !== 'undefined' && float < arg.min) return false;
if (arg.max !== null && typeof arg.max !== 'undefined' && float > arg.max) return false;
return true;
}
parse(val) {
return Number.parseFloat(val);
}
}
+17
View File
@@ -0,0 +1,17 @@
const ArgumentType = require('../ArgumentType');
module.exports = class GroupArgumentType extends ArgumentType {
constructor(client) {
super(client, 'group');
}
validate(val) {
const groups = this.client.registry.findGroups(val);
if (groups.size === 1) return true;
return false;
}
parse(val) {
return this.client.registry.findGroups(val).first();
}
}
+20
View File
@@ -0,0 +1,20 @@
const ArgumentType = require('../ArgumentType');
module.exports = class IntegerArgumentType extends ArgumentType {
constructor(client) {
super(client, 'integer');
}
validate(val, msg, arg) {
const int = Number.parseInt(val);
if (Number.isNaN(int)) return false;
if (arg.oneOf && !arg.oneOf.includes(int)) return false;
if (arg.min !== null && typeof arg.min !== 'undefined' && int < arg.min) return false;
if (arg.max !== null && typeof arg.max !== 'undefined' && int > arg.max) return false;
return true;
}
parse(val) {
return Number.parseInt(val);
}
}
+51
View File
@@ -0,0 +1,51 @@
const ArgumentType = require('../ArgumentType');
module.exports = class MemberArgumentType extends ArgumentType {
constructor(client) {
super(client, 'member');
}
async validate(val, msg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if (matches) {
try {
const member = await msg.guild.members.fetch(await this.client.users.fetch(matches[1]));
if (!member) return false;
return true;
} catch (err) {
return false;
}
}
const search = val.toLowerCase();
const members = msg.guild.members.cache.filter(memberFilterInexact(search));
if (members.size === 0) return false;
if (members.size === 1) return true;
const exactMembers = members.filter(memberFilterExact(search));
if (exactMembers.size === 1) return true;
return false;
}
parse(val, msg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if (matches) return msg.guild.members.resolve(matches[1]) || null;
const search = val.toLowerCase();
const members = msg.guild.members.cache.filter(memberFilterInexact(search));
if (members.size === 0) return null;
if (members.size === 1) return members.first();
const exactMembers = members.filter(memberFilterExact(search));
if (exactMembers.size === 1) return exactMembers.first();
return null;
}
}
function memberFilterExact(search) {
return mem => mem.user.username.toLowerCase() === search ||
(mem.nickname && mem.nickname.toLowerCase() === search) ||
mem.tag.toLowerCase() === search;
}
function memberFilterInexact(search) {
return mem => mem.user.username.toLowerCase().includes(search) ||
(mem.nickname && mem.nickname.toLowerCase().includes(search)) ||
mem.tag.toLowerCase().includes(search);
}
+16
View File
@@ -0,0 +1,16 @@
const ArgumentType = require('../ArgumentType');
module.exports = class MessageArgumentType extends ArgumentType {
constructor(client) {
super(client, 'message');
}
async validate(val, msg) {
if (!/^[0-9]+$/.test(val)) return false;
return Boolean(await msg.channel.messages.fetch(val).catch(() => null));
}
parse(val, msg) {
return msg.channel.messages.cache.get(val);
}
}
+39
View File
@@ -0,0 +1,39 @@
const ArgumentType = require('../ArgumentType');
module.exports = class RoleArgumentType extends ArgumentType {
constructor(client) {
super(client, 'role');
}
validate(val, msg) {
const matches = val.match(/^(?:<@&)?([0-9]+)>?$/);
if (matches) return msg.guild.roles.cache.has(matches[1]);
const search = val.toLowerCase();
const roles = msg.guild.roles.cache.filter(nameFilterInexact(search));
if (roles.size === 0) return false;
if (roles.size === 1) return true;
const exactRoles = roles.filter(nameFilterExact(search));
if (exactRoles.size === 1) return true;
return false;
}
parse(val, msg) {
const matches = val.match(/^(?:<@&)?([0-9]+)>?$/);
if (matches) return msg.guild.roles.cache.get(matches[1]) || null;
const search = val.toLowerCase();
const roles = msg.guild.roles.cache.filter(nameFilterInexact(search));
if (roles.size === 0) return null;
if (roles.size === 1) return roles.first();
const exactRoles = roles.filter(nameFilterExact(search));
if (exactRoles.size === 1) return exactRoles.first();
return null;
}
}
function nameFilterExact(search) {
return thing => thing.name.toLowerCase() === search;
}
function nameFilterInexact(search) {
return thing => thing.name.toLowerCase().includes(search);
}
+18
View File
@@ -0,0 +1,18 @@
const ArgumentType = require('../ArgumentType');
module.exports = class StringArgumentType extends ArgumentType {
constructor(client) {
super(client, 'string');
}
validate(val, msg, arg) {
if (arg.oneOf && !arg.oneOf.includes(val.toLowerCase())) return false;
if (arg.min !== null && typeof arg.min !== 'undefined' && val.length < arg.min) return false;
if (arg.max !== null && typeof arg.max !== 'undefined' && val.length > arg.max) return false;
return true;
}
parse(val) {
return val;
}
}
+53
View File
@@ -0,0 +1,53 @@
const ArgumentType = require('../ArgumentType');
module.exports = class UserArgumentType extends ArgumentType {
constructor(client) {
super(client, 'user');
}
async validate(val, msg, arg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if (matches) {
try {
const user = await msg.client.users.fetch(matches[1]);
if (!user) return false;
return true;
} catch (err) {
return false;
}
}
if (!msg.guild) return false;
const search = val.toLowerCase();
const members = msg.guild.members.cache.filter(memberFilterInexact(search));
if (members.size === 0) return false;
if (members.size === 1) return true;
const exactMembers = members.filter(memberFilterExact(search));
if (exactMembers.size === 1) return true;
return false;
}
parse(val, msg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if (matches) return msg.client.users.cache.get(matches[1]) || null;
if (!msg.guild) return null;
const search = val.toLowerCase();
const members = msg.guild.members.cache.filter(memberFilterInexact(search));
if (members.size === 0) return null;
if (members.size === 1) return members.first().user;
const exactMembers = members.filter(memberFilterExact(search));
if (exactMembers.size === 1) return exactMembers.first().user;
return null;
}
}
function memberFilterExact(search) {
return mem => mem.user.username.toLowerCase() === search ||
(mem.nickname && mem.nickname.toLowerCase() === search) ||
mem.tag.toLowerCase() === search;
}
function memberFilterInexact(search) {
return mem => mem.user.username.toLowerCase().includes(search) ||
(mem.nickname && mem.nickname.toLowerCase().includes(search)) ||
mem.tag.toLowerCase().includes(search);
}
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "xiao",
"version": "140.3.1",
"version": "141.0.0",
"description": "Your personal server companion.",
"main": "Xiao.js",
"private": true,
@@ -15,12 +15,10 @@
},
"keywords": [
"bot",
"commando",
"discord",
"discord-api",
"discord-bot",
"discord-js",
"discord-js-commando"
"discord-js"
],
"author": "dragonfire535 <danielbodendahl@gmail.com>",
"license": "UNLICENSED",
@@ -52,7 +50,6 @@
"custom-translate": "^2.2.8",
"didyoumean2": "^4.2.0",
"discord.js": "github:discordjs/discord.js",
"discord.js-commando": "github:discordjs/Commando",
"discord.js-docs": "github:TeeSeal/discord.js-docs",
"dotenv": "^9.0.2",
"emoji-regex": "^9.2.2",
@@ -70,6 +67,7 @@
"kuroshiro": "^1.1.2",
"kuroshiro-analyzer-kuromoji": "^1.1.0",
"mathjs": "^9.4.0",
"minimist": "^1.2.5",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"moment-timezone": "^0.5.33",
+2 -109
View File
@@ -1,4 +1,4 @@
const { CommandoClient } = require('discord.js-commando');
const CommandClient = require('../framework/Client');
const { WebhookClient } = require('discord.js');
const request = require('node-superfetch');
const Collection = require('@discordjs/collection');
@@ -12,7 +12,6 @@ const path = require('path');
const Redis = require('./Redis');
const Font = require('./Font');
const BotList = require('./BotList');
const Patreon = require('./Patreon');
const PhoneManager = require('./phone/PhoneManager');
const TimerManager = require('./remind/TimerManager');
const PokemonStore = require('./pokemon/PokemonStore');
@@ -26,7 +25,7 @@ const {
COMMAND_CHANNEL_ID
} = process.env;
module.exports = class XiaoClient extends CommandoClient {
module.exports = class XiaoClient extends CommandClient {
constructor(options) {
super(options);
@@ -42,8 +41,6 @@ module.exports = class XiaoClient extends CommandoClient {
this.webhook = new WebhookClient(XIAO_WEBHOOK_ID, XIAO_WEBHOOK_TOKEN, { disableMentions: 'everyone' });
this.timers = new TimerManager(this);
this.botList = new BotList(this);
this.patreon = new Patreon();
this.blacklist = { guild: [], user: [] };
this.pokemon = new PokemonStore();
this.games = new Collection();
this.dispatchers = new Map();
@@ -72,110 +69,6 @@ module.exports = class XiaoClient extends CommandoClient {
moment.tz.link('America/New_York|Dragon');
}
importBlacklist() {
const read = fs.readFileSync(path.join(__dirname, '..', 'blacklist.json'), { encoding: 'utf8' });
const file = JSON.parse(read);
if (typeof file !== 'object' || Array.isArray(file)) return null;
if (!file.guild || !file.user) return null;
for (const id of file.guild) {
if (typeof id !== 'string') continue;
if (this.blacklist.guild.includes(id)) continue;
this.blacklist.guild.push(id);
}
for (const id of file.user) {
if (typeof id !== 'string') continue;
if (this.blacklist.user.includes(id)) continue;
this.blacklist.user.push(id);
}
return file;
}
exportBlacklist() {
let text = '{\n "guild": [\n ';
if (this.blacklist.guild.length) {
for (const id of this.blacklist.guild) {
text += `"${id}",\n `;
}
text = text.slice(0, -4);
}
text += '\n ],\n "user": [\n ';
if (this.blacklist.user.length) {
for (const id of this.blacklist.user) {
text += `"${id}",\n `;
}
text = text.slice(0, -4);
}
text += '\n ]\n}\n';
const buf = Buffer.from(text);
fs.writeFileSync(path.join(__dirname, '..', 'blacklist.json'), buf, { encoding: 'utf8' });
return buf;
}
importCommandLeaderboard(add = false) {
const read = fs.readFileSync(path.join(__dirname, '..', 'command-leaderboard.json'), {
encoding: 'utf8'
});
const file = JSON.parse(read);
if (typeof file !== 'object' || Array.isArray(file)) return null;
for (const [id, value] of Object.entries(file)) {
if (typeof value !== 'number') continue;
const found = this.registry.commands.get(id);
if (!found || found.uses === undefined) continue;
if (add) found.uses += value;
else found.uses = value;
}
return file;
}
exportCommandLeaderboard() {
let text = '{';
for (const command of this.registry.commands.values()) {
if (command.unknown) continue;
if (command.uses === undefined) continue;
text += `\n "${command.name}": ${command.uses},`;
}
text = text.slice(0, -1);
text += '\n}\n';
const buf = Buffer.from(text);
fs.writeFileSync(path.join(__dirname, '..', 'command-leaderboard.json'), buf, {
encoding: 'utf8'
});
return buf;
}
importLastRun() {
const read = fs.readFileSync(path.join(__dirname, '..', 'command-last-run.json'), {
encoding: 'utf8'
});
const file = JSON.parse(read);
if (typeof file !== 'object' || Array.isArray(file)) return null;
for (const [id, value] of Object.entries(file)) {
if (!value) continue;
const date = new Date(value);
if (date.toString() === 'Invalid Date') continue;
const found = this.registry.commands.get(id);
if (!found || found.lastRun === undefined) continue;
found.lastRun = date;
}
return file;
}
exportLastRun() {
let text = '{';
for (const command of this.registry.commands.values()) {
if (command.unknown) continue;
if (command.lastRun === undefined) continue;
text += `\n "${command.name}": ${command.lastRun ? `"${command.lastRun.toISOString()}"` : null},`;
}
text = text.slice(0, -1);
text += '\n}\n';
const buf = Buffer.from(text);
fs.writeFileSync(path.join(__dirname, '..', 'command-last-run.json'), buf, {
encoding: 'utf8'
});
return buf;
}
async fetchAdultSiteList(force = false) {
if (!force && this.adultSiteList) return this.adultSiteList;
const { text } = await request
-32
View File
@@ -1,32 +0,0 @@
const { Command } = require('discord.js-commando');
const { stripIndents } = require('common-tags');
module.exports = class XiaoCommand extends Command {
constructor(client, info) {
if (!info.argsPromptLimit) info.argsPromptLimit = 2;
super(client, info);
this.patronOnly = info.patronOnly || false;
this.argsSingleQuotes = info.argsSingleQuotes || false;
this.throttling = info.unknown ? null : info.throttling || { usages: 2, duration: 5 };
this.uses = 0;
this.lastRun = null;
this.credit = info.credit || [];
this.credit.push({
name: 'Dragon Fire',
url: 'https://github.com/dragonfire535',
reason: 'Code'
});
}
hasPermission(msg, ownerOverride) {
if (this.client.isOwner(msg.author)) return true;
if (this.patronOnly && !this.client.patreon.isPatron(msg.author.id)) {
return stripIndents`
The \`${this.name}\` command can only be used by Patrons.
Visit <https://www.patreon.com/xiaodiscord> to sign-up!
`;
}
return super.hasPermission(msg, ownerOverride);
}
};
+2 -2
View File
@@ -8,9 +8,9 @@ module.exports = class AutoReplyCommand extends Command {
this.throttling = null;
}
run(msg, args, fromPattern) {
run(msg) {
if (msg.guild && !msg.channel.permissionsFor(this.client.user).has('SEND_MESSAGES')) return null;
const text = this.generateText(fromPattern);
const text = this.generateText();
if (!text) return null;
return this.reply ? msg.reply(text) : msg.say(text);
}
+1 -5
View File
@@ -18,11 +18,7 @@ module.exports = class SubredditCommand extends Command {
});
}
async run(msg, { subreddit }, fromPattern) {
if (fromPattern) {
if (msg.guild && !msg.channel.permissionsFor(this.client.user).has('SEND_MESSAGES')) return null;
subreddit = msg.patternMatches[1];
}
async run(msg, { subreddit }) {
if (!subreddit) subreddit = typeof this.subreddit === 'function' ? this.subreddit() : this.subreddit;
try {
const post = await this.random(subreddit, msg.channel.nsfw);
+2 -2
View File
@@ -1,7 +1,7 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
const codeblock = /```(?:(\S+)\n)?\s*([^]+?)\s*```/i;
module.exports = class CodeArgumentType extends ArgumentType {
module.exports = class CodeArgument extends Argument {
constructor(client) {
super(client, 'code');
}
+4 -8
View File
@@ -1,14 +1,13 @@
const { ArgumentType, util: { disambiguation } } = require('discord.js-commando');
const { escapeMarkdown } = require('discord.js');
const Argument = require('../framework/ArgumentType');
module.exports = class FontArgumentType extends ArgumentType {
module.exports = class FontArgument extends Argument {
constructor(client) {
super(client, 'font');
}
validate(value) {
const choice = value.toLowerCase();
let found = this.client.fonts.filter(font => {
const found = this.client.fonts.filter(font => {
if (font.isFallback) return false;
if (font.name.toLowerCase().includes(choice)) return true;
if (font.filenameNoExt.toLowerCase().includes(choice)) return true;
@@ -22,10 +21,7 @@ module.exports = class FontArgumentType extends ArgumentType {
return false;
});
if (foundExact.size === 1) return true;
if (foundExact.size > 0) found = foundExact;
return found.size <= 15
? `${disambiguation(found.map(font => escapeMarkdown(font.filenameNoExt)), 'fonts', null)}\n`
: 'Multiple fonts found. Please be more specific.';
return false;
}
parse(value) {
+12 -12
View File
@@ -1,25 +1,25 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
module.exports = class ImageOrAvatarArgumentType extends ArgumentType {
module.exports = class ImageOrAvatarArgument extends Argument {
constructor(client) {
super(client, 'image-or-avatar');
}
async validate(value, msg, arg, currentMsg) {
const image = await this.client.registry.types.get('image').validate(value, msg, arg, currentMsg);
async validate(value, msg, arg) {
const image = await this.client.registry.types.get('image').validate(value, msg, arg);
if (image) return image;
return this.client.registry.types.get('user').validate(value, msg, arg, currentMsg);
return this.client.registry.types.get('user').validate(value, msg, arg);
}
async parse(value, msg, arg, currentMsg) {
const image = this.client.registry.types.get('image').parse(value, msg, arg, currentMsg);
async parse(value, msg, arg) {
const image = this.client.registry.types.get('image').parse(value, msg, arg);
if (image) return image;
const user = await this.client.registry.types.get('user').parse(value, msg, arg, currentMsg);
return user.displayAvatarURL({ format: 'png', size: 512 });
const user = await this.client.registry.types.get('user').parse(value, msg, arg);
return user.displayAvatarURL({ format: 'png', size: arg.avatarSize || 512 });
}
isEmpty(value, msg, arg, currentMsg) {
return this.client.registry.types.get('image').isEmpty(value, msg, arg, currentMsg)
&& this.client.registry.types.get('user').isEmpty(value, msg, arg, currentMsg);
isEmpty(value, msg, arg) {
return this.client.registry.types.get('image').isEmpty(value, msg, arg)
&& this.client.registry.types.get('user').isEmpty(value, msg, arg);
}
};
+10 -10
View File
@@ -1,18 +1,18 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
const fileTypeRe = /\.(jpe?g|png|gif|jfif|bmp)(\?.+)?$/i;
const request = require('node-superfetch');
const validURL = require('valid-url');
module.exports = class ImageArgumentType extends ArgumentType {
module.exports = class ImageArgument extends Argument {
constructor(client) {
super(client, 'image');
}
async validate(value, msg, arg, currentMsg) {
const attachment = currentMsg.attachments.first();
async validate(value, msg) {
const attachment = msg.attachments.first();
if (attachment) {
if (attachment.size > 8e+6) return 'Please provide an image under 8 MB.';
if (!fileTypeRe.test(attachment.name)) return 'Please only send PNG, JPG, BMP, or GIF format images.';
if (attachment.size > 8e+6) return false;
if (!fileTypeRe.test(attachment.name)) return false;
return true;
}
if (fileTypeRe.test(value.toLowerCase())) {
@@ -27,15 +27,15 @@ module.exports = class ImageArgumentType extends ArgumentType {
return false;
}
parse(value, msg, arg, currentMsg) {
const attachment = currentMsg.attachments.first();
parse(value, msg) {
const attachment = msg.attachments.first();
if (attachment) return attachment.url;
if (fileTypeRe.test(value.toLowerCase())) return value;
return null;
}
isEmpty(value, msg, arg, currentMsg) {
if (currentMsg.attachments.size) return false;
isEmpty(value, msg) {
if (msg.attachments.size) return false;
return !value;
}
};
+2 -2
View File
@@ -1,7 +1,7 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
const { months, shorthand } = require('../assets/json/month');
module.exports = class MonthArgumentType extends ArgumentType {
module.exports = class MonthArgument extends Argument {
constructor(client) {
super(client, 'month');
}
+2 -2
View File
@@ -1,6 +1,6 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
module.exports = class PokemonArgumentType extends ArgumentType {
module.exports = class PokemonArgument extends Argument {
constructor(client) {
super(client, 'pokemon');
}
+3 -3
View File
@@ -1,14 +1,14 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
const sherlock = require('sherlockjs');
module.exports = class SherlockType extends ArgumentType {
module.exports = class SherlockType extends Argument {
constructor(client) {
super(client, 'sherlock');
}
validate(value) {
const time = sherlock.parse(value);
if (!time.startDate) return 'Please provide a valid starting time.';
if (!time.startDate) return false;
return true;
}
+2 -2
View File
@@ -1,9 +1,9 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
const cityTimezones = require('city-timezones');
const { ZipToTz } = require('zip-to-timezone');
const moment = require('moment-timezone');
module.exports = class TimezoneType extends ArgumentType {
module.exports = class TimezoneType extends Argument {
constructor(client) {
super(client, 'timezone');
}
+2 -2
View File
@@ -1,8 +1,8 @@
const { ArgumentType } = require('discord.js-commando');
const Argument = require('../framework/ArgumentType');
const { URL } = require('url');
const validURL = require('valid-url');
module.exports = class UrlType extends ArgumentType {
module.exports = class UrlType extends Argument {
constructor(client) {
super(client, 'url');
}
+4
View File
@@ -17,6 +17,10 @@ module.exports = class Util {
return new Promise(resolve => setTimeout(resolve, ms));
}
static escapeRegex(str) {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
}
static shuffle(array) {
const arr = array.slice(0);
for (let i = arr.length - 1; i >= 0; i--) {