mirror of
https://github.com/sern-handler/website
synced 2026-06-28 02:32:23 +00:00
nice ref
This commit is contained in:
@@ -5,9 +5,6 @@ sidebar:
|
||||
order: 10
|
||||
---
|
||||
|
||||
:::danger
|
||||
This contains version 2 code. Please view [transitioning to v3](/v3/guide/walkthrough/transition) for the `Service` API.
|
||||
:::
|
||||
|
||||
Since version 2.0.0, dependency injection, thanks to [iti](https://github.com/molszanski/iti), is a feature to customize your bot's utilities and structures.
|
||||
|
||||
@@ -127,3 +124,6 @@ import { Steps } from '@astrojs/starlight/components';
|
||||
|
||||
4. Now, when your bot starts, the `init` method will be called. 🎉
|
||||
</Steps>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,14 +5,6 @@ sidebar:
|
||||
order: 8
|
||||
---
|
||||
|
||||
:::danger
|
||||
This is version 3 api only!!
|
||||
:::
|
||||
|
||||
:::tip
|
||||
**TLDR:** The direct upgrade to `useContainer`. if you set up a bot with `create-bot`, check `dependencies.d.ts`.
|
||||
Dependencies are the types that Services uses.
|
||||
:::
|
||||
|
||||
You need some way to use dependencies in your command module. Services to the rescue!
|
||||
|
||||
|
||||
143
src/content/docs/v4/reference/dependencies.mdx
Normal file
143
src/content/docs/v4/reference/dependencies.mdx
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: Dependencies
|
||||
description: Customize & manage stateful dependencies
|
||||
sidebar:
|
||||
order: 5
|
||||
---
|
||||
|
||||
Manage objects which contain lots of state. If you were a previous user of Sapphire, dependency injection is the moral equivalent of `container`.
|
||||
Dependency injection promotes maintainability and helps organize imports.
|
||||
|
||||
For example, a minimal setup for any project might look like this:
|
||||
|
||||
import { FileTree } from '@astrojs/starlight/components';
|
||||
|
||||
<FileTree>
|
||||
- src/
|
||||
- index.js **(your main file and client)**
|
||||
- **dependencies.d.ts** **(for intellisense)**
|
||||
</FileTree>
|
||||
|
||||
|
||||
|
||||
```js title="src/index.js"
|
||||
const client = new Client({
|
||||
...options,
|
||||
});
|
||||
|
||||
await makeDependencies((root) => {
|
||||
root.add("@sern/client", client),
|
||||
});
|
||||
```
|
||||
|
||||
Everything else is handled. However, you may want customize things.
|
||||
|
||||
## Adding Dependencies to Root
|
||||
|
||||
Each sern built dependency must implement its contracts:
|
||||
|
||||
- `@sern/logger`: Logging data → [`Logging`](/v3/api/interfaces/logging)
|
||||
- `@sern/errors`: Handling errors and lifetime → [`ErrorHandling`](/v3/api/interfaces/errorhandling)
|
||||
- `@sern/emitter`: The key to emit events and occurences in a project → [`Emitter`](/v3/api/interfaces/emitter)
|
||||
|
||||
## Lifecycle Hooks
|
||||
Dependencies can call a function throughout you bot's lifetime.
|
||||
|
||||
### Init
|
||||
|
||||
> Your object needs to initiate things. Developers are allowed to use `async` and `await`.
|
||||
|
||||
import { Steps } from '@astrojs/starlight/components';
|
||||
|
||||
<Steps>
|
||||
1. Do you need to perform intializing behavior for a dependency?
|
||||
```js title="src/database.js"
|
||||
import pg from 'pg'
|
||||
const { Client } = pg
|
||||
class Database {
|
||||
__database = new Client()
|
||||
async init() {
|
||||
await this.__database.connect();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Modify your `Dependencies` interface:
|
||||
```js title="src/dependencies.d.ts" {4}
|
||||
import { Database } from './services/database.js'
|
||||
interface Dependencies extends CoreDependencies {
|
||||
database: Database;
|
||||
}
|
||||
```
|
||||
|
||||
3. Make sure its been added:
|
||||
```ts title="src/index.ts" {3}
|
||||
await makeDependencies(({ add }) => {
|
||||
add('database', new Database())
|
||||
})
|
||||
```
|
||||
|
||||
4. Now, when your bot starts, the `init` method will be called. 🎉
|
||||
|
||||
</Steps>
|
||||
|
||||
### Dispose
|
||||
> Your object needs to destroy things before shutdown, if a crash occurs
|
||||
|
||||
|
||||
<Steps>
|
||||
1. Do you need to perform intializing behavior for a dependency?
|
||||
```js title="src/database.js"
|
||||
import pg from 'pg'
|
||||
const { Client } = pg
|
||||
class Database {
|
||||
__database = new Client()
|
||||
async init() {
|
||||
await this.__database.connect();
|
||||
}
|
||||
async dispose() {
|
||||
console.log("Disposing database")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Make sure its been added:
|
||||
```js title="src/index.ts" {3}
|
||||
await makeDependencies(({ add }) => {
|
||||
add('database', new Database())
|
||||
})
|
||||
```
|
||||
4. Now, when your bot starts, the `dispose` method will be called. 🎉
|
||||
</Steps>
|
||||
|
||||
:::tip
|
||||
Both `dispose` and `init` are exposed as interfaces in TypeScript. For extra typesafety it may be feasible to implement these interfaces.
|
||||
```ts
|
||||
import { type Init } from '@sern/handler'
|
||||
import pg from 'pg'
|
||||
const { Client } = pg
|
||||
class Database implements Init {
|
||||
__database = new Client()
|
||||
async init() {
|
||||
await this.__database.connect();
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
## Usage in Commands
|
||||
> This is for command modules, plugins only. event modules would have to use the `Service` api
|
||||
|
||||
Your dependencies are located in SDT.
|
||||
```ts
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
execute: (ctx, sdt) =>{
|
||||
sdt.deps.database // Database
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Service
|
||||
Service api is used for places where sern cannot inject into parameters properly. [View](../../../v3/guide/walkthrough/services/)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Getting Started
|
||||
description: Get started with the sern framework
|
||||
sidebar:
|
||||
order: 2
|
||||
order: 1
|
||||
---
|
||||
|
||||
import PackageManagers from '~/components/PackageManagers.astro';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Modules
|
||||
description: Learn how to create modules for your sern bot
|
||||
sidebar:
|
||||
order: 2
|
||||
order: 3
|
||||
---
|
||||
|
||||
|
||||
@@ -10,12 +10,11 @@ import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
## Introduction
|
||||
|
||||
sern operates with **modules**. At its core, modules contain `type` and `execute` fields on an object, with some code to possibly run before
|
||||
executing.
|
||||
sern operates with **modules**. At its core, modules contain `type`,`execute`, and [`plugins`](../plugins) (code ran before `execute`).
|
||||
|
||||
## Modules
|
||||
|
||||
We'll walk you through creating your first command module.
|
||||
We'll walk you through creating your first **command** module.
|
||||
|
||||
If you installed a new project via the CLI, your file should be here:
|
||||
|
||||
@@ -37,11 +36,10 @@ import { commandModule, CommandType } from "@sern/handler";
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "A ping command",
|
||||
execute: async (ctx, sdt) => {
|
||||
execute: async (ctx, sdt) => { // ctx is Context, sdt is SDT type
|
||||
await ctx.reply("Pong 🏓");
|
||||
},
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -75,7 +73,7 @@ So, lets say you want to make a command module that listens to **buttons**, or A
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "A ping command",
|
||||
execute: async (ctx, sdt) => {
|
||||
execute: async (ctx, sdt) => {
|
||||
const editButton = new ButtonBuilder({
|
||||
customId: "btn",
|
||||
label: "Click me",
|
||||
@@ -109,7 +107,7 @@ Components can carry metadata. This comes in handy when handling multiple compon
|
||||
|
||||
<Tabs syncKey="language-preference">
|
||||
<TabItem value="js" label="Send Component with Custom Id">
|
||||
```diff title="src/commands/ping.js"
|
||||
```js title="src/commands/ping.js" {8}
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
import { ButtonStyle, ActionRowBuilder, ButtonBuilder } from 'discord.js';
|
||||
export default commandModule({
|
||||
@@ -117,8 +115,7 @@ Components can carry metadata. This comes in handy when handling multiple compon
|
||||
description: "A ping command",
|
||||
execute: async (ctx, sdt) => {
|
||||
const editButton = new ButtonBuilder({
|
||||
- customId: "btn",
|
||||
+ customId: "btn/1061421834341462036",
|
||||
customId: "btn/1061421834341462036",
|
||||
label: "Click me",
|
||||
emoji: "🛠",
|
||||
style: ButtonStyle.Primary,
|
||||
@@ -148,3 +145,65 @@ Components can carry metadata. This comes in handy when handling multiple compon
|
||||
:::tip
|
||||
The first `/` is significant in custom ids. On the left of it is the custom id to be matched, and on the right is any user defined data.
|
||||
:::
|
||||
|
||||
## Event Modules
|
||||
We are now moving to event modules, which listens to the vast streams of data provided by
|
||||
- `node-cron`, `EventType.Cron`
|
||||
- `sern`, `EventType.Sern`
|
||||
- `discord.js`, `EventType.Discord`
|
||||
- `yourself`, `EventType.External`
|
||||
|
||||
|
||||
<FileTree>
|
||||
- src/events/
|
||||
- **messageCreate.js** **(right here, probably)**
|
||||
- ...
|
||||
</FileTree>
|
||||
|
||||
### Listening to Discord Events
|
||||
|
||||
```js title="src/events/messageCreate.js"
|
||||
import { eventModule, EventType } from "@sern/handler";
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Discord,
|
||||
execute: async (message) => {
|
||||
console.log(`${message.user} said`, message.content)
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::tip
|
||||
Typescript users can use `discordEvent`, a specialized eventModule with typings for discord.js events.
|
||||
:::
|
||||
|
||||
|
||||
### Scheduling with node-cron
|
||||
|
||||
```js
|
||||
import { EventType, eventModule } from "@sern/handler";
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Cron,
|
||||
pattern: "* * * * *", // Run this every minute
|
||||
execute: (args) => {
|
||||
console.log("cron cron")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Run once
|
||||
This works for ALL event modules.
|
||||
```js {6}
|
||||
import { EventType, eventModule } from "@sern/handler";
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Cron,
|
||||
pattern: "* * * * *", // Run this every minute
|
||||
once: true,
|
||||
execute: (args) => {
|
||||
console.log("cron cron")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
159
src/content/docs/v4/reference/plugins.mdx
Normal file
159
src/content/docs/v4/reference/plugins.mdx
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
title: Plugins
|
||||
description: Run code before execution
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
:::tip
|
||||
**TLDR:** Plugins are reusable pieces of code and are installable via `sern plugins`.
|
||||
They can modify the module's fields and also perform preconditions.
|
||||
Put them into the `plugins` field of a [module](../modules).
|
||||
:::
|
||||
|
||||
## Installation
|
||||
|
||||
Chances are, you just want your bot to work. Plugins can preprocess and create reusable conditions for modules.
|
||||
|
||||
To install plugins, you can use the CLI:
|
||||
|
||||
```sh
|
||||
sern plugins
|
||||
```
|
||||
:::tip
|
||||
Feel free to contribute to the [repository](https://github.com/sern-handler/awesome-plugins)!
|
||||
:::
|
||||
|
||||
:::caution
|
||||
Some plugins only work with specific command types. Most, however, are targeted towards slash / both modules.
|
||||
:::
|
||||
|
||||
import { Steps } from '@astrojs/starlight/components';
|
||||
|
||||
<Steps>
|
||||
1. Install your favorite(s) (or the ones that look the coolest). I installed the `ownerOnly` plugin.
|
||||
2. Thank the creator of the plugin. (mandatory)
|
||||
3. Add the plugin to your module in the `plugins` field.
|
||||
</Steps>
|
||||
|
||||
```ts title="src/commands/ping.ts" {6}
|
||||
import { commandModule, CommandType } from '@sern/handler'
|
||||
import { ownerOnly } from '../plugins'
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Both,
|
||||
plugins: [ownerOnly(['182326315813306368'])],
|
||||
description: 'ping command',
|
||||
execute: (ctx) => {
|
||||
ctx.reply('hello, owner');
|
||||
}
|
||||
})
|
||||
```
|
||||
┗|` O′|┛ perfect, your first plugin!
|
||||
|
||||
## Creating Plugins
|
||||
|
||||
Plugins are essentially functions that use the controller object to determine whether to continue or stop the execution of a command.
|
||||
|
||||
## Init Plugins
|
||||
|
||||
Init plugins modify how commands are loaded or do preprocessing.
|
||||
```ts title="src/plugins/updateDescription.js" {3} {9}
|
||||
import { CommandInitPlugin } from "@sern/handler";
|
||||
|
||||
export const updateDescription = (description: string) => {
|
||||
return CommandInitPlugin(({ updateModule, deps }) => {
|
||||
if(description.length > 100) {
|
||||
deps.logger?.info({ message: "Invalid description" })
|
||||
console.error("Description is invalid")
|
||||
return controller.stop("From updateDescription: description is invalid");
|
||||
}
|
||||
updateModule({ description });
|
||||
return controller.next(); // continue to next plugin
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## Control Plugins
|
||||
|
||||

|
||||
|
||||
|
||||
```js
|
||||
import { CommandControlPlugin } from "@sern/handler";
|
||||
|
||||
export const inGuild = (guildId: string) => {
|
||||
return CommandInitPlugin(ctx, sdt) => {
|
||||
if(ctx.guild.id !== guildId) {
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
});
|
||||
};
|
||||
|
||||
```
|
||||
<Steps>
|
||||
1. An event is emitted by `discord.js`.
|
||||
2. This event is passed to all control plugins **in order!!**,
|
||||
3. If all are successful, the command is executed.
|
||||
</Steps>
|
||||
|
||||
:::note
|
||||
Calling `controller.stop()` notifies sern that this command should not be run, and command is ignored.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Control Plugins are good for filtering, preconditions, parsing.
|
||||
:::
|
||||
|
||||
|
||||
### Controller Object
|
||||
|
||||
The controller object is passed into every plugin. It has two methods: `next` and `stop`.
|
||||
|
||||
Plugins use the controller to control the flow of the command. For example, if a plugin fails, it calls `controller.stop()` to prevent the command from executing.
|
||||
|
||||
```ts
|
||||
// Reference object, import this from @sern/handler
|
||||
const controller = {
|
||||
next: (val?: Record<string,unknown>) => Ok(val),
|
||||
stop: (val?: string) => Err(val),
|
||||
};
|
||||
```
|
||||
|
||||
### Passing state with SDT
|
||||
> SDT = state, [dependencies](../dependencies), type (very creative)
|
||||
|
||||
Controllers can pass data downstream. That is, plugins can recieve data from previous plugin calls.
|
||||
If all control plugins are successful, the final state is passed to the module `execute`.
|
||||
|
||||
```js
|
||||
import { commandModule, CommandControlPlugin, CommandType } from '@sern/handler'
|
||||
|
||||
const plugin = CommandControlPlugin((ctx, sdt) => {
|
||||
return controller.next({ a: 'from plugin1' });
|
||||
});
|
||||
const plugin2 = CommandControlPlugin((ctx, sdt) => {
|
||||
return controller.next({ b: ctx.user.id + "from plugin2" });
|
||||
})
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
plugins: [plugin, plugin2],
|
||||
execute: (ctx, sdt) => {
|
||||
console.log(sdt.state) // { a: 'from plugin1', b: '182326315813306368 from plugin2' }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::tip
|
||||
State passing is a glorified asynchronous [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce).
|
||||
:::
|
||||
|
||||
#### Caveats
|
||||
Passing data with the same key will get overridden by the latest plugin.
|
||||
It is recommended to namespace data keys if you have multiple plugins, or you can ensure no keys are overridden by the
|
||||
plugin chain.
|
||||
```js
|
||||
return controller.next({ 'cheese-plugin/data' : "From cheese-plugin" })
|
||||
```
|
||||
93
src/content/docs/v4/reference/presence.mdx
Normal file
93
src/content/docs/v4/reference/presence.mdx
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Presence
|
||||
description: Manage your bot's presence programatically
|
||||
sidebar:
|
||||
order: 5
|
||||
---
|
||||
|
||||
|
||||
## Presence
|
||||
Your bot should have a personality. (or invite link)
|
||||
|
||||
### Once
|
||||
|
||||
Your presence will be set once, after discord.js `Client` is called.
|
||||
|
||||
```js title="./src/presence.js"
|
||||
import { Presence } from '@sern/handler'
|
||||
import { ActivityType } from 'discord.js';
|
||||
|
||||
const activity = { type: ActivityType.Listening, name: "what's bofa" };
|
||||
|
||||
export default Presence.module({
|
||||
execute: () => {
|
||||
return Presence
|
||||
.of({ activities: [activity], status: "idle" })
|
||||
.once();
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Repeated
|
||||
|
||||
Set your presence on intervals or events emitted.
|
||||
|
||||
```js title="./src/presence.js"
|
||||
import { Presence } from '@sern/handler'
|
||||
import { ActivityType, ClientPresenceStatus } from 'discord.js';
|
||||
|
||||
/**
|
||||
* Sorry for using any[]
|
||||
* @param array {any[]}
|
||||
*/
|
||||
function shuffleArray(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return [...array];
|
||||
}
|
||||
|
||||
const statuses = [[ActivityType.Watching, "the sern community", "online"],
|
||||
[ActivityType.Listening, "Evo", "dnd"],
|
||||
[ActivityType.Playing, "with @sern/cli", "idle"],
|
||||
[ActivityType.Watching, "sern bots", "dnd"],
|
||||
[ActivityType.Watching, "github stars go brrr", "online"],
|
||||
[ActivityType.Listening, "Spotify", "dnd"],
|
||||
[ActivityType.Listening, "what's bofa", "idle"]];
|
||||
|
||||
export default Presence.module({
|
||||
execute: () => {
|
||||
const [type, name, status] = statuses.at(0)!;
|
||||
return Presence
|
||||
//start your presence with this.
|
||||
.of({ activities: [ { type, name } ], status })
|
||||
.repeated(() => {
|
||||
const [type, name, status] = [...shuffleArray(statuses)].shift()!;
|
||||
return {
|
||||
status,
|
||||
activities: [{ type, name }]
|
||||
};
|
||||
}, 60_000); //repeat and setPresence with returned result every minute
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Inject dependencies
|
||||
```js title="./src/presence.js" {7-8}
|
||||
import { Presence } from '@sern/handler'
|
||||
import { ActivityType } from 'discord.js';
|
||||
|
||||
const activity = { type: ActivityType.Listening, name: "what's bofa" };
|
||||
export default Presence.module({
|
||||
inject: ['@sern/logger'],
|
||||
execute: (logger) => {
|
||||
logger?.info({ message: "Presence changed" });
|
||||
return Presence
|
||||
.of({ activities: [activity], status: "idle" })
|
||||
.once();
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Project Layout
|
||||
description: The layout of a sern project
|
||||
sidebar:
|
||||
order: 1
|
||||
order: 2
|
||||
---
|
||||
|
||||
Usually, a project should look like this: <br />
|
||||
|
||||
Reference in New Issue
Block a user