From 41444dde43ccf16540385929d57c8beb439c4a51 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sat, 13 Nov 2021 22:58:33 -0500 Subject: [PATCH 01/30] feat: precondition guides --- .vscode/settings.json | 3 +- docs/Guide/preconditions/channel-types.mdx | 75 +++++- docs/Guide/preconditions/command-cooldown.mdx | 213 +++++++++++++++- .../creating-your-own-preconditions.mdx | 223 +++++++++++++++- .../preconditions/handling-permissions.mdx | 129 +++++++++- docs/Guide/preconditions/nsfw-filter.mdx | 65 ++++- .../reporting-precondition-failure.mdx | 241 ++++++++++++++++++ 7 files changed, 939 insertions(+), 10 deletions(-) create mode 100644 docs/Guide/preconditions/reporting-precondition-failure.mdx diff --git a/.vscode/settings.json b/.vscode/settings.json index c72a7367..2e64f7f3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,6 +17,7 @@ "**/projects/": true, }, "cSpell.words": [ - "favna" + "favna", + "nsfw" ] } diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index 706846f3..7197ff28 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -3,4 +3,77 @@ sidebar_position: 3 title: Setting the types of channel a command can run in --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [`runIn`][runIn] command option can be used to specify the types of channels a command can run in. This can be useful +if you're developing a command that, for example, displays the roles of a user. In that scenario, you'll want to make sure +that the command can only be run in guild channels. + +:::info + +[Valid values][runInTypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, +`'GUILD_PUBLIC_THREAD'`, `'GUILD_PRIVATE_THREAD'`, and `'GUILD_ANY'` + +::: + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class RolesCommand extends Command { + constructor(context) { + super(context, { + // ... + runIn: 'GUILD_ANY' // Only run in guild channels + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + runIn: 'GUILD_ANY' // Only run in guild channels + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + runIn: 'GUILD_ANY' // Only run in guild channels +}) +export class PingCommand extends Command {} +``` + + + + +If you try to run a command in direct messages, you'll now find that nothing happens. + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[runIn]: ../../Documentation/api-framework/interfaces/CommandOptions#runin +[runInTypes]: ../../Documentation/api-framework/enums/CommandOptionsRunTypeEnum +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 732a36e8..c76fe3cd 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -1,6 +1,215 @@ --- -sidebar_position: 0 +sidebar_position: 2 title: Configuring command cooldowns --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. +Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, +cooldowns can be used in specific commands via the [`cooldownDelay`][cooldownDelay] property in their options. +This represents the time in milliseconds a user will have to wait after using a command to use it again. Here's a basic example: + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000 // 10 seconds + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000 // 10 seconds + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + cooldownDelay: 10000 // 10 seconds +}) +export class PingCommand extends Command {} +``` + + + + +If you now try to run this command, and then run it again within 10 seconds, the command won't execute! + +## User Exceptions + +It's very common to not want cooldowns to apply to certain people, for example, the bot owner. +This can be achieved by adding [`cooldownFilteredUsers`][cooldownFilteredUsers] to the options. +This option should be an array of users ID that the bot can ignore when calculating cooldowns. + + + + +```javascript {8} +const { Command } = require('@sapphire/framework'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner + }); + } +}; +``` + + + + +```javascript {8} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner + }); + } +} +``` + + + + +```typescript {7} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + cooldownDelay: 10000 // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner +}) +export class PingCommand extends Command {} +``` + + + + +## Advanced Usage + +Accompanying `cooldownDelay`, you also have access to the options +[`cooldownLimit`](cooldownLimit) and [`cooldownScope`](cooldownScope). + +`cooldownLimit` will define how many times a +command can be used before a cooldown is put into affect. This value is set to `1` by default. +For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that you'd be able to +use the command twice every 10 seconds. + +Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have +a cooldown that applies per guild, for example, instead of per user. [Valid options](scopes) are `'CHANNEL'`, `'GLOBAL`', +`'GUILD'`, and `'USER'` (default). + +## Client-wide Cooldowns + +Sometimes you'll find a use case where you want specific cooldown +options to apply to all commands in your client. This can be achieved by adding [`defaultCooldown`][defaultCooldown] to +your [`SapphireClient`][sapphire] options. You can use any of the properties shown above with this option. + + + + +```javascript {5-10} +const { SapphireClient } = require('@sapphire/framework'); + +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], + defaultCooldown: { + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner + cooldownLimit: 2, // Allow 2 uses before ratelimiting + cooldownScope: 'CHANNEL' // Scope cooldown to channel + } +}); + +void client.login('your-token-goes-here'); +``` + + + + +```javascript {5-10} +import { SapphireClient } from '@sapphire/framework'; + +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], + defaultCooldown: { + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner + cooldownLimit: 2, // Allow 2 uses before ratelimiting + cooldownScope: 'CHANNEL' // Scope cooldown to channel + } +}); + +await client.login('your-token-goes-here'); +``` + + + + +```typescript {5-10} +import { SapphireClient } from '@sapphire/framework'; + +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], + defaultCooldown: { + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner + cooldownLimit: 2, // Allow 2 uses before ratelimiting + cooldownScope: 'CHANNEL' // Scope cooldown to channel + } +}); + +void client.login('your-token-goes-here'); +``` + + + + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[cooldownDelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay +[cooldownFilteredUsers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers +[cooldownLimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit +[defaultCooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown +[sapphire]: ../../Documentation/api-framework/classes/SapphireClient +[scopes]: ../../Documentation/api-framework/enums/BucketScope +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index ed8c4329..2fa60004 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -1,6 +1,225 @@ --- -sidebar_position: 4 +sidebar_position: 0 title: Creating your own preconditions --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Preconditions are classes that will determine whether or not a command should be blocked according to a certain condition. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, +such as for [creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], +[handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an +easy system for you to create your own preconditions! + +Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will create +a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. + +Your directory should now look something like this: + +```bash {9-10} +├── node_modules +├── package.json +└── src + ├── commands + │ └── ping.js + ├── index.js + ├── listeners + │ └── ready.js + └── preconditions + └── OwnerOnly.js +``` + +The purpose of our `OwnerOnly` precondition is just as the name suggests: to check if the user is the bot owner. +It can be used for developer commands, such as `eval`. + +## Creating a Precondition class + +Preconditions are made by extending the Sapphire [`Precondition`](precondition) class and exporting it. + + + + +```javascript +const { Precondition } = require('@sapphire/framework'); + +module.exports = class OwnerOnlyPrecondition extends Precondition {}; +``` + + + + +```javascript {4-10} +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition {} +``` + + + + +```typescript +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition {} +``` + + + + +Next, we can create a [`run`][preconditionRun] function to execute our logic. This function should either return [`this.ok()`][preconditionOk] +or [`this.error(...)`][preconditionError] to signify whether the command should be blocked. + + + + +```javascript {4-8} +const { Precondition } = require('@sapphire/framework'); + +module.exports = class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ message: 'Only the bot owner can use this command!' }) + } +}; +``` + + + + +```javascript {4-8} +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ message: 'Only the bot owner can use this command!' }) + } +} +``` + + + + +```typescript {5-9} +import type { Message } from '@sapphire/framework'; +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message: Message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ message: 'Only the bot owner can use this command!' }) + } +} +``` + + + +## Using Preconditions in Commands + +To attach a precondition to a command, you simply have to input its name in an array in the command's +[`preconditions`][preconditions-option] option. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + preconditions: ['OwnerOnly'] + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + preconditions: ['OwnerOnly'] + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + preconditions: ['OwnerOnly'] +}) +export class PingCommand extends Command {} +``` + + + + +Now, if someone who is not the bot owner executes the ping command, nothing will happen! + +:::caution + +For TypeScript users, there's an extra step to make this work. To increase the security of Sapphire's types, you'll need +to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example [here][preconditions-augment]. + +::: + +By default, no error message will be sent or logged when a command is denied because of a precondition. To learn how to +configure this, please read [Reporting Precondition Failures][reporting-precondition-failure]. + +## Advanced Usage + +Sapphire also has a builtin system for advanced conditional precondition logic through nested arrays. +By default, all preconditions in the given array must pass for the command to be run. +However, you can use nested arrays to create `OR` functionality. This could be useful if you'd like a command to be run +if the user is either a moderator _or_ an admin. + +Furthermore, if you create a nested array within a nested array, you'll receive `AND` functionality once more. Arrays can +be nested infinitely with the same pattern for optimal control over your preconditions. + +Consider the following array of preconditions: + +```js +[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel'] +``` + +:::note + +None of the above preconditions are builtin, and you'd have to create them separately. + +::: + +For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` +and `ModOnly`. + +[creating-cooldowns]: ./command-cooldown +[restricting-channel-types]: ./channel-types +[handling-permissions]: ./handling-permissions +[nsfw-filter]: ./nsfw-filter +[creating-commands]: ../getting-started/creating-a-basic-command +[creating-listeners]: ../listeners/creating-your-first-listeners +[precondition]: ../../Documentation/api-framework/classes/Precondition +[preconditionRun]: ../../Documentation/api-framework/classes/Precondition#run +[preconditionOk]: ../../Documentation/api-framework/classes/Precondition#ok +[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[preconditions-option]: ../../Documentation/api-framework/interfaces/CommandOptions#preconditions +[preconditions-interface]: ../../Documentation/api-framework/interfaces/Preconditions +[preconditions-augment]: https://github.com/sapphiredev/examples/blob/main/examples/with-typescript-recommended/src/preconditions/OwnerOnly.ts#L13-L17 +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 40cae020..a38e076a 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -1,6 +1,131 @@ --- -sidebar_position: 2 +sidebar_position: 4 title: Handling permissions --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +One of the most basic needs of a Discord bot is to be able to deny command access to users based on their permissions or +lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, +so we can restrict usage using the [`requiredUserPermissions`][requiredUserPermissions] option. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'] + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'] + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + requiredUserPermissions: ['BAN_MEMBERS'] +}) +export class BanCommand extends Command {} +``` + + + + +Users without the `BAN_MEMBERS` permission will now be unable to use the command! + +## Handling Client Permissions + +It's also a good idea to verify the inverse: does the _bot_ have the `BAN_MEMBERS` permission? We can use the +[`requiredClientPermissions`][requiredClientPermissions] option the same way to prevent the command from being used if not. + + + + +```javascript {8} +const { Command } = require('@sapphire/framework'); + +module.exports = class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] + }); + } +}; +``` + + + + +```javascript {8} +import { Command } from '@sapphire/framework'; + +export class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] + }); + } +} +``` + + + + +```typescript {7} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] +}) +export class BanCommand extends Command {} +``` + + + + +`BanCommand` now requires the command executor _and_ the client to have sufficient permissions to execute! + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[requiredUserPermissions]: ../../Documentation/api-framework/CommandOptions#requireduserpermissions +[requiredClientPermissions]: ../../Documentation/api-framework/CommandOptions#requiredclientpermissions +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index ef5a6139..1b9993cb 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -1,6 +1,67 @@ --- -sidebar_position: 1 +sidebar_position: 5 title: Locking commands to NSFW channels --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. +This can be simply achieved by adding the `nsfw` option to the command. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class NSFWCommand extends Command { + constructor(context) { + super(context, { + // ... + nsfw: true + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class NSFWCommand extends Command { + constructor(context) { + super(context, { + // ... + nsfw: true + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + nsfw: true +}) +export class NSFWCommand extends Command {} +``` + + + + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx new file mode 100644 index 00000000..c997566e --- /dev/null +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -0,0 +1,241 @@ +--- +sidebar_position: 1 +title: Reporting precondition failure +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +When a precondition fails, it's usually important for the user to know why. For example, if they hit a cooldown or lack +permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a message. + +To change this, we'll need to create a `commandDenied` listener, which is triggered when a precondition +fails. For more information on how to create listeners, see the [`Creating Listeners`][listeners] section. + +:::caution + +The `commandDenied` event shouldn't be confused with the `commandError` event, which is triggered when a command throws +an error. + +::: + +`commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, +and the [`CommandDeniedPayload`][payload], which includes necessary context. + + + + +```javascript +const { Listener } = require('@sapphire/framework'); + +module.exports = class CommandDeniedListener extends Listener { + run(error, { message }) { + // ... + } +}; +``` + + + + +```javascript +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + run(error, { message }) { + // ... + } +} +``` + + + + +```typescript +import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + public run(error: UserError, { message }: CommandDeniedPayload) { + // ... + } +} +``` + + + + +The `message` property of the `error` parameter will include the error message, as the name suggests. +In [Creating Preconditions][creating-preconditions], you can find that we defined this property +within the `this.error()` method! + +There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. +That is what we'll do in this example: + + + + +```javascript {5} +const { Listener } = require('@sapphire/framework'); + +module.exports = class CommandDeniedListener extends Listener { + run(error, { message }) { + return message.channel.send(error.message); + } +}; +``` + + + + +```javascript {5} +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + run(error, { message }) { + return message.channel.send(error.message); + } +} +``` + + + + +```typescript {5} +import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + public run(error: UserError, { message }: CommandDeniedPayload) { + return message.channel.send(error.message); + } +} +``` + + + + +## Ignoring Precondition Failures + +If someone who isn't a bot owner tries to use a command intended only for the bot owner, sometimes you don't want to +send a message notifying them that they don't have permission. Instead, you'd rather let the command be blocked silently. +To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to contain information +about the context in which the error was thrown, and the value can be absolutely anything. + +We can take advantage of this by passing the value `{ silent: true }` to the [`this.error()`][preconditionError] call of +a function. We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to +demonstrate this. + + + + +```javascript {9} +const { Precondition } = require('@sapphire/framework'); + +module.exports = class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ + message: 'Only the bot owner can use this command!' + context: { silent: true } + }) + } +}; +``` + + + + +```javascript {9} +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ + message: 'Only the bot owner can use this command!', + context: { silent: true } + }) + } +} +``` + + + + +```typescript {10} +import type { Message } from '@sapphire/framework'; +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message: Message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ + message: 'Only the bot owner can use this command!' + context: { silent: true } + }) + } +} +``` + + + +We can then detect this property in our listener, and ignore the failure if we find it. + + + + +```javascript {5} +const { Listener } = require('@sapphire/framework'); + +module.exports = class CommandDeniedListener extends Listener { + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } +}; +``` + + + + +```javascript {5} +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } +} +``` + + + + +```typescript {8} +import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + public run(error: UserError, { message }: CommandDeniedPayload) { + // A bit of a type hack is required for TypeScript + // Since `error.context` is `unknown` + if (Reflect.get(Object(context), 'silent')) return; + return message.channel.send(error.message); + } +} +``` + + + + +[listeners]: ../listeners/creating-your-first-listeners +[error]: ../../Documentation/api-framework/classes/UserError +[payload]: ../../Documentation/api-framework/interfaces/CommandDeniedPayload +[context]: ../../Documentation/api-framework/classes/UserError#context +[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[creating-preconditions]: ./creating-your-own-preconditions From c19f2aa79112dadf2c6c896909aa70e308667abb Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 14 Nov 2021 08:14:54 -0500 Subject: [PATCH 02/30] fix line highlighting --- docs/Guide/preconditions/creating-your-own-preconditions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 2fa60004..3dcf0376 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -49,7 +49,7 @@ module.exports = class OwnerOnlyPrecondition extends Precondition {}; -```javascript {4-10} +```javascript import { Precondition } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition {} From 7809b3a8806d6e2b92f1ea95a02609caf8f79c4d Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 14 Nov 2021 10:14:29 -0500 Subject: [PATCH 03/30] fix links --- docs/Guide/preconditions/command-cooldown.mdx | 4 ++-- docs/Guide/preconditions/creating-your-own-preconditions.mdx | 4 ++-- docs/Guide/preconditions/handling-permissions.mdx | 4 ++-- docs/Guide/preconditions/reporting-precondition-failure.mdx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index c76fe3cd..172c1b50 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -123,7 +123,7 @@ export class PingCommand extends Command {} ## Advanced Usage Accompanying `cooldownDelay`, you also have access to the options -[`cooldownLimit`](cooldownLimit) and [`cooldownScope`](cooldownScope). +[`cooldownLimit`][cooldownLimit] and [`cooldownScope`][cooldownScope]. `cooldownLimit` will define how many times a command can be used before a cooldown is put into affect. This value is set to `1` by default. @@ -131,7 +131,7 @@ For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will ef use the command twice every 10 seconds. Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have -a cooldown that applies per guild, for example, instead of per user. [Valid options](scopes) are `'CHANNEL'`, `'GLOBAL`', +a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, `'GLOBAL`', `'GUILD'`, and `'USER'` (default). ## Client-wide Cooldowns diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 3dcf0376..1c968d8e 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -35,7 +35,7 @@ It can be used for developer commands, such as `eval`. ## Creating a Precondition class -Preconditions are made by extending the Sapphire [`Precondition`](precondition) class and exporting it. +Preconditions are made by extending the Sapphire [`Precondition`][precondition] class and exporting it. @@ -214,7 +214,7 @@ and `ModOnly`. [handling-permissions]: ./handling-permissions [nsfw-filter]: ./nsfw-filter [creating-commands]: ../getting-started/creating-a-basic-command -[creating-listeners]: ../listeners/creating-your-first-listeners +[creating-listeners]: ../listeners/creating-your-own-listeners [precondition]: ../../Documentation/api-framework/classes/Precondition [preconditionRun]: ../../Documentation/api-framework/classes/Precondition#run [preconditionOk]: ../../Documentation/api-framework/classes/Precondition#ok diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index a38e076a..688b33c8 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -126,6 +126,6 @@ To learn how to send a message to the command executor when a precondition fails ::: -[requiredUserPermissions]: ../../Documentation/api-framework/CommandOptions#requireduserpermissions -[requiredClientPermissions]: ../../Documentation/api-framework/CommandOptions#requiredclientpermissions +[requiredUserPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requireduserpermissions +[requiredClientPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requiredclientpermissions [reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index c997566e..7f58034a 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -233,7 +233,7 @@ export class CommandDeniedListener extends Listener { -[listeners]: ../listeners/creating-your-first-listeners +[listeners]: ../listeners/creating-your-own-listeners [error]: ../../Documentation/api-framework/classes/UserError [payload]: ../../Documentation/api-framework/interfaces/CommandDeniedPayload [context]: ../../Documentation/api-framework/classes/UserError#context From 3e801b112d557f58d05a6df8e554bdd1fc2100c1 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Mon, 15 Nov 2021 15:20:35 -0500 Subject: [PATCH 04/30] refactors and fixes --- .../reporting-precondition-failure.mdx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 7f58034a..4e92db5e 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -101,7 +101,7 @@ export class CommandDeniedListener extends Listener { -```typescript {5} +```typescript {6} import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; @@ -122,9 +122,8 @@ send a message notifying them that they don't have permission. Instead, you'd ra To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to contain information about the context in which the error was thrown, and the value can be absolutely anything. -We can take advantage of this by passing the value `{ silent: true }` to the [`this.error()`][preconditionError] call of -a function. We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to -demonstrate this. +We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionError] options. +We'll use the `OwnerOnly` precondition we made in[Creating Preconditions][creating-preconditions] to demonstrate this. @@ -183,7 +182,7 @@ export class OwnerOnlyPrecondition extends Precondition { -We can then detect this property in our listener, and ignore the failure if we find it. +We can then check if this property exists on the error in our listener, and ignore the failure if we find it. @@ -222,9 +221,9 @@ import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { public run(error: UserError, { message }: CommandDeniedPayload) { - // A bit of a type hack is required for TypeScript - // Since `error.context` is `unknown` - if (Reflect.get(Object(context), 'silent')) return; + // A bit of a hack is required for TypeScript + // Since `error.context` is of type `unknown` + if (Reflect.get(Object(error.context), 'silent')) return; return message.channel.send(error.message); } } From 1185820eda5f419b8c96934bd3d6b63ea4583651 Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Sat, 20 Nov 2021 17:37:11 +0100 Subject: [PATCH 05/30] style: formatting --- docs/Guide/preconditions/channel-types.mdx | 17 +-- docs/Guide/preconditions/command-cooldown.mdx | 62 +++++------ .../creating-your-own-preconditions.mdx | 57 +++++----- .../preconditions/handling-permissions.mdx | 16 +-- docs/Guide/preconditions/nsfw-filter.mdx | 9 +- .../reporting-precondition-failure.mdx | 103 +++++++++--------- 6 files changed, 136 insertions(+), 128 deletions(-) diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index 7197ff28..39a7261f 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -6,13 +6,13 @@ title: Setting the types of channel a command can run in import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -The [`runIn`][runIn] command option can be used to specify the types of channels a command can run in. This can be useful -if you're developing a command that, for example, displays the roles of a user. In that scenario, you'll want to make sure -that the command can only be run in guild channels. +The [`runIn`][runin] command option can be used to specify the types of channels a command can run in. This can be +useful if you're developing a command that, for example, displays the roles of a user. In that scenario, you'll want to +make sure that the command can only be run in guild channels. :::info -[Valid values][runInTypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, +[Valid values][runintypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, `'GUILD_PUBLIC_THREAD'`, `'GUILD_PRIVATE_THREAD'`, and `'GUILD_ANY'` ::: @@ -70,10 +70,11 @@ If you try to run a command in direct messages, you'll now find that nothing hap :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[runIn]: ../../Documentation/api-framework/interfaces/CommandOptions#runin -[runInTypes]: ../../Documentation/api-framework/enums/CommandOptionsRunTypeEnum -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[runin]: ../../Documentation/api-framework/interfaces/CommandOptions#runin +[runintypes]: ../../Documentation/api-framework/enums/CommandOptionsRunTypeEnum +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 172c1b50..4cad8938 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -6,10 +6,10 @@ title: Configuring command cooldowns import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. -Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, -cooldowns can be used in specific commands via the [`cooldownDelay`][cooldownDelay] property in their options. -This represents the time in milliseconds a user will have to wait after using a command to use it again. Here's a basic example: +Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. Luckily, Sapphire makes it easy to +integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the +[`cooldownDelay`][cooldowndelay] property in their options. This represents the time in milliseconds a user will have to +wait after using a command to use it again. Here's a basic example: @@ -64,9 +64,9 @@ If you now try to run this command, and then run it again within 10 seconds, the ## User Exceptions -It's very common to not want cooldowns to apply to certain people, for example, the bot owner. -This can be achieved by adding [`cooldownFilteredUsers`][cooldownFilteredUsers] to the options. -This option should be an array of users ID that the bot can ignore when calculating cooldowns. +It's very common to not want cooldowns to apply to certain people, for example, the bot owner. This can be achieved by +adding [`cooldownFilteredUsers`][cooldownfilteredusers] to the options. This option should be an array of users ID that +the bot can ignore when calculating cooldowns. @@ -122,23 +122,22 @@ export class PingCommand extends Command {} ## Advanced Usage -Accompanying `cooldownDelay`, you also have access to the options -[`cooldownLimit`][cooldownLimit] and [`cooldownScope`][cooldownScope]. +Accompanying `cooldownDelay`, you also have access to the options [`cooldownLimit`][cooldownlimit] and +[`cooldownScope`][cooldownscope]. -`cooldownLimit` will define how many times a -command can be used before a cooldown is put into affect. This value is set to `1` by default. -For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that you'd be able to -use the command twice every 10 seconds. +`cooldownLimit` will define how many times a command can be used before a cooldown is put into affect. This value is set +to `1` by default. For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that +you'd be able to use the command twice every 10 seconds. -Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have -a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, `'GLOBAL`', -`'GUILD'`, and `'USER'` (default). +Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to +have a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, +`'GLOBAL`', `'GUILD'`, and `'USER'` (default). ## Client-wide Cooldowns -Sometimes you'll find a use case where you want specific cooldown -options to apply to all commands in your client. This can be achieved by adding [`defaultCooldown`][defaultCooldown] to -your [`SapphireClient`][sapphire] options. You can use any of the properties shown above with this option. +Sometimes you'll find a use case where you want specific cooldown options to apply to all commands in your client. This +can be achieved by adding [`defaultCooldown`][defaultcooldown] to your [`SapphireClient`][sapphire] options. You can use +any of the properties shown above with this option. @@ -146,8 +145,8 @@ your [`SapphireClient`][sapphire] options. You can use any of the properties sho ```javascript {5-10} const { SapphireClient } = require('@sapphire/framework'); -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { cooldownDelay: 10000, // 10 seconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner @@ -165,8 +164,8 @@ void client.login('your-token-goes-here'); ```javascript {5-10} import { SapphireClient } from '@sapphire/framework'; -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { cooldownDelay: 10000, // 10 seconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner @@ -184,8 +183,8 @@ await client.login('your-token-goes-here'); ```typescript {5-10} import { SapphireClient } from '@sapphire/framework'; -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { cooldownDelay: 10000, // 10 seconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner @@ -202,14 +201,15 @@ void client.login('your-token-goes-here'); :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[cooldownDelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay -[cooldownFilteredUsers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers -[cooldownLimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit -[defaultCooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown +[cooldowndelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay +[cooldownfilteredusers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers +[cooldownlimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit +[defaultcooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown [sapphire]: ../../Documentation/api-framework/classes/SapphireClient [scopes]: ../../Documentation/api-framework/enums/BucketScope -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 1c968d8e..2349a981 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -6,14 +6,14 @@ title: Creating your own preconditions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be blocked according to a certain condition. -Sapphire has many built in preconditions ready for use that are outlined in the coming pages, -such as for [creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], -[handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an -easy system for you to create your own preconditions! +Preconditions are classes that will determine whether or not a command should be blocked according to a certain +condition. Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for +[creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling +permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system +for you to create your own preconditions! -Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will create -a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. +Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will +create a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. Your directory should now look something like this: @@ -30,8 +30,8 @@ Your directory should now look something like this: └── OwnerOnly.js ``` -The purpose of our `OwnerOnly` precondition is just as the name suggests: to check if the user is the bot owner. -It can be used for developer commands, such as `eval`. +The purpose of our `OwnerOnly` precondition is just as the name suggests: to check if the user is the bot owner. It can +be used for developer commands, such as `eval`. ## Creating a Precondition class @@ -67,8 +67,9 @@ export class OwnerOnlyPrecondition extends Precondition {} -Next, we can create a [`run`][preconditionRun] function to execute our logic. This function should either return [`this.ok()`][preconditionOk] -or [`this.error(...)`][preconditionError] to signify whether the command should be blocked. +Next, we can create a [`run`][preconditionrun] function to execute our logic. This function should either return +[`this.ok()`][preconditionok] or [`this.error(...)`][preconditionerror] to signify whether the command should be +blocked. @@ -111,10 +112,11 @@ export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ message: 'Only the bot owner can use this command!' }) + : this.error({ message: 'Only the bot owner can use this command!' }); } } ``` + @@ -177,7 +179,8 @@ Now, if someone who is not the bot owner executes the ping command, nothing will :::caution For TypeScript users, there's an extra step to make this work. To increase the security of Sapphire's types, you'll need -to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example [here][preconditions-augment]. +to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example +[here][preconditions-augment]. ::: @@ -186,18 +189,17 @@ configure this, please read [Reporting Precondition Failures][reporting-precondi ## Advanced Usage -Sapphire also has a builtin system for advanced conditional precondition logic through nested arrays. -By default, all preconditions in the given array must pass for the command to be run. -However, you can use nested arrays to create `OR` functionality. This could be useful if you'd like a command to be run -if the user is either a moderator _or_ an admin. +Sapphire also has a builtin system for advanced conditional precondition logic through nested arrays. By default, all +preconditions in the given array must pass for the command to be run. However, you can use nested arrays to create `OR` +functionality. This could be useful if you'd like a command to be run if the user is either a moderator _or_ an admin. -Furthermore, if you create a nested array within a nested array, you'll receive `AND` functionality once more. Arrays can -be nested infinitely with the same pattern for optimal control over your preconditions. +Furthermore, if you create a nested array within a nested array, you'll receive `AND` functionality once more. Arrays +can be nested infinitely with the same pattern for optimal control over your preconditions. Consider the following array of preconditions: ```js -[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel'] +[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel']; ``` :::note @@ -206,8 +208,8 @@ None of the above preconditions are builtin, and you'd have to create them separ ::: -For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` -and `ModOnly`. +For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` and +`ModOnly`. [creating-cooldowns]: ./command-cooldown [restricting-channel-types]: ./channel-types @@ -216,10 +218,11 @@ and `ModOnly`. [creating-commands]: ../getting-started/creating-a-basic-command [creating-listeners]: ../listeners/creating-your-own-listeners [precondition]: ../../Documentation/api-framework/classes/Precondition -[preconditionRun]: ../../Documentation/api-framework/classes/Precondition#run -[preconditionOk]: ../../Documentation/api-framework/classes/Precondition#ok -[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[preconditionrun]: ../../Documentation/api-framework/classes/Precondition#run +[preconditionok]: ../../Documentation/api-framework/classes/Precondition#ok +[preconditionerror]: ../../Documentation/api-framework/classes/Precondition#error [preconditions-option]: ../../Documentation/api-framework/interfaces/CommandOptions#preconditions [preconditions-interface]: ../../Documentation/api-framework/interfaces/Preconditions -[preconditions-augment]: https://github.com/sapphiredev/examples/blob/main/examples/with-typescript-recommended/src/preconditions/OwnerOnly.ts#L13-L17 -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[preconditions-augment]: + https://github.com/sapphiredev/examples/blob/main/examples/with-typescript-recommended/src/preconditions/OwnerOnly.ts#L13-L17 +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 688b33c8..136754e3 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -7,8 +7,8 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; One of the most basic needs of a Discord bot is to be able to deny command access to users based on their permissions or -lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, -so we can restrict usage using the [`requiredUserPermissions`][requiredUserPermissions] option. +lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban +people, so we can restrict usage using the [`requiredUserPermissions`][requireduserpermissions] option. @@ -64,7 +64,8 @@ Users without the `BAN_MEMBERS` permission will now be unable to use the command ## Handling Client Permissions It's also a good idea to verify the inverse: does the _bot_ have the `BAN_MEMBERS` permission? We can use the -[`requiredClientPermissions`][requiredClientPermissions] option the same way to prevent the command from being used if not. +[`requiredClientPermissions`][requiredclientpermissions] option the same way to prevent the command from being used if +not. @@ -122,10 +123,11 @@ export class BanCommand extends Command {} :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[requiredUserPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requireduserpermissions -[requiredClientPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requiredclientpermissions -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[requireduserpermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requireduserpermissions +[requiredclientpermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requiredclientpermissions +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index 1b9993cb..fec951d4 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -6,8 +6,8 @@ title: Locking commands to NSFW channels import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. -This can be simply achieved by adding the `nsfw` option to the command. +Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. This can be simply achieved +by adding the `nsfw` option to the command. @@ -60,8 +60,9 @@ export class NSFWCommand extends Command {} :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 4e92db5e..0a2dcec0 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -7,10 +7,11 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; When a precondition fails, it's usually important for the user to know why. For example, if they hit a cooldown or lack -permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a message. +permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a +message. -To change this, we'll need to create a `commandDenied` listener, which is triggered when a precondition -fails. For more information on how to create listeners, see the [`Creating Listeners`][listeners] section. +To change this, we'll need to create a `commandDenied` listener, which is triggered when a precondition fails. For more +information on how to create listeners, see the [`Creating Listeners`][listeners] section. :::caution @@ -19,8 +20,8 @@ an error. ::: -`commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, -and the [`CommandDeniedPayload`][payload], which includes necessary context. +`commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, and +the [`CommandDeniedPayload`][payload], which includes necessary context. @@ -29,9 +30,9 @@ and the [`CommandDeniedPayload`][payload], which includes necessary context. const { Listener } = require('@sapphire/framework'); module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } + run(error, { message }) { + // ... + } }; ``` @@ -42,9 +43,9 @@ module.exports = class CommandDeniedListener extends Listener { import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } + run(error, { message }) { + // ... + } } ``` @@ -56,21 +57,20 @@ import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - public run(error: UserError, { message }: CommandDeniedPayload) { - // ... - } + public run(error: UserError, { message }: CommandDeniedPayload) { + // ... + } } ``` -The `message` property of the `error` parameter will include the error message, as the name suggests. -In [Creating Preconditions][creating-preconditions], you can find that we defined this property -within the `this.error()` method! +The `message` property of the `error` parameter will include the error message, as the name suggests. In [Creating +Preconditions][creating-preconditions], you can find that we defined this property within the `this.error()` method! -There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. -That is what we'll do in this example: +There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the +user. That is what we'll do in this example: @@ -79,9 +79,9 @@ That is what we'll do in this example: const { Listener } = require('@sapphire/framework'); module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } + run(error, { message }) { + return message.channel.send(error.message); + } }; ``` @@ -92,9 +92,9 @@ module.exports = class CommandDeniedListener extends Listener { import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } + run(error, { message }) { + return message.channel.send(error.message); + } } ``` @@ -106,9 +106,9 @@ import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - public run(error: UserError, { message }: CommandDeniedPayload) { - return message.channel.send(error.message); - } + public run(error: UserError, { message }: CommandDeniedPayload) { + return message.channel.send(error.message); + } } ``` @@ -118,11 +118,11 @@ export class CommandDeniedListener extends Listener { ## Ignoring Precondition Failures If someone who isn't a bot owner tries to use a command intended only for the bot owner, sometimes you don't want to -send a message notifying them that they don't have permission. Instead, you'd rather let the command be blocked silently. -To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to contain information -about the context in which the error was thrown, and the value can be absolutely anything. +send a message notifying them that they don't have permission. Instead, you'd rather let the command be blocked +silently. To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to +contain information about the context in which the error was thrown, and the value can be absolutely anything. -We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionError] options. +We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionerror] options. We'll use the `OwnerOnly` precondition we made in[Creating Preconditions][creating-preconditions] to demonstrate this. @@ -135,8 +135,8 @@ module.exports = class OwnerOnlyPrecondition extends Precondition { public run(message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ - message: 'Only the bot owner can use this command!' + : this.error({ + message: 'Only the bot owner can use this command!' context: { silent: true } }) } @@ -153,7 +153,7 @@ export class OwnerOnlyPrecondition extends Precondition { public run(message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ + : this.error({ message: 'Only the bot owner can use this command!', context: { silent: true } }) @@ -172,13 +172,14 @@ export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ + : this.error({ message: 'Only the bot owner can use this command!' context: { silent: true } }) } } ``` + @@ -191,10 +192,10 @@ We can then check if this property exists on the error in our listener, and igno const { Listener } = require('@sapphire/framework'); module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - if (error.context.silent) return; - return message.channel.send(error.message); - } + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } }; ``` @@ -205,10 +206,10 @@ module.exports = class CommandDeniedListener extends Listener { import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - run(error, { message }) { - if (error.context.silent) return; - return message.channel.send(error.message); - } + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } } ``` @@ -220,12 +221,12 @@ import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - public run(error: UserError, { message }: CommandDeniedPayload) { - // A bit of a hack is required for TypeScript - // Since `error.context` is of type `unknown` - if (Reflect.get(Object(error.context), 'silent')) return; - return message.channel.send(error.message); - } + public run(error: UserError, { message }: CommandDeniedPayload) { + // A bit of a hack is required for TypeScript + // Since `error.context` is of type `unknown` + if (Reflect.get(Object(error.context), 'silent')) return; + return message.channel.send(error.message); + } } ``` @@ -236,5 +237,5 @@ export class CommandDeniedListener extends Listener { [error]: ../../Documentation/api-framework/classes/UserError [payload]: ../../Documentation/api-framework/interfaces/CommandDeniedPayload [context]: ../../Documentation/api-framework/classes/UserError#context -[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[preconditionerror]: ../../Documentation/api-framework/classes/Precondition#error [creating-preconditions]: ./creating-your-own-preconditions From b6d088987667e0cf348358fccf5f5426989f396b Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Thu, 25 Nov 2021 10:30:56 -0500 Subject: [PATCH 06/30] implement favna's fixes --- docs/Guide/preconditions/command-cooldown.mdx | 91 ++++++++++++++++--- .../creating-your-own-preconditions.mdx | 14 +-- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 4cad8938..614a1d2f 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -8,8 +8,8 @@ import TabItem from '@theme/TabItem'; Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the -[`cooldownDelay`][cooldowndelay] property in their options. This represents the time in milliseconds a user will have to -wait after using a command to use it again. Here's a basic example: +[`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of milliseconds that a user will +have to wait after using a command to use it again. Here's a basic example: @@ -21,7 +21,7 @@ module.exports = class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds }); } }; @@ -37,7 +37,7 @@ export class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds }); } } @@ -52,7 +52,7 @@ import { Command, CommandOptions } from '@sapphire/framework'; @ApplyOptions({ // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds }) export class PingCommand extends Command {} ``` @@ -60,7 +60,69 @@ export class PingCommand extends Command {} -If you now try to run this command, and then run it again within 10 seconds, the command won't execute! +If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error will +be thrown. You can learn how to process that error [here][reporting-precondition-failure]. + +:::info + +`cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To help, +you can use [@sapphire/time-utilities], which includes utilities for time transformers. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); +const { Time } = require('@sapphire/time-utilities'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: Time.Second * 10; // Much easier for humans to parse + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; +import { Time } from '@sapphire/time-utilities'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: Time.Second * 10; // Much easier for humans to parse + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; +import { Time } from '@sapphire/time-utilities'; + +@ApplyOptions({ + // ... + cooldownDelay: Time.Second * 10; // Much easier for humans to parse +}) +export class PingCommand extends Command {} +``` + + + + +You can view the docs [here](timeutils). + +::: ## User Exceptions @@ -78,7 +140,7 @@ module.exports = class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner }); } @@ -95,7 +157,7 @@ export class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner }); } @@ -111,7 +173,7 @@ import { Command, CommandOptions } from '@sapphire/framework'; @ApplyOptions({ // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner }) export class PingCommand extends Command {} @@ -126,8 +188,8 @@ Accompanying `cooldownDelay`, you also have access to the options [`cooldownLimi [`cooldownScope`][cooldownscope]. `cooldownLimit` will define how many times a command can be used before a cooldown is put into affect. This value is set -to `1` by default. For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that -you'd be able to use the command twice every 10 seconds. +to `1` by default. For example, a `cooldownDelay` of `10_000` milliseconds and a `cooldownLimit` of 2 will effectively +mean that you'd be able to use the command twice every 10 seconds. Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, @@ -148,7 +210,7 @@ const { SapphireClient } = require('@sapphire/framework'); const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting cooldownScope: 'CHANNEL' // Scope cooldown to channel @@ -167,7 +229,7 @@ import { SapphireClient } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting cooldownScope: 'CHANNEL' // Scope cooldown to channel @@ -186,7 +248,7 @@ import { SapphireClient } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting cooldownScope: 'CHANNEL' // Scope cooldown to channel @@ -213,3 +275,4 @@ Failure][reporting-precondition-failure]. [sapphire]: ../../Documentation/api-framework/classes/SapphireClient [scopes]: ../../Documentation/api-framework/enums/BucketScope [reporting-precondition-failure]: ./reporting-precondition-failure +[timeutils]: ../../Documentation/api-utilities/enums/sapphire_time_utilities.Time \ No newline at end of file diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 2349a981..46232e2f 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -6,11 +6,11 @@ title: Creating your own preconditions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be blocked according to a certain -condition. Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for +Preconditions are classes that will determine whether or not a command should be blocked according to certain conditions. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system -for you to create your own preconditions! +for you to create your own preconditions. Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will create a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. @@ -68,8 +68,8 @@ export class OwnerOnlyPrecondition extends Precondition {} Next, we can create a [`run`][preconditionrun] function to execute our logic. This function should either return -[`this.ok()`][preconditionok] or [`this.error(...)`][preconditionerror] to signify whether the command should be -blocked. +[`this.ok()`][preconditionok] to signify the condition has passed, or [`this.error(...)`][preconditionerror] to signify +the command should be denied. @@ -208,8 +208,8 @@ None of the above preconditions are builtin, and you'd have to create them separ ::: -For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` and -`ModOnly`. +For a command with these preconditions to pass the denial checks, the `InVoiceChannel` precondition must pass, as well +as `AdminOnly` _or_ both `OwnerOnly` and [creating-cooldowns]: ./command-cooldown [restricting-channel-types]: ./channel-types From 60bee928d43fad126a14b688095c62b7d94f012d Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sat, 27 Nov 2021 11:48:50 -0500 Subject: [PATCH 07/30] links --- docs/Guide/preconditions/command-cooldown.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 614a1d2f..9a9d3004 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -120,7 +120,7 @@ export class PingCommand extends Command {} -You can view the docs [here](timeutils). +You can view the docs [here][timeutils]. ::: From a6bfe368cc9e39ade507be2edb231ab00c2ff245 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sat, 27 Nov 2021 20:11:43 -0500 Subject: [PATCH 08/30] fixes --- docs/Guide/preconditions/channel-types.mdx | 21 ++--- docs/Guide/preconditions/command-cooldown.mdx | 88 ++++++++++--------- .../creating-your-own-preconditions.mdx | 46 +++++----- .../preconditions/handling-permissions.mdx | 44 ++++++---- docs/Guide/preconditions/nsfw-filter.mdx | 20 +++-- .../reporting-precondition-failure.mdx | 10 +-- 6 files changed, 125 insertions(+), 104 deletions(-) diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index 39a7261f..fd703ea7 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -12,8 +12,7 @@ make sure that the command can only be run in guild channels. :::info -[Valid values][runintypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, -`'GUILD_PUBLIC_THREAD'`, `'GUILD_PRIVATE_THREAD'`, and `'GUILD_ANY'` +You can view the valid `runIn` values [here][runintypes]. ::: @@ -52,15 +51,17 @@ export class PingCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - runIn: 'GUILD_ANY' // Only run in guild channels -}) -export class PingCommand extends Command {} +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + runIn: 'GUILD_ANY' // Only run in guild channels + }); + } +} ``` diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 614a1d2f..448223aa 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -6,10 +6,10 @@ title: Configuring command cooldowns import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. Luckily, Sapphire makes it easy to -integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the -[`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of milliseconds that a user will -have to wait after using a command to use it again. Here's a basic example: +Cooldowns are of vital importance for many bots to avoid excessive command usage, API ratelimits, and so on. Luckily, +Sapphire makes it easy to integrate them into your commands! At its simplest level, cooldowns can be used in specific +commands via the [`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of +milliseconds that a user will have to wait after using a command to use it again. Here's a basic example: @@ -46,27 +46,29 @@ export class PingCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; - -@ApplyOptions({ - // ... - cooldownDelay: 10_000 // 10_000 milliseconds -}) -export class PingCommand extends Command {} +```typescript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + cooldownDelay: 10_000 // 10_000 milliseconds + }); + } +} ``` -If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error will -be thrown. You can learn how to process that error [here][reporting-precondition-failure]. +If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error +will be thrown. You can learn how to process that error [here][reporting-precondition-failure]. :::info -`cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To help, -you can use [@sapphire/time-utilities], which includes utilities for time transformers. +`cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To +help, you can use [@sapphire/time-utilities][timeutils], which includes utilities for time transformers. @@ -79,7 +81,7 @@ module.exports = class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to parse + cooldownDelay: Time.Second * 10; // Much easier for humans to read }); } }; @@ -96,8 +98,7 @@ export class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to parse - }); + cooldownDelay: Time.Second * 10; // Much easier for humans to read } } ``` @@ -106,21 +107,23 @@ export class PingCommand extends Command { ```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +import { Command } from '@sapphire/framework'; import { Time } from '@sapphire/time-utilities'; -@ApplyOptions({ - // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to parse -}) -export class PingCommand extends Command {} +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + cooldownDelay: Time.Second * 10 // Much easier for humans to read + }); + } +} ``` -You can view the docs [here](timeutils). +You can view the docs [here][timeenum]. ::: @@ -168,15 +171,17 @@ export class PingCommand extends Command { ```typescript {7} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; - -@ApplyOptions({ - // ... - cooldownDelay: 10_000 // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner -}) -export class PingCommand extends Command {} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + cooldownDelay: 10_000 // 10_000 milliseconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner + }); + } +} ``` @@ -192,8 +197,7 @@ to `1` by default. For example, a `cooldownDelay` of `10_000` milliseconds and a mean that you'd be able to use the command twice every 10 seconds. Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to -have a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, -`'GLOBAL`', `'GUILD'`, and `'USER'` (default). +have a cooldown that applies per guild, for example, instead of per user. Valid scopes can be found [here][scopes]. ## Client-wide Cooldowns @@ -236,7 +240,7 @@ const client = new SapphireClient({ } }); -await client.login('your-token-goes-here'); +void client.login('your-token-goes-here'); ``` @@ -271,8 +275,10 @@ Failure][reporting-precondition-failure]. [cooldowndelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay [cooldownfilteredusers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers [cooldownlimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit +[cooldownscope]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownscope [defaultcooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown [sapphire]: ../../Documentation/api-framework/classes/SapphireClient [scopes]: ../../Documentation/api-framework/enums/BucketScope [reporting-precondition-failure]: ./reporting-precondition-failure -[timeutils]: ../../Documentation/api-utilities/enums/sapphire_time_utilities.Time \ No newline at end of file +[timeutils]: ../../Documentation/api-utilities/modules/sapphire_time_utilities +[timeenum]: ../../Documentation/api-utilities/enums/sapphire_time_utilities.Time diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 46232e2f..9fd1b0e7 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -6,9 +6,9 @@ title: Creating your own preconditions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be blocked according to certain conditions. -Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for -[creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling +Preconditions are classes that will determine whether or not a command should be ran according to certain conditions. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating +cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system for you to create your own preconditions. @@ -104,9 +104,8 @@ export class OwnerOnlyPrecondition extends Precondition { -```typescript {5-9} -import type { Message } from '@sapphire/framework'; -import { Precondition } from '@sapphire/framework'; +```typescript {4-8} +import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { @@ -160,15 +159,17 @@ export class PingCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - preconditions: ['OwnerOnly'] -}) -export class PingCommand extends Command {} +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + preconditions: ['OwnerOnly'] + }); + } +} ``` @@ -182,6 +183,11 @@ For TypeScript users, there's an extra step to make this work. To increase the s to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example [here][preconditions-augment]. + + ::: By default, no error message will be sent or logged when a command is denied because of a precondition. To learn how to @@ -198,18 +204,18 @@ can be nested infinitely with the same pattern for optimal control over your pre Consider the following array of preconditions: -```js -[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel']; -``` - -:::note +:::warning None of the above preconditions are builtin, and you'd have to create them separately. ::: +```js +[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel']; +``` + For a command with these preconditions to pass the denial checks, the `InVoiceChannel` precondition must pass, as well -as `AdminOnly` _or_ both `OwnerOnly` and +as `AdminOnly` _or_ both `OwnerOnly` and `ModOnly`. [creating-cooldowns]: ./command-cooldown [restricting-channel-types]: ./channel-types diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 136754e3..50cd4d40 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -10,6 +10,8 @@ One of the most basic needs of a Discord bot is to be able to deny command acces lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, so we can restrict usage using the [`requiredUserPermissions`][requireduserpermissions] option. + + @@ -45,15 +47,17 @@ export class BanCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - requiredUserPermissions: ['BAN_MEMBERS'] -}) -export class BanCommand extends Command {} +export class BanCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'] + }); + } +} ``` @@ -67,6 +71,8 @@ It's also a good idea to verify the inverse: does the _bot_ have the `BAN_MEMBER [`requiredClientPermissions`][requiredclientpermissions] option the same way to prevent the command from being used if not. + + @@ -104,16 +110,18 @@ export class BanCommand extends Command { -```typescript {7} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; - -@ApplyOptions({ - // ... - requiredUserPermissions: ['BAN_MEMBERS'], - requiredClientPermissions: ['BAN_MEMBERS'] -}) -export class BanCommand extends Command {} +```typescript {8} +import { Command } from '@sapphire/framework'; + +export class BanCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] + }); + } +} ``` diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index fec951d4..7c018c22 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -7,7 +7,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. This can be simply achieved -by adding the `nsfw` option to the command. +by setting the `nsfw` option in the command. @@ -44,15 +44,17 @@ export class NSFWCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - nsfw: true -}) -export class NSFWCommand extends Command {} +export class NSFWCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + nsfw: true + }); + } +} ``` diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 0a2dcec0..3aaea66d 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -123,7 +123,7 @@ silently. To do this, we can make use of the [`context`][context] property of `U contain information about the context in which the error was thrown, and the value can be absolutely anything. We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionerror] options. -We'll use the `OwnerOnly` precondition we made in[Creating Preconditions][creating-preconditions] to demonstrate this. +We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to demonstrate this. @@ -164,9 +164,8 @@ export class OwnerOnlyPrecondition extends Precondition { -```typescript {10} -import type { Message } from '@sapphire/framework'; -import { Precondition } from '@sapphire/framework'; +```typescript {9} +import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { @@ -217,8 +216,7 @@ export class CommandDeniedListener extends Listener { ```typescript {8} -import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; -import { Listener } from '@sapphire/framework'; +import { UserError, CommandDeniedPayload, Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { public run(error: UserError, { message }: CommandDeniedPayload) { From 33f5512c017bbd0144898171253c9320173ccdde Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sat, 27 Nov 2021 20:29:33 -0500 Subject: [PATCH 09/30] prefer enums --- docs/Guide/preconditions/channel-types.mdx | 12 ++++++------ docs/Guide/preconditions/command-cooldown.mdx | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index fd703ea7..a5c5ba14 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -20,13 +20,13 @@ You can view the valid `runIn` values [here][runintypes]. ```javascript {7} -const { Command } = require('@sapphire/framework'); +const { Command, CommandOptionsRunTypeEnum } = require('@sapphire/framework'); module.exports = class RolesCommand extends Command { constructor(context) { super(context, { // ... - runIn: 'GUILD_ANY' // Only run in guild channels + runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels }); } }; @@ -36,13 +36,13 @@ module.exports = class RolesCommand extends Command { ```javascript {7} -import { Command } from '@sapphire/framework'; +import { Command, CommandOptionsRunTypeEnum } from '@sapphire/framework'; export class PingCommand extends Command { constructor(context) { super(context, { // ... - runIn: 'GUILD_ANY' // Only run in guild channels + runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels }); } } @@ -52,13 +52,13 @@ export class PingCommand extends Command { ```typescript {7} -import { Command } from '@sapphire/framework'; +import { Command, CommandOptionsRunTypeEnum } from '@sapphire/framework'; export class PingCommand extends Command { public constructor(context: Command.Context) { super(context, { // ... - runIn: 'GUILD_ANY' // Only run in guild channels + runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels }); } } diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 448223aa..8a6523c1 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -209,7 +209,7 @@ any of the properties shown above with this option. ```javascript {5-10} -const { SapphireClient } = require('@sapphire/framework'); +const { SapphireClient, BucketScope } = require('@sapphire/framework'); const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], @@ -217,7 +217,7 @@ const client = new SapphireClient({ cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: 'CHANNEL' // Scope cooldown to channel + cooldownScope: BucketScope.Channel // Scope cooldown to channel } }); @@ -228,7 +228,7 @@ void client.login('your-token-goes-here'); ```javascript {5-10} -import { SapphireClient } from '@sapphire/framework'; +import { SapphireClient, BucketScope } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], @@ -236,7 +236,7 @@ const client = new SapphireClient({ cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: 'CHANNEL' // Scope cooldown to channel + cooldownScope: BucketScope.Channel // Scope cooldown to channel } }); @@ -247,7 +247,7 @@ void client.login('your-token-goes-here'); ```typescript {5-10} -import { SapphireClient } from '@sapphire/framework'; +import { SapphireClient, BucketScope } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], @@ -255,7 +255,7 @@ const client = new SapphireClient({ cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: 'CHANNEL' // Scope cooldown to channel + cooldownScope: BucketScope.Channel // Scope cooldown to channel } }); From 02e7df0fe86635e71b6db88eaa826eda759f6292 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 28 Nov 2021 08:49:02 -0500 Subject: [PATCH 10/30] add "what are pre" page --- docs/Guide/preconditions/channel-types.mdx | 2 +- docs/Guide/preconditions/command-cooldown.mdx | 2 +- .../creating-your-own-preconditions.mdx | 15 +++------------ docs/Guide/preconditions/handling-permissions.mdx | 2 +- docs/Guide/preconditions/nsfw-filter.mdx | 2 +- .../reporting-precondition-failure.mdx | 2 +- .../preconditions/what-are-preconditions.mdx | 15 +++++++++++++++ 7 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 docs/Guide/preconditions/what-are-preconditions.mdx diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index a5c5ba14..1c5b37a3 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 title: Setting the types of channel a command can run in --- diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 8a6523c1..b8937e30 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 title: Configuring command cooldowns --- diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 9fd1b0e7..9cd462c5 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -1,19 +1,14 @@ --- -sidebar_position: 0 +sidebar_position: 1 title: Creating your own preconditions --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be ran according to certain conditions. -Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating -cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling -permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system -for you to create your own preconditions. - Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will -create a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. +start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` +file. Your directory should now look something like this: @@ -217,10 +212,6 @@ None of the above preconditions are builtin, and you'd have to create them separ For a command with these preconditions to pass the denial checks, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` and `ModOnly`. -[creating-cooldowns]: ./command-cooldown -[restricting-channel-types]: ./channel-types -[handling-permissions]: ./handling-permissions -[nsfw-filter]: ./nsfw-filter [creating-commands]: ../getting-started/creating-a-basic-command [creating-listeners]: ../listeners/creating-your-own-listeners [precondition]: ../../Documentation/api-framework/classes/Precondition diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 50cd4d40..869ec850 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 title: Handling permissions --- diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index 7c018c22..1e6d622e 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 title: Locking commands to NSFW channels --- diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 3aaea66d..a9e65098 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 title: Reporting precondition failure --- diff --git a/docs/Guide/preconditions/what-are-preconditions.mdx b/docs/Guide/preconditions/what-are-preconditions.mdx new file mode 100644 index 00000000..d7a86080 --- /dev/null +++ b/docs/Guide/preconditions/what-are-preconditions.mdx @@ -0,0 +1,15 @@ +--- +sidebar_position: 0 +title: What are preconditions and how do they work? +--- + +Preconditions are classes that will determine whether or not a command should be ran according to certain conditions. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating +cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling +permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system +for you to create your own preconditions, which you can learn how to use on the next page. + +[creating-cooldowns]: ./command-cooldown +[restricting-channel-types]: ./channel-types +[handling-permissions]: ./handling-permissions +[nsfw-filter]: ./nsfw-filter From b2ece166d3ad551aaed1cc0cc6dc1d8b6751c9aa Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sat, 13 Nov 2021 22:58:33 -0500 Subject: [PATCH 11/30] feat: precondition guides --- .vscode/settings.json | 3 +- docs/Guide/preconditions/channel-types.mdx | 75 +++++- docs/Guide/preconditions/command-cooldown.mdx | 213 +++++++++++++++- .../creating-your-own-preconditions.mdx | 223 +++++++++++++++- .../preconditions/handling-permissions.mdx | 129 +++++++++- docs/Guide/preconditions/nsfw-filter.mdx | 65 ++++- .../reporting-precondition-failure.mdx | 241 ++++++++++++++++++ 7 files changed, 939 insertions(+), 10 deletions(-) create mode 100644 docs/Guide/preconditions/reporting-precondition-failure.mdx diff --git a/.vscode/settings.json b/.vscode/settings.json index c72a7367..2e64f7f3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,6 +17,7 @@ "**/projects/": true, }, "cSpell.words": [ - "favna" + "favna", + "nsfw" ] } diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index 706846f3..7197ff28 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -3,4 +3,77 @@ sidebar_position: 3 title: Setting the types of channel a command can run in --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The [`runIn`][runIn] command option can be used to specify the types of channels a command can run in. This can be useful +if you're developing a command that, for example, displays the roles of a user. In that scenario, you'll want to make sure +that the command can only be run in guild channels. + +:::info + +[Valid values][runInTypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, +`'GUILD_PUBLIC_THREAD'`, `'GUILD_PRIVATE_THREAD'`, and `'GUILD_ANY'` + +::: + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class RolesCommand extends Command { + constructor(context) { + super(context, { + // ... + runIn: 'GUILD_ANY' // Only run in guild channels + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + runIn: 'GUILD_ANY' // Only run in guild channels + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + runIn: 'GUILD_ANY' // Only run in guild channels +}) +export class PingCommand extends Command {} +``` + + + + +If you try to run a command in direct messages, you'll now find that nothing happens. + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[runIn]: ../../Documentation/api-framework/interfaces/CommandOptions#runin +[runInTypes]: ../../Documentation/api-framework/enums/CommandOptionsRunTypeEnum +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 732a36e8..c76fe3cd 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -1,6 +1,215 @@ --- -sidebar_position: 0 +sidebar_position: 2 title: Configuring command cooldowns --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. +Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, +cooldowns can be used in specific commands via the [`cooldownDelay`][cooldownDelay] property in their options. +This represents the time in milliseconds a user will have to wait after using a command to use it again. Here's a basic example: + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000 // 10 seconds + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000 // 10 seconds + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + cooldownDelay: 10000 // 10 seconds +}) +export class PingCommand extends Command {} +``` + + + + +If you now try to run this command, and then run it again within 10 seconds, the command won't execute! + +## User Exceptions + +It's very common to not want cooldowns to apply to certain people, for example, the bot owner. +This can be achieved by adding [`cooldownFilteredUsers`][cooldownFilteredUsers] to the options. +This option should be an array of users ID that the bot can ignore when calculating cooldowns. + + + + +```javascript {8} +const { Command } = require('@sapphire/framework'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner + }); + } +}; +``` + + + + +```javascript {8} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner + }); + } +} +``` + + + + +```typescript {7} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + cooldownDelay: 10000 // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner +}) +export class PingCommand extends Command {} +``` + + + + +## Advanced Usage + +Accompanying `cooldownDelay`, you also have access to the options +[`cooldownLimit`](cooldownLimit) and [`cooldownScope`](cooldownScope). + +`cooldownLimit` will define how many times a +command can be used before a cooldown is put into affect. This value is set to `1` by default. +For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that you'd be able to +use the command twice every 10 seconds. + +Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have +a cooldown that applies per guild, for example, instead of per user. [Valid options](scopes) are `'CHANNEL'`, `'GLOBAL`', +`'GUILD'`, and `'USER'` (default). + +## Client-wide Cooldowns + +Sometimes you'll find a use case where you want specific cooldown +options to apply to all commands in your client. This can be achieved by adding [`defaultCooldown`][defaultCooldown] to +your [`SapphireClient`][sapphire] options. You can use any of the properties shown above with this option. + + + + +```javascript {5-10} +const { SapphireClient } = require('@sapphire/framework'); + +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], + defaultCooldown: { + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner + cooldownLimit: 2, // Allow 2 uses before ratelimiting + cooldownScope: 'CHANNEL' // Scope cooldown to channel + } +}); + +void client.login('your-token-goes-here'); +``` + + + + +```javascript {5-10} +import { SapphireClient } from '@sapphire/framework'; + +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], + defaultCooldown: { + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner + cooldownLimit: 2, // Allow 2 uses before ratelimiting + cooldownScope: 'CHANNEL' // Scope cooldown to channel + } +}); + +await client.login('your-token-goes-here'); +``` + + + + +```typescript {5-10} +import { SapphireClient } from '@sapphire/framework'; + +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], + defaultCooldown: { + cooldownDelay: 10000, // 10 seconds + cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner + cooldownLimit: 2, // Allow 2 uses before ratelimiting + cooldownScope: 'CHANNEL' // Scope cooldown to channel + } +}); + +void client.login('your-token-goes-here'); +``` + + + + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[cooldownDelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay +[cooldownFilteredUsers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers +[cooldownLimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit +[defaultCooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown +[sapphire]: ../../Documentation/api-framework/classes/SapphireClient +[scopes]: ../../Documentation/api-framework/enums/BucketScope +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index ed8c4329..2fa60004 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -1,6 +1,225 @@ --- -sidebar_position: 4 +sidebar_position: 0 title: Creating your own preconditions --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Preconditions are classes that will determine whether or not a command should be blocked according to a certain condition. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, +such as for [creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], +[handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an +easy system for you to create your own preconditions! + +Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will create +a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. + +Your directory should now look something like this: + +```bash {9-10} +├── node_modules +├── package.json +└── src + ├── commands + │ └── ping.js + ├── index.js + ├── listeners + │ └── ready.js + └── preconditions + └── OwnerOnly.js +``` + +The purpose of our `OwnerOnly` precondition is just as the name suggests: to check if the user is the bot owner. +It can be used for developer commands, such as `eval`. + +## Creating a Precondition class + +Preconditions are made by extending the Sapphire [`Precondition`](precondition) class and exporting it. + + + + +```javascript +const { Precondition } = require('@sapphire/framework'); + +module.exports = class OwnerOnlyPrecondition extends Precondition {}; +``` + + + + +```javascript {4-10} +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition {} +``` + + + + +```typescript +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition {} +``` + + + + +Next, we can create a [`run`][preconditionRun] function to execute our logic. This function should either return [`this.ok()`][preconditionOk] +or [`this.error(...)`][preconditionError] to signify whether the command should be blocked. + + + + +```javascript {4-8} +const { Precondition } = require('@sapphire/framework'); + +module.exports = class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ message: 'Only the bot owner can use this command!' }) + } +}; +``` + + + + +```javascript {4-8} +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ message: 'Only the bot owner can use this command!' }) + } +} +``` + + + + +```typescript {5-9} +import type { Message } from '@sapphire/framework'; +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message: Message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ message: 'Only the bot owner can use this command!' }) + } +} +``` + + + +## Using Preconditions in Commands + +To attach a precondition to a command, you simply have to input its name in an array in the command's +[`preconditions`][preconditions-option] option. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + preconditions: ['OwnerOnly'] + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + preconditions: ['OwnerOnly'] + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + preconditions: ['OwnerOnly'] +}) +export class PingCommand extends Command {} +``` + + + + +Now, if someone who is not the bot owner executes the ping command, nothing will happen! + +:::caution + +For TypeScript users, there's an extra step to make this work. To increase the security of Sapphire's types, you'll need +to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example [here][preconditions-augment]. + +::: + +By default, no error message will be sent or logged when a command is denied because of a precondition. To learn how to +configure this, please read [Reporting Precondition Failures][reporting-precondition-failure]. + +## Advanced Usage + +Sapphire also has a builtin system for advanced conditional precondition logic through nested arrays. +By default, all preconditions in the given array must pass for the command to be run. +However, you can use nested arrays to create `OR` functionality. This could be useful if you'd like a command to be run +if the user is either a moderator _or_ an admin. + +Furthermore, if you create a nested array within a nested array, you'll receive `AND` functionality once more. Arrays can +be nested infinitely with the same pattern for optimal control over your preconditions. + +Consider the following array of preconditions: + +```js +[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel'] +``` + +:::note + +None of the above preconditions are builtin, and you'd have to create them separately. + +::: + +For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` +and `ModOnly`. + +[creating-cooldowns]: ./command-cooldown +[restricting-channel-types]: ./channel-types +[handling-permissions]: ./handling-permissions +[nsfw-filter]: ./nsfw-filter +[creating-commands]: ../getting-started/creating-a-basic-command +[creating-listeners]: ../listeners/creating-your-first-listeners +[precondition]: ../../Documentation/api-framework/classes/Precondition +[preconditionRun]: ../../Documentation/api-framework/classes/Precondition#run +[preconditionOk]: ../../Documentation/api-framework/classes/Precondition#ok +[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[preconditions-option]: ../../Documentation/api-framework/interfaces/CommandOptions#preconditions +[preconditions-interface]: ../../Documentation/api-framework/interfaces/Preconditions +[preconditions-augment]: https://github.com/sapphiredev/examples/blob/main/examples/with-typescript-recommended/src/preconditions/OwnerOnly.ts#L13-L17 +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 40cae020..a38e076a 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -1,6 +1,131 @@ --- -sidebar_position: 2 +sidebar_position: 4 title: Handling permissions --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +One of the most basic needs of a Discord bot is to be able to deny command access to users based on their permissions or +lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, +so we can restrict usage using the [`requiredUserPermissions`][requiredUserPermissions] option. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'] + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'] + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + requiredUserPermissions: ['BAN_MEMBERS'] +}) +export class BanCommand extends Command {} +``` + + + + +Users without the `BAN_MEMBERS` permission will now be unable to use the command! + +## Handling Client Permissions + +It's also a good idea to verify the inverse: does the _bot_ have the `BAN_MEMBERS` permission? We can use the +[`requiredClientPermissions`][requiredClientPermissions] option the same way to prevent the command from being used if not. + + + + +```javascript {8} +const { Command } = require('@sapphire/framework'); + +module.exports = class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] + }); + } +}; +``` + + + + +```javascript {8} +import { Command } from '@sapphire/framework'; + +export class BanCommand extends Command { + constructor(context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] + }); + } +} +``` + + + + +```typescript {7} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] +}) +export class BanCommand extends Command {} +``` + + + + +`BanCommand` now requires the command executor _and_ the client to have sufficient permissions to execute! + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[requiredUserPermissions]: ../../Documentation/api-framework/CommandOptions#requireduserpermissions +[requiredClientPermissions]: ../../Documentation/api-framework/CommandOptions#requiredclientpermissions +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index ef5a6139..1b9993cb 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -1,6 +1,67 @@ --- -sidebar_position: 1 +sidebar_position: 5 title: Locking commands to NSFW channels --- -## TODO +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. +This can be simply achieved by adding the `nsfw` option to the command. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); + +module.exports = class NSFWCommand extends Command { + constructor(context) { + super(context, { + // ... + nsfw: true + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; + +export class NSFWCommand extends Command { + constructor(context) { + super(context, { + // ... + nsfw: true + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; + +@ApplyOptions({ + // ... + nsfw: true +}) +export class NSFWCommand extends Command {} +``` + + + + +:::tip + +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. + +::: + +[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx new file mode 100644 index 00000000..c997566e --- /dev/null +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -0,0 +1,241 @@ +--- +sidebar_position: 1 +title: Reporting precondition failure +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +When a precondition fails, it's usually important for the user to know why. For example, if they hit a cooldown or lack +permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a message. + +To change this, we'll need to create a `commandDenied` listener, which is triggered when a precondition +fails. For more information on how to create listeners, see the [`Creating Listeners`][listeners] section. + +:::caution + +The `commandDenied` event shouldn't be confused with the `commandError` event, which is triggered when a command throws +an error. + +::: + +`commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, +and the [`CommandDeniedPayload`][payload], which includes necessary context. + + + + +```javascript +const { Listener } = require('@sapphire/framework'); + +module.exports = class CommandDeniedListener extends Listener { + run(error, { message }) { + // ... + } +}; +``` + + + + +```javascript +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + run(error, { message }) { + // ... + } +} +``` + + + + +```typescript +import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + public run(error: UserError, { message }: CommandDeniedPayload) { + // ... + } +} +``` + + + + +The `message` property of the `error` parameter will include the error message, as the name suggests. +In [Creating Preconditions][creating-preconditions], you can find that we defined this property +within the `this.error()` method! + +There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. +That is what we'll do in this example: + + + + +```javascript {5} +const { Listener } = require('@sapphire/framework'); + +module.exports = class CommandDeniedListener extends Listener { + run(error, { message }) { + return message.channel.send(error.message); + } +}; +``` + + + + +```javascript {5} +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + run(error, { message }) { + return message.channel.send(error.message); + } +} +``` + + + + +```typescript {5} +import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + public run(error: UserError, { message }: CommandDeniedPayload) { + return message.channel.send(error.message); + } +} +``` + + + + +## Ignoring Precondition Failures + +If someone who isn't a bot owner tries to use a command intended only for the bot owner, sometimes you don't want to +send a message notifying them that they don't have permission. Instead, you'd rather let the command be blocked silently. +To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to contain information +about the context in which the error was thrown, and the value can be absolutely anything. + +We can take advantage of this by passing the value `{ silent: true }` to the [`this.error()`][preconditionError] call of +a function. We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to +demonstrate this. + + + + +```javascript {9} +const { Precondition } = require('@sapphire/framework'); + +module.exports = class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ + message: 'Only the bot owner can use this command!' + context: { silent: true } + }) + } +}; +``` + + + + +```javascript {9} +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ + message: 'Only the bot owner can use this command!', + context: { silent: true } + }) + } +} +``` + + + + +```typescript {10} +import type { Message } from '@sapphire/framework'; +import { Precondition } from '@sapphire/framework'; + +export class OwnerOnlyPrecondition extends Precondition { + public run(message: Message) { + return message.author.id === 'YOUR_ID' + ? this.ok() + : this.error({ + message: 'Only the bot owner can use this command!' + context: { silent: true } + }) + } +} +``` + + + +We can then detect this property in our listener, and ignore the failure if we find it. + + + + +```javascript {5} +const { Listener } = require('@sapphire/framework'); + +module.exports = class CommandDeniedListener extends Listener { + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } +}; +``` + + + + +```javascript {5} +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } +} +``` + + + + +```typescript {8} +import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; +import { Listener } from '@sapphire/framework'; + +export class CommandDeniedListener extends Listener { + public run(error: UserError, { message }: CommandDeniedPayload) { + // A bit of a type hack is required for TypeScript + // Since `error.context` is `unknown` + if (Reflect.get(Object(context), 'silent')) return; + return message.channel.send(error.message); + } +} +``` + + + + +[listeners]: ../listeners/creating-your-first-listeners +[error]: ../../Documentation/api-framework/classes/UserError +[payload]: ../../Documentation/api-framework/interfaces/CommandDeniedPayload +[context]: ../../Documentation/api-framework/classes/UserError#context +[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[creating-preconditions]: ./creating-your-own-preconditions From dbba8dc8045aa09a730610073b484a72c90c4e11 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 14 Nov 2021 08:14:54 -0500 Subject: [PATCH 12/30] fix line highlighting --- docs/Guide/preconditions/creating-your-own-preconditions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 2fa60004..3dcf0376 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -49,7 +49,7 @@ module.exports = class OwnerOnlyPrecondition extends Precondition {}; -```javascript {4-10} +```javascript import { Precondition } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition {} From 897af5b82db0e994613f780862c5ad481949446f Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 14 Nov 2021 10:14:29 -0500 Subject: [PATCH 13/30] fix links --- docs/Guide/preconditions/command-cooldown.mdx | 4 ++-- docs/Guide/preconditions/creating-your-own-preconditions.mdx | 4 ++-- docs/Guide/preconditions/handling-permissions.mdx | 4 ++-- docs/Guide/preconditions/reporting-precondition-failure.mdx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index c76fe3cd..172c1b50 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -123,7 +123,7 @@ export class PingCommand extends Command {} ## Advanced Usage Accompanying `cooldownDelay`, you also have access to the options -[`cooldownLimit`](cooldownLimit) and [`cooldownScope`](cooldownScope). +[`cooldownLimit`][cooldownLimit] and [`cooldownScope`][cooldownScope]. `cooldownLimit` will define how many times a command can be used before a cooldown is put into affect. This value is set to `1` by default. @@ -131,7 +131,7 @@ For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will ef use the command twice every 10 seconds. Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have -a cooldown that applies per guild, for example, instead of per user. [Valid options](scopes) are `'CHANNEL'`, `'GLOBAL`', +a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, `'GLOBAL`', `'GUILD'`, and `'USER'` (default). ## Client-wide Cooldowns diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 3dcf0376..1c968d8e 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -35,7 +35,7 @@ It can be used for developer commands, such as `eval`. ## Creating a Precondition class -Preconditions are made by extending the Sapphire [`Precondition`](precondition) class and exporting it. +Preconditions are made by extending the Sapphire [`Precondition`][precondition] class and exporting it. @@ -214,7 +214,7 @@ and `ModOnly`. [handling-permissions]: ./handling-permissions [nsfw-filter]: ./nsfw-filter [creating-commands]: ../getting-started/creating-a-basic-command -[creating-listeners]: ../listeners/creating-your-first-listeners +[creating-listeners]: ../listeners/creating-your-own-listeners [precondition]: ../../Documentation/api-framework/classes/Precondition [preconditionRun]: ../../Documentation/api-framework/classes/Precondition#run [preconditionOk]: ../../Documentation/api-framework/classes/Precondition#ok diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index a38e076a..688b33c8 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -126,6 +126,6 @@ To learn how to send a message to the command executor when a precondition fails ::: -[requiredUserPermissions]: ../../Documentation/api-framework/CommandOptions#requireduserpermissions -[requiredClientPermissions]: ../../Documentation/api-framework/CommandOptions#requiredclientpermissions +[requiredUserPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requireduserpermissions +[requiredClientPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requiredclientpermissions [reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index c997566e..7f58034a 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -233,7 +233,7 @@ export class CommandDeniedListener extends Listener { -[listeners]: ../listeners/creating-your-first-listeners +[listeners]: ../listeners/creating-your-own-listeners [error]: ../../Documentation/api-framework/classes/UserError [payload]: ../../Documentation/api-framework/interfaces/CommandDeniedPayload [context]: ../../Documentation/api-framework/classes/UserError#context From 2b3105657c01b66a1d3ec2e98c863e5ad40257c4 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Mon, 15 Nov 2021 15:20:35 -0500 Subject: [PATCH 14/30] refactors and fixes --- .../reporting-precondition-failure.mdx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 7f58034a..4e92db5e 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -101,7 +101,7 @@ export class CommandDeniedListener extends Listener { -```typescript {5} +```typescript {6} import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; @@ -122,9 +122,8 @@ send a message notifying them that they don't have permission. Instead, you'd ra To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to contain information about the context in which the error was thrown, and the value can be absolutely anything. -We can take advantage of this by passing the value `{ silent: true }` to the [`this.error()`][preconditionError] call of -a function. We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to -demonstrate this. +We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionError] options. +We'll use the `OwnerOnly` precondition we made in[Creating Preconditions][creating-preconditions] to demonstrate this. @@ -183,7 +182,7 @@ export class OwnerOnlyPrecondition extends Precondition { -We can then detect this property in our listener, and ignore the failure if we find it. +We can then check if this property exists on the error in our listener, and ignore the failure if we find it. @@ -222,9 +221,9 @@ import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { public run(error: UserError, { message }: CommandDeniedPayload) { - // A bit of a type hack is required for TypeScript - // Since `error.context` is `unknown` - if (Reflect.get(Object(context), 'silent')) return; + // A bit of a hack is required for TypeScript + // Since `error.context` is of type `unknown` + if (Reflect.get(Object(error.context), 'silent')) return; return message.channel.send(error.message); } } From 40a75b83d63dff47a0dfcf37a8d3c3420b2e3012 Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Sat, 20 Nov 2021 17:37:11 +0100 Subject: [PATCH 15/30] style: formatting --- docs/Guide/preconditions/channel-types.mdx | 17 +-- docs/Guide/preconditions/command-cooldown.mdx | 62 +++++------ .../creating-your-own-preconditions.mdx | 57 +++++----- .../preconditions/handling-permissions.mdx | 16 +-- docs/Guide/preconditions/nsfw-filter.mdx | 9 +- .../reporting-precondition-failure.mdx | 103 +++++++++--------- 6 files changed, 136 insertions(+), 128 deletions(-) diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index 7197ff28..39a7261f 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -6,13 +6,13 @@ title: Setting the types of channel a command can run in import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -The [`runIn`][runIn] command option can be used to specify the types of channels a command can run in. This can be useful -if you're developing a command that, for example, displays the roles of a user. In that scenario, you'll want to make sure -that the command can only be run in guild channels. +The [`runIn`][runin] command option can be used to specify the types of channels a command can run in. This can be +useful if you're developing a command that, for example, displays the roles of a user. In that scenario, you'll want to +make sure that the command can only be run in guild channels. :::info -[Valid values][runInTypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, +[Valid values][runintypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, `'GUILD_PUBLIC_THREAD'`, `'GUILD_PRIVATE_THREAD'`, and `'GUILD_ANY'` ::: @@ -70,10 +70,11 @@ If you try to run a command in direct messages, you'll now find that nothing hap :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[runIn]: ../../Documentation/api-framework/interfaces/CommandOptions#runin -[runInTypes]: ../../Documentation/api-framework/enums/CommandOptionsRunTypeEnum -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[runin]: ../../Documentation/api-framework/interfaces/CommandOptions#runin +[runintypes]: ../../Documentation/api-framework/enums/CommandOptionsRunTypeEnum +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 172c1b50..4cad8938 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -6,10 +6,10 @@ title: Configuring command cooldowns import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. -Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, -cooldowns can be used in specific commands via the [`cooldownDelay`][cooldownDelay] property in their options. -This represents the time in milliseconds a user will have to wait after using a command to use it again. Here's a basic example: +Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. Luckily, Sapphire makes it easy to +integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the +[`cooldownDelay`][cooldowndelay] property in their options. This represents the time in milliseconds a user will have to +wait after using a command to use it again. Here's a basic example: @@ -64,9 +64,9 @@ If you now try to run this command, and then run it again within 10 seconds, the ## User Exceptions -It's very common to not want cooldowns to apply to certain people, for example, the bot owner. -This can be achieved by adding [`cooldownFilteredUsers`][cooldownFilteredUsers] to the options. -This option should be an array of users ID that the bot can ignore when calculating cooldowns. +It's very common to not want cooldowns to apply to certain people, for example, the bot owner. This can be achieved by +adding [`cooldownFilteredUsers`][cooldownfilteredusers] to the options. This option should be an array of users ID that +the bot can ignore when calculating cooldowns. @@ -122,23 +122,22 @@ export class PingCommand extends Command {} ## Advanced Usage -Accompanying `cooldownDelay`, you also have access to the options -[`cooldownLimit`][cooldownLimit] and [`cooldownScope`][cooldownScope]. +Accompanying `cooldownDelay`, you also have access to the options [`cooldownLimit`][cooldownlimit] and +[`cooldownScope`][cooldownscope]. -`cooldownLimit` will define how many times a -command can be used before a cooldown is put into affect. This value is set to `1` by default. -For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that you'd be able to -use the command twice every 10 seconds. +`cooldownLimit` will define how many times a command can be used before a cooldown is put into affect. This value is set +to `1` by default. For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that +you'd be able to use the command twice every 10 seconds. -Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have -a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, `'GLOBAL`', -`'GUILD'`, and `'USER'` (default). +Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to +have a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, +`'GLOBAL`', `'GUILD'`, and `'USER'` (default). ## Client-wide Cooldowns -Sometimes you'll find a use case where you want specific cooldown -options to apply to all commands in your client. This can be achieved by adding [`defaultCooldown`][defaultCooldown] to -your [`SapphireClient`][sapphire] options. You can use any of the properties shown above with this option. +Sometimes you'll find a use case where you want specific cooldown options to apply to all commands in your client. This +can be achieved by adding [`defaultCooldown`][defaultcooldown] to your [`SapphireClient`][sapphire] options. You can use +any of the properties shown above with this option. @@ -146,8 +145,8 @@ your [`SapphireClient`][sapphire] options. You can use any of the properties sho ```javascript {5-10} const { SapphireClient } = require('@sapphire/framework'); -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { cooldownDelay: 10000, // 10 seconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner @@ -165,8 +164,8 @@ void client.login('your-token-goes-here'); ```javascript {5-10} import { SapphireClient } from '@sapphire/framework'; -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { cooldownDelay: 10000, // 10 seconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner @@ -184,8 +183,8 @@ await client.login('your-token-goes-here'); ```typescript {5-10} import { SapphireClient } from '@sapphire/framework'; -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], +const client = new SapphireClient({ + intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { cooldownDelay: 10000, // 10 seconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner @@ -202,14 +201,15 @@ void client.login('your-token-goes-here'); :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[cooldownDelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay -[cooldownFilteredUsers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers -[cooldownLimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit -[defaultCooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown +[cooldowndelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay +[cooldownfilteredusers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers +[cooldownlimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit +[defaultcooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown [sapphire]: ../../Documentation/api-framework/classes/SapphireClient [scopes]: ../../Documentation/api-framework/enums/BucketScope -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 1c968d8e..2349a981 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -6,14 +6,14 @@ title: Creating your own preconditions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be blocked according to a certain condition. -Sapphire has many built in preconditions ready for use that are outlined in the coming pages, -such as for [creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], -[handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an -easy system for you to create your own preconditions! +Preconditions are classes that will determine whether or not a command should be blocked according to a certain +condition. Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for +[creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling +permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system +for you to create your own preconditions! -Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will create -a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. +Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will +create a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. Your directory should now look something like this: @@ -30,8 +30,8 @@ Your directory should now look something like this: └── OwnerOnly.js ``` -The purpose of our `OwnerOnly` precondition is just as the name suggests: to check if the user is the bot owner. -It can be used for developer commands, such as `eval`. +The purpose of our `OwnerOnly` precondition is just as the name suggests: to check if the user is the bot owner. It can +be used for developer commands, such as `eval`. ## Creating a Precondition class @@ -67,8 +67,9 @@ export class OwnerOnlyPrecondition extends Precondition {} -Next, we can create a [`run`][preconditionRun] function to execute our logic. This function should either return [`this.ok()`][preconditionOk] -or [`this.error(...)`][preconditionError] to signify whether the command should be blocked. +Next, we can create a [`run`][preconditionrun] function to execute our logic. This function should either return +[`this.ok()`][preconditionok] or [`this.error(...)`][preconditionerror] to signify whether the command should be +blocked. @@ -111,10 +112,11 @@ export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ message: 'Only the bot owner can use this command!' }) + : this.error({ message: 'Only the bot owner can use this command!' }); } } ``` + @@ -177,7 +179,8 @@ Now, if someone who is not the bot owner executes the ping command, nothing will :::caution For TypeScript users, there's an extra step to make this work. To increase the security of Sapphire's types, you'll need -to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example [here][preconditions-augment]. +to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example +[here][preconditions-augment]. ::: @@ -186,18 +189,17 @@ configure this, please read [Reporting Precondition Failures][reporting-precondi ## Advanced Usage -Sapphire also has a builtin system for advanced conditional precondition logic through nested arrays. -By default, all preconditions in the given array must pass for the command to be run. -However, you can use nested arrays to create `OR` functionality. This could be useful if you'd like a command to be run -if the user is either a moderator _or_ an admin. +Sapphire also has a builtin system for advanced conditional precondition logic through nested arrays. By default, all +preconditions in the given array must pass for the command to be run. However, you can use nested arrays to create `OR` +functionality. This could be useful if you'd like a command to be run if the user is either a moderator _or_ an admin. -Furthermore, if you create a nested array within a nested array, you'll receive `AND` functionality once more. Arrays can -be nested infinitely with the same pattern for optimal control over your preconditions. +Furthermore, if you create a nested array within a nested array, you'll receive `AND` functionality once more. Arrays +can be nested infinitely with the same pattern for optimal control over your preconditions. Consider the following array of preconditions: ```js -[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel'] +[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel']; ``` :::note @@ -206,8 +208,8 @@ None of the above preconditions are builtin, and you'd have to create them separ ::: -For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` -and `ModOnly`. +For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` and +`ModOnly`. [creating-cooldowns]: ./command-cooldown [restricting-channel-types]: ./channel-types @@ -216,10 +218,11 @@ and `ModOnly`. [creating-commands]: ../getting-started/creating-a-basic-command [creating-listeners]: ../listeners/creating-your-own-listeners [precondition]: ../../Documentation/api-framework/classes/Precondition -[preconditionRun]: ../../Documentation/api-framework/classes/Precondition#run -[preconditionOk]: ../../Documentation/api-framework/classes/Precondition#ok -[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[preconditionrun]: ../../Documentation/api-framework/classes/Precondition#run +[preconditionok]: ../../Documentation/api-framework/classes/Precondition#ok +[preconditionerror]: ../../Documentation/api-framework/classes/Precondition#error [preconditions-option]: ../../Documentation/api-framework/interfaces/CommandOptions#preconditions [preconditions-interface]: ../../Documentation/api-framework/interfaces/Preconditions -[preconditions-augment]: https://github.com/sapphiredev/examples/blob/main/examples/with-typescript-recommended/src/preconditions/OwnerOnly.ts#L13-L17 -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[preconditions-augment]: + https://github.com/sapphiredev/examples/blob/main/examples/with-typescript-recommended/src/preconditions/OwnerOnly.ts#L13-L17 +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 688b33c8..136754e3 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -7,8 +7,8 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; One of the most basic needs of a Discord bot is to be able to deny command access to users based on their permissions or -lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, -so we can restrict usage using the [`requiredUserPermissions`][requiredUserPermissions] option. +lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban +people, so we can restrict usage using the [`requiredUserPermissions`][requireduserpermissions] option. @@ -64,7 +64,8 @@ Users without the `BAN_MEMBERS` permission will now be unable to use the command ## Handling Client Permissions It's also a good idea to verify the inverse: does the _bot_ have the `BAN_MEMBERS` permission? We can use the -[`requiredClientPermissions`][requiredClientPermissions] option the same way to prevent the command from being used if not. +[`requiredClientPermissions`][requiredclientpermissions] option the same way to prevent the command from being used if +not. @@ -122,10 +123,11 @@ export class BanCommand extends Command {} :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[requiredUserPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requireduserpermissions -[requiredClientPermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requiredclientpermissions -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[requireduserpermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requireduserpermissions +[requiredclientpermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requiredclientpermissions +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index 1b9993cb..fec951d4 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -6,8 +6,8 @@ title: Locking commands to NSFW channels import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. -This can be simply achieved by adding the `nsfw` option to the command. +Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. This can be simply achieved +by adding the `nsfw` option to the command. @@ -60,8 +60,9 @@ export class NSFWCommand extends Command {} :::tip -To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition Failure][reporting-precondition-failure]. +To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition +Failure][reporting-precondition-failure]. ::: -[reporting-precondition-failure]: ./reporting-precondition-failure \ No newline at end of file +[reporting-precondition-failure]: ./reporting-precondition-failure diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 4e92db5e..0a2dcec0 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -7,10 +7,11 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; When a precondition fails, it's usually important for the user to know why. For example, if they hit a cooldown or lack -permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a message. +permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a +message. -To change this, we'll need to create a `commandDenied` listener, which is triggered when a precondition -fails. For more information on how to create listeners, see the [`Creating Listeners`][listeners] section. +To change this, we'll need to create a `commandDenied` listener, which is triggered when a precondition fails. For more +information on how to create listeners, see the [`Creating Listeners`][listeners] section. :::caution @@ -19,8 +20,8 @@ an error. ::: -`commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, -and the [`CommandDeniedPayload`][payload], which includes necessary context. +`commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, and +the [`CommandDeniedPayload`][payload], which includes necessary context. @@ -29,9 +30,9 @@ and the [`CommandDeniedPayload`][payload], which includes necessary context. const { Listener } = require('@sapphire/framework'); module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } + run(error, { message }) { + // ... + } }; ``` @@ -42,9 +43,9 @@ module.exports = class CommandDeniedListener extends Listener { import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } + run(error, { message }) { + // ... + } } ``` @@ -56,21 +57,20 @@ import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - public run(error: UserError, { message }: CommandDeniedPayload) { - // ... - } + public run(error: UserError, { message }: CommandDeniedPayload) { + // ... + } } ``` -The `message` property of the `error` parameter will include the error message, as the name suggests. -In [Creating Preconditions][creating-preconditions], you can find that we defined this property -within the `this.error()` method! +The `message` property of the `error` parameter will include the error message, as the name suggests. In [Creating +Preconditions][creating-preconditions], you can find that we defined this property within the `this.error()` method! -There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. -That is what we'll do in this example: +There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the +user. That is what we'll do in this example: @@ -79,9 +79,9 @@ That is what we'll do in this example: const { Listener } = require('@sapphire/framework'); module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } + run(error, { message }) { + return message.channel.send(error.message); + } }; ``` @@ -92,9 +92,9 @@ module.exports = class CommandDeniedListener extends Listener { import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } + run(error, { message }) { + return message.channel.send(error.message); + } } ``` @@ -106,9 +106,9 @@ import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - public run(error: UserError, { message }: CommandDeniedPayload) { - return message.channel.send(error.message); - } + public run(error: UserError, { message }: CommandDeniedPayload) { + return message.channel.send(error.message); + } } ``` @@ -118,11 +118,11 @@ export class CommandDeniedListener extends Listener { ## Ignoring Precondition Failures If someone who isn't a bot owner tries to use a command intended only for the bot owner, sometimes you don't want to -send a message notifying them that they don't have permission. Instead, you'd rather let the command be blocked silently. -To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to contain information -about the context in which the error was thrown, and the value can be absolutely anything. +send a message notifying them that they don't have permission. Instead, you'd rather let the command be blocked +silently. To do this, we can make use of the [`context`][context] property of `UserError`s. This property aims to +contain information about the context in which the error was thrown, and the value can be absolutely anything. -We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionError] options. +We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionerror] options. We'll use the `OwnerOnly` precondition we made in[Creating Preconditions][creating-preconditions] to demonstrate this. @@ -135,8 +135,8 @@ module.exports = class OwnerOnlyPrecondition extends Precondition { public run(message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ - message: 'Only the bot owner can use this command!' + : this.error({ + message: 'Only the bot owner can use this command!' context: { silent: true } }) } @@ -153,7 +153,7 @@ export class OwnerOnlyPrecondition extends Precondition { public run(message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ + : this.error({ message: 'Only the bot owner can use this command!', context: { silent: true } }) @@ -172,13 +172,14 @@ export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { return message.author.id === 'YOUR_ID' ? this.ok() - : this.error({ + : this.error({ message: 'Only the bot owner can use this command!' context: { silent: true } }) } } ``` + @@ -191,10 +192,10 @@ We can then check if this property exists on the error in our listener, and igno const { Listener } = require('@sapphire/framework'); module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - if (error.context.silent) return; - return message.channel.send(error.message); - } + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } }; ``` @@ -205,10 +206,10 @@ module.exports = class CommandDeniedListener extends Listener { import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - run(error, { message }) { - if (error.context.silent) return; - return message.channel.send(error.message); - } + run(error, { message }) { + if (error.context.silent) return; + return message.channel.send(error.message); + } } ``` @@ -220,12 +221,12 @@ import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { - public run(error: UserError, { message }: CommandDeniedPayload) { - // A bit of a hack is required for TypeScript - // Since `error.context` is of type `unknown` - if (Reflect.get(Object(error.context), 'silent')) return; - return message.channel.send(error.message); - } + public run(error: UserError, { message }: CommandDeniedPayload) { + // A bit of a hack is required for TypeScript + // Since `error.context` is of type `unknown` + if (Reflect.get(Object(error.context), 'silent')) return; + return message.channel.send(error.message); + } } ``` @@ -236,5 +237,5 @@ export class CommandDeniedListener extends Listener { [error]: ../../Documentation/api-framework/classes/UserError [payload]: ../../Documentation/api-framework/interfaces/CommandDeniedPayload [context]: ../../Documentation/api-framework/classes/UserError#context -[preconditionError]: ../../Documentation/api-framework/classes/Precondition#error +[preconditionerror]: ../../Documentation/api-framework/classes/Precondition#error [creating-preconditions]: ./creating-your-own-preconditions From ba0428c994aa4ff5d3c86bfc55c471ca0890e3e7 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Thu, 25 Nov 2021 10:30:56 -0500 Subject: [PATCH 16/30] implement favna's fixes --- docs/Guide/preconditions/command-cooldown.mdx | 91 ++++++++++++++++--- .../creating-your-own-preconditions.mdx | 14 +-- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 4cad8938..614a1d2f 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -8,8 +8,8 @@ import TabItem from '@theme/TabItem'; Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the -[`cooldownDelay`][cooldowndelay] property in their options. This represents the time in milliseconds a user will have to -wait after using a command to use it again. Here's a basic example: +[`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of milliseconds that a user will +have to wait after using a command to use it again. Here's a basic example: @@ -21,7 +21,7 @@ module.exports = class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds }); } }; @@ -37,7 +37,7 @@ export class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds }); } } @@ -52,7 +52,7 @@ import { Command, CommandOptions } from '@sapphire/framework'; @ApplyOptions({ // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds }) export class PingCommand extends Command {} ``` @@ -60,7 +60,69 @@ export class PingCommand extends Command {} -If you now try to run this command, and then run it again within 10 seconds, the command won't execute! +If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error will +be thrown. You can learn how to process that error [here][reporting-precondition-failure]. + +:::info + +`cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To help, +you can use [@sapphire/time-utilities], which includes utilities for time transformers. + + + + +```javascript {7} +const { Command } = require('@sapphire/framework'); +const { Time } = require('@sapphire/time-utilities'); + +module.exports = class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: Time.Second * 10; // Much easier for humans to parse + }); + } +}; +``` + + + + +```javascript {7} +import { Command } from '@sapphire/framework'; +import { Time } from '@sapphire/time-utilities'; + +export class PingCommand extends Command { + constructor(context) { + super(context, { + // ... + cooldownDelay: Time.Second * 10; // Much easier for humans to parse + }); + } +} +``` + + + + +```typescript {6} +import { ApplyOptions } from '@sapphire/decorators'; +import { Command, CommandOptions } from '@sapphire/framework'; +import { Time } from '@sapphire/time-utilities'; + +@ApplyOptions({ + // ... + cooldownDelay: Time.Second * 10; // Much easier for humans to parse +}) +export class PingCommand extends Command {} +``` + + + + +You can view the docs [here](timeutils). + +::: ## User Exceptions @@ -78,7 +140,7 @@ module.exports = class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner }); } @@ -95,7 +157,7 @@ export class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner }); } @@ -111,7 +173,7 @@ import { Command, CommandOptions } from '@sapphire/framework'; @ApplyOptions({ // ... - cooldownDelay: 10000 // 10 seconds + cooldownDelay: 10_000 // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner }) export class PingCommand extends Command {} @@ -126,8 +188,8 @@ Accompanying `cooldownDelay`, you also have access to the options [`cooldownLimi [`cooldownScope`][cooldownscope]. `cooldownLimit` will define how many times a command can be used before a cooldown is put into affect. This value is set -to `1` by default. For example, a `cooldownDelay` of 10 seconds, and a `cooldownLimit` of 2 will effectively mean that -you'd be able to use the command twice every 10 seconds. +to `1` by default. For example, a `cooldownDelay` of `10_000` milliseconds and a `cooldownLimit` of 2 will effectively +mean that you'd be able to use the command twice every 10 seconds. Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to have a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, @@ -148,7 +210,7 @@ const { SapphireClient } = require('@sapphire/framework'); const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting cooldownScope: 'CHANNEL' // Scope cooldown to channel @@ -167,7 +229,7 @@ import { SapphireClient } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting cooldownScope: 'CHANNEL' // Scope cooldown to channel @@ -186,7 +248,7 @@ import { SapphireClient } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], defaultCooldown: { - cooldownDelay: 10000, // 10 seconds + cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting cooldownScope: 'CHANNEL' // Scope cooldown to channel @@ -213,3 +275,4 @@ Failure][reporting-precondition-failure]. [sapphire]: ../../Documentation/api-framework/classes/SapphireClient [scopes]: ../../Documentation/api-framework/enums/BucketScope [reporting-precondition-failure]: ./reporting-precondition-failure +[timeutils]: ../../Documentation/api-utilities/enums/sapphire_time_utilities.Time \ No newline at end of file diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 2349a981..46232e2f 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -6,11 +6,11 @@ title: Creating your own preconditions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be blocked according to a certain -condition. Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for +Preconditions are classes that will determine whether or not a command should be blocked according to certain conditions. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system -for you to create your own preconditions! +for you to create your own preconditions. Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will create a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. @@ -68,8 +68,8 @@ export class OwnerOnlyPrecondition extends Precondition {} Next, we can create a [`run`][preconditionrun] function to execute our logic. This function should either return -[`this.ok()`][preconditionok] or [`this.error(...)`][preconditionerror] to signify whether the command should be -blocked. +[`this.ok()`][preconditionok] to signify the condition has passed, or [`this.error(...)`][preconditionerror] to signify +the command should be denied. @@ -208,8 +208,8 @@ None of the above preconditions are builtin, and you'd have to create them separ ::: -For the command to be run, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` and -`ModOnly`. +For a command with these preconditions to pass the denial checks, the `InVoiceChannel` precondition must pass, as well +as `AdminOnly` _or_ both `OwnerOnly` and [creating-cooldowns]: ./command-cooldown [restricting-channel-types]: ./channel-types From 7409decb686ad13a018a711a1c759f7ae3a13eb3 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sat, 27 Nov 2021 20:11:43 -0500 Subject: [PATCH 17/30] fixes --- docs/Guide/preconditions/channel-types.mdx | 21 ++--- docs/Guide/preconditions/command-cooldown.mdx | 88 ++++++++++--------- .../creating-your-own-preconditions.mdx | 46 +++++----- .../preconditions/handling-permissions.mdx | 44 ++++++---- docs/Guide/preconditions/nsfw-filter.mdx | 20 +++-- .../reporting-precondition-failure.mdx | 10 +-- 6 files changed, 125 insertions(+), 104 deletions(-) diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index 39a7261f..fd703ea7 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -12,8 +12,7 @@ make sure that the command can only be run in guild channels. :::info -[Valid values][runintypes] for `runIn` are `'DM'`, `'GUILD_TEXT'`, `'GUILD_NEWS'`, `'GUILD_NEWS_THREAD'`, -`'GUILD_PUBLIC_THREAD'`, `'GUILD_PRIVATE_THREAD'`, and `'GUILD_ANY'` +You can view the valid `runIn` values [here][runintypes]. ::: @@ -52,15 +51,17 @@ export class PingCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - runIn: 'GUILD_ANY' // Only run in guild channels -}) -export class PingCommand extends Command {} +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + runIn: 'GUILD_ANY' // Only run in guild channels + }); + } +} ``` diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 614a1d2f..448223aa 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -6,10 +6,10 @@ title: Configuring command cooldowns import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Cooldowns are of vital importance for many bots to avoid spam, API ratelimits, etc. Luckily, Sapphire makes it easy to -integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the -[`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of milliseconds that a user will -have to wait after using a command to use it again. Here's a basic example: +Cooldowns are of vital importance for many bots to avoid excessive command usage, API ratelimits, and so on. Luckily, +Sapphire makes it easy to integrate them into your commands! At its simplest level, cooldowns can be used in specific +commands via the [`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of +milliseconds that a user will have to wait after using a command to use it again. Here's a basic example: @@ -46,27 +46,29 @@ export class PingCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; - -@ApplyOptions({ - // ... - cooldownDelay: 10_000 // 10_000 milliseconds -}) -export class PingCommand extends Command {} +```typescript {7} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + cooldownDelay: 10_000 // 10_000 milliseconds + }); + } +} ``` -If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error will -be thrown. You can learn how to process that error [here][reporting-precondition-failure]. +If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error +will be thrown. You can learn how to process that error [here][reporting-precondition-failure]. :::info -`cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To help, -you can use [@sapphire/time-utilities], which includes utilities for time transformers. +`cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To +help, you can use [@sapphire/time-utilities][timeutils], which includes utilities for time transformers. @@ -79,7 +81,7 @@ module.exports = class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to parse + cooldownDelay: Time.Second * 10; // Much easier for humans to read }); } }; @@ -96,8 +98,7 @@ export class PingCommand extends Command { constructor(context) { super(context, { // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to parse - }); + cooldownDelay: Time.Second * 10; // Much easier for humans to read } } ``` @@ -106,21 +107,23 @@ export class PingCommand extends Command { ```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +import { Command } from '@sapphire/framework'; import { Time } from '@sapphire/time-utilities'; -@ApplyOptions({ - // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to parse -}) -export class PingCommand extends Command {} +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + cooldownDelay: Time.Second * 10 // Much easier for humans to read + }); + } +} ``` -You can view the docs [here](timeutils). +You can view the docs [here][timeenum]. ::: @@ -168,15 +171,17 @@ export class PingCommand extends Command { ```typescript {7} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; - -@ApplyOptions({ - // ... - cooldownDelay: 10_000 // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner -}) -export class PingCommand extends Command {} +import { Command } from '@sapphire/framework'; + +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + cooldownDelay: 10_000 // 10_000 milliseconds + cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner + }); + } +} ``` @@ -192,8 +197,7 @@ to `1` by default. For example, a `cooldownDelay` of `10_000` milliseconds and a mean that you'd be able to use the command twice every 10 seconds. Another useful option is `cooldownScope`, which will define the scope of the cooldown. This is useful if you want to -have a cooldown that applies per guild, for example, instead of per user. [Valid options][scopes] are `'CHANNEL'`, -`'GLOBAL`', `'GUILD'`, and `'USER'` (default). +have a cooldown that applies per guild, for example, instead of per user. Valid scopes can be found [here][scopes]. ## Client-wide Cooldowns @@ -236,7 +240,7 @@ const client = new SapphireClient({ } }); -await client.login('your-token-goes-here'); +void client.login('your-token-goes-here'); ``` @@ -271,8 +275,10 @@ Failure][reporting-precondition-failure]. [cooldowndelay]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldowndelay [cooldownfilteredusers]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownfilteredusers [cooldownlimit]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownlimit +[cooldownscope]: ../../Documentation/api-framework/interfaces/CommandOptions#cooldownscope [defaultcooldown]: ../../Documentation/api-framework/interfaces/SapphireClientOptions#defaultcooldown [sapphire]: ../../Documentation/api-framework/classes/SapphireClient [scopes]: ../../Documentation/api-framework/enums/BucketScope [reporting-precondition-failure]: ./reporting-precondition-failure -[timeutils]: ../../Documentation/api-utilities/enums/sapphire_time_utilities.Time \ No newline at end of file +[timeutils]: ../../Documentation/api-utilities/modules/sapphire_time_utilities +[timeenum]: ../../Documentation/api-utilities/enums/sapphire_time_utilities.Time diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 46232e2f..9fd1b0e7 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -6,9 +6,9 @@ title: Creating your own preconditions import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be blocked according to certain conditions. -Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for -[creating cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling +Preconditions are classes that will determine whether or not a command should be ran according to certain conditions. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating +cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system for you to create your own preconditions. @@ -104,9 +104,8 @@ export class OwnerOnlyPrecondition extends Precondition { -```typescript {5-9} -import type { Message } from '@sapphire/framework'; -import { Precondition } from '@sapphire/framework'; +```typescript {4-8} +import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { @@ -160,15 +159,17 @@ export class PingCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - preconditions: ['OwnerOnly'] -}) -export class PingCommand extends Command {} +export class PingCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + preconditions: ['OwnerOnly'] + }); + } +} ``` @@ -182,6 +183,11 @@ For TypeScript users, there's an extra step to make this work. To increase the s to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example [here][preconditions-augment]. + + ::: By default, no error message will be sent or logged when a command is denied because of a precondition. To learn how to @@ -198,18 +204,18 @@ can be nested infinitely with the same pattern for optimal control over your pre Consider the following array of preconditions: -```js -[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel']; -``` - -:::note +:::warning None of the above preconditions are builtin, and you'd have to create them separately. ::: +```js +[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel']; +``` + For a command with these preconditions to pass the denial checks, the `InVoiceChannel` precondition must pass, as well -as `AdminOnly` _or_ both `OwnerOnly` and +as `AdminOnly` _or_ both `OwnerOnly` and `ModOnly`. [creating-cooldowns]: ./command-cooldown [restricting-channel-types]: ./channel-types diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 136754e3..50cd4d40 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -10,6 +10,8 @@ One of the most basic needs of a Discord bot is to be able to deny command acces lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, so we can restrict usage using the [`requiredUserPermissions`][requireduserpermissions] option. + + @@ -45,15 +47,17 @@ export class BanCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - requiredUserPermissions: ['BAN_MEMBERS'] -}) -export class BanCommand extends Command {} +export class BanCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'] + }); + } +} ``` @@ -67,6 +71,8 @@ It's also a good idea to verify the inverse: does the _bot_ have the `BAN_MEMBER [`requiredClientPermissions`][requiredclientpermissions] option the same way to prevent the command from being used if not. + + @@ -104,16 +110,18 @@ export class BanCommand extends Command { -```typescript {7} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; - -@ApplyOptions({ - // ... - requiredUserPermissions: ['BAN_MEMBERS'], - requiredClientPermissions: ['BAN_MEMBERS'] -}) -export class BanCommand extends Command {} +```typescript {8} +import { Command } from '@sapphire/framework'; + +export class BanCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + requiredUserPermissions: ['BAN_MEMBERS'], + requiredClientPermissions: ['BAN_MEMBERS'] + }); + } +} ``` diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index fec951d4..7c018c22 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -7,7 +7,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. This can be simply achieved -by adding the `nsfw` option to the command. +by setting the `nsfw` option in the command. @@ -44,15 +44,17 @@ export class NSFWCommand extends Command { -```typescript {6} -import { ApplyOptions } from '@sapphire/decorators'; -import { Command, CommandOptions } from '@sapphire/framework'; +```typescript {7} +import { Command } from '@sapphire/framework'; -@ApplyOptions({ - // ... - nsfw: true -}) -export class NSFWCommand extends Command {} +export class NSFWCommand extends Command { + public constructor(context: Command.Context) { + super(context, { + // ... + nsfw: true + }); + } +} ``` diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 0a2dcec0..3aaea66d 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -123,7 +123,7 @@ silently. To do this, we can make use of the [`context`][context] property of `U contain information about the context in which the error was thrown, and the value can be absolutely anything. We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionerror] options. -We'll use the `OwnerOnly` precondition we made in[Creating Preconditions][creating-preconditions] to demonstrate this. +We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to demonstrate this. @@ -164,9 +164,8 @@ export class OwnerOnlyPrecondition extends Precondition { -```typescript {10} -import type { Message } from '@sapphire/framework'; -import { Precondition } from '@sapphire/framework'; +```typescript {9} +import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { public run(message: Message) { @@ -217,8 +216,7 @@ export class CommandDeniedListener extends Listener { ```typescript {8} -import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; -import { Listener } from '@sapphire/framework'; +import { UserError, CommandDeniedPayload, Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { public run(error: UserError, { message }: CommandDeniedPayload) { From 659cf9a20ca60ece951a555b6ffb7af40a0d5d6d Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sat, 27 Nov 2021 20:29:33 -0500 Subject: [PATCH 18/30] prefer enums --- docs/Guide/preconditions/channel-types.mdx | 12 ++++++------ docs/Guide/preconditions/command-cooldown.mdx | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index fd703ea7..a5c5ba14 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -20,13 +20,13 @@ You can view the valid `runIn` values [here][runintypes]. ```javascript {7} -const { Command } = require('@sapphire/framework'); +const { Command, CommandOptionsRunTypeEnum } = require('@sapphire/framework'); module.exports = class RolesCommand extends Command { constructor(context) { super(context, { // ... - runIn: 'GUILD_ANY' // Only run in guild channels + runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels }); } }; @@ -36,13 +36,13 @@ module.exports = class RolesCommand extends Command { ```javascript {7} -import { Command } from '@sapphire/framework'; +import { Command, CommandOptionsRunTypeEnum } from '@sapphire/framework'; export class PingCommand extends Command { constructor(context) { super(context, { // ... - runIn: 'GUILD_ANY' // Only run in guild channels + runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels }); } } @@ -52,13 +52,13 @@ export class PingCommand extends Command { ```typescript {7} -import { Command } from '@sapphire/framework'; +import { Command, CommandOptionsRunTypeEnum } from '@sapphire/framework'; export class PingCommand extends Command { public constructor(context: Command.Context) { super(context, { // ... - runIn: 'GUILD_ANY' // Only run in guild channels + runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels }); } } diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 448223aa..8a6523c1 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -209,7 +209,7 @@ any of the properties shown above with this option. ```javascript {5-10} -const { SapphireClient } = require('@sapphire/framework'); +const { SapphireClient, BucketScope } = require('@sapphire/framework'); const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], @@ -217,7 +217,7 @@ const client = new SapphireClient({ cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: 'CHANNEL' // Scope cooldown to channel + cooldownScope: BucketScope.Channel // Scope cooldown to channel } }); @@ -228,7 +228,7 @@ void client.login('your-token-goes-here'); ```javascript {5-10} -import { SapphireClient } from '@sapphire/framework'; +import { SapphireClient, BucketScope } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], @@ -236,7 +236,7 @@ const client = new SapphireClient({ cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: 'CHANNEL' // Scope cooldown to channel + cooldownScope: BucketScope.Channel // Scope cooldown to channel } }); @@ -247,7 +247,7 @@ void client.login('your-token-goes-here'); ```typescript {5-10} -import { SapphireClient } from '@sapphire/framework'; +import { SapphireClient, BucketScope } from '@sapphire/framework'; const client = new SapphireClient({ intents: ['GUILDS', 'GUILD_MESSAGES'], @@ -255,7 +255,7 @@ const client = new SapphireClient({ cooldownDelay: 10_000, // 10_000 milliseconds cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: 'CHANNEL' // Scope cooldown to channel + cooldownScope: BucketScope.Channel // Scope cooldown to channel } }); From bf980ac69c6644767a01ec0c6be6f454a572b16a Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 28 Nov 2021 08:49:02 -0500 Subject: [PATCH 19/30] add "what are pre" page --- docs/Guide/preconditions/channel-types.mdx | 2 +- docs/Guide/preconditions/command-cooldown.mdx | 2 +- .../creating-your-own-preconditions.mdx | 15 +++------------ docs/Guide/preconditions/handling-permissions.mdx | 2 +- docs/Guide/preconditions/nsfw-filter.mdx | 2 +- .../reporting-precondition-failure.mdx | 2 +- .../preconditions/what-are-preconditions.mdx | 15 +++++++++++++++ 7 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 docs/Guide/preconditions/what-are-preconditions.mdx diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index a5c5ba14..1c5b37a3 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 title: Setting the types of channel a command can run in --- diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 8a6523c1..b8937e30 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 title: Configuring command cooldowns --- diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 9fd1b0e7..9cd462c5 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -1,19 +1,14 @@ --- -sidebar_position: 0 +sidebar_position: 1 title: Creating your own preconditions --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -Preconditions are classes that will determine whether or not a command should be ran according to certain conditions. -Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating -cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling -permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system -for you to create your own preconditions. - Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will -create a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. +start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` +file. Your directory should now look something like this: @@ -217,10 +212,6 @@ None of the above preconditions are builtin, and you'd have to create them separ For a command with these preconditions to pass the denial checks, the `InVoiceChannel` precondition must pass, as well as `AdminOnly` _or_ both `OwnerOnly` and `ModOnly`. -[creating-cooldowns]: ./command-cooldown -[restricting-channel-types]: ./channel-types -[handling-permissions]: ./handling-permissions -[nsfw-filter]: ./nsfw-filter [creating-commands]: ../getting-started/creating-a-basic-command [creating-listeners]: ../listeners/creating-your-own-listeners [precondition]: ../../Documentation/api-framework/classes/Precondition diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 50cd4d40..869ec850 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 title: Handling permissions --- diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index 7c018c22..1e6d622e 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 title: Locking commands to NSFW channels --- diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index 3aaea66d..a9e65098 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 title: Reporting precondition failure --- diff --git a/docs/Guide/preconditions/what-are-preconditions.mdx b/docs/Guide/preconditions/what-are-preconditions.mdx new file mode 100644 index 00000000..d7a86080 --- /dev/null +++ b/docs/Guide/preconditions/what-are-preconditions.mdx @@ -0,0 +1,15 @@ +--- +sidebar_position: 0 +title: What are preconditions and how do they work? +--- + +Preconditions are classes that will determine whether or not a command should be ran according to certain conditions. +Sapphire has many built in preconditions ready for use that are outlined in the coming pages, such as for [creating +cooldowns][creating-cooldowns], [restricting channel types][restricting-channel-types], [handling +permissions][handling-permissions], and [filtering nsfw channels][nsfw-filter]. However, we also provide an easy system +for you to create your own preconditions, which you can learn how to use on the next page. + +[creating-cooldowns]: ./command-cooldown +[restricting-channel-types]: ./channel-types +[handling-permissions]: ./handling-permissions +[nsfw-filter]: ./nsfw-filter From f64466ef4cc79f94796bcb87d380892c74713ff5 Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Sun, 28 Nov 2021 19:25:01 +0100 Subject: [PATCH 20/30] chore: insert commit message here permalink: http://whatthecommit.com/83099b224ff42a51fda5015eedc9d95e --- docs/Guide/preconditions/creating-your-own-preconditions.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 9cd462c5..fc0b9cd3 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -7,8 +7,8 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will -start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` -file. +start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an +`OwnerOnly` file. Your directory should now look something like this: From 4939401104121984f67f642265cadd77356b99ab Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Sun, 28 Nov 2021 21:57:10 +0100 Subject: [PATCH 21/30] Update docs/Guide/preconditions/creating-your-own-preconditions.mdx Co-authored-by: Vlad Frangu --- docs/Guide/preconditions/creating-your-own-preconditions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index fc0b9cd3..95cbb13c 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -201,7 +201,7 @@ Consider the following array of preconditions: :::warning -None of the above preconditions are builtin, and you'd have to create them separately. +None of the following preconditions are bundled with Sapphire; as such you'd have to create them yourself! ::: From c4fc58f7fb59e8910e74e551f809d7defecb5f32 Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Sun, 28 Nov 2021 21:57:17 +0100 Subject: [PATCH 22/30] Update docs/Guide/preconditions/handling-permissions.mdx Co-authored-by: Vlad Frangu --- docs/Guide/preconditions/handling-permissions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 869ec850..c1515dc8 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -127,7 +127,7 @@ export class BanCommand extends Command { -`BanCommand` now requires the command executor _and_ the client to have sufficient permissions to execute! +With these changes, `BanCommand` now requires the command executor _and_ the client to have the `BAN_MEMBERS` permission to execute! :::tip From bce70940bf8edd1967c694d4ec08344839c08ecd Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Sun, 28 Nov 2021 21:58:59 +0100 Subject: [PATCH 23/30] chore: should work i guess... permalink: http://whatthecommit.com/c400ea845c84477175310b99df94bb83 --- docs/Guide/preconditions/handling-permissions.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index c1515dc8..e409d871 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -127,7 +127,8 @@ export class BanCommand extends Command { -With these changes, `BanCommand` now requires the command executor _and_ the client to have the `BAN_MEMBERS` permission to execute! +With these changes, `BanCommand` now requires the command executor _and_ the client to have the `BAN_MEMBERS` permission +to execute! :::tip From c917c1a005f50522319b8fec64d2bd241bdc91c5 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 5 Dec 2021 22:46:36 -0500 Subject: [PATCH 24/30] Convert tabs to plugin --- docs/Guide/preconditions/channel-types.mdx | 43 +---- docs/Guide/preconditions/command-cooldown.mdx | 172 +----------------- .../creating-your-own-preconditions.mdx | 107 +---------- .../preconditions/handling-permissions.mdx | 85 +-------- docs/Guide/preconditions/nsfw-filter.mdx | 43 +---- .../reporting-precondition-failure.mdx | 118 +----------- 6 files changed, 15 insertions(+), 553 deletions(-) diff --git a/docs/Guide/preconditions/channel-types.mdx b/docs/Guide/preconditions/channel-types.mdx index 1c5b37a3..d1e405f9 100644 --- a/docs/Guide/preconditions/channel-types.mdx +++ b/docs/Guide/preconditions/channel-types.mdx @@ -3,9 +3,6 @@ sidebar_position: 4 title: Setting the types of channel a command can run in --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - The [`runIn`][runin] command option can be used to specify the types of channels a command can run in. This can be useful if you're developing a command that, for example, displays the roles of a user. In that scenario, you'll want to make sure that the command can only be run in guild channels. @@ -16,42 +13,7 @@ You can view the valid `runIn` values [here][runintypes]. ::: - - - -```javascript {7} -const { Command, CommandOptionsRunTypeEnum } = require('@sapphire/framework'); - -module.exports = class RolesCommand extends Command { - constructor(context) { - super(context, { - // ... - runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels - }); - } -}; -``` - - - - -```javascript {7} -import { Command, CommandOptionsRunTypeEnum } from '@sapphire/framework'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels - }); - } -} -``` - - - - -```typescript {7} +```typescript ts2esm2cjs|{7}|{7} import { Command, CommandOptionsRunTypeEnum } from '@sapphire/framework'; export class PingCommand extends Command { @@ -64,9 +26,6 @@ export class PingCommand extends Command { } ``` - - - If you try to run a command in direct messages, you'll now find that nothing happens. :::tip diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index b8937e30..9674a05c 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -3,50 +3,12 @@ sidebar_position: 3 title: Configuring command cooldowns --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - Cooldowns are of vital importance for many bots to avoid excessive command usage, API ratelimits, and so on. Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the [`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of milliseconds that a user will have to wait after using a command to use it again. Here's a basic example: - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000 // 10_000 milliseconds - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000 // 10_000 milliseconds - }); - } -} -``` - - - - -```typescript {7} +```typescript ts2esm2cjs|{7}|{7} import { Command } from '@sapphire/framework'; export class PingCommand extends Command { @@ -59,9 +21,6 @@ export class PingCommand extends Command { } ``` - - - If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error will be thrown. You can learn how to process that error [here][reporting-precondition-failure]. @@ -70,43 +29,7 @@ will be thrown. You can learn how to process that error [here][reporting-precond `cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To help, you can use [@sapphire/time-utilities][timeutils], which includes utilities for time transformers. - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); -const { Time } = require('@sapphire/time-utilities'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to read - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; -import { Time } from '@sapphire/time-utilities'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to read - } -} -``` - - - - -```typescript {6} +```typescript ts2esm2cjs|{8}|{8} import { Command } from '@sapphire/framework'; import { Time } from '@sapphire/time-utilities'; @@ -120,9 +43,6 @@ export class PingCommand extends Command { } ``` - - - You can view the docs [here][timeenum]. ::: @@ -133,44 +53,7 @@ It's very common to not want cooldowns to apply to certain people, for example, adding [`cooldownFilteredUsers`][cooldownfilteredusers] to the options. This option should be an array of users ID that the bot can ignore when calculating cooldowns. - - - -```javascript {8} -const { Command } = require('@sapphire/framework'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner - }); - } -}; -``` - - - - -```javascript {8} -import { Command } from '@sapphire/framework'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner - }); - } -} -``` - - - - -```typescript {7} +```typescript ts2esm2cjs|{7}|{7} import { Command } from '@sapphire/framework'; export class PingCommand extends Command { @@ -184,9 +67,6 @@ export class PingCommand extends Command { } ``` - - - ## Advanced Usage Accompanying `cooldownDelay`, you also have access to the options [`cooldownLimit`][cooldownlimit] and @@ -205,29 +85,7 @@ Sometimes you'll find a use case where you want specific cooldown options to app can be achieved by adding [`defaultCooldown`][defaultcooldown] to your [`SapphireClient`][sapphire] options. You can use any of the properties shown above with this option. - - - -```javascript {5-10} -const { SapphireClient, BucketScope } = require('@sapphire/framework'); - -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], - defaultCooldown: { - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner - cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: BucketScope.Channel // Scope cooldown to channel - } -}); - -void client.login('your-token-goes-here'); -``` - - - - -```javascript {5-10} +```typescript ts2esm2cjs|{5-10}|{5-10} import { SapphireClient, BucketScope } from '@sapphire/framework'; const client = new SapphireClient({ @@ -243,28 +101,6 @@ const client = new SapphireClient({ void client.login('your-token-goes-here'); ``` - - - -```typescript {5-10} -import { SapphireClient, BucketScope } from '@sapphire/framework'; - -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], - defaultCooldown: { - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner - cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: BucketScope.Channel // Scope cooldown to channel - } -}); - -void client.login('your-token-goes-here'); -``` - - - - :::tip To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 9cd462c5..fd1b1ad5 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -3,9 +3,6 @@ sidebar_position: 1 title: Creating your own preconditions --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. @@ -32,74 +29,17 @@ be used for developer commands, such as `eval`. Preconditions are made by extending the Sapphire [`Precondition`][precondition] class and exporting it. - - - -```javascript -const { Precondition } = require('@sapphire/framework'); - -module.exports = class OwnerOnlyPrecondition extends Precondition {}; -``` - - - - -```javascript -import { Precondition } from '@sapphire/framework'; - -export class OwnerOnlyPrecondition extends Precondition {} -``` - - - - -```typescript +```typescript ts2esm2cjs import { Precondition } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition {} ``` - - - Next, we can create a [`run`][preconditionrun] function to execute our logic. This function should either return [`this.ok()`][preconditionok] to signify the condition has passed, or [`this.error(...)`][preconditionerror] to signify the command should be denied. - - - -```javascript {4-8} -const { Precondition } = require('@sapphire/framework'); - -module.exports = class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ message: 'Only the bot owner can use this command!' }) - } -}; -``` - - - - -```javascript {4-8} -import { Precondition } from '@sapphire/framework'; - -export class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ message: 'Only the bot owner can use this command!' }) - } -} -``` - - - - -```typescript {4-8} +```typescript ts2esm2cjs|{4-8}|{4-8} import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { @@ -111,50 +51,12 @@ export class OwnerOnlyPrecondition extends Precondition { } ``` - - - ## Using Preconditions in Commands To attach a precondition to a command, you simply have to input its name in an array in the command's [`preconditions`][preconditions-option] option. - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - preconditions: ['OwnerOnly'] - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - preconditions: ['OwnerOnly'] - }); - } -} -``` - - - - -```typescript {7} +```typescript ts2esm2cjs|{7}|{7} import { Command } from '@sapphire/framework'; export class PingCommand extends Command { @@ -167,9 +69,6 @@ export class PingCommand extends Command { } ``` - - - Now, if someone who is not the bot owner executes the ping command, nothing will happen! :::caution diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 869ec850..a8ee8271 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -3,51 +3,13 @@ sidebar_position: 5 title: Handling permissions --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - One of the most basic needs of a Discord bot is to be able to deny command access to users based on their permissions or lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, so we can restrict usage using the [`requiredUserPermissions`][requireduserpermissions] option. - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); - -module.exports = class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'] - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; - -export class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'] - }); - } -} -``` - - - - -```typescript {7} +```typescript ts2esm2cjs|{7}|{7} import { Command } from '@sapphire/framework'; export class BanCommand extends Command { @@ -60,9 +22,6 @@ export class BanCommand extends Command { } ``` - - - Users without the `BAN_MEMBERS` permission will now be unable to use the command! ## Handling Client Permissions @@ -73,44 +32,7 @@ not. - - - -```javascript {8} -const { Command } = require('@sapphire/framework'); - -module.exports = class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'], - requiredClientPermissions: ['BAN_MEMBERS'] - }); - } -}; -``` - - - - -```javascript {8} -import { Command } from '@sapphire/framework'; - -export class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'], - requiredClientPermissions: ['BAN_MEMBERS'] - }); - } -} -``` - - - - -```typescript {8} +```typescript ts2esm2cjs|{8}|{8} import { Command } from '@sapphire/framework'; export class BanCommand extends Command { @@ -124,9 +46,6 @@ export class BanCommand extends Command { } ``` - - - `BanCommand` now requires the command executor _and_ the client to have sufficient permissions to execute! :::tip diff --git a/docs/Guide/preconditions/nsfw-filter.mdx b/docs/Guide/preconditions/nsfw-filter.mdx index 1e6d622e..7b6d61fd 100644 --- a/docs/Guide/preconditions/nsfw-filter.mdx +++ b/docs/Guide/preconditions/nsfw-filter.mdx @@ -3,48 +3,10 @@ sidebar_position: 6 title: Locking commands to NSFW channels --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - Sometimes it can be necessary to lock certain commands to NSFW (not safe for work) channels. This can be simply achieved by setting the `nsfw` option in the command. - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); - -module.exports = class NSFWCommand extends Command { - constructor(context) { - super(context, { - // ... - nsfw: true - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; - -export class NSFWCommand extends Command { - constructor(context) { - super(context, { - // ... - nsfw: true - }); - } -} -``` - - - - -```typescript {7} +```typescript ts2esm2cjs|{7}|{7} import { Command } from '@sapphire/framework'; export class NSFWCommand extends Command { @@ -57,9 +19,6 @@ export class NSFWCommand extends Command { } ``` - - - :::tip To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index a9e65098..d0404600 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -3,9 +3,6 @@ sidebar_position: 2 title: Reporting precondition failure --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - When a precondition fails, it's usually important for the user to know why. For example, if they hit a cooldown or lack permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a message. @@ -23,36 +20,7 @@ an error. `commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, and the [`CommandDeniedPayload`][payload], which includes necessary context. - - - -```javascript -const { Listener } = require('@sapphire/framework'); - -module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } -}; -``` - - - - -```javascript -import { Listener } from '@sapphire/framework'; - -export class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } -} -``` - - - - -```typescript +```typescript ts2esm2cjs import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; @@ -63,47 +31,14 @@ export class CommandDeniedListener extends Listener { } ``` - - - The `message` property of the `error` parameter will include the error message, as the name suggests. In [Creating Preconditions][creating-preconditions], you can find that we defined this property within the `this.error()` method! There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. That is what we'll do in this example: - - - -```javascript {5} -const { Listener } = require('@sapphire/framework'); - -module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } -}; -``` - - - - -```javascript {5} -import { Listener } from '@sapphire/framework'; - -export class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } -} -``` - - - - -```typescript {6} -import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; -import { Listener } from '@sapphire/framework'; +```typescript ts2esm2cjs|{6}|{6} +import { Listener, UserError, CommandDeniedPayload } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { public run(error: UserError, { message }: CommandDeniedPayload) { @@ -112,9 +47,6 @@ export class CommandDeniedListener extends Listener { } ``` - - - ## Ignoring Precondition Failures If someone who isn't a bot owner tries to use a command intended only for the bot owner, sometimes you don't want to @@ -125,46 +57,7 @@ contain information about the context in which the error was thrown, and the val We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionerror] options. We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to demonstrate this. - - - -```javascript {9} -const { Precondition } = require('@sapphire/framework'); - -module.exports = class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ - message: 'Only the bot owner can use this command!' - context: { silent: true } - }) - } -}; -``` - - - - -```javascript {9} -import { Precondition } from '@sapphire/framework'; - -export class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ - message: 'Only the bot owner can use this command!', - context: { silent: true } - }) - } -} -``` - - - - -```typescript {9} +```typescript ts2esm2cjs|{9}|{9} import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { @@ -179,9 +72,6 @@ export class OwnerOnlyPrecondition extends Precondition { } ``` - - - We can then check if this property exists on the error in our listener, and ignore the failure if we find it. From 87cd8d7d7f1084aec94dc5d7266ff9a046860399 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 5 Dec 2021 22:54:22 -0500 Subject: [PATCH 25/30] Merge branch 'feat/preconditionGuides' of https://github.com/Lioness100/website into feat/preconditionGuides From b3c131c4bd76d69fdcc52207651f9aca6231e54f Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 5 Dec 2021 23:01:52 -0500 Subject: [PATCH 26/30] conflicts --- docs/Guide/preconditions/command-cooldown.mdx | 195 ------------------ .../creating-your-own-preconditions.mdx | 132 ------------ .../preconditions/handling-permissions.mdx | 99 --------- .../reporting-precondition-failure.mdx | 135 ------------ 4 files changed, 561 deletions(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index be88770a..9674a05c 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -3,57 +3,12 @@ sidebar_position: 3 title: Configuring command cooldowns --- -<<<<<<< HEAD -======= -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd Cooldowns are of vital importance for many bots to avoid excessive command usage, API ratelimits, and so on. Luckily, Sapphire makes it easy to integrate them into your commands! At its simplest level, cooldowns can be used in specific commands via the [`cooldownDelay`][cooldowndelay] property in the command's options. This value is amount of milliseconds that a user will have to wait after using a command to use it again. Here's a basic example: -<<<<<<< HEAD ```typescript ts2esm2cjs|{7}|{7} -======= - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000 // 10_000 milliseconds - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000 // 10_000 milliseconds - }); - } -} -``` - - - - -```typescript {7} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Command } from '@sapphire/framework'; export class PingCommand extends Command { @@ -66,12 +21,6 @@ export class PingCommand extends Command { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error will be thrown. You can learn how to process that error [here][reporting-precondition-failure]. @@ -80,47 +29,7 @@ will be thrown. You can learn how to process that error [here][reporting-precond `cooldownDelay` only accepts a value of milliseconds, which is not known to be the easiest to read or calculate. To help, you can use [@sapphire/time-utilities][timeutils], which includes utilities for time transformers. -<<<<<<< HEAD ```typescript ts2esm2cjs|{8}|{8} -======= - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); -const { Time } = require('@sapphire/time-utilities'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to read - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; -import { Time } from '@sapphire/time-utilities'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: Time.Second * 10; // Much easier for humans to read - } -} -``` - - - - -```typescript {6} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Command } from '@sapphire/framework'; import { Time } from '@sapphire/time-utilities'; @@ -134,12 +43,6 @@ export class PingCommand extends Command { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd You can view the docs [here][timeenum]. ::: @@ -150,48 +53,7 @@ It's very common to not want cooldowns to apply to certain people, for example, adding [`cooldownFilteredUsers`][cooldownfilteredusers] to the options. This option should be an array of users ID that the bot can ignore when calculating cooldowns. -<<<<<<< HEAD ```typescript ts2esm2cjs|{7}|{7} -======= - - - -```javascript {8} -const { Command } = require('@sapphire/framework'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner - }); - } -}; -``` - - - - -```javascript {8} -import { Command } from '@sapphire/framework'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner - }); - } -} -``` - - - - -```typescript {7} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Command } from '@sapphire/framework'; export class PingCommand extends Command { @@ -205,12 +67,6 @@ export class PingCommand extends Command { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd ## Advanced Usage Accompanying `cooldownDelay`, you also have access to the options [`cooldownLimit`][cooldownlimit] and @@ -229,33 +85,7 @@ Sometimes you'll find a use case where you want specific cooldown options to app can be achieved by adding [`defaultCooldown`][defaultcooldown] to your [`SapphireClient`][sapphire] options. You can use any of the properties shown above with this option. -<<<<<<< HEAD ```typescript ts2esm2cjs|{5-10}|{5-10} -======= - - - -```javascript {5-10} -const { SapphireClient, BucketScope } = require('@sapphire/framework'); - -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], - defaultCooldown: { - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner - cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: BucketScope.Channel // Scope cooldown to channel - } -}); - -void client.login('your-token-goes-here'); -``` - - - - -```javascript {5-10} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { SapphireClient, BucketScope } from '@sapphire/framework'; const client = new SapphireClient({ @@ -271,31 +101,6 @@ const client = new SapphireClient({ void client.login('your-token-goes-here'); ``` -<<<<<<< HEAD -======= - - - -```typescript {5-10} -import { SapphireClient, BucketScope } from '@sapphire/framework'; - -const client = new SapphireClient({ - intents: ['GUILDS', 'GUILD_MESSAGES'], - defaultCooldown: { - cooldownDelay: 10_000, // 10_000 milliseconds - cooldownFilteredUsers: ['YOUR_ID'], // Ignore the bot owner - cooldownLimit: 2, // Allow 2 uses before ratelimiting - cooldownScope: BucketScope.Channel // Scope cooldown to channel - } -}); - -void client.login('your-token-goes-here'); -``` - - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd :::tip To learn how to send a message to the command executor when a precondition fails, see [Reporting Precondition diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index d83ac213..18b500b8 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -3,18 +3,9 @@ sidebar_position: 1 title: Creating your own preconditions --- -<<<<<<< HEAD -Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will -start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` -file. -======= -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an `OwnerOnly` file. ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd Your directory should now look something like this: @@ -38,85 +29,17 @@ be used for developer commands, such as `eval`. Preconditions are made by extending the Sapphire [`Precondition`][precondition] class and exporting it. -<<<<<<< HEAD ```typescript ts2esm2cjs -======= - - - -```javascript -const { Precondition } = require('@sapphire/framework'); - -module.exports = class OwnerOnlyPrecondition extends Precondition {}; -``` - - - - -```javascript import { Precondition } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition {} ``` - - - -```typescript ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd -import { Precondition } from '@sapphire/framework'; - -export class OwnerOnlyPrecondition extends Precondition {} -``` - -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd Next, we can create a [`run`][preconditionrun] function to execute our logic. This function should either return [`this.ok()`][preconditionok] to signify the condition has passed, or [`this.error(...)`][preconditionerror] to signify the command should be denied. -<<<<<<< HEAD ```typescript ts2esm2cjs|{4-8}|{4-8} -======= - - - -```javascript {4-8} -const { Precondition } = require('@sapphire/framework'); - -module.exports = class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ message: 'Only the bot owner can use this command!' }) - } -}; -``` - - - - -```javascript {4-8} -import { Precondition } from '@sapphire/framework'; - -export class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ message: 'Only the bot owner can use this command!' }) - } -} -``` - - - - -```typescript {4-8} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { @@ -128,57 +51,12 @@ export class OwnerOnlyPrecondition extends Precondition { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd ## Using Preconditions in Commands To attach a precondition to a command, you simply have to input its name in an array in the command's [`preconditions`][preconditions-option] option. -<<<<<<< HEAD ```typescript ts2esm2cjs|{7}|{7} -======= - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); - -module.exports = class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - preconditions: ['OwnerOnly'] - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; - -export class PingCommand extends Command { - constructor(context) { - super(context, { - // ... - preconditions: ['OwnerOnly'] - }); - } -} -``` - - - - -```typescript {7} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Command } from '@sapphire/framework'; export class PingCommand extends Command { @@ -191,12 +69,6 @@ export class PingCommand extends Command { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd Now, if someone who is not the bot owner executes the ping command, nothing will happen! :::caution @@ -228,11 +100,7 @@ Consider the following array of preconditions: :::warning -<<<<<<< HEAD -None of the above preconditions are builtin, and you'd have to create them separately. -======= None of the following preconditions are bundled with Sapphire; as such you'd have to create them yourself! ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd ::: diff --git a/docs/Guide/preconditions/handling-permissions.mdx b/docs/Guide/preconditions/handling-permissions.mdx index 19c75a18..f714b9eb 100644 --- a/docs/Guide/preconditions/handling-permissions.mdx +++ b/docs/Guide/preconditions/handling-permissions.mdx @@ -3,58 +3,13 @@ sidebar_position: 5 title: Handling permissions --- -<<<<<<< HEAD -======= -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd One of the most basic needs of a Discord bot is to be able to deny command access to users based on their permissions or lack thereof. A common example would be moderation commands. Most people wouldn't want regular users to be able to ban people, so we can restrict usage using the [`requiredUserPermissions`][requireduserpermissions] option. -<<<<<<< HEAD ```typescript ts2esm2cjs|{7}|{7} -======= - - - -```javascript {7} -const { Command } = require('@sapphire/framework'); - -module.exports = class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'] - }); - } -}; -``` - - - - -```javascript {7} -import { Command } from '@sapphire/framework'; - -export class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'] - }); - } -} -``` - - - - -```typescript {7} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Command } from '@sapphire/framework'; export class BanCommand extends Command { @@ -67,12 +22,6 @@ export class BanCommand extends Command { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd Users without the `BAN_MEMBERS` permission will now be unable to use the command! ## Handling Client Permissions @@ -83,48 +32,7 @@ not. -<<<<<<< HEAD ```typescript ts2esm2cjs|{8}|{8} -======= - - - -```javascript {8} -const { Command } = require('@sapphire/framework'); - -module.exports = class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'], - requiredClientPermissions: ['BAN_MEMBERS'] - }); - } -}; -``` - - - - -```javascript {8} -import { Command } from '@sapphire/framework'; - -export class BanCommand extends Command { - constructor(context) { - super(context, { - // ... - requiredUserPermissions: ['BAN_MEMBERS'], - requiredClientPermissions: ['BAN_MEMBERS'] - }); - } -} -``` - - - - -```typescript {8} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Command } from '@sapphire/framework'; export class BanCommand extends Command { @@ -138,15 +46,8 @@ export class BanCommand extends Command { } ``` -<<<<<<< HEAD -`BanCommand` now requires the command executor _and_ the client to have sufficient permissions to execute! -======= - - - With these changes, `BanCommand` now requires the command executor _and_ the client to have the `BAN_MEMBERS` permission to execute! ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd :::tip diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index f5fe320f..d0404600 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -3,12 +3,6 @@ sidebar_position: 2 title: Reporting precondition failure --- -<<<<<<< HEAD -======= -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd When a precondition fails, it's usually important for the user to know why. For example, if they hit a cooldown or lack permissions, that should somehow be communicated. However, by default, nothing will happen if a precondition blocks a message. @@ -26,40 +20,7 @@ an error. `commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, and the [`CommandDeniedPayload`][payload], which includes necessary context. -<<<<<<< HEAD ```typescript ts2esm2cjs -======= - - - -```javascript -const { Listener } = require('@sapphire/framework'); - -module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } -}; -``` - - - - -```javascript -import { Listener } from '@sapphire/framework'; - -export class CommandDeniedListener extends Listener { - run(error, { message }) { - // ... - } -} -``` - - - - -```typescript ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; @@ -70,55 +31,14 @@ export class CommandDeniedListener extends Listener { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd The `message` property of the `error` parameter will include the error message, as the name suggests. In [Creating Preconditions][creating-preconditions], you can find that we defined this property within the `this.error()` method! There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. That is what we'll do in this example: -<<<<<<< HEAD ```typescript ts2esm2cjs|{6}|{6} import { Listener, UserError, CommandDeniedPayload } from '@sapphire/framework'; -======= - - - -```javascript {5} -const { Listener } = require('@sapphire/framework'); - -module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } -}; -``` - - - - -```javascript {5} -import { Listener } from '@sapphire/framework'; - -export class CommandDeniedListener extends Listener { - run(error, { message }) { - return message.channel.send(error.message); - } -} -``` - - - - -```typescript {6} -import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; -import { Listener } from '@sapphire/framework'; ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd export class CommandDeniedListener extends Listener { public run(error: UserError, { message }: CommandDeniedPayload) { @@ -127,12 +47,6 @@ export class CommandDeniedListener extends Listener { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd ## Ignoring Precondition Failures If someone who isn't a bot owner tries to use a command intended only for the bot owner, sometimes you don't want to @@ -143,50 +57,7 @@ contain information about the context in which the error was thrown, and the val We can take advantage of this by adding `context: { silent: true }` to the [`this.error()`][preconditionerror] options. We'll use the `OwnerOnly` precondition we made in [Creating Preconditions][creating-preconditions] to demonstrate this. -<<<<<<< HEAD ```typescript ts2esm2cjs|{9}|{9} -======= - - - -```javascript {9} -const { Precondition } = require('@sapphire/framework'); - -module.exports = class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ - message: 'Only the bot owner can use this command!' - context: { silent: true } - }) - } -}; -``` - - - - -```javascript {9} -import { Precondition } from '@sapphire/framework'; - -export class OwnerOnlyPrecondition extends Precondition { - public run(message) { - return message.author.id === 'YOUR_ID' - ? this.ok() - : this.error({ - message: 'Only the bot owner can use this command!', - context: { silent: true } - }) - } -} -``` - - - - -```typescript {9} ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd import { Precondition, Message } from '@sapphire/framework'; export class OwnerOnlyPrecondition extends Precondition { @@ -201,12 +72,6 @@ export class OwnerOnlyPrecondition extends Precondition { } ``` -<<<<<<< HEAD -======= - - - ->>>>>>> bce70940bf8edd1967c694d4ec08344839c08ecd We can then check if this property exists on the error in our listener, and ignore the failure if we find it. From 814c6b0d73701865d6191a31130023962166d71b Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Sun, 5 Dec 2021 23:04:49 -0500 Subject: [PATCH 27/30] vladdy fixes --- .../preconditions/creating-your-own-preconditions.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 18b500b8..314f16e1 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -4,8 +4,8 @@ title: Creating your own preconditions --- Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will -start by creating a `preconditions` subdirectory in your project's entry point directory. Inside, we'll make an -`OwnerOnly` file. +start by creating a `preconditions` subdirectory in your project's entry point directory. For this guide, we'll be building +out an `OwnerOnly` precondition, that prevents anyone but the application owner from running the command. Your directory should now look something like this: @@ -23,7 +23,7 @@ Your directory should now look something like this: ``` The purpose of our `OwnerOnly` precondition is just as the name suggests: to check if the user is the bot owner. It can -be used for developer commands, such as `eval`. +be used for developer commands, such as commands that evaluate expressions or present internal debugging information. ## Creating a Precondition class @@ -69,7 +69,7 @@ export class PingCommand extends Command { } ``` -Now, if someone who is not the bot owner executes the ping command, nothing will happen! +Now, if someone who is not the bot owner executes the `ping` command, nothing will happen! :::caution From 997b983240d105f42b32d0836b457edc136aee97 Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Mon, 6 Dec 2021 19:49:54 -0500 Subject: [PATCH 28/30] Apply suggestions from favna Co-authored-by: Jeroen Claassens --- docs/Guide/preconditions/command-cooldown.mdx | 8 ++++---- .../preconditions/reporting-precondition-failure.mdx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 9674a05c..3430fb59 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -15,13 +15,13 @@ export class PingCommand extends Command { public constructor(context: Command.Context) { super(context, { // ... - cooldownDelay: 10_000 // 10_000 milliseconds + cooldownDelay: 10_000 // 10_000 milliseconds (10 seconds) }); } } ``` -If you now try to run this command, and then run it again within 10 seconds, the command won't execute, and an error +If you now try to run this command, and then run it again within 10_000 milliseconds (10 seconds), the command won't execute, and an error will be thrown. You can learn how to process that error [here][reporting-precondition-failure]. :::info @@ -53,14 +53,14 @@ It's very common to not want cooldowns to apply to certain people, for example, adding [`cooldownFilteredUsers`][cooldownfilteredusers] to the options. This option should be an array of users ID that the bot can ignore when calculating cooldowns. -```typescript ts2esm2cjs|{7}|{7} +```typescript ts2esm2cjs|{8}|{8} import { Command } from '@sapphire/framework'; export class PingCommand extends Command { public constructor(context: Command.Context) { super(context, { // ... - cooldownDelay: 10_000 // 10_000 milliseconds + cooldownDelay: 10_000 // 10_000 milliseconds (10 seconds) cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner }); } diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index d0404600..2b4e0f09 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -20,7 +20,7 @@ an error. `commandDenied` supplies the following information: the [`UserError`][error] that was created from the precondition, and the [`CommandDeniedPayload`][payload], which includes necessary context. -```typescript ts2esm2cjs +```typescript ts2esm2cjs|{4}|{5} import type { UserError, CommandDeniedPayload } from '@sapphire/framework'; import { Listener } from '@sapphire/framework'; @@ -37,7 +37,7 @@ Preconditions][creating-preconditions], you can find that we defined this proper There are many possibilities for what you can do with the error, but the simplest is to just send it directly to the user. That is what we'll do in this example: -```typescript ts2esm2cjs|{6}|{6} +```typescript ts2esm2cjs|{5}|{5} import { Listener, UserError, CommandDeniedPayload } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { From b29493539e5e2ec5c0a0f593bb18bc6ff5afd50a Mon Sep 17 00:00:00 2001 From: Lioness100 Date: Mon, 6 Dec 2021 19:58:17 -0500 Subject: [PATCH 29/30] convert from tabs & prettify --- .../creating-your-own-preconditions.mdx | 4 +- .../reporting-precondition-failure.mdx | 46 ++++--------------- 2 files changed, 12 insertions(+), 38 deletions(-) diff --git a/docs/Guide/preconditions/creating-your-own-preconditions.mdx b/docs/Guide/preconditions/creating-your-own-preconditions.mdx index 314f16e1..12077ce3 100644 --- a/docs/Guide/preconditions/creating-your-own-preconditions.mdx +++ b/docs/Guide/preconditions/creating-your-own-preconditions.mdx @@ -4,8 +4,8 @@ title: Creating your own preconditions --- Just as we did in both [Creating Commands][creating-commands] and [Creating Listeners][creating-listeners], we will -start by creating a `preconditions` subdirectory in your project's entry point directory. For this guide, we'll be building -out an `OwnerOnly` precondition, that prevents anyone but the application owner from running the command. +start by creating a `preconditions` subdirectory in your project's entry point directory. For this guide, we'll be +building out an `OwnerOnly` precondition, that prevents anyone but the application owner from running the command. Your directory should now look something like this: diff --git a/docs/Guide/preconditions/reporting-precondition-failure.mdx b/docs/Guide/preconditions/reporting-precondition-failure.mdx index d0404600..c020c53b 100644 --- a/docs/Guide/preconditions/reporting-precondition-failure.mdx +++ b/docs/Guide/preconditions/reporting-precondition-failure.mdx @@ -74,52 +74,26 @@ export class OwnerOnlyPrecondition extends Precondition { We can then check if this property exists on the error in our listener, and ignore the failure if we find it. - - - -```javascript {5} -const { Listener } = require('@sapphire/framework'); - -module.exports = class CommandDeniedListener extends Listener { - run(error, { message }) { - if (error.context.silent) return; - return message.channel.send(error.message); - } -}; -``` - - - - -```javascript {5} -import { Listener } from '@sapphire/framework'; - -export class CommandDeniedListener extends Listener { - run(error, { message }) { - if (error.context.silent) return; - return message.channel.send(error.message); - } -} -``` - - - - -```typescript {8} +```typescript ts2esm2cjs|{5}|{5} import { UserError, CommandDeniedPayload, Listener } from '@sapphire/framework'; export class CommandDeniedListener extends Listener { public run(error: UserError, { message }: CommandDeniedPayload) { - // A bit of a hack is required for TypeScript - // Since `error.context` is of type `unknown` if (Reflect.get(Object(error.context), 'silent')) return; return message.channel.send(error.message); } } ``` - - +:::note + +In the code block above, we use `if (Reflect.get(Object(error.context), 'silent'))` as opposed to +`if (error.context.silent)` for TypeScript. When writing JavaScript code you can use the latter just fine. + +To clarify this, with TypeScript `error.context` has the type `unknown`, so trying to write `error.context.silent` will +throw a TypeScript error for trying to read property `silent` of type `unknown`. + +::: [listeners]: ../listeners/creating-your-own-listeners [error]: ../../Documentation/api-framework/classes/UserError From 4e67b82c4988cf51e1712487229839c933a551aa Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Wed, 8 Dec 2021 00:14:41 +0100 Subject: [PATCH 30/30] chore: formatting --- docs/Guide/preconditions/command-cooldown.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Guide/preconditions/command-cooldown.mdx b/docs/Guide/preconditions/command-cooldown.mdx index 3430fb59..5f4c8370 100644 --- a/docs/Guide/preconditions/command-cooldown.mdx +++ b/docs/Guide/preconditions/command-cooldown.mdx @@ -21,8 +21,8 @@ export class PingCommand extends Command { } ``` -If you now try to run this command, and then run it again within 10_000 milliseconds (10 seconds), the command won't execute, and an error -will be thrown. You can learn how to process that error [here][reporting-precondition-failure]. +If you now try to run this command, and then run it again within 10_000 milliseconds (10 seconds), the command won't +execute, and an error will be thrown. You can learn how to process that error [here][reporting-precondition-failure]. :::info