diff --git a/src/classes/AoiBase.js b/src/classes/AoiBase.js index e6c4ab608..064553578 100644 --- a/src/classes/AoiBase.js +++ b/src/classes/AoiBase.js @@ -11,6 +11,7 @@ const { EventstoFile } = require("../utils/Constants.js"); const Database = require("./Database.js"); +const { MacrosManager } = require("./Macros.js"); const CacheManager = require("./CacheManager.js"); const { CommandManager } = require("./Commands.js"); const { Group } = require("@aoijs/aoi.structures"); @@ -55,6 +56,7 @@ class BaseClient extends Client { this.cacheManager = new CacheManager(this); this.variableManager = new VariableManager(this); + this.macros = new MacrosManager(); if ( options.disableAoiDB !== true && @@ -119,6 +121,15 @@ class BaseClient extends Client { loader.load(this.cmd, directory, debug); } + /** + * Adds many macros to the manager. + * @param {import("..").MacroOptions[]} macros - The macros to be added. + * @returns {void} + */ + macro(...macros) { + this.macros.addMany([...macros]); + } + status(...statuses) { for (const status of statuses) { status.type = diff --git a/src/classes/Commands.js b/src/classes/Commands.js index 3c7213d1f..d2985b131 100644 --- a/src/classes/Commands.js +++ b/src/classes/Commands.js @@ -1,13 +1,25 @@ +const { hasMacros, resolveMacros } = require("./Macros"); const { Group } = require("@aoijs/aoi.structures"); class Command { + /** + * Creates a new instance of the `Command` class. + * @param {import("..").BaseCommand} data - The command data. + * @param {import("..").AoiClient} client - The AoiClient instance. + */ constructor(data = {}, client) { - this.__client__ = client; - if (typeof data.code === "string") { - this.code = data.code; - } else { + if (typeof data.code !== "string") { throw new TypeError(`Missing or invalid 'code' property in '${data.name}' command \n Path: '${data.__path__}'`); } + + this.__client__ = client; + + if (hasMacros(client.macros.list(), data.code)) { + data.code = resolveMacros(client.macros.toArray(), data.code); + } + + this.code = data.code; + Object.entries(data).forEach(([key, value]) => (this[key] = value)); this.functions = this.serializeFunctions(); this.codeLines = this.serializeCode(); diff --git a/src/classes/Macros.js b/src/classes/Macros.js new file mode 100644 index 000000000..c5d9746ff --- /dev/null +++ b/src/classes/Macros.js @@ -0,0 +1,109 @@ +class MacrosManager { + /** + * @type {Map} + */ + cache = new Map(); + + /** + * Adds a macro to the manager. + * @param {import("..").MacroOptions} macro - The macro to be added. + * @returns {this} + */ + add(macro) { + macro.name = macro.name.startsWith("#") + ? macro.name.slice(1) + : macro.name; + + this.cache.set(macro.name, macro); + + return this; + } + + /** + * Adds many macros to the manager. + * @param {import("..").MacroOptions[]} macros - The macros to be added. + * @returns {this} + */ + addMany(macros) { + for (const macro of macros) { + this.add(macro); + } + return this; + } + + /** + * Get a macro. + * @param {string | ((macro: import("..").MacroOptions) => boolean)} name - The name of the macro. + * @returns {?import("..").MacroOptions} + */ + get(name) { + if (typeof name === "string") return this.cache.get(name) || null; + + return this.toArray().find(name) || null; + } + + /** + * Return the list of cached macro names. + * @returns {string[]} + */ + list() { + return [...this.cache.keys()]; + } + + /** + * Return an array of cached macros. + * @returns {import("..").MacroOptions[]} + */ + toArray() { + return [...this.cache.values()]; + } +}; + +/** + * Creates a pattern to match the cached macros. + * @param {string[]} names - The names of the macros. + * @returns {RegExp} + */ +function createMacrosPattern(names) { + return new RegExp(`#(${names.join("|")})`, "g"); +}; + +/** + * Check whether the given code has macros inside. + * @param {string[]} names - The names of the cached macros. + * @param {string} code - The code to be tested. + * @returns {boolean} + */ +function hasMacros(names, code) { + return Array.isArray(code.match(createMacrosPattern(names))) +}; + +/** + * Resolve the macros in the command code with actual code. + * @param {import("..").MacroOptions[]} macros - The cached macros. + * @param {string} code - The code to be resolved. + * @returns {string} + */ +function resolveMacros(macros, code) { + const matchedMacros = [...new Set(code.match(createMacrosPattern(macros.map(m => m.name))) ?? [])]; + let newCode = code; + + for (const matchedMacro of matchedMacros) { + newCode = newCode.replace( + createMacrosPattern( + [matchedMacro.slice(1)] + ), + macros.find( + macro => macro.name === matchedMacro.slice(1) + )?.code || "" + ); + } + + return newCode; +}; + +module.exports = { + hasMacros, + MacrosManager, + resolveMacros +}; \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts index e5b94b845..d00851079 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -226,10 +226,12 @@ export declare class BaseClient extends Client { interactionManager: InteractionManager; cacheManager: CacheManager; variableManager: any; + macros: MacrosManager; prefix: string | string[]; db: KeyValue | Transmitter; statuses: Group; constructor(options: ClientOptions); + macro(...macros: MacroOptions[]): void; status(...statuses: StatusOption[]): void; loadCommands(directory: string, debug?: boolean): void; variables(data: Record, table?: string): void; @@ -534,4 +536,42 @@ export interface Data = Record; + /** + * Adds a macro to the manager. + */ + add(macro: MacroOptions): this; + /** + * Adds many macros to the manager. + */ + addMany(macros: MacroOptions[]): this; + /** + * Get a macro. + */ + get(name: string | ((macro: MacroOptions) => boolean)): MacroOptions | null; + /** + * Return the list of cached macro names. + */ + list(): string[]; + /** + * Return an array of cached macros. + */ + toArray(): MacroOptions[]; } \ No newline at end of file