generated from sapphiredev/sapphire-template
-
-
Notifications
You must be signed in to change notification settings - Fork 41
feat: precondition guides #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
41444dd
feat: precondition guides
Lioness100 c19f2aa
fix line highlighting
Lioness100 7809b3a
fix links
Lioness100 3e801b1
refactors and fixes
Lioness100 1185820
style: formatting
favna b6d0889
implement favna's fixes
Lioness100 60bee92
links
Lioness100 a6bfe36
fixes
Lioness100 cfc0f4b
test
Lioness100 33f5512
prefer enums
Lioness100 02e7df0
add "what are pre" page
Lioness100 b2ece16
feat: precondition guides
Lioness100 dbba8dc
fix line highlighting
Lioness100 897af5b
fix links
Lioness100 2b31056
refactors and fixes
Lioness100 40a75b8
style: formatting
favna ba0428c
implement favna's fixes
Lioness100 7409dec
fixes
Lioness100 659cf9a
prefer enums
Lioness100 bf980ac
add "what are pre" page
Lioness100 f64466e
chore: insert commit message here
favna 4939401
Update docs/Guide/preconditions/creating-your-own-preconditions.mdx
favna c4fc58f
Update docs/Guide/preconditions/handling-permissions.mdx
favna bce7094
chore: should work i guess...
favna c917c1a
Convert tabs to plugin
Lioness100 728a386
Merge branch 'feat/preconditionGuides' of https://github.com/Lioness1…
Lioness100 87cd8d7
Merge branch 'feat/preconditionGuides' of https://github.com/Lioness1…
Lioness100 04f1646
Merge branch 'feat/preconditionGuides' of https://github.com/Lioness1…
Lioness100 b3c131c
conflicts
Lioness100 814c6b0
vladdy fixes
Lioness100 997b983
Apply suggestions from favna
Lioness100 b294935
convert from tabs & prettify
Lioness100 527fed0
Merge branch 'feat/preconditionGuides' of https://github.com/Lioness1…
Lioness100 4e67b82
chore: formatting
favna File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
"**/projects/": true, | ||
}, | ||
"cSpell.words": [ | ||
"favna" | ||
"favna", | ||
"nsfw" | ||
] | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,40 @@ | ||
--- | ||
sidebar_position: 3 | ||
sidebar_position: 4 | ||
title: Setting the types of channel a command can run in | ||
--- | ||
|
||
## TODO | ||
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 | ||
|
||
You can view the valid `runIn` values [here][runintypes]. | ||
|
||
::: | ||
|
||
```typescript ts2esm2cjs|{7}|{7} | ||
import { Command, CommandOptionsRunTypeEnum } from '@sapphire/framework'; | ||
|
||
export class PingCommand extends Command { | ||
public constructor(context: Command.Context) { | ||
super(context, { | ||
// ... | ||
runIn: CommandOptionsRunTypeEnum.GuildAny // Only run in guild channels | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,120 @@ | ||
--- | ||
sidebar_position: 0 | ||
sidebar_position: 3 | ||
title: Configuring command cooldowns | ||
--- | ||
|
||
## TODO | ||
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: | ||
|
||
```typescript ts2esm2cjs|{7}|{7} | ||
import { Command } from '@sapphire/framework'; | ||
|
||
export class PingCommand extends Command { | ||
public constructor(context: Command.Context) { | ||
super(context, { | ||
// ... | ||
cooldownDelay: 10_000 // 10_000 milliseconds (10 seconds) | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
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 | ||
|
||
`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. | ||
|
||
```typescript ts2esm2cjs|{8}|{8} | ||
import { Command } from '@sapphire/framework'; | ||
import { Time } from '@sapphire/time-utilities'; | ||
|
||
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][timeenum]. | ||
|
||
::: | ||
|
||
## 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 | ||
favna marked this conversation as resolved.
Show resolved
Hide resolved
|
||
the bot can ignore when calculating cooldowns. | ||
|
||
```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 (10 seconds) | ||
cooldownFilteredUsers: ['YOUR_ID'] // Ignore the bot owner | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
## Advanced Usage | ||
|
||
Accompanying `cooldownDelay`, you also have access to the options [`cooldownLimit`][cooldownlimit] and | ||
[`cooldownScope`][cooldownscope]. | ||
favna marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`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_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 scopes can be found [here][scopes]. | ||
|
||
## 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. | ||
|
||
```typescript ts2esm2cjs|{5-10}|{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 | ||
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/modules/sapphire_time_utilities | ||
[timeenum]: ../../Documentation/api-utilities/enums/sapphire_time_utilities.Time |
122 changes: 120 additions & 2 deletions
122
docs/Guide/preconditions/creating-your-own-preconditions.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,124 @@ | ||
--- | ||
sidebar_position: 4 | ||
sidebar_position: 1 | ||
title: Creating your own preconditions | ||
--- | ||
|
||
## TODO | ||
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. | ||
|
||
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 commands that evaluate expressions or present internal debugging information. | ||
|
||
## Creating a Precondition class | ||
|
||
Preconditions are made by extending the Sapphire [`Precondition`][precondition] class and exporting it. | ||
|
||
```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. | ||
|
||
```typescript ts2esm2cjs|{4-8}|{4-8} | ||
import { Precondition, Message } 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. | ||
|
||
```typescript ts2esm2cjs|{7}|{7} | ||
import { Command } from '@sapphire/framework'; | ||
|
||
export class PingCommand extends Command { | ||
public constructor(context: Command.Context) { | ||
super(context, { | ||
// ... | ||
preconditions: ['OwnerOnly'] | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
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 | ||
Lioness100 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
to augment Sapphire's [`Preconditions`][preconditions-interface] interface. Please see an official example | ||
[here][preconditions-augment]. | ||
vladfrangu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<!-- | ||
TODO: Once we have a dedicated TypeScript page this should link to that instead of a code example. | ||
Including why augmenting with `never` works and the difference between that and augmenting with an object. | ||
--> | ||
|
||
::: | ||
|
||
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: | ||
|
||
:::warning | ||
|
||
None of the following preconditions are bundled with Sapphire; as such you'd have to create them yourself! | ||
favna marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
::: | ||
|
||
```js | ||
[['AdminOnly', ['ModOnly', 'OwnerOnly']], 'InVoiceChannel']; | ||
``` | ||
|
||
For a command with these preconditions to pass the denial checks, the `InVoiceChannel` precondition must pass, as well | ||
favna marked this conversation as resolved.
Show resolved
Hide resolved
|
||
as `AdminOnly` _or_ both `OwnerOnly` 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 | ||
[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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,61 @@ | ||
--- | ||
sidebar_position: 2 | ||
sidebar_position: 5 | ||
title: Handling permissions | ||
--- | ||
|
||
## TODO | ||
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. | ||
|
||
<!-- TODO: info block redirecting to subcommands ts guide for using corresponding decorators --> | ||
|
||
```typescript ts2esm2cjs|{7}|{7} | ||
import { Command } from '@sapphire/framework'; | ||
|
||
export class BanCommand extends Command { | ||
public constructor(context: Command.Context) { | ||
super(context, { | ||
// ... | ||
requiredUserPermissions: ['BAN_MEMBERS'] | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
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. | ||
|
||
<!-- TODO: info block redirecting to subcommands ts guide for using corresponding decorators --> | ||
|
||
```typescript ts2esm2cjs|{8}|{8} | ||
import { Command } from '@sapphire/framework'; | ||
|
||
export class BanCommand extends Command { | ||
public constructor(context: Command.Context) { | ||
super(context, { | ||
// ... | ||
requiredUserPermissions: ['BAN_MEMBERS'], | ||
requiredClientPermissions: ['BAN_MEMBERS'] | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
With these changes, `BanCommand` now requires the command executor _and_ the client to have the `BAN_MEMBERS` permission | ||
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/interfaces/CommandOptions#requireduserpermissions | ||
[requiredclientpermissions]: ../../Documentation/api-framework/interfaces/CommandOptions#requiredclientpermissions | ||
[reporting-precondition-failure]: ./reporting-precondition-failure |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,29 @@ | ||
--- | ||
sidebar_position: 1 | ||
sidebar_position: 6 | ||
title: Locking commands to NSFW channels | ||
--- | ||
|
||
## TODO | ||
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. | ||
|
||
```typescript ts2esm2cjs|{7}|{7} | ||
import { Command } from '@sapphire/framework'; | ||
|
||
export class NSFWCommand extends Command { | ||
public constructor(context: Command.Context) { | ||
super(context, { | ||
// ... | ||
nsfw: true | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
:::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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.