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:
@@ -1,8 +1,8 @@
|
|||||||
require('dotenv').config();
|
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 { mkdir } = require('fs/promises');
|
||||||
const path = require('path');
|
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 = require('./structures/Client');
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
commandPrefix: XIAO_PREFIX,
|
commandPrefix: XIAO_PREFIX,
|
||||||
@@ -16,7 +16,6 @@ const client = new Client({
|
|||||||
intents: [Intents.NON_PRIVILEGED, Intents.FLAGS.GUILD_MEMBERS]
|
intents: [Intents.NON_PRIVILEGED, Intents.FLAGS.GUILD_MEMBERS]
|
||||||
});
|
});
|
||||||
const { formatNumber, checkFileExists } = require('./util/Util');
|
const { formatNumber, checkFileExists } = require('./util/Util');
|
||||||
const aprilFoolsMsgs = require('./assets/json/april-fools');
|
|
||||||
|
|
||||||
client.registry
|
client.registry
|
||||||
.registerDefaultTypes()
|
.registerDefaultTypes()
|
||||||
@@ -52,13 +51,6 @@ client.registry
|
|||||||
['roleplay', 'Roleplay'],
|
['roleplay', 'Roleplay'],
|
||||||
['other', 'Other']
|
['other', 'Other']
|
||||||
])
|
])
|
||||||
.registerDefaultCommands({
|
|
||||||
help: false,
|
|
||||||
ping: false,
|
|
||||||
prefix: false,
|
|
||||||
commandState: false,
|
|
||||||
unknownCommand: false
|
|
||||||
})
|
|
||||||
.registerCommandsIn(path.join(__dirname, 'commands'));
|
.registerCommandsIn(path.join(__dirname, 'commands'));
|
||||||
|
|
||||||
client.on('ready', async () => {
|
client.on('ready', async () => {
|
||||||
@@ -248,7 +240,7 @@ client.on('message', async msg => {
|
|||||||
const hasEmbed = msg.embeds.length !== 0;
|
const hasEmbed = msg.embeds.length !== 0;
|
||||||
if (msg.author.bot || (!hasText && !hasImage && !hasEmbed)) return;
|
if (msg.author.bot || (!hasText && !hasImage && !hasEmbed)) return;
|
||||||
if (client.blacklist.user.includes(msg.author.id)) 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;
|
if (client.games.has(msg.channel.id)) return;
|
||||||
|
|
||||||
// Cleverbot handler
|
// Cleverbot handler
|
||||||
@@ -340,8 +332,8 @@ client.on('guildDelete', async guild => {
|
|||||||
client.on('guildMemberRemove', async member => {
|
client.on('guildMemberRemove', async member => {
|
||||||
if (member.id === client.user.id) return null;
|
if (member.id === client.user.id) return null;
|
||||||
const channel = member.guild.systemChannel;
|
const channel = member.guild.systemChannel;
|
||||||
if (!channel || !channel.permissionsFor(client.user).has(Permissions.FLAGS.SEND_MESSAGES)) return null;
|
if (!channel || !channel.permissionsFor(client.user).has('SEND_MESSAGES')) return null;
|
||||||
if (member.guild.systemChannelFlags.has(SystemChannelFlags.FLAGS.SUPPRESS_JOIN_NOTIFICATIONS)) return null;
|
if (member.guild.systemChannelFlags.has('SUPPRESS_JOIN_NOTIFICATIONS')) return null;
|
||||||
if (channel.topic && channel.topic.includes('<xiao:disable-leave>')) return null;
|
if (channel.topic && channel.topic.includes('<xiao:disable-leave>')) return null;
|
||||||
try {
|
try {
|
||||||
const leaveMessage = client.leaveMessages[Math.floor(Math.random() * client.leaveMessages.length)];
|
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 => {
|
client.on('commandRun', async command => {
|
||||||
if (command.unknown) return;
|
if (command.unknown) return;
|
||||||
client.logger.info(`[COMMAND] ${command.name} was used.`);
|
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();
|
const channel = await client.fetchCommandChannel();
|
||||||
channel.send(`\`${command.name}\` was used! It has now been used **${formatNumber(command.uses)}** times!`)
|
channel.send(`\`${command.name}\` was used! It has now been used **${formatNumber(command.uses)}** times!`)
|
||||||
.catch(() => null);
|
.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.on('commandError', (command, err) => client.logger.error(`[COMMAND:${command.name}]\n${err.stack}`));
|
||||||
|
|
||||||
client.login(XIAO_TOKEN);
|
client.login(XIAO_TOKEN);
|
||||||
|
|||||||
@@ -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."
|
|
||||||
]
|
|
||||||
@@ -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": ""
|
||||||
|
}
|
||||||
@@ -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';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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,7 +1,7 @@
|
|||||||
const Command = require('../../structures/Command');
|
const Command = require('../../structures/Command');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const { MessageEmbed } = require('discord.js');
|
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 {
|
module.exports = class RoleCommand extends Command {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const Command = require('../../structures/Command');
|
const Command = require('../../structures/Command');
|
||||||
const { MessageEmbed } = require('discord.js');
|
const { MessageEmbed } = require('discord.js');
|
||||||
const { util: { permissions } } = require('discord.js-commando');
|
const permissions = require('../../assets/json/permission-names');
|
||||||
const { stripIndents } = require('common-tags');
|
const { stripIndents } = require('common-tags');
|
||||||
|
|
||||||
module.exports = class HelpCommand extends Command {
|
module.exports = class HelpCommand extends Command {
|
||||||
@@ -31,7 +31,7 @@ module.exports = class HelpCommand extends Command {
|
|||||||
const embed = new MessageEmbed()
|
const embed = new MessageEmbed()
|
||||||
.setTitle(`Command List (Page ${i + 1})`)
|
.setTitle(`Command List (Page ${i + 1})`)
|
||||||
.setDescription(stripIndents`
|
.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.'}
|
${nsfw ? '' : 'Use in an NSFW channel to see NSFW commands.'}
|
||||||
`)
|
`)
|
||||||
.setColor(0x00AE86);
|
.setColor(0x00AE86);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
const Command = require('../../structures/Command');
|
const Command = require('../../structures/Command');
|
||||||
const { MessageEmbed, version: djsVersion } = require('discord.js');
|
const { MessageEmbed, version: djsVersion } = require('discord.js');
|
||||||
const { version: commandoVersion } = require('discord.js-commando');
|
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
require('moment-duration-format');
|
require('moment-duration-format');
|
||||||
const { formatNumber, embedURL } = require('../../util/Util');
|
const { formatNumber, embedURL } = require('../../util/Util');
|
||||||
@@ -39,7 +38,7 @@ module.exports = class InfoCommand extends Command {
|
|||||||
.addField('❯ Version', `v${version}`, true)
|
.addField('❯ Version', `v${version}`, true)
|
||||||
.addField('❯ Node.js', process.version, true)
|
.addField('❯ Node.js', process.version, true)
|
||||||
.addField('❯ Discord.js', `v${djsVersion}`, true)
|
.addField('❯ Discord.js', `v${djsVersion}`, true)
|
||||||
.addField('❯ Commando', `v${commandoVersion}`, true)
|
.addField('❯ Framework', 'Custom', true)
|
||||||
.addField('❯ Dependencies', Object.keys(deps).sort().join(', '));
|
.addField('❯ Dependencies', Object.keys(deps).sort().join(', '));
|
||||||
return msg.embed(embed);
|
return msg.embed(embed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const Command = require('../../structures/Command');
|
const Command = require('../../structures/Command');
|
||||||
const { stripIndents } = require('common-tags');
|
|
||||||
|
|
||||||
module.exports = class PrefixCommand extends Command {
|
module.exports = class PrefixCommand extends Command {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
@@ -14,9 +13,6 @@ module.exports = class PrefixCommand extends Command {
|
|||||||
|
|
||||||
run(msg) {
|
run(msg) {
|
||||||
const prefix = msg.guild ? msg.guild.commandPrefix : this.client.commandPrefix;
|
const prefix = msg.guild ? msg.guild.commandPrefix : this.client.commandPrefix;
|
||||||
return msg.reply(stripIndents`
|
return msg.reply(prefix ? `The command prefix is \`${prefix}\`.` : 'There is no command prefix.');
|
||||||
${prefix ? `The command prefix is \`${prefix}\`.` : 'There is no command prefix.'}
|
|
||||||
To run a command, use ${msg.anyUsage('<command>')}.
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module.exports = class UnknownCommandCommand extends Command {
|
|||||||
run(msg) {
|
run(msg) {
|
||||||
if (!msg.guild) return null;
|
if (!msg.guild) return null;
|
||||||
const commands = this.makeCommandArray(this.client.isOwner(msg.author), msg.channel.nsfw);
|
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 str = command ? command[2] : msg.content.split(' ')[0];
|
||||||
const results = didYouMean(str, commands, { returnType: ReturnTypeEnums.ALL_SORTED_MATCHES });
|
const results = didYouMean(str, commands, { returnType: ReturnTypeEnums.ALL_SORTED_MATCHES });
|
||||||
return msg.reply(stripIndents`
|
return msg.reply(stripIndents`
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
+3
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "xiao",
|
"name": "xiao",
|
||||||
"version": "140.3.1",
|
"version": "141.0.0",
|
||||||
"description": "Your personal server companion.",
|
"description": "Your personal server companion.",
|
||||||
"main": "Xiao.js",
|
"main": "Xiao.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -15,12 +15,10 @@
|
|||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bot",
|
"bot",
|
||||||
"commando",
|
|
||||||
"discord",
|
"discord",
|
||||||
"discord-api",
|
"discord-api",
|
||||||
"discord-bot",
|
"discord-bot",
|
||||||
"discord-js",
|
"discord-js"
|
||||||
"discord-js-commando"
|
|
||||||
],
|
],
|
||||||
"author": "dragonfire535 <danielbodendahl@gmail.com>",
|
"author": "dragonfire535 <danielbodendahl@gmail.com>",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
@@ -52,7 +50,6 @@
|
|||||||
"custom-translate": "^2.2.8",
|
"custom-translate": "^2.2.8",
|
||||||
"didyoumean2": "^4.2.0",
|
"didyoumean2": "^4.2.0",
|
||||||
"discord.js": "github:discordjs/discord.js",
|
"discord.js": "github:discordjs/discord.js",
|
||||||
"discord.js-commando": "github:discordjs/Commando",
|
|
||||||
"discord.js-docs": "github:TeeSeal/discord.js-docs",
|
"discord.js-docs": "github:TeeSeal/discord.js-docs",
|
||||||
"dotenv": "^9.0.2",
|
"dotenv": "^9.0.2",
|
||||||
"emoji-regex": "^9.2.2",
|
"emoji-regex": "^9.2.2",
|
||||||
@@ -70,6 +67,7 @@
|
|||||||
"kuroshiro": "^1.1.2",
|
"kuroshiro": "^1.1.2",
|
||||||
"kuroshiro-analyzer-kuromoji": "^1.1.0",
|
"kuroshiro-analyzer-kuromoji": "^1.1.0",
|
||||||
"mathjs": "^9.4.0",
|
"mathjs": "^9.4.0",
|
||||||
|
"minimist": "^1.2.5",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"moment-duration-format": "^2.3.2",
|
"moment-duration-format": "^2.3.2",
|
||||||
"moment-timezone": "^0.5.33",
|
"moment-timezone": "^0.5.33",
|
||||||
|
|||||||
+2
-109
@@ -1,4 +1,4 @@
|
|||||||
const { CommandoClient } = require('discord.js-commando');
|
const CommandClient = require('../framework/Client');
|
||||||
const { WebhookClient } = require('discord.js');
|
const { WebhookClient } = require('discord.js');
|
||||||
const request = require('node-superfetch');
|
const request = require('node-superfetch');
|
||||||
const Collection = require('@discordjs/collection');
|
const Collection = require('@discordjs/collection');
|
||||||
@@ -12,7 +12,6 @@ const path = require('path');
|
|||||||
const Redis = require('./Redis');
|
const Redis = require('./Redis');
|
||||||
const Font = require('./Font');
|
const Font = require('./Font');
|
||||||
const BotList = require('./BotList');
|
const BotList = require('./BotList');
|
||||||
const Patreon = require('./Patreon');
|
|
||||||
const PhoneManager = require('./phone/PhoneManager');
|
const PhoneManager = require('./phone/PhoneManager');
|
||||||
const TimerManager = require('./remind/TimerManager');
|
const TimerManager = require('./remind/TimerManager');
|
||||||
const PokemonStore = require('./pokemon/PokemonStore');
|
const PokemonStore = require('./pokemon/PokemonStore');
|
||||||
@@ -26,7 +25,7 @@ const {
|
|||||||
COMMAND_CHANNEL_ID
|
COMMAND_CHANNEL_ID
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
module.exports = class XiaoClient extends CommandoClient {
|
module.exports = class XiaoClient extends CommandClient {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(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.webhook = new WebhookClient(XIAO_WEBHOOK_ID, XIAO_WEBHOOK_TOKEN, { disableMentions: 'everyone' });
|
||||||
this.timers = new TimerManager(this);
|
this.timers = new TimerManager(this);
|
||||||
this.botList = new BotList(this);
|
this.botList = new BotList(this);
|
||||||
this.patreon = new Patreon();
|
|
||||||
this.blacklist = { guild: [], user: [] };
|
|
||||||
this.pokemon = new PokemonStore();
|
this.pokemon = new PokemonStore();
|
||||||
this.games = new Collection();
|
this.games = new Collection();
|
||||||
this.dispatchers = new Map();
|
this.dispatchers = new Map();
|
||||||
@@ -72,110 +69,6 @@ module.exports = class XiaoClient extends CommandoClient {
|
|||||||
moment.tz.link('America/New_York|Dragon');
|
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) {
|
async fetchAdultSiteList(force = false) {
|
||||||
if (!force && this.adultSiteList) return this.adultSiteList;
|
if (!force && this.adultSiteList) return this.adultSiteList;
|
||||||
const { text } = await request
|
const { text } = await request
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -8,9 +8,9 @@ module.exports = class AutoReplyCommand extends Command {
|
|||||||
this.throttling = null;
|
this.throttling = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
run(msg, args, fromPattern) {
|
run(msg) {
|
||||||
if (msg.guild && !msg.channel.permissionsFor(this.client.user).has('SEND_MESSAGES')) return null;
|
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;
|
if (!text) return null;
|
||||||
return this.reply ? msg.reply(text) : msg.say(text);
|
return this.reply ? msg.reply(text) : msg.say(text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,7 @@ module.exports = class SubredditCommand extends Command {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(msg, { subreddit }, fromPattern) {
|
async run(msg, { subreddit }) {
|
||||||
if (fromPattern) {
|
|
||||||
if (msg.guild && !msg.channel.permissionsFor(this.client.user).has('SEND_MESSAGES')) return null;
|
|
||||||
subreddit = msg.patternMatches[1];
|
|
||||||
}
|
|
||||||
if (!subreddit) subreddit = typeof this.subreddit === 'function' ? this.subreddit() : this.subreddit;
|
if (!subreddit) subreddit = typeof this.subreddit === 'function' ? this.subreddit() : this.subreddit;
|
||||||
try {
|
try {
|
||||||
const post = await this.random(subreddit, msg.channel.nsfw);
|
const post = await this.random(subreddit, msg.channel.nsfw);
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
const { ArgumentType } = require('discord.js-commando');
|
const Argument = require('../framework/ArgumentType');
|
||||||
const codeblock = /```(?:(\S+)\n)?\s*([^]+?)\s*```/i;
|
const codeblock = /```(?:(\S+)\n)?\s*([^]+?)\s*```/i;
|
||||||
|
|
||||||
module.exports = class CodeArgumentType extends ArgumentType {
|
module.exports = class CodeArgument extends Argument {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
super(client, 'code');
|
super(client, 'code');
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-8
@@ -1,14 +1,13 @@
|
|||||||
const { ArgumentType, util: { disambiguation } } = require('discord.js-commando');
|
const Argument = require('../framework/ArgumentType');
|
||||||
const { escapeMarkdown } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = class FontArgumentType extends ArgumentType {
|
module.exports = class FontArgument extends Argument {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
super(client, 'font');
|
super(client, 'font');
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(value) {
|
validate(value) {
|
||||||
const choice = value.toLowerCase();
|
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.isFallback) return false;
|
||||||
if (font.name.toLowerCase().includes(choice)) return true;
|
if (font.name.toLowerCase().includes(choice)) return true;
|
||||||
if (font.filenameNoExt.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;
|
return false;
|
||||||
});
|
});
|
||||||
if (foundExact.size === 1) return true;
|
if (foundExact.size === 1) return true;
|
||||||
if (foundExact.size > 0) found = foundExact;
|
return false;
|
||||||
return found.size <= 15
|
|
||||||
? `${disambiguation(found.map(font => escapeMarkdown(font.filenameNoExt)), 'fonts', null)}\n`
|
|
||||||
: 'Multiple fonts found. Please be more specific.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(value) {
|
parse(value) {
|
||||||
|
|||||||
+12
-12
@@ -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) {
|
constructor(client) {
|
||||||
super(client, 'image-or-avatar');
|
super(client, 'image-or-avatar');
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(value, msg, arg, currentMsg) {
|
async validate(value, msg, arg) {
|
||||||
const image = await this.client.registry.types.get('image').validate(value, msg, arg, currentMsg);
|
const image = await this.client.registry.types.get('image').validate(value, msg, arg);
|
||||||
if (image) return image;
|
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) {
|
async parse(value, msg, arg) {
|
||||||
const image = this.client.registry.types.get('image').parse(value, msg, arg, currentMsg);
|
const image = this.client.registry.types.get('image').parse(value, msg, arg);
|
||||||
if (image) return image;
|
if (image) return image;
|
||||||
const user = await this.client.registry.types.get('user').parse(value, msg, arg, currentMsg);
|
const user = await this.client.registry.types.get('user').parse(value, msg, arg);
|
||||||
return user.displayAvatarURL({ format: 'png', size: 512 });
|
return user.displayAvatarURL({ format: 'png', size: arg.avatarSize || 512 });
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty(value, msg, arg, currentMsg) {
|
isEmpty(value, msg, arg) {
|
||||||
return this.client.registry.types.get('image').isEmpty(value, msg, arg, currentMsg)
|
return this.client.registry.types.get('image').isEmpty(value, msg, arg)
|
||||||
&& this.client.registry.types.get('user').isEmpty(value, msg, arg, currentMsg);
|
&& this.client.registry.types.get('user').isEmpty(value, msg, arg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+10
-10
@@ -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 fileTypeRe = /\.(jpe?g|png|gif|jfif|bmp)(\?.+)?$/i;
|
||||||
const request = require('node-superfetch');
|
const request = require('node-superfetch');
|
||||||
const validURL = require('valid-url');
|
const validURL = require('valid-url');
|
||||||
|
|
||||||
module.exports = class ImageArgumentType extends ArgumentType {
|
module.exports = class ImageArgument extends Argument {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
super(client, 'image');
|
super(client, 'image');
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(value, msg, arg, currentMsg) {
|
async validate(value, msg) {
|
||||||
const attachment = currentMsg.attachments.first();
|
const attachment = msg.attachments.first();
|
||||||
if (attachment) {
|
if (attachment) {
|
||||||
if (attachment.size > 8e+6) return 'Please provide an image under 8 MB.';
|
if (attachment.size > 8e+6) return false;
|
||||||
if (!fileTypeRe.test(attachment.name)) return 'Please only send PNG, JPG, BMP, or GIF format images.';
|
if (!fileTypeRe.test(attachment.name)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (fileTypeRe.test(value.toLowerCase())) {
|
if (fileTypeRe.test(value.toLowerCase())) {
|
||||||
@@ -27,15 +27,15 @@ module.exports = class ImageArgumentType extends ArgumentType {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(value, msg, arg, currentMsg) {
|
parse(value, msg) {
|
||||||
const attachment = currentMsg.attachments.first();
|
const attachment = msg.attachments.first();
|
||||||
if (attachment) return attachment.url;
|
if (attachment) return attachment.url;
|
||||||
if (fileTypeRe.test(value.toLowerCase())) return value;
|
if (fileTypeRe.test(value.toLowerCase())) return value;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty(value, msg, arg, currentMsg) {
|
isEmpty(value, msg) {
|
||||||
if (currentMsg.attachments.size) return false;
|
if (msg.attachments.size) return false;
|
||||||
return !value;
|
return !value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
const { ArgumentType } = require('discord.js-commando');
|
const Argument = require('../framework/ArgumentType');
|
||||||
const { months, shorthand } = require('../assets/json/month');
|
const { months, shorthand } = require('../assets/json/month');
|
||||||
|
|
||||||
module.exports = class MonthArgumentType extends ArgumentType {
|
module.exports = class MonthArgument extends Argument {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
super(client, 'month');
|
super(client, 'month');
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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) {
|
constructor(client) {
|
||||||
super(client, 'pokemon');
|
super(client, 'pokemon');
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
const { ArgumentType } = require('discord.js-commando');
|
const Argument = require('../framework/ArgumentType');
|
||||||
const sherlock = require('sherlockjs');
|
const sherlock = require('sherlockjs');
|
||||||
|
|
||||||
module.exports = class SherlockType extends ArgumentType {
|
module.exports = class SherlockType extends Argument {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
super(client, 'sherlock');
|
super(client, 'sherlock');
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(value) {
|
validate(value) {
|
||||||
const time = sherlock.parse(value);
|
const time = sherlock.parse(value);
|
||||||
if (!time.startDate) return 'Please provide a valid starting time.';
|
if (!time.startDate) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
const { ArgumentType } = require('discord.js-commando');
|
const Argument = require('../framework/ArgumentType');
|
||||||
const cityTimezones = require('city-timezones');
|
const cityTimezones = require('city-timezones');
|
||||||
const { ZipToTz } = require('zip-to-timezone');
|
const { ZipToTz } = require('zip-to-timezone');
|
||||||
const moment = require('moment-timezone');
|
const moment = require('moment-timezone');
|
||||||
|
|
||||||
module.exports = class TimezoneType extends ArgumentType {
|
module.exports = class TimezoneType extends Argument {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
super(client, 'timezone');
|
super(client, 'timezone');
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
const { ArgumentType } = require('discord.js-commando');
|
const Argument = require('../framework/ArgumentType');
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
const validURL = require('valid-url');
|
const validURL = require('valid-url');
|
||||||
|
|
||||||
module.exports = class UrlType extends ArgumentType {
|
module.exports = class UrlType extends Argument {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
super(client, 'url');
|
super(client, 'url');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ module.exports = class Util {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static escapeRegex(str) {
|
||||||
|
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
static shuffle(array) {
|
static shuffle(array) {
|
||||||
const arr = array.slice(0);
|
const arr = array.slice(0);
|
||||||
for (let i = arr.length - 1; i >= 0; i--) {
|
for (let i = arr.length - 1; i >= 0; i--) {
|
||||||
|
|||||||
Reference in New Issue
Block a user