UNPKG

@sapphire/framework

Version:

Discord bot framework built for advanced and amazing bots.

1 lines • 16 kB
{"version":3,"file":"SapphireClient.mjs","names":["Events"],"sources":["../../../src/lib/SapphireClient.ts"],"sourcesContent":["import { container, Store, StoreRegistry } from '@sapphire/pieces';\nimport type { Awaitable } from '@sapphire/utilities';\nimport { Client, type ClientOptions, type Message, type Snowflake } from 'discord.js';\nimport type { URL } from 'node:url';\nimport { loadApplicationCommandRegistriesListeners } from '../optional-listeners/application-command-registries-listeners/_load';\nimport { loadErrorListeners } from '../optional-listeners/error-listeners/_load';\nimport { loadMessageCommandListeners } from '../optional-listeners/message-command-listeners/_load';\nimport type { Plugin } from './plugins/Plugin';\nimport { PluginManager } from './plugins/PluginManager';\nimport { ArgumentStore } from './structures/ArgumentStore';\nimport { CommandStore } from './structures/CommandStore';\nimport { InteractionHandlerStore } from './structures/InteractionHandlerStore';\nimport { ListenerStore } from './structures/ListenerStore';\nimport { PreconditionStore } from './structures/PreconditionStore';\nimport { BucketScope, PluginHook } from './types/Enums';\nimport { Events } from './types/Events';\nimport { acquire } from './utils/application-commands/ApplicationCommandRegistries';\nimport { LogLevel, type ILogger } from './utils/logger/ILogger';\nimport { Logger } from './utils/logger/Logger';\n\n// Load built-in pieces\nimport '../arguments/_load';\nimport '../listeners/_load';\nimport '../preconditions/_load';\n\ncontainer.applicationCommandRegistries = { acquire };\n\n/**\n * A valid prefix in Sapphire.\n * * `string`: a single prefix, e.g. `'!'`.\n * * `string[]`: an array of prefixes, e.g. `['!', '.']`.\n * * `null`: disabled prefix, locks the bot's command usage to mentions only.\n */\nexport type SapphirePrefix = string | readonly string[] | null;\n\nexport interface SapphirePrefixHook {\n\t(message: Message): Awaitable<SapphirePrefix>;\n}\n\nexport interface SapphireClientOptions {\n\t/**\n\t * The base user directory, if set to `null`, Sapphire will not call {@linkcode StoreRegistry.registerPath()},\n\t * meaning that you will need to manually set each folder for each store or use {@linkcode StoreRegistry.loadPiece()}.\n\t * Please read the aforementioned methods' documentation for more information.\n\t * @since 1.0.0\n\t * @default undefined\n\t */\n\tbaseUserDirectory?: URL | string | null;\n\n\t/**\n\t * Whether commands can be case-insensitive\n\t * @since 1.0.0\n\t * @default false\n\t */\n\tcaseInsensitiveCommands?: boolean | null;\n\n\t/**\n\t * Whether prefixes can be case-insensitive\n\t * @since 1.0.0\n\t * @default false\n\t */\n\tcaseInsensitivePrefixes?: boolean | null;\n\n\t/**\n\t * The default prefix, in case of `null`, only mention prefix will trigger the bot's commands.\n\t * @since 1.0.0\n\t * @default null\n\t */\n\tdefaultPrefix?: SapphirePrefix;\n\n\t/**\n\t * The regex prefix, an alternative to a mention or regular prefix to allow creating natural language command messages\n\t * @since 1.0.0\n\t * @example\n\t * ```typescript\n\t * /^(hey +)?bot[,! ]/i\n\t *\n\t * // Matches:\n\t * // - hey bot,\n\t * // - hey bot!\n\t * // - hey bot\n\t * // - bot,\n\t * // - bot!\n\t * // - bot\n\t * ```\n\t */\n\tregexPrefix?: RegExp;\n\n\t/**\n\t * The prefix hook, by default it is a callback function that returns {@link SapphireClientOptions.defaultPrefix}.\n\t * @since 1.0.0\n\t * @default () => client.options.defaultPrefix\n\t */\n\tfetchPrefix?: SapphirePrefixHook;\n\n\t/**\n\t * The client's ID, this is automatically set by the CoreReady event.\n\t * @since 1.0.0\n\t * @default this.client.user?.id ?? null\n\t */\n\tid?: Snowflake;\n\n\t/**\n\t * The logger options, defaults to an instance of {@link Logger} when {@link ClientLoggerOptions.instance} is not specified.\n\t * @since 1.0.0\n\t * @default { instance: new Logger(LogLevel.Info) }\n\t */\n\tlogger?: ClientLoggerOptions;\n\n\t/**\n\t * Whether trace logging should be enabled.\n\t * @since 2.0.0\n\t * @default container.logger.has(LogLevel.Trace)\n\t */\n\tenableLoaderTraceLoggings?: boolean;\n\n\t/**\n\t * If Sapphire should load the pre-included application command registries status listeners that log the status of registering application commands to the {@link SapphireClient.logger} instance.\n\t * This includes the events {@link Events.ApplicationCommandRegistriesInitialising} and {@link Events.ApplicationCommandRegistriesRegistered}.\n\t * @since 4.4.0\n\t * @default true\n\t */\n\tloadApplicationCommandRegistriesStatusListeners?: boolean;\n\n\t/**\n\t * If Sapphire should load the pre-included error event listeners that log any encountered errors to the {@link SapphireClient.logger} instance\n\t * @since 1.0.0\n\t * @default true\n\t */\n\tloadDefaultErrorListeners?: boolean;\n\n\t/**\n\t * If Sapphire should load the pre-included message command listeners that are used to process incoming messages for commands.\n\t * @since 3.0.0\n\t * @default false\n\t */\n\tloadMessageCommandListeners?: boolean;\n\n\t/**\n\t * Controls whether the bot will automatically appear to be typing when a command is accepted.\n\t * @default false\n\t */\n\ttyping?: boolean;\n\n\t/**\n\t * Sets the default cooldown time for all commands.\n\t * @default \"No cooldown options\"\n\t */\n\tdefaultCooldown?: CooldownOptions;\n\t/**\n\t * Controls whether the bot has mention as a prefix disabled\n\t * @default false\n\t */\n\tdisableMentionPrefix?: boolean;\n\n\t/**\n\t * Whenever starting the bot process Sapphire may report errors when failing to fetch guild commands.\n\t * One of the causes for this can be when a bot was invited to a server without the `application.commands` scope.\n\t *\n\t * Normally this produce a log in the console at the WARN level, however because bot lists have a tendency to invite your\n\t * bot specifically without the scope to ensure that your Chat Input and Context Menu commands do not show up as usable commands\n\t * in that server, you may want to include their guild ids in this list.\n\t *\n\t * By adding ids to this list, whenever a guild id matches one of the ids in the list no warning log message will be emitted for that guild.\n\t *\n\t * By setting this value to `true`, no warning log message will be emitted for any guilds we couldn't fetch the commands from.\n\t *\n\t * Note that this specifically applies to the warning log:\n\t *\n\t * > ApplicationCommandRegistries: Failed to fetch guild commands for guild \\<guild name\\> (\\<guild id\\>). Make sure to authorize your application with the \"applications.commands\" scope in that guild.\n\t */\n\tpreventFailedToFetchLogForGuilds?: string[] | true;\n}\n\n/**\n * The base {@link Client} extension that makes Sapphire work. When building a Discord bot with the framework, the developer\n * must either use this class, or extend it.\n *\n * Sapphire also automatically detects the folders to scan for pieces, please read {@link StoreRegistry.registerPath}\n * for reference. This method is called at the start of the {@link SapphireClient.login} method.\n *\n * @see {@link SapphireClientOptions} for all options available to the Sapphire Client. You can also provide all of discord.js' [ClientOptions](https://discord.js.org/docs/packages/discord.js/main/ClientOptions:Interface)\n *\n * @since 1.0.0\n * @example\n * ```typescript\n * const client = new SapphireClient({\n * presence: {\n * activity: {\n * name: 'for commands!',\n * type: 'LISTENING'\n * }\n * }\n * });\n *\n * client.login(process.env.DISCORD_TOKEN)\n * .catch(console.error);\n * ```\n *\n * @example\n * ```typescript\n * // Automatically scan from a specific directory, e.g. the main\n * // file is at `/home/me/bot/index.js` and all your pieces are at\n * // `/home/me/bot/pieces` (e.g. `/home/me/bot/pieces/commands/MyCommand.js`):\n * const client = new SapphireClient({\n * baseUserDirectory: join(__dirname, 'pieces'),\n * // More options...\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Opt-out automatic scanning:\n * const client = new SapphireClient({\n * baseUserDirectory: null,\n * // More options...\n * });\n * ```\n */\nexport class SapphireClient<Ready extends boolean = boolean> extends Client<Ready> {\n\t/**\n\t * The client's ID, used for the user prefix.\n\t * @since 1.0.0\n\t */\n\tpublic override id: Snowflake | null = null;\n\n\t/**\n\t * The method to be overridden by the developer.\n\t * @since 1.0.0\n\t * @return A string for a single prefix, an array of strings for matching multiple, or null for no match (mention prefix only).\n\t * @example\n\t * ```typescript\n\t * // Return always the same prefix (unconfigurable):\n\t * client.fetchPrefix = () => '!';\n\t * ```\n\t * @example\n\t * ```typescript\n\t * // Retrieving the prefix from a SQL database:\n\t * client.fetchPrefix = async (message) => {\n\t * // note: driver is something generic and depends on how you connect to your database\n\t * const guild = await driver.getOne('SELECT prefix FROM public.guild WHERE id = $1', [message.guild.id]);\n\t * return guild?.prefix ?? '!';\n\t * };\n\t * ```\n\t * @example\n\t * ```typescript\n\t * // Retrieving the prefix from an ORM:\n\t * client.fetchPrefix = async (message) => {\n\t * // note: driver is something generic and depends on how you connect to your database\n\t * const guild = await driver.getRepository(GuildEntity).findOne({ id: message.guild.id });\n\t * return guild?.prefix ?? '!';\n\t * };\n\t * ```\n\t */\n\tpublic override fetchPrefix: SapphirePrefixHook;\n\n\t/**\n\t * The logger to be used by the framework and plugins. By default, a {@link Logger} instance is used, which emits the\n\t * messages to the console.\n\t * @since 1.0.0\n\t */\n\tpublic override logger: ILogger;\n\n\t/**\n\t * Whether the bot has mention as a prefix disabled\n\t * @default false\n\t * @example\n\t * ```typescript\n\t * client.disableMentionPrefix = false;\n\t * ```\n\t */\n\tpublic disableMentionPrefix?: boolean;\n\n\t/**\n\t * The registered stores.\n\t * @since 1.0.0\n\t */\n\tpublic override stores: StoreRegistry;\n\n\tpublic constructor(options: ClientOptions) {\n\t\tsuper(options);\n\n\t\tcontainer.client = this;\n\n\t\tfor (const plugin of SapphireClient.plugins.values(PluginHook.PreGenericsInitialization)) {\n\t\t\tplugin.hook.call(this, options);\n\t\t\tthis.emit(Events.PluginLoaded, plugin.type, plugin.name);\n\t\t}\n\n\t\tthis.logger = options.logger?.instance ?? new Logger(options.logger?.level ?? LogLevel.Info);\n\t\tcontainer.logger = this.logger;\n\t\tif (options.enableLoaderTraceLoggings ?? container.logger.has(LogLevel.Trace)) {\n\t\t\tStore.logger = container.logger.trace.bind(container.logger);\n\t\t}\n\n\t\tthis.stores = container.stores;\n\n\t\tthis.fetchPrefix = options.fetchPrefix ?? (() => this.options.defaultPrefix ?? null);\n\t\tthis.disableMentionPrefix = options.disableMentionPrefix;\n\n\t\tfor (const plugin of SapphireClient.plugins.values(PluginHook.PreInitialization)) {\n\t\t\tplugin.hook.call(this, options);\n\t\t\tthis.emit(Events.PluginLoaded, plugin.type, plugin.name);\n\t\t}\n\n\t\tthis.id = options.id ?? null;\n\n\t\tthis.stores\n\t\t\t.register(new ArgumentStore()) //\n\t\t\t.register(new CommandStore())\n\t\t\t.register(new InteractionHandlerStore())\n\t\t\t.register(new ListenerStore())\n\t\t\t.register(new PreconditionStore());\n\n\t\tif (options.loadApplicationCommandRegistriesStatusListeners !== false) {\n\t\t\tloadApplicationCommandRegistriesListeners();\n\t\t}\n\n\t\tif (options.loadDefaultErrorListeners !== false) {\n\t\t\tloadErrorListeners();\n\t\t}\n\n\t\tif (options.loadMessageCommandListeners === true) {\n\t\t\tloadMessageCommandListeners();\n\t\t}\n\n\t\tfor (const plugin of SapphireClient.plugins.values(PluginHook.PostInitialization)) {\n\t\t\tplugin.hook.call(this, options);\n\t\t\tthis.emit(Events.PluginLoaded, plugin.type, plugin.name);\n\t\t}\n\t}\n\n\t/**\n\t * Loads all pieces, then logs the client in, establishing a websocket connection to Discord.\n\t * @since 1.0.0\n\t * @param token Token of the account to log in with.\n\t * @return Token of the account used.\n\t */\n\tpublic override async login(token?: string) {\n\t\t// Register the user directory if not null:\n\t\tif (this.options.baseUserDirectory !== null) {\n\t\t\tthis.stores.registerPath(this.options.baseUserDirectory);\n\t\t}\n\n\t\t// Call pre-login plugins:\n\t\tfor (const plugin of SapphireClient.plugins.values(PluginHook.PreLogin)) {\n\t\t\tawait plugin.hook.call(this, this.options);\n\t\t\tthis.emit(Events.PluginLoaded, plugin.type, plugin.name);\n\t\t}\n\n\t\t// Loads all stores, then call login:\n\t\tawait Promise.all([...this.stores.values()].map((store) => store.loadAll()));\n\t\tconst login = await super.login(token);\n\n\t\t// Call post-login plugins:\n\t\tfor (const plugin of SapphireClient.plugins.values(PluginHook.PostLogin)) {\n\t\t\tawait plugin.hook.call(this, this.options);\n\t\t\tthis.emit(Events.PluginLoaded, plugin.type, plugin.name);\n\t\t}\n\n\t\treturn login;\n\t}\n\n\tpublic static plugins = new PluginManager();\n\n\tpublic static use(plugin: typeof Plugin) {\n\t\tthis.plugins.use(plugin);\n\t\treturn this;\n\t}\n}\n\nexport interface ClientLoggerOptions {\n\tlevel?: LogLevel;\n\tinstance?: ILogger;\n}\n\nexport interface CooldownOptions {\n\tscope?: BucketScope;\n\tdelay?: number;\n\tlimit?: number;\n\tfilteredUsers?: Snowflake[];\n\tfilteredCommands?: string[];\n}\n\ndeclare module 'discord.js' {\n\tinterface Client {\n\t\tid: Snowflake | null;\n\t\tlogger: ILogger;\n\t\tstores: StoreRegistry;\n\t\tfetchPrefix: SapphirePrefixHook;\n\t}\n\n\tinterface ClientOptions extends SapphireClientOptions {}\n}\n\ndeclare module '@sapphire/pieces' {\n\tinterface Container {\n\t\tclient: SapphireClient;\n\t\tlogger: ILogger;\n\t\tapplicationCommandRegistries: {\n\t\t\tacquire: typeof acquire;\n\t\t};\n\t}\n\n\tinterface StoreRegistryEntries {\n\t\targuments: ArgumentStore;\n\t\tcommands: CommandStore;\n\t\t'interaction-handlers': InteractionHandlerStore;\n\t\tlisteners: ListenerStore;\n\t\tpreconditions: PreconditionStore;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAyBA,UAAU,+BAA+B,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkMpD,IAAa,iBAAb,MAAa,uBAAwD,OAAc;CA4DlF,AAAO,YAAY,SAAwB;AAC1C,QAAM,QAAQ;OAxDC,KAAuB;AA0DtC,YAAU,SAAS;AAEnB,OAAK,MAAM,UAAU,eAAe,QAAQ,OAAO,WAAW,0BAA0B,EAAE;AACzF,UAAO,KAAK,KAAK,MAAM,QAAQ;AAC/B,QAAK,KAAKA,SAAO,cAAc,OAAO,MAAM,OAAO,KAAK;;AAGzD,OAAK,SAAS,QAAQ,QAAQ,YAAY,IAAI,OAAO,QAAQ,QAAQ,SAAS,SAAS,KAAK;AAC5F,YAAU,SAAS,KAAK;AACxB,MAAI,QAAQ,6BAA6B,UAAU,OAAO,IAAI,SAAS,MAAM,CAC5E,OAAM,SAAS,UAAU,OAAO,MAAM,KAAK,UAAU,OAAO;AAG7D,OAAK,SAAS,UAAU;AAExB,OAAK,cAAc,QAAQ,sBAAsB,KAAK,QAAQ,iBAAiB;AAC/E,OAAK,uBAAuB,QAAQ;AAEpC,OAAK,MAAM,UAAU,eAAe,QAAQ,OAAO,WAAW,kBAAkB,EAAE;AACjF,UAAO,KAAK,KAAK,MAAM,QAAQ;AAC/B,QAAK,KAAKA,SAAO,cAAc,OAAO,MAAM,OAAO,KAAK;;AAGzD,OAAK,KAAK,QAAQ,MAAM;AAExB,OAAK,OACH,SAAS,IAAI,eAAe,CAAC,CAC7B,SAAS,IAAI,cAAc,CAAC,CAC5B,SAAS,IAAI,yBAAyB,CAAC,CACvC,SAAS,IAAI,eAAe,CAAC,CAC7B,SAAS,IAAI,mBAAmB,CAAC;AAEnC,MAAI,QAAQ,oDAAoD,MAC/D,4CAA2C;AAG5C,MAAI,QAAQ,8BAA8B,MACzC,qBAAoB;AAGrB,MAAI,QAAQ,gCAAgC,KAC3C,8BAA6B;AAG9B,OAAK,MAAM,UAAU,eAAe,QAAQ,OAAO,WAAW,mBAAmB,EAAE;AAClF,UAAO,KAAK,KAAK,MAAM,QAAQ;AAC/B,QAAK,KAAKA,SAAO,cAAc,OAAO,MAAM,OAAO,KAAK;;;;;;;;;CAU1D,MAAsB,MAAM,OAAgB;AAE3C,MAAI,KAAK,QAAQ,sBAAsB,KACtC,MAAK,OAAO,aAAa,KAAK,QAAQ,kBAAkB;AAIzD,OAAK,MAAM,UAAU,eAAe,QAAQ,OAAO,WAAW,SAAS,EAAE;AACxE,SAAM,OAAO,KAAK,KAAK,MAAM,KAAK,QAAQ;AAC1C,QAAK,KAAKA,SAAO,cAAc,OAAO,MAAM,OAAO,KAAK;;AAIzD,QAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,OAAO,QAAQ,CAAC,CAAC,KAAK,UAAU,MAAM,SAAS,CAAC,CAAC;EAC5E,MAAM,QAAQ,MAAM,MAAM,MAAM,MAAM;AAGtC,OAAK,MAAM,UAAU,eAAe,QAAQ,OAAO,WAAW,UAAU,EAAE;AACzE,SAAM,OAAO,KAAK,KAAK,MAAM,KAAK,QAAQ;AAC1C,QAAK,KAAKA,SAAO,cAAc,OAAO,MAAM,OAAO,KAAK;;AAGzD,SAAO;;CAKR,OAAc,IAAI,QAAuB;AACxC,OAAK,QAAQ,IAAI,OAAO;AACxB,SAAO;;;eAJM,UAAU,IAAI,eAAe"}