Split command resolution part of help command

This commit is contained in:
WatDuhHekBro 2021-04-09 23:06:16 -05:00
parent 20fb2135c7
commit 72ff144cc0
12 changed files with 146 additions and 86 deletions

View file

@ -1,61 +1,28 @@
import {Command, NamedCommand, loadableCommands, categories, getPermissionName, CHANNEL_TYPE} from "../../core"; import {Command, NamedCommand, CHANNEL_TYPE, getPermissionName, getCommandList, getCommandInfo} from "../../core";
import {toTitleCase, requireAllCasesHandledFor} from "../../lib"; import {requireAllCasesHandledFor} from "../../lib";
export default new NamedCommand({ export default new NamedCommand({
description: "Lists all commands. If a command is specified, their arguments are listed as well.", description: "Lists all commands. If a command is specified, their arguments are listed as well.",
usage: "([command, [subcommand/type], ...])", usage: "([command, [subcommand/type], ...])",
aliases: ["h"], aliases: ["h"],
async run({message, channel, guild, author, member, client, args}) { async run({message, channel, guild, author, member, client, args}) {
const commands = await loadableCommands; const commands = await getCommandList();
let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``; let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``;
for (const [category, headers] of categories) { for (const [category, commandList] of commands) {
let tmp = `\n\n===[ ${toTitleCase(category)} ]===`; output += `\n\n===[ ${category} ]===`;
// Ignore empty categories, including ["test"]. for (const command of commandList) output += `\n- \`${command.name}\`: ${command.description}`;
let hasActualCommands = false;
for (const header of headers) {
if (header !== "test") {
const command = commands.get(header)!;
tmp += `\n- \`${header}\`: ${command.description}`;
hasActualCommands = true;
}
}
if (hasActualCommands) output += tmp;
} }
channel.send(output, {split: true}); channel.send(output, {split: true});
}, },
any: new Command({ any: new Command({
async run({message, channel, guild, author, member, client, args}) { async run({message, channel, guild, author, member, client, args}) {
// Setup the root command const [result, category] = await getCommandInfo(args);
const commands = await loadableCommands; if (typeof result === "string") return channel.send(result);
let header = args.shift() as string;
let command = commands.get(header);
if (!command || header === "test") return channel.send(`No command found by the name \`${header}\`.`);
if (!(command instanceof NamedCommand))
return channel.send(`Command is not a proper instance of NamedCommand.`);
if (command.name) header = command.name;
// Search categories
let category = "Unknown";
for (const [referenceCategory, headers] of categories) {
if (headers.includes(header)) {
category = toTitleCase(referenceCategory);
break;
}
}
// Gather info
const result = await command.resolveInfo(args);
if (result.type === "error") return channel.send(result.message);
let append = ""; let append = "";
command = result.command; const command = result.command;
const header = result.args.length > 0 ? `${result.header} ${result.args.join(" ")}` : result.header;
if (result.args.length > 0) header += " " + result.args.join(" ");
if (command.usage === "") { if (command.usage === "") {
const list: string[] = []; const list: string[] = [];

View file

@ -102,7 +102,7 @@ export default new NamedCommand({
description: "Display info about a guild by finding its name or ID.", description: "Display info about a guild by finding its name or ID.",
async run({message, channel, guild, author, member, client, args}) { async run({message, channel, guild, author, member, client, args}) {
// If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild // If a guild ID is provided (avoid the "number" subcommand because of inaccuracies), search for that guild
if (args.length === 1 && /^\d{17,19}$/.test(args[0])) { if (args.length === 1 && /^\d{17,}$/.test(args[0])) {
const id = args[0]; const id = args[0];
const targetGuild = client.guilds.cache.get(id); const targetGuild = client.guilds.cache.get(id);

View file

@ -16,7 +16,7 @@ export default new NamedCommand({
"Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i",
async run({message, channel, guild, author, member, client, args}) { async run({message, channel, guild, author, member, client, args}) {
// If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward) // If a guild ID is provided, filter all emotes by that guild (but only if there aren't any arguments afterward)
if (args.length === 1 && /^\d{17,19}$/.test(args[0])) { if (args.length === 1 && /^\d{17,}$/.test(args[0])) {
const guildID: string = args[0]; const guildID: string = args[0];
displayEmoteList( displayEmoteList(

View file

@ -17,8 +17,8 @@ export default new NamedCommand({
// handles reacts by message id/distance // handles reacts by message id/distance
else if (args.length >= 2) { else if (args.length >= 2) {
const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator.
const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/;
const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/; const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/;
// https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button) // https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
if (URLPattern.test(last)) { if (URLPattern.test(last)) {
@ -70,7 +70,7 @@ export default new NamedCommand({
args.pop(); args.pop();
} }
// <Message ID> // <Message ID>
else if (/^\d{17,19}$/.test(last)) { else if (/^\d{17,}$/.test(last)) {
try { try {
target = await channel.messages.fetch(last); target = await channel.messages.fetch(last);
} catch { } catch {

View file

@ -30,14 +30,16 @@ import {parseVars, requireAllCasesHandledFor} from "../lib";
*/ */
// RegEx patterns used for identifying/extracting each type from a string argument. // RegEx patterns used for identifying/extracting each type from a string argument.
// The reason why \d{17,} is used is because the max safe number for JS numbers is 16 characters when stringified (decimal). Beyond that are IDs.
const patterns = { const patterns = {
channel: /^<#(\d{17,19})>$/, channel: /^<#(\d{17,})>$/,
role: /^<@&(\d{17,19})>$/, role: /^<@&(\d{17,})>$/,
emote: /^<a?:.*?:(\d{17,19})>$/, emote: /^<a?:.*?:(\d{17,})>$/,
messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,19}|@me)\/(\d{17,19})\/(\d{17,19})$/, // The message type won't include <username>#<tag>. At that point, you may as well just use a search usernames function. Even then, tags would only be taken into account to differentiate different users with identical usernames.
messagePair: /^(\d{17,19})-(\d{17,19})$/, messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,}|@me)\/(\d{17,})\/(\d{17,})$/,
user: /^<@!?(\d{17,19})>$/, messagePair: /^(\d{17,})-(\d{17,})$/,
id: /^(\d{17,19})$/ user: /^<@!?(\d{17,})>$/,
id: /^(\d{17,})$/
}; };
// Maybe add a guild redirect... somehow? // Maybe add a guild redirect... somehow?
@ -106,9 +108,10 @@ interface ExecuteCommandMetadata {
permission: number; permission: number;
nsfw: boolean; nsfw: boolean;
channelType: CHANNEL_TYPE; channelType: CHANNEL_TYPE;
symbolicArgs: string[]; // i.e. <channel> instead of <#...>
} }
interface CommandInfo { export interface CommandInfo {
readonly type: "info"; readonly type: "info";
readonly command: Command; readonly command: Command;
readonly subcommandInfo: Collection<string, Command>; readonly subcommandInfo: Collection<string, Command>;
@ -117,6 +120,7 @@ interface CommandInfo {
readonly nsfw: boolean; readonly nsfw: boolean;
readonly channelType: CHANNEL_TYPE; readonly channelType: CHANNEL_TYPE;
readonly args: string[]; readonly args: string[];
readonly header: string;
} }
interface CommandInfoError { interface CommandInfoError {
@ -131,14 +135,9 @@ interface CommandInfoMetadata {
args: string[]; args: string[];
usage: string; usage: string;
readonly originalArgs: string[]; readonly originalArgs: string[];
readonly header: string;
} }
export const defaultMetadata = {
permission: 0,
nsfw: false,
channelType: CHANNEL_TYPE.ANY
};
// Each Command instance represents a block that links other Command instances under it. // Each Command instance represents a block that links other Command instances under it.
export class Command { export class Command {
public readonly description: string; public readonly description: string;
@ -298,12 +297,15 @@ export class Command {
// Then capture any potential errors. // Then capture any potential errors.
try { try {
if (typeof this.run === "string") { if (typeof this.run === "string") {
// Although I *could* add an option in the launcher to attach arbitrary variables to this var string...
// I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string.
await menu.channel.send( await menu.channel.send(
parseVars( parseVars(
this.run, this.run,
{ {
author: menu.author.toString(), author: menu.author.toString(),
prefix: getPrefix(menu.guild) prefix: getPrefix(menu.guild),
command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}`
}, },
"???" "???"
) )
@ -332,6 +334,7 @@ export class Command {
const isMessagePair = patterns.messagePair.test(param); const isMessagePair = patterns.messagePair.test(param);
if (this.subcommands.has(param)) { if (this.subcommands.has(param)) {
metadata.symbolicArgs.push(param);
return this.subcommands.get(param)!.execute(args, menu, metadata); return this.subcommands.get(param)!.execute(args, menu, metadata);
} else if (this.channel && patterns.channel.test(param)) { } else if (this.channel && patterns.channel.test(param)) {
const id = patterns.channel.exec(param)![1]; const id = patterns.channel.exec(param)![1];
@ -339,6 +342,7 @@ export class Command {
// Users can only enter in this format for text channels, so this restricts it to that. // Users can only enter in this format for text channels, so this restricts it to that.
if (channel instanceof TextChannel) { if (channel instanceof TextChannel) {
metadata.symbolicArgs.push("<channel>");
menu.args.push(channel); menu.args.push(channel);
return this.channel.execute(args, menu, metadata); return this.channel.execute(args, menu, metadata);
} else { } else {
@ -358,6 +362,7 @@ export class Command {
const role = menu.guild.roles.cache.get(id); const role = menu.guild.roles.cache.get(id);
if (role) { if (role) {
metadata.symbolicArgs.push("<role>");
menu.args.push(role); menu.args.push(role);
return this.role.execute(args, menu, metadata); return this.role.execute(args, menu, metadata);
} else { } else {
@ -370,6 +375,7 @@ export class Command {
const emote = menu.client.emojis.cache.get(id); const emote = menu.client.emojis.cache.get(id);
if (emote) { if (emote) {
metadata.symbolicArgs.push("<emote>");
menu.args.push(emote); menu.args.push(emote);
return this.emote.execute(args, menu, metadata); return this.emote.execute(args, menu, metadata);
} else { } else {
@ -395,6 +401,7 @@ export class Command {
if (channel instanceof TextChannel || channel instanceof DMChannel) { if (channel instanceof TextChannel || channel instanceof DMChannel) {
try { try {
metadata.symbolicArgs.push("<message>");
menu.args.push(await channel.messages.fetch(messageID)); menu.args.push(await channel.messages.fetch(messageID));
return this.message.execute(args, menu, metadata); return this.message.execute(args, menu, metadata);
} catch { } catch {
@ -411,6 +418,7 @@ export class Command {
const id = patterns.user.exec(param)![1]; const id = patterns.user.exec(param)![1];
try { try {
metadata.symbolicArgs.push("<user>");
menu.args.push(await menu.client.users.fetch(id)); menu.args.push(await menu.client.users.fetch(id));
return this.user.execute(args, menu, metadata); return this.user.execute(args, menu, metadata);
} catch { } catch {
@ -419,6 +427,7 @@ export class Command {
}; };
} }
} else if (this.id && this.idType && patterns.id.test(param)) { } else if (this.id && this.idType && patterns.id.test(param)) {
metadata.symbolicArgs.push("<id>");
const id = patterns.id.exec(param)![1]; const id = patterns.id.exec(param)![1];
// Probably modularize the findXByY code in general in libd. // Probably modularize the findXByY code in general in libd.
@ -486,9 +495,11 @@ export class Command {
requireAllCasesHandledFor(this.idType); requireAllCasesHandledFor(this.idType);
} }
} else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") { } else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") {
metadata.symbolicArgs.push("<number>");
menu.args.push(Number(param)); menu.args.push(Number(param));
return this.number.execute(args, menu, metadata); return this.number.execute(args, menu, metadata);
} else if (this.any) { } else if (this.any) {
metadata.symbolicArgs.push("<any>");
menu.args.push(param); menu.args.push(param);
return this.any.execute(args, menu, metadata); return this.any.execute(args, menu, metadata);
} else { } else {
@ -502,8 +513,16 @@ export class Command {
} }
// What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands. // What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands.
public async resolveInfo(args: string[]): Promise<CommandInfo | CommandInfoError> { public async resolveInfo(args: string[], header: string): Promise<CommandInfo | CommandInfoError> {
return this.resolveInfoInternal(args, {...defaultMetadata, args: [], usage: "", originalArgs: [...args]}); return this.resolveInfoInternal(args, {
permission: 0,
nsfw: false,
channelType: CHANNEL_TYPE.ANY,
header,
args: [],
usage: "",
originalArgs: [...args]
});
} }
private async resolveInfoInternal( private async resolveInfoInternal(

View file

@ -1,6 +1,5 @@
import {Client, Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; import {Client, Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js";
import {loadableCommands} from "./loader"; import {loadableCommands} from "./loader";
import {defaultMetadata} from "./command";
import {getPrefix} from "./interface"; import {getPrefix} from "./interface";
// For custom message events that want to cancel the command handler on certain conditions. // For custom message events that want to cancel the command handler on certain conditions.
@ -20,6 +19,13 @@ const lastCommandInfo: {
channel: null channel: null
}; };
const defaultMetadata = {
permission: 0,
nsfw: false,
channelType: 0, // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
symbolicArgs: []
};
// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined. // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
// Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild. // Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild.
export function attachMessageHandlerToClient(client: Client) { export function attachMessageHandlerToClient(client: Client) {

View file

@ -1,3 +1,4 @@
// Onion Lasers Command Handler //
export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; export {Command, NamedCommand, CHANNEL_TYPE} from "./command";
export {addInterceptRule} from "./handler"; export {addInterceptRule} from "./handler";
export {launch} from "./interface"; export {launch} from "./interface";
@ -12,5 +13,5 @@ export {
getMemberByUsername, getMemberByUsername,
callMemberByUsername callMemberByUsername
} from "./libd"; } from "./libd";
export {loadableCommands, categories} from "./loader"; export {getCommandList, getCommandInfo} from "./loader";
export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions";

View file

@ -25,6 +25,11 @@ export function botHasPermission(guild: Guild | null, permission: number): boole
// Pagination function that allows for customization via a callback. // Pagination function that allows for customization via a callback.
// Define your own pages outside the function because this only manages the actual turning of pages. // Define your own pages outside the function because this only manages the actual turning of pages.
const FIVE_BACKWARDS_EMOJI = "⏪";
const BACKWARDS_EMOJI = "⬅️";
const FORWARDS_EMOJI = "➡️";
const FIVE_FORWARDS_EMOJI = "⏩";
/** /**
* Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it. * Takes a message and some additional parameters and makes a reaction page with it. All the pagination logic is taken care of but nothing more, the page index is returned and you have to send a callback to do something with it.
*/ */
@ -43,30 +48,42 @@ export async function paginate(
const turn = (amount: number) => { const turn = (amount: number) => {
page += amount; page += amount;
if (page < 0) page += total; if (page >= total) {
else if (page >= total) page -= total; page %= total;
} else if (page < 0) {
// Assuming 3 total pages, it's a bit tricker, but if we just take the modulo of the absolute value (|page| % total), we get (1 2 0 ...), and we just need the pattern (2 1 0 ...). It needs to reverse order except for when it's 0. I want to find a better solution, but for the time being... total - (|page| % total) unless (|page| % total) = 0, then return 0.
const flattened = Math.abs(page) % total;
if (flattened !== 0) page = total - flattened;
}
message.edit(callback(page, true)); message.edit(callback(page, true));
}; };
const BACKWARDS_EMOJI = "⬅️";
const FORWARDS_EMOJI = "➡️";
const handle = (emote: string, reacterID: string) => { const handle = (emote: string, reacterID: string) => {
if (senderID === reacterID) { if (senderID === reacterID) {
switch (emote) { switch (emote) {
case FIVE_BACKWARDS_EMOJI:
if (total > 5) turn(-5);
break;
case BACKWARDS_EMOJI: case BACKWARDS_EMOJI:
turn(-1); turn(-1);
break; break;
case FORWARDS_EMOJI: case FORWARDS_EMOJI:
turn(1); turn(1);
break; break;
case FIVE_FORWARDS_EMOJI:
if (total > 5) turn(5);
break;
} }
} }
}; };
// Listen for reactions and call the handler. // Listen for reactions and call the handler.
let backwardsReactionFive = total > 5 ? await message.react(FIVE_BACKWARDS_EMOJI) : null;
let backwardsReaction = await message.react(BACKWARDS_EMOJI); let backwardsReaction = await message.react(BACKWARDS_EMOJI);
let forwardsReaction = await message.react(FORWARDS_EMOJI); let forwardsReaction = await message.react(FORWARDS_EMOJI);
let forwardsReactionFive = total > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null;
unreactEventListeners.set(message.id, handle); unreactEventListeners.set(message.id, handle);
const collector = message.createReactionCollector( const collector = message.createReactionCollector(
(reaction, user) => { (reaction, user) => {
if (user.id === senderID) { if (user.id === senderID) {
@ -88,8 +105,10 @@ export async function paginate(
// When time's up, remove the bot's own reactions. // When time's up, remove the bot's own reactions.
collector.on("end", () => { collector.on("end", () => {
unreactEventListeners.delete(message.id); unreactEventListeners.delete(message.id);
backwardsReactionFive?.users.remove(message.author);
backwardsReaction.users.remove(message.author); backwardsReaction.users.remove(message.author);
forwardsReaction.users.remove(message.author); forwardsReaction.users.remove(message.author);
forwardsReactionFive?.users.remove(message.author);
}); });
} }
} }

View file

@ -1,13 +1,14 @@
import {Collection} from "discord.js"; import {Collection} from "discord.js";
import glob from "glob"; import glob from "glob";
import {Command, NamedCommand} from "./command"; import {NamedCommand, CommandInfo} from "./command";
import {toTitleCase} from "../lib";
// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command. // Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command.
export const categories = new Collection<string, string[]>(); const categories = new Collection<string, string[]>();
/** Returns the cache of the commands if it exists and searches the directory if not. */ /** Returns the cache of the commands if it exists and searches the directory if not. */
export const loadableCommands = (async () => { export const loadableCommands = (async () => {
const commands = new Collection<string, Command>(); const commands = new Collection<string, NamedCommand>();
// Include all .ts files recursively in "src/commands/". // Include all .ts files recursively in "src/commands/".
const files = await globP("src/commands/**/*.ts"); const files = await globP("src/commands/**/*.ts");
// Extract the usable parts from "src/commands/" if: // Extract the usable parts from "src/commands/" if:
@ -79,3 +80,53 @@ function globP(path: string) {
}); });
}); });
} }
/**
* Returns a list of categories and their associated commands.
*/
export async function getCommandList(): Promise<Collection<string, NamedCommand[]>> {
const list = new Collection<string, NamedCommand[]>();
const commands = await loadableCommands;
for (const [category, headers] of categories) {
const commandList: NamedCommand[] = [];
for (const header of headers.filter((header) => header !== "test")) commandList.push(commands.get(header)!);
// Ignore empty categories like "miscellaneous" (if it's empty).
if (commandList.length > 0) list.set(toTitleCase(category), commandList);
}
return list;
}
/**
* Resolves a command based on the arguments given.
* - Returns a string if there was an error.
* - Returns a CommandInfo/category tuple if it was a success.
*/
export async function getCommandInfo(args: string[]): Promise<[CommandInfo, string] | string> {
// Use getCommandList() instead if you're just getting the list of all commands.
if (args.length === 0) return "No arguments were provided!";
// Setup the root command
const commands = await loadableCommands;
let header = args.shift()!;
const command = commands.get(header);
if (!command || header === "test") return `No command found by the name \`${header}\`.`;
if (!(command instanceof NamedCommand)) return "Command is not a proper instance of NamedCommand.";
// If it's an alias, set the header to the original command name.
if (command.name) header = command.name;
// Search categories
let category = "Unknown";
for (const [referenceCategory, headers] of categories) {
if (headers.includes(header)) {
category = toTitleCase(referenceCategory);
break;
}
}
// Gather info
const result = await command.resolveInfo(args, header);
if (result.type === "error") return result.message;
else return [result, category];
}

View file

@ -144,14 +144,13 @@ export abstract class GenericStructure {
constructor(tag?: string) { constructor(tag?: string) {
this.__meta__ = tag || this.__meta__; this.__meta__ = tag || this.__meta__;
Object.defineProperty(this, "__meta__", {
enumerable: false
});
} }
public save(asynchronous = true) { public save(asynchronous = true) {
const tag = this.__meta__; FileManager.write(this.__meta__, this, asynchronous);
/// @ts-ignore
delete this.__meta__;
FileManager.write(tag, this, asynchronous);
this.__meta__ = tag;
} }
} }

View file

@ -57,7 +57,7 @@ client.on("message", async (message) => {
export function extractFirstMessageLink(message: string): [string, string, string] | null { export function extractFirstMessageLink(message: string): [string, string, string] | null {
const messageLinkMatch = message.match( const messageLinkMatch = message.match(
/([!<])?https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19})(>)?/ /([!<])?https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,})(>)?/
); );
if (messageLinkMatch === null) return null; if (messageLinkMatch === null) return null;
const [, leftToken, guildID, channelID, messageID, rightToken] = messageLinkMatch; const [, leftToken, guildID, channelID, messageID, rightToken] = messageLinkMatch;

View file

@ -91,15 +91,13 @@ class StorageStructure extends GenericStructure {
super("storage"); super("storage");
this.users = {}; this.users = {};
this.guilds = {}; this.guilds = {};
for (let id in data.users) if (/\d{17,}/g.test(id)) this.users[id] = new User(data.users[id]);
for (let id in data.users) if (/\d{17,19}/g.test(id)) this.users[id] = new User(data.users[id]); for (let id in data.guilds) if (/\d{17,}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]);
for (let id in data.guilds) if (/\d{17,19}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]);
} }
/** Gets a user's profile if they exist and generate one if not. */ /** Gets a user's profile if they exist and generate one if not. */
public getUser(id: string): User { public getUser(id: string): User {
if (!/\d{17,19}/g.test(id)) if (!/\d{17,}/g.test(id))
console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`); console.warn(`"${id}" is not a valid user ID! It will be erased when the data loads again.`);
if (id in this.users) return this.users[id]; if (id in this.users) return this.users[id];
@ -112,7 +110,7 @@ class StorageStructure extends GenericStructure {
/** Gets a guild's settings if they exist and generate one if not. */ /** Gets a guild's settings if they exist and generate one if not. */
public getGuild(id: string): Guild { public getGuild(id: string): Guild {
if (!/\d{17,19}/g.test(id)) if (!/\d{17,}/g.test(id))
console.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`); console.warn(`"${id}" is not a valid guild ID! It will be erased when the data loads again.`);
if (id in this.guilds) return this.guilds[id]; if (id in this.guilds) return this.guilds[id];