mirror of
https://github.com/arthur-pbty/xiao.git
synced 2026-06-03 23:36:43 +02:00
Framework Rewrite
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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'));
|
||||
}
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user