@sapphire/framework
Version:
Discord bot framework built for advanced and amazing bots.
1 lines • 17.3 kB
Source Map (JSON)
{"version":3,"sources":["../../../src/lib/SapphireClient.ts"],"names":[],"mappings":"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","file":"SapphireClient.mjs","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"]}