mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Split command resolution part of help command
This commit is contained in:
		
							parent
							
								
									20fb2135c7
								
							
						
					
					
						commit
						72ff144cc0
					
				
					 12 changed files with 146 additions and 86 deletions
				
			
		|  | @ -1,61 +1,28 @@ | |||
| import {Command, NamedCommand, loadableCommands, categories, getPermissionName, CHANNEL_TYPE} from "../../core"; | ||||
| import {toTitleCase, requireAllCasesHandledFor} from "../../lib"; | ||||
| import {Command, NamedCommand, CHANNEL_TYPE, getPermissionName, getCommandList, getCommandInfo} from "../../core"; | ||||
| import {requireAllCasesHandledFor} from "../../lib"; | ||||
| 
 | ||||
| export default new NamedCommand({ | ||||
|     description: "Lists all commands. If a command is specified, their arguments are listed as well.", | ||||
|     usage: "([command, [subcommand/type], ...])", | ||||
|     aliases: ["h"], | ||||
|     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/...])\``; | ||||
| 
 | ||||
|         for (const [category, headers] of categories) { | ||||
|             let tmp = `\n\n===[ ${toTitleCase(category)} ]===`; | ||||
|             // Ignore empty categories, including ["test"].
 | ||||
|             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; | ||||
|         for (const [category, commandList] of commands) { | ||||
|             output += `\n\n===[ ${category} ]===`; | ||||
|             for (const command of commandList) output += `\n- \`${command.name}\`: ${command.description}`; | ||||
|         } | ||||
| 
 | ||||
|         channel.send(output, {split: true}); | ||||
|     }, | ||||
|     any: new Command({ | ||||
|         async run({message, channel, guild, author, member, client, args}) { | ||||
|             // Setup the root command
 | ||||
|             const commands = await loadableCommands; | ||||
|             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); | ||||
| 
 | ||||
|             const [result, category] = await getCommandInfo(args); | ||||
|             if (typeof result === "string") return channel.send(result); | ||||
|             let append = ""; | ||||
|             command = result.command; | ||||
| 
 | ||||
|             if (result.args.length > 0) header += " " + result.args.join(" "); | ||||
|             const command = result.command; | ||||
|             const header = result.args.length > 0 ? `${result.header} ${result.args.join(" ")}` : result.header; | ||||
| 
 | ||||
|             if (command.usage === "") { | ||||
|                 const list: string[] = []; | ||||
|  |  | |||
|  | @ -102,7 +102,7 @@ export default new NamedCommand({ | |||
|                 description: "Display info about a guild by finding its name or ID.", | ||||
|                 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 (args.length === 1 && /^\d{17,19}$/.test(args[0])) { | ||||
|                     if (args.length === 1 && /^\d{17,}$/.test(args[0])) { | ||||
|                         const id = args[0]; | ||||
|                         const targetGuild = client.guilds.cache.get(id); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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", | ||||
|         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 (args.length === 1 && /^\d{17,19}$/.test(args[0])) { | ||||
|             if (args.length === 1 && /^\d{17,}$/.test(args[0])) { | ||||
|                 const guildID: string = args[0]; | ||||
| 
 | ||||
|                 displayEmoteList( | ||||
|  |  | |||
|  | @ -17,8 +17,8 @@ export default new NamedCommand({ | |||
|         // handles reacts by message id/distance
 | ||||
|         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 URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; | ||||
|             const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/; | ||||
|             const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/; | ||||
|             const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; | ||||
| 
 | ||||
|             // https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
 | ||||
|             if (URLPattern.test(last)) { | ||||
|  | @ -70,7 +70,7 @@ export default new NamedCommand({ | |||
|                 args.pop(); | ||||
|             } | ||||
|             // <Message ID>
 | ||||
|             else if (/^\d{17,19}$/.test(last)) { | ||||
|             else if (/^\d{17,}$/.test(last)) { | ||||
|                 try { | ||||
|                     target = await channel.messages.fetch(last); | ||||
|                 } catch { | ||||
|  |  | |||
|  | @ -30,14 +30,16 @@ import {parseVars, requireAllCasesHandledFor} from "../lib"; | |||
|  */ | ||||
| 
 | ||||
| // 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 = { | ||||
|     channel: /^<#(\d{17,19})>$/, | ||||
|     role: /^<@&(\d{17,19})>$/, | ||||
|     emote: /^<a?:.*?:(\d{17,19})>$/, | ||||
|     messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,19}|@me)\/(\d{17,19})\/(\d{17,19})$/, | ||||
|     messagePair: /^(\d{17,19})-(\d{17,19})$/, | ||||
|     user: /^<@!?(\d{17,19})>$/, | ||||
|     id: /^(\d{17,19})$/ | ||||
|     channel: /^<#(\d{17,})>$/, | ||||
|     role: /^<@&(\d{17,})>$/, | ||||
|     emote: /^<a?:.*?:(\d{17,})>$/, | ||||
|     // 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.
 | ||||
|     messageLink: /^https?:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/channels\/(?:\d{17,}|@me)\/(\d{17,})\/(\d{17,})$/, | ||||
|     messagePair: /^(\d{17,})-(\d{17,})$/, | ||||
|     user: /^<@!?(\d{17,})>$/, | ||||
|     id: /^(\d{17,})$/ | ||||
| }; | ||||
| 
 | ||||
| // Maybe add a guild redirect... somehow?
 | ||||
|  | @ -106,9 +108,10 @@ interface ExecuteCommandMetadata { | |||
|     permission: number; | ||||
|     nsfw: boolean; | ||||
|     channelType: CHANNEL_TYPE; | ||||
|     symbolicArgs: string[]; // i.e. <channel> instead of <#...>
 | ||||
| } | ||||
| 
 | ||||
| interface CommandInfo { | ||||
| export interface CommandInfo { | ||||
|     readonly type: "info"; | ||||
|     readonly command: Command; | ||||
|     readonly subcommandInfo: Collection<string, Command>; | ||||
|  | @ -117,6 +120,7 @@ interface CommandInfo { | |||
|     readonly nsfw: boolean; | ||||
|     readonly channelType: CHANNEL_TYPE; | ||||
|     readonly args: string[]; | ||||
|     readonly header: string; | ||||
| } | ||||
| 
 | ||||
| interface CommandInfoError { | ||||
|  | @ -131,14 +135,9 @@ interface CommandInfoMetadata { | |||
|     args: string[]; | ||||
|     usage: 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.
 | ||||
| export class Command { | ||||
|     public readonly description: string; | ||||
|  | @ -298,12 +297,15 @@ export class Command { | |||
|             // Then capture any potential errors.
 | ||||
|             try { | ||||
|                 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( | ||||
|                         parseVars( | ||||
|                             this.run, | ||||
|                             { | ||||
|                                 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); | ||||
| 
 | ||||
|         if (this.subcommands.has(param)) { | ||||
|             metadata.symbolicArgs.push(param); | ||||
|             return this.subcommands.get(param)!.execute(args, menu, metadata); | ||||
|         } else if (this.channel && patterns.channel.test(param)) { | ||||
|             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.
 | ||||
|             if (channel instanceof TextChannel) { | ||||
|                 metadata.symbolicArgs.push("<channel>"); | ||||
|                 menu.args.push(channel); | ||||
|                 return this.channel.execute(args, menu, metadata); | ||||
|             } else { | ||||
|  | @ -358,6 +362,7 @@ export class Command { | |||
|             const role = menu.guild.roles.cache.get(id); | ||||
| 
 | ||||
|             if (role) { | ||||
|                 metadata.symbolicArgs.push("<role>"); | ||||
|                 menu.args.push(role); | ||||
|                 return this.role.execute(args, menu, metadata); | ||||
|             } else { | ||||
|  | @ -370,6 +375,7 @@ export class Command { | |||
|             const emote = menu.client.emojis.cache.get(id); | ||||
| 
 | ||||
|             if (emote) { | ||||
|                 metadata.symbolicArgs.push("<emote>"); | ||||
|                 menu.args.push(emote); | ||||
|                 return this.emote.execute(args, menu, metadata); | ||||
|             } else { | ||||
|  | @ -395,6 +401,7 @@ export class Command { | |||
| 
 | ||||
|             if (channel instanceof TextChannel || channel instanceof DMChannel) { | ||||
|                 try { | ||||
|                     metadata.symbolicArgs.push("<message>"); | ||||
|                     menu.args.push(await channel.messages.fetch(messageID)); | ||||
|                     return this.message.execute(args, menu, metadata); | ||||
|                 } catch { | ||||
|  | @ -411,6 +418,7 @@ export class Command { | |||
|             const id = patterns.user.exec(param)![1]; | ||||
| 
 | ||||
|             try { | ||||
|                 metadata.symbolicArgs.push("<user>"); | ||||
|                 menu.args.push(await menu.client.users.fetch(id)); | ||||
|                 return this.user.execute(args, menu, metadata); | ||||
|             } catch { | ||||
|  | @ -419,6 +427,7 @@ export class Command { | |||
|                 }; | ||||
|             } | ||||
|         } else if (this.id && this.idType && patterns.id.test(param)) { | ||||
|             metadata.symbolicArgs.push("<id>"); | ||||
|             const id = patterns.id.exec(param)![1]; | ||||
| 
 | ||||
|             // Probably modularize the findXByY code in general in libd.
 | ||||
|  | @ -486,9 +495,11 @@ export class Command { | |||
|                     requireAllCasesHandledFor(this.idType); | ||||
|             } | ||||
|         } else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") { | ||||
|             metadata.symbolicArgs.push("<number>"); | ||||
|             menu.args.push(Number(param)); | ||||
|             return this.number.execute(args, menu, metadata); | ||||
|         } else if (this.any) { | ||||
|             metadata.symbolicArgs.push("<any>"); | ||||
|             menu.args.push(param); | ||||
|             return this.any.execute(args, menu, metadata); | ||||
|         } 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.
 | ||||
|     public async resolveInfo(args: string[]): Promise<CommandInfo | CommandInfoError> { | ||||
|         return this.resolveInfoInternal(args, {...defaultMetadata, args: [], usage: "", originalArgs: [...args]}); | ||||
|     public async resolveInfo(args: string[], header: string): Promise<CommandInfo | CommandInfoError> { | ||||
|         return this.resolveInfoInternal(args, { | ||||
|             permission: 0, | ||||
|             nsfw: false, | ||||
|             channelType: CHANNEL_TYPE.ANY, | ||||
|             header, | ||||
|             args: [], | ||||
|             usage: "", | ||||
|             originalArgs: [...args] | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private async resolveInfoInternal( | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import {Client, Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; | ||||
| import {loadableCommands} from "./loader"; | ||||
| import {defaultMetadata} from "./command"; | ||||
| import {getPrefix} from "./interface"; | ||||
| 
 | ||||
| // For custom message events that want to cancel the command handler on certain conditions.
 | ||||
|  | @ -20,6 +19,13 @@ const lastCommandInfo: { | |||
|     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: 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) { | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| // Onion Lasers Command Handler //
 | ||||
| export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; | ||||
| export {addInterceptRule} from "./handler"; | ||||
| export {launch} from "./interface"; | ||||
|  | @ -12,5 +13,5 @@ export { | |||
|     getMemberByUsername, | ||||
|     callMemberByUsername | ||||
| } from "./libd"; | ||||
| export {loadableCommands, categories} from "./loader"; | ||||
| export {getCommandList, getCommandInfo} from "./loader"; | ||||
| export {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; | ||||
|  |  | |||
|  | @ -25,6 +25,11 @@ export function botHasPermission(guild: Guild | null, permission: number): boole | |||
| // 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.
 | ||||
| 
 | ||||
| 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. | ||||
|  */ | ||||
|  | @ -43,30 +48,42 @@ export async function paginate( | |||
|         const turn = (amount: number) => { | ||||
|             page += amount; | ||||
| 
 | ||||
|             if (page < 0) page += total; | ||||
|             else if (page >= total) page -= total; | ||||
|             if (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)); | ||||
|         }; | ||||
|         const BACKWARDS_EMOJI = "⬅️"; | ||||
|         const FORWARDS_EMOJI = "➡️"; | ||||
|         const handle = (emote: string, reacterID: string) => { | ||||
|             if (senderID === reacterID) { | ||||
|                 switch (emote) { | ||||
|                     case FIVE_BACKWARDS_EMOJI: | ||||
|                         if (total > 5) turn(-5); | ||||
|                         break; | ||||
|                     case BACKWARDS_EMOJI: | ||||
|                         turn(-1); | ||||
|                         break; | ||||
|                     case FORWARDS_EMOJI: | ||||
|                         turn(1); | ||||
|                         break; | ||||
|                     case FIVE_FORWARDS_EMOJI: | ||||
|                         if (total > 5) turn(5); | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         // 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 forwardsReaction = await message.react(FORWARDS_EMOJI); | ||||
|         let forwardsReactionFive = total > 5 ? await message.react(FIVE_FORWARDS_EMOJI) : null; | ||||
|         unreactEventListeners.set(message.id, handle); | ||||
| 
 | ||||
|         const collector = message.createReactionCollector( | ||||
|             (reaction, user) => { | ||||
|                 if (user.id === senderID) { | ||||
|  | @ -88,8 +105,10 @@ export async function paginate( | |||
|         // When time's up, remove the bot's own reactions.
 | ||||
|         collector.on("end", () => { | ||||
|             unreactEventListeners.delete(message.id); | ||||
|             backwardsReactionFive?.users.remove(message.author); | ||||
|             backwardsReaction.users.remove(message.author); | ||||
|             forwardsReaction.users.remove(message.author); | ||||
|             forwardsReactionFive?.users.remove(message.author); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| import {Collection} from "discord.js"; | ||||
| 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.
 | ||||
| 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. */ | ||||
| export const loadableCommands = (async () => { | ||||
|     const commands = new Collection<string, Command>(); | ||||
|     const commands = new Collection<string, NamedCommand>(); | ||||
|     // Include all .ts files recursively in "src/commands/".
 | ||||
|     const files = await globP("src/commands/**/*.ts"); | ||||
|     // 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]; | ||||
| } | ||||
|  |  | |||
|  | @ -144,14 +144,13 @@ export abstract class GenericStructure { | |||
| 
 | ||||
|     constructor(tag?: string) { | ||||
|         this.__meta__ = tag || this.__meta__; | ||||
|         Object.defineProperty(this, "__meta__", { | ||||
|             enumerable: false | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public save(asynchronous = true) { | ||||
|         const tag = this.__meta__; | ||||
|         /// @ts-ignore
 | ||||
|         delete this.__meta__; | ||||
|         FileManager.write(tag, this, asynchronous); | ||||
|         this.__meta__ = tag; | ||||
|         FileManager.write(this.__meta__, this, asynchronous); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ client.on("message", async (message) => { | |||
| 
 | ||||
| export function extractFirstMessageLink(message: string): [string, string, string] | null { | ||||
|     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; | ||||
|     const [, leftToken, guildID, channelID, messageID, rightToken] = messageLinkMatch; | ||||
|  |  | |||
|  | @ -91,15 +91,13 @@ class StorageStructure extends GenericStructure { | |||
|         super("storage"); | ||||
|         this.users = {}; | ||||
|         this.guilds = {}; | ||||
| 
 | ||||
|         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,19}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]); | ||||
|         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.guilds) if (/\d{17,}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]); | ||||
|     } | ||||
| 
 | ||||
|     /** Gets a user's profile if they exist and generate one if not. */ | ||||
|     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.`); | ||||
| 
 | ||||
|         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. */ | ||||
|     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.`); | ||||
| 
 | ||||
|         if (id in this.guilds) return this.guilds[id]; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue