mirror of
https://github.com/sern-handler/website
synced 2026-06-06 01:16:47 +00:00
scrap ttt
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
---
|
||||
title: command
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
import GuideFeedback from "../../../../src/components/GuideFeedback";
|
||||
|
||||
# Commands
|
||||
|
||||
## Example command
|
||||
- View the command **ping.js** in `src/commands`.
|
||||
- Make a new file next to **ping.js** called **tictactoe.js**
|
||||
- Copy **ping.js** contents into **tictactoe.js**.
|
||||
We base **tictactoe.js** from **ping.js**
|
||||
|
||||
:::tip
|
||||
The name of your command will be the name of your file.
|
||||
:::
|
||||
|
||||
Each slash command will follow this similar structure.
|
||||
In this tutorial, maybe you were smart enough to guess, but we'll be making tictactoe!
|
||||
|
||||
## New command, tic-tac-toe
|
||||
- Instead of **CommandType.Both**, `type` property should be **CommandType.Slash**
|
||||
- This is to keep it simple. You'll see later, but slash commands work well with message components.
|
||||
- Give it a description.
|
||||
- run `npm run commands:publish`
|
||||
- Your command should be usable on discord now!
|
||||
|
||||
### Result
|
||||
Your command should now look something along the lines of this:
|
||||
```js title=./commands/tictactoe.js
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "I do tictactoe.",
|
||||
execute: async (ctx) => {
|
||||
await ctx.reply("Pwease wait. dis command in pwogwess"); // 👻
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
<GuideFeedback />
|
||||
@@ -1,106 +0,0 @@
|
||||
---
|
||||
title: Logic
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
import GuideFeedback from "../../../../src/components/GuideFeedback";
|
||||
|
||||
## Game rules
|
||||
- Two players max.
|
||||
- Each player is assigned **X** or **O**
|
||||
- The first person to get 3 in a row of their letter wins.
|
||||
|
||||
Get the option selected, adding this to the top of your **execute** function:
|
||||
```js
|
||||
const opponent = ctx.options.getUser("opponent");
|
||||
```
|
||||
:::warning
|
||||
Since we are in a Slash command, the above is valid. If we were to interoperate between Text Commands and Slash Commands,
|
||||
we'd have to check if the current [Context](https://sern.dev/docs/api/classes/Context) is the correct type. In our current setup,
|
||||
this won't happen since we did not set our command type to **Both**.
|
||||
:::
|
||||
|
||||
We need to ensure we're not playing against **an invalid user**. Invalid users are those that
|
||||
- are bots **or**
|
||||
- ourselves.
|
||||
|
||||
Create helper function **isInvalidUser** which returns a boolean satisfying the conditions above.
|
||||
|
||||
```js
|
||||
const isInvalidUser = (user) => {
|
||||
return user.id === "182326315813306368" || user.bot;
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
You can easily access anyone's user id by [enabling dev mode](https://beebom.com/how-enable-disable-developer-mode-discord).
|
||||
:::
|
||||
|
||||
Cool, now let's call it, preventing invalid users from being opponents. Put the code snippet before any of the UI logic:
|
||||
```js title="tictactoe, execute function"
|
||||
if(isInvalidUser(opponent)) {
|
||||
return;
|
||||
}
|
||||
```
|
||||
We **return**, or **stop** execution of the function if the condition `isInvalidUser` is true.
|
||||
:::info
|
||||
A step further would be extracting `isInvalidUser` into a separate plugin, if multiple commands share this same logic.
|
||||
:::
|
||||
Before we go any further, let's test. Start up the bot again.
|
||||
- Try running `/tictactoe` with a bot.
|
||||
- What happens?
|
||||
- It may be beneficial to give the user some feedback instead of a **return**.
|
||||
- Try running `/tictactoe` with yourself
|
||||
- What happens?
|
||||
- It may be beneficial to give the user some feedback instead of a **return**.
|
||||
- Try running `/tictactoe` in a DM channel with the bot itself.
|
||||
- What happens?
|
||||
|
||||
Great! notice how in a DM channel, the command is entirely **useless**.
|
||||
Let's use our **channelType** plugin to ensure this doesn't happen.
|
||||
- Add the **plugins** field to your commandModule, adding the channelType plugin.
|
||||
```js
|
||||
plugins: [channelType( )]
|
||||
```
|
||||
- Call it with the right parameters.
|
||||
- All plugins come with documentation in the same file
|
||||
- In our case, we need to supply an array of ChannelTypes, and an optional message.
|
||||
```js
|
||||
plugins: [channelType([ChannelType.GuildText], "You cannot execute this command here.")]
|
||||
```
|
||||
:::info
|
||||
You can publish commands to specific guilds using special configuration.
|
||||
For more information, view the docs [here](https://sern.dev/docs/cli/publish).
|
||||
For educational and simplicity purposes, I'm using a plugin here.
|
||||
:::
|
||||
|
||||
Great! When we rerunning these test conditions, it should catch and respond to these conditions.
|
||||
|
||||
|
||||
## Collectors
|
||||
We know how to display components, but what about responding to them?
|
||||
Discord.js gives us the Collector API, which at its core, listens to incoming interactions from discord.js. It essentially comes in two flavors.
|
||||
- **await**
|
||||
- **callback based**
|
||||
|
||||
**Await** is [easy](https://www.infoq.com/presentations/Simple-Made-Easy/), but it comes at the cost of only collecting `one` at a time. Also,
|
||||
it is blocking on other commands attempting to be run; When we await a component,
|
||||
All other commands will be waiting as well. So, we'll be using callback based, lazily waiting. Let's create a collector to the message sent by the bot. How do we get that message?
|
||||
Luckily `ctx.reply` returns the [Message](https://discord.js.org/#/docs/discord.js/main/class/Message) sent. Let's capture that in a variable.
|
||||
|
||||
- Capture the result of ctx.reply in a variable called **resultMessage**.
|
||||
```js
|
||||
const resultMessage = await ctx.reply({ message, components: grid });
|
||||
```
|
||||
- Create a collector from the message's property `.createMessageComponentCollector()`
|
||||
- Accept only interactions of type **Button** (we are making a tictactoe game of solely buttons),
|
||||
- Make sure to import **ComponentType** from discord.js
|
||||
- Give it a time limit of 60_000 milliseconds aka 1 minute.
|
||||
```js
|
||||
// verbosity!! //ignore this
|
||||
const collector = responseMessage.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
time: 60_000
|
||||
});
|
||||
```
|
||||
---
|
||||
<GuideFeedback />
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
title: plugins
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
import GuideFeedback from "../../../../src/components/GuideFeedback";
|
||||
|
||||
# What are plugins?
|
||||
|
||||
## Code ran before a command to modify output.
|
||||
|
||||
|
||||
sern has a bunch of premade plugins thanks to our awesome community, which you can install via the cli!
|
||||
|
||||
## Forward:
|
||||
|
||||
- Run: `sern plugins`, selecting the **channelType** plugin
|
||||
- Thank the creator(s) of the plugin. (Thank you)
|
||||
- Import your plugin at the top of the file like this
|
||||
```js
|
||||
import { channelType } from '../plugins/channelType.js'
|
||||
```
|
||||
|
||||
Once you download the `channelType` plugin, You may continue.
|
||||
|
||||
## Aside:
|
||||
|
||||
### How to use plugins?
|
||||
Lets take an aside to show how to install plugins.
|
||||
- Run: `sern plugins`, selecting plugins
|
||||
- Thank the creator(s) of the plugin. (Thank you)
|
||||
|
||||
```js
|
||||
import { commandModule, CommandType } from '@sern/handler'
|
||||
import { channelType } from '../plugins/channelType.js'
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "haliburt",
|
||||
// here:
|
||||
plugins: [channelType([ChannelType.GuildText], 'This cannot be used here')]
|
||||
})
|
||||
|
||||
```
|
||||
Keep in mind some plugins cannot run for every type of interaction sern handles.
|
||||
For example, the **channelType** plugin will probably not work in a modal, and if you are using typescript,
|
||||
the type checker won't allow it. However, there are some more generic plugins, one being **fromCallback**, which can
|
||||
run in any command / component.
|
||||
|
||||
### How to contribute plugins?:
|
||||
- view [here](../../../guide/walkthrough/plugins).
|
||||
|
||||
---
|
||||
<GuideFeedback />
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: intro
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
import GuideFeedback from "../../../../src/components/GuideFeedback";
|
||||
|
||||
> How hard can this be?
|
||||
|
||||
This will probably be the longest chapter in the guide. Don't worry though, once we're done, making commands is [**fodder**](https://www.merriam-webster.com/dictionary/fodder).
|
||||
|
||||
Here are some inspirational quotes to make you get motivated.
|
||||
> "If you don't make mistakes, you're not working on hard enough problems." ― Frank Wilczek
|
||||
|
||||
> "Dwell on the beauty of life. Watch the stars, and see yourself running with them." ― Marcus Aurelius, Meditations
|
||||
|
||||
> "Never let the future disturb you. You will meet it, if you have to, with the same weapons of reason which today arm you against the present." ― Marcus Aurelius, Meditations
|
||||
|
||||
If you want to add more, feel free to pull request this, but not too many please!
|
||||
|
||||
TODO!!! - ADD TIC TAC TOE GIF HERE
|
||||
|
||||
---
|
||||
<GuideFeedback />
|
||||
@@ -1,122 +0,0 @@
|
||||
---
|
||||
title: UI
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
import GuideFeedback from "../../../../src/components/GuideFeedback";
|
||||
|
||||
## Message Components
|
||||
|
||||
Discord has a rich api for developers, and they allow bots to use and interact with certain UI components.
|
||||
Some of these include
|
||||
|
||||
- Buttons
|
||||
- Modals (technically not a component)
|
||||
- Select Menu
|
||||
- Action Rows
|
||||
|
||||
Let's use the mush we call [**brain**](https://www.ninds.nih.gov/health-information/public-education/brain-basics/brain-basics-know-your-brain).
|
||||
|
||||
> **Question**: What components do we need to create a tic tac toe game?
|
||||
|
||||
> **Answer**: Buttons!!!!
|
||||
|
||||
|
||||
### Rules
|
||||
Obviously we can't spam the user interface with lots of components; Discord knows this as well.
|
||||
|
||||
Per the [**discord api documentation**](https://discord.com/developers/docs/interactions/message-components#action-rows):
|
||||
- You can have up to 5 Action Rows per message
|
||||
- Maximum of **5** components in an Action Row
|
||||
- An Action Row cannot contain another Action Row
|
||||
- Select Menus take up an entire Action Row, and cannot have buttons
|
||||
|
||||
|
||||
Do the math, and we can have up to 5 (action rows) times 5 (per row), 25 buttons in one message
|
||||
|
||||
### Custom Ids
|
||||
We also need some way to distinguish buttons between other buttons. Whenever a user clicks a button, we are notified that **some** interaction has happened, but from **where?** Custom ids to the rescue. Think of these like 'classes' or 'ids' on html tags. **Each component that we need to handle
|
||||
will have to be retrieved by their custom id in order to properly handle it**
|
||||
|
||||
|
||||
We'll get more into this in the next chapter.
|
||||
|
||||
|
||||
### Design
|
||||
Our UI will contain a message, and a total of 9 buttons, laid out in a 3x3 grid.
|
||||
|
||||
TODO!!! PICTURE HERE
|
||||
|
||||
|
||||
### Implementation
|
||||
|
||||
How do we represent this in code? In discord.js, the library we're using, we can represent this via their builder api. Let's create some utility functions before diving straight in.
|
||||
|
||||
```js title="tictactoe.js"
|
||||
const EMPTY = "\0"; // A little trick to show buttons with no 'label'
|
||||
const button = (custom_id, label=EMPTY) => {
|
||||
return new ButtonBuilder({
|
||||
custom_id,
|
||||
label,
|
||||
style: ButtonStyle.Secondary
|
||||
});
|
||||
}
|
||||
|
||||
const row = (...elements) => {
|
||||
return new ActionRowBuilder().addComponents(elements);
|
||||
}
|
||||
```
|
||||
The names for these are pretty self explanatory. **EMPTY** is a placeholder for labels in a button.
|
||||
|
||||
In our **execute** function, let's build the Action Rows.
|
||||
```js title="tictactoe.js, in the execute body"
|
||||
const message = "Tic Tac Toe";
|
||||
|
||||
//Our TicTacToe grid. consists of 3 rows, and in each row, 3 buttons.
|
||||
//Each button has a unique 'customId' of a number.
|
||||
const grid = [row(button('0'), button('1'), button('2')),
|
||||
row(button('3'), button('4'), button('5')),
|
||||
row(button('6'), button('7'), button('8'))];
|
||||
|
||||
|
||||
await ctx.reply("Pwease wait. dis command in pwogwess"); // 👻
|
||||
```
|
||||
> Question: Does this grid satisfy the rules stated above?
|
||||
|
||||
> Answer: Yes! We have 3 Action Rows in total, with 3 Buttons in each. Less than 25 Components!
|
||||
|
||||
Let's try displaying this to Discord. We're not just displaying plain text anymore, we need to change ctx.reply.
|
||||
|
||||
- Change how we call `ctx.reply`. Give it an object instead:
|
||||
```js
|
||||
// await ctx.reply("Pwease wait. dis command in pwogwess"); // 👻
|
||||
|
||||
await ctx.reply({ content: message, components: grid });
|
||||
```
|
||||
One more thing, we need to play against someone, so we'll need to update our commandModule.
|
||||
- Add the import **ApplicationCommandOptionType** from discord.js
|
||||
- Add a new **option** of type **User** to the **options** field of your commandModule.
|
||||
This will display on the user's end as an option to choose from.
|
||||
```js
|
||||
options: [
|
||||
{
|
||||
name: "opponent",
|
||||
description: "Opponent you would like to play with",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
```
|
||||
- Run `npm run commands:publish` once more to update it.
|
||||
|
||||
:::tip
|
||||
Anytime we add a new slash command or its options, its a good idea to run `npm run commands:publish`
|
||||
:::
|
||||
|
||||
Run your bot.
|
||||
- Congrats! You got something to display.
|
||||
Notice how when you try to click, it will say something like `interaction failed to respond`.
|
||||
**Next chapter**, let's wire everything up.
|
||||
|
||||
---
|
||||
<GuideFeedback />
|
||||
Reference in New Issue
Block a user