feat: add site back to website (temp) (#3267)

* feat: add site back to website

* refactor: use same sidebar

* fix: delete generated docs
This commit is contained in:
Jonathan Ho
2023-12-07 01:00:20 +00:00
committed by GitHub
parent 721b8a82f6
commit 79cc362c22
83 changed files with 10046 additions and 1 deletions
@@ -0,0 +1,7 @@
---
sidebar_position: 5
---
# Same as discord.js [collection][def]
[def]: https://discord.js.org/#/docs/collection/main/class/Collection
@@ -0,0 +1,7 @@
---
sidebar_position: 4
---
# Documentation [at][def]
[def]: https://deno.land/x/amethyst@v4.3.4/mod.ts?s=AmethystEmbed
@@ -0,0 +1 @@
{ "label": "Amethyst Framework", "position": 4 }
+99
View File
@@ -0,0 +1,99 @@
---
sidebar_position: 2
---
# Creating an client
Let's review each choice and what it does.
- `owners`, You may specify the proprietors of the bot using this. The inhibitors make use of this.
- `prefix`, The string a user should use at the beginning of their message to identify it as a command to the bot. Only
message commands can use this, and the parameter can be either a string or a function.
- `botMentionAsPrefix`, Determines whether a user's mention of a bot qualifies as a prefix.
- `ignoreBots`, Allow bots to execute commands.
- `defaultCooldown`, Defualt cooldown for all commands.
- `ignoreCooldown`, List of people who bypass cooldowns.
- `commandDir`, Path to the command directory used by the fileloader.
- `eventDir`, Path to the event directory used by the fileloader.
- `inhibitorDir`, Path to the inhibitor directory used by the fileloader.
- `prefixCaseSensitive`, Indicates whether or not the prefix is case-sensitive.
- `extras`, Extras that are used by your client, such as a database instance or a music player.
## Client Extras
When using discord.js we often do stuff like `client.musicplayer=player;` and in order to maintain this ease Amethyst
allows you to do `client.extras.musicplayer=player;`.
NOTE: Typing will not work on `client.extras`.
## Client Properties
```ts
user: User;
events: AmethystEvents;
messageCollectors: AmethystCollection<string, MessageCollector>;
componentCollectors: AmethystCollection<bigint, ComponentCollector>;
reactionCollectors: AmethystCollection<bigint, ReactionCollector>;
runningTasks: runningTasks;
tasks: AmethystCollection<string, AmethystTask>;
category: AmethystCollection<string, Category>;
inhibitors: AmethystCollection<
string,
<T extends Command = Command>(
bot: AmethystBot,
command: T,
options: { memberId?: bigint; channelId: bigint; guildId?: bigint }
) => true | AmethystError
>;
owners?: bigint[];
botMentionAsPrefix?: boolean;
prefixCaseSensitive?: boolean;
defaultCooldown?: CommandCooldown;
ignoreCooldown?: bigint[];
guildOnly?: boolean;
messageQuotedArguments?: boolean;
ignoreBots?: boolean;
dmOnly?: boolean;
eventHandler: AmethystEventHandler;
extras: any;
prefix?:
| string
| string[]
| ((bot: AmethystBot, message: Message) => Async<string | string[]>);
on(name: string, callback: (...args: any) => unknown): void;
once(name: string, callback: (...args: any) => unknown): void;
amethystUtils: {
awaitComponent(
messageId: bigint,
options?: ComponentCollectorOptions & { maxUsage?: number }
): Promise<Interaction[]>,
awaitReaction(
messageId: bigint,
options?: ReactionCollectorOptions & { maxUsage?: number }
): Promise<AmethystReaction[]>,
awaitMessage(
memberId: bigint,
channelId: bigint,
options?: MessageCollectorOptions & { maxUsage?: number }
): Promise<Message[]>,
createCommand(command: CommandOptions): void,
createCategory(category: CategoryOptions): void,
updateCategory(category: CategoryOptions): void,
createTask(task: AmethystTask): void,
clearTasks(): void,
createInhibitor<T extends Command = Command>(
name: string,
inhibitor: (
bot: AmethystBot,
command: T,
options?: { memberId?: bigint; guildId?: bigint; channelId: bigint }
) => true | AmethystError
): void,
deleteInhibitor(name: string): void,
updateSlashCommands(): void,
}
```
## [Documentation](https://deno.land/x/amethyst@v4.2.0/mod.ts?s=AmethystBotOptions)
+82
View File
@@ -0,0 +1,82 @@
---
sidebar_position: 3
---
# Lets Create a simple bot in Node.js
- **Step 1**: Create a typescript project with index.ts as main file.
- **Step 2**: Installing packages. Install following packages.
```bash
npm i @thereallonewolf/amethystframework
```
- **Step 3**: Create a index.ts file.
- **Step 4**: Add following code in index.ts file, replacing TOKEN with your bot token.
```ts
import { createBot, GatewayIntents, startBot } from 'discordeno'
import { enableCachePlugin, enableCacheSweepers } from 'discordeno/cache-plugin'
import {
AmethystBot,
Category,
Command,
Context,
enableAmethystPlugin,
Event,
} from '@thereallonewolf/amethystframework'
let baseClient = createBot({
token: 'TOKEN',
intents:
GatewayIntents.Guilds |
GatewayIntents.GuildMessages |
GatewayIntents.MessageContent,
})
//@ts-ignore
let client = enableAmethystPlugin(enableCachePlugin(baseClient), {
botMentionAsPrefix: true,
prefix: '!', //Can be a function or a string.
ignoreBots: false,
})
enableCacheSweepers(client)
startBot(client)
@Category({
name: 'general',
description: 'My general commands',
uniqueCommands: true,
default: '', //As all the commands are unique so no need to set the default command.
})
export class General {
@Command({
name: 'ping',
description: 'Pong!',
commandType: ['application', 'message'],
category: 'general',
args: [],
})
async ping(bot: AmethystBot, ctx: Context) {
ctx.reply({ content: 'Pong!' })
}
@Event('ready')
async ready() {
console.log('I am ready!')
client.amethystUtils.updateSlashCommands()
}
}
```
- **Step 5**: Invite your bot and compile index.ts and run it. Then you can use `/general ping` or `!ping`
- **Step 6**: Useful links:
1. Command Options can be found
[here](https://github.com/AmethystFramework/framework/blob/master/src/types/commandOptions.ts).
2. Category Options [here](https://github.com/AmethystFramework/framework/blob/master/src/types/categoryOptions.ts)
3. Full [Documentation](https://deno.land/x/amethyst)
+54
View File
@@ -0,0 +1,54 @@
---
sidebar_position: 1
---
# Amethyst
Amethyst is a [Discordeno](https://github.com/discordeno/discordeno) plugin that is incredibly robust and flexible. It
promotes standard practices and is geared at bigger bots.
This framework is not for you if you cannot utilise Maps and Sets without reading them up. We presume you have a solid
foundation in typescript/javascript and dicord bots.
[Documentation](https://deno.land/x/amethyst)
## Features
- Thanks to Amethyst's adaptability, you can change a lot of things and add features as you see appropriate.
- A developer may create slash or message interactions with Amethyst.
- Assistance with interactions, such as selection, built-in buttons, and more.
- Custom discord.js like event system without the event emitter.
- Explore more incredible features of our framework.
## Why Amethyst?
Amethyst makes use of the [Discordeno](https://github.com/discordeno/discordeno) plugin system to streamline your coding
process and help you get going more quickly. Support for message and slash commands that doesn't need altering any code.
- Fully programmable
- Easy to learn and utilise.
- Conversion from Discord.js bots is simple.
## Ideas
The objective is to provide a framework that can be used with both Node.js and Deno while minimising transitions and
rewrites.
- Easy to understand and use.
- Cover up complexity in the engine keeping the end user interface as simple as possible.
## Future Updates
Creation of discord setup wizards for commands like welcome, context menus and paginated messages.
## Installation
Deno: [link](https://deno.land/x/amethyst)
Npm:
```bash
npm i @thereallonewolf/amethystframework
```
+49
View File
@@ -0,0 +1,49 @@
---
sidebar_position: 2
---
import BrowserOnly from '@docusaurus/BrowserOnly';
# Architecture
## Overview
import FlowChart from '@site/src/components/architecture/FlowChart'
<BrowserOnly>
{() => <FlowChart />}
</BrowserOnly>
Discordeno have three main components/process, gateway, bot and rest. Websocket events from Discord, such as connecting, restarting, heartbeating, and transmitting websocket messages to Discord, are handled and maintained by the Gateway process. All Discord events are handled and converted by the Bot process, which also activates your code, such as the execute function upon message creation. All http requests to Discord, including proxying and ratelimiting, are handled by the Rest process.
## Gateway Process
import FlowChart2 from '@site/src/components/architecture/FlowChart2'
<BrowserOnly>
{() => <FlowChart2 />}
</BrowserOnly>
The Gateway process have two part the gateway manager and the gateway [shard](https://discord.com/developers/docs/topics/gateway#sharding), the gateway manager oversees the gateway shard.
### Gateway Manager
The gateway manager spawns the right amount of shard acording to data from discord's [getGatewayBot](https://discord.com/developers/docs/topics/gateway#get-gateway-bot) endpoint, user can override the value of gatewayBot by directly passing the value. The manager control the order or sequence of shard identifying base on the session start limit listed in the getGatewayBot to prevent hitting the ratelimiting. By default the manager will check the getGatewayBot endpoint every 8 hours and reshard if the number of shard changed shard. Check [here](/tutorial/big-bot-guide/gateway#understanding-gateway-manager) for more information about the gateway manager.
### Gateway Shard
Any event is passed to the handleMessage method by the gateway shard, which also establishes a websocket connection to Discord. The handleMessage method will examine the event and only deliver genuine events to the bot by intercepting and processing websocket-related events like hello, resume, heartbeat, and ready. You can modify the handleMessage to suit your needs, but it is not advised unless you are certain of your actions because the connection depends on it. After the function has finished processing the event, it will either pass the event directly (in the same process), via the rest api, a message queue, or another mechanism depending on the user's customization.
## Bot Process
import FlowChart3 from '@site/src/components/architecture/FlowChart3'
<BrowserOnly>
{() => <FlowChart3 handlerFilter={(handler) => handler.startsWith('handleChannel')}/>}
</BrowserOnly>
A simplified version of function used inside of the bot process, showing only the handlers, transformers and event related to channel event
### Bot
When an event arrives from the gateway, the bot process receives it and passes it all to the handleDiscordPayload method. The handleDiscordPayload method will invoke raw events for each event and route calls to handlers, transformers, and events at three different layers for processing. The handler will use transformers to alter the event's contents before calling the event. The data will be transformed into typescript after being stripped of pointless properties, given a new name, and having bitwise permission flags abstracted.
## Rest Process
TBC
+12
View File
@@ -0,0 +1,12 @@
---
sidebar_position: 10
---
# Benchmark
Benchmark runs on every commit pushed on the Discordeno's main branch
import Benchmark from '@site/src/components/Benchmark'
<Benchmark />
@@ -0,0 +1,4 @@
{
"label": "Big Bot Guide",
"position": 2
}
+119
View File
@@ -0,0 +1,119 @@
---
sidebar_position: 4
sidebar_label: Step 3 - Cache
---
# Step 3: Standalone Cache Process
The next part of this is going to be about making a standalone cache process. By now, you should have both a REST and a
Gateway process ready. Before, we start handling events we should build a Cache handler.
## Why Use Standalone Cache Process?
A standalone cache process allows you to retain cached data even after bot restarts. For example, if you are caching
member roles to track when a role was added or removed, you may want to cache the members. The question then comes to
play, when deciding where to keep your cache. Another reason to use this is, whether or not you are using a standalone
gateway process.
- Start rest process
- Start event handler process (bot)
- Start gateway process.
- Guild create events arrive providing all the data needed to cache in the bot process.
- Restart event handler process(maybe for an update/reboot)
- You lost all guilds/channels/permissions etc and can not get them again without restarting gateway. This defeats the
entire point of the standalone gateway.
If your cache is tied to the bot processes which is not tied to the gateway you lose all this info. The next thought is
to just keep the Cache entirely in the gateway process however, I do not like this personally however, should you desire
this you can do this as well. The reason I prefer not to do this is when your bot needs to make requests to your cache,
you do not want it occupying the thread for processing other gateway events arriving from discord. A separate cache
process makes it so it uses an entirely separate thread and will not slow down anything else.
## Understand Cache Types
When I use the term cache process, this is interchangeable with any similar term such as "custom cache", "redis cache",
"pgsql cache", etc... The fact is you can keep this "cache" anywhere. For this guide, we will implement a very simple
cache using pgsql. Feel free to modify this any way you like as advanced as you like. The point is Discordeno cache is
flexible enough to let you use anything for your Cache storage.
## Setting Up The Cache
This step is for you to create the base schema for your cache. For example, if you want to implement a pgsql or redis
cache perhaps you want to prepare the tables/schema. For this guide, we are just going to do a quick little hack to get
a custom cache working.
Create a file in a path like `src/bot/cache/schema.sql`
```sql
CREATE TABLE IF NOT EXISTS "users" (
id bigint NOT NULL,
username text COLLATE pg_catalog."default" NOT NULL,
discriminator text COLLATE pg_catalog."default" NOT NULL,
bot boolean,
CONSTRAINT "users_pkey" PRIMARY KEY (id)
)
```
Note that you can cache only properties you want and leave all other properties that you won't use.
Now that we have this schema ready for our users cache. Go ahead and repeat this for all other cache tables.
Cache Tables:
- users
- members
- guilds
- channels
- threads
- messages
- presences
- unavailableGuilds
Once you are finished continue forward, for the purpose of keeping this guide short we wont cover each table.
> You should also run this file to prepare your pgsql and have your pgsql database running by now. Or whatever, cache
> service you use.
### Cache Handler
Now we will initiate our cache service. This may be different for you based on your choice of cache type. Since we are
using PGSQL for our cache layer, we will now instantiate it.
```ts
import { postgres } from '../../../deps.ts'
// YOU CUSTOM PGSQL INFO GOES HERE
const DATABASE_USERNAME = ''
const DATABASE_PASSWORD = ''
const DATABASE_NAME = ''
const DATABASE_HOST = ''
const DATABASE_PORT = 8956
const DATABASE_MAX = 20
export const psql = postgres({
username: DATABASE_USERNAME,
password: DATABASE_PASSWORD,
database: DATABASE_NAME,
host: DATABASE_HOST,
port: DATABASE_PORT,
max: DATABASE_MAX,
/*onnotice: (data) => {
logger.psql(`${data.severity} ${bgBrightBlack(`[${data.code}| ${data.file}:${data.line}]`)}`, data.message);
},*/
types: {
bigint: postgres.BigInt,
},
})
```
To use the PGSQL driver we are using in this guide you can insert this into your `deps.ts`.
```ts
// @deno-types="https://denopkg.com/porsager/postgres@e2a8595d7aa8c3c838b83b9bca7b890c1707ad2c/types/index.d.ts"
export { default as postgres } from 'https://denopkg.com/porsager/postgres@e2a8595d7aa8c3c838b83b9bca7b890c1707ad2c/deno/lib/index.js'
```
> Note: Remember you can use any driver you like. For deno users we prefer to use this library for PGSQL because it is
> more stable and more performant.
Now that the cache layer is ready, we can proceed to begin creating our bot.
+261
View File
@@ -0,0 +1,261 @@
---
sidebar_position: 5
sidebar_label: Step 4 - Event Handler
---
# Step 4: Creating Standalone Event Handler
Now we are about to start working on the bot code itself. The last 3 steps should be completed by the time you reach
this. The event handler process will be listening for events from any number of gateway instances and be ready to handle
them.
In this guide, we may use the term `Bot` or the term `event handler`, remember that these refer to the same thing. This
is your main bot code.
## Why Use Standalone Event Handler Process?
The standalone event handler is the portion of your bot code that you will be changing the most. The three previous
steps created processes that are intended to never be turned off. This process is designed to let you restart whenever
you wish and be incredibly quick to restart. Since we don't have the delay to start up shards anymore, your code becomes
reloaded instantly.
## Creating Event Handlers
Create a file path like `src/bot/mod.ts`.
```ts
import { DISCORD_TOKEN } from '../../configs.ts'
import { Collection, createBot, Intents } from '../../deps.ts'
import { psql } from './cache/mod.ts'
export const bot = createBot({
token: DISCORD_TOKEN,
botId: 270010330782892032n,
intents: Intents.Guilds | Intents.GuildMessages,
events: {
messageCreate: function (bot, message) {
console.log('message arrived')
},
},
})
```
Alright that was a lot of code. Now let's break it down little by little.
### Understanding createBot()
**Basic Keys**
- `token` if you can't figure this out stop reading and find another guide please. Thanks.
- `botId` This is going to be your bot id. The reason we require this here is because we are going to set up a
standalone gateway process. With most other libs, they can fill this information using the READY event. However, since
our gateway is designed not to reboot, we are not going to get the READY event whenever we restart our bot. This means
we won't be able to fill this information later. Another method to get the id is to use the `token` but discord
developers have mentioned that this behavior is not documented and not supposed to be relied on to remain stable. Due
to these reasons, we chose to just require the bot id be passed here.
- `applicationId` is an optional choice if your bot is old and has a unique id different from it's bot id.
- `intents`: Provide the intents you like using a bitwise OR operation (eg. `Intents.Guilds | Intents.GuildsMessages`).
String form supports autocomplete and type safety.
- `events`: These are your event handler functions. When a MESSAGE_CREATE event arrives from Discord it will be
processed here. We will set up the routing to run these functions later in the guide but for now you can see how to
set it up. Note, you can create these functions in separate files and just import them here as you wish.
## Using Your Cache
Since we are using a standalone gateway, a custom cache is essentially required as explained in step 3 of this guide.
Here we'll have some basic functions to make use of the cache we created in step 3.
```ts
const cache = {
/** Get a single item from the table */
async get(key) {
return await psql`SELECT * FROM ${psql(
tables[table],
)} WHERE "id" = ${psql.types.bigint(key)}`
},
/** Completely empty this table. */
async clear() {
await psql`TRUNCATE TABLE ${psql(tables[table])}`
},
/** Delete the data related to this key from table. */
async delete(key) {
await psql`DELETE FROM ${psql(
tables[table],
)} WHERE "id" = ${psql.types.bigint(key)}`
return true
},
/** Check if there is data assigned to this key. */
async has(key) {
return Boolean(
await psql`SELECT 1 FROM ${psql(
tables[table],
)} WHERE "id" = ${psql.types.bigint(key)}`,
)
},
/** Check how many items are stored in this table. */
async size() {
return (await psql`SELECT COUNT("id") FROM ${psql(tables[table])}`).count
},
/** Store new data to this table. */
async set(key, data) {
await psql`INSERT INTO ${psql(tables[table])} ${psql(
data,
...Object.keys(data),
)}`
return true
},
// THESE TWO ARE USELESS FOR CUSTOM CACHE BUT NEED TO SHUT UP TS ERRORS
async forEach(callback) {},
async filter(callback) {
return new Collection()
},
}
```
You can insert any code you desire for your cache system here. Since we were using PGSQL, we used sql queries to make
these requests. However, should you need to communicate to Redis or anything else of your choice, you can do so here.
> Note: The .filter() and .forEach() methods are unnecessary and should not be used for your bot as they are not
> optimized for performance. These are made for smaller bot users who would not leave itoh alone and in order to please
> them itoh gave them their hearts desire! LMAO!
## Customizing Internal Code
One of the best parts about discordeno is the flexibility. In order to show this off, we will use the `user` example but
you can apply this to any part of the library.
### Why Is Customizing Important?
At large scale, every single property can become expensive to store in your cache. For example, if your bot does not
make use of a `channel.topic` why storing potentially millions of strings in your memory for something you never
need/user. This could save you potentially GBs of memory to just remove this one property.
### Customizing Process
First, let's create a file in some path like `src/bot/internals/mod.ts`. Note that we will create quite a few files
below simply to keep code cleaner and simpler, in expectation that it will grow more complex later. You can merge them
as you wish.
```ts
import { Bot } from '../../../deps.ts'
import { customizeBotTransformers } from './transformers/mod.ts'
export function customizeBotInternals(bot: Bot) {
bot = customizeBotTransformers(bot)
// ADD AS MANY MORE CUSTOMIZATIONS HERE AS YOU LIKE TO HANDLERS, HELPERS, UTILS ETC...
return bot
}
```
We also need to add another file now at `src/bot/internals/transformers/mod.ts`
```ts
import { Bot } from '../../../../deps.ts'
import { customizeUserTransformer } from './user.ts'
export function customizeBotTransformers(bot: Bot) {
bot = customizeUserTransformer(bot)
// ADD ANY MORE CUSTOM TRANSFORMERS HERE
return bot
}
```
One more file at `src/bot/internals/transformers/user.ts`
```ts
import { Bot, DiscordenoUser, transformUser } from '../../../../deps.ts'
export function customizeUserTransformer(bot: Bot) {
bot.transformers.user = function (bot, payload) {
// REMOVE USELESS PROPS OUR BOT DOESNT USE
const {
system,
locale,
verified,
email,
flags,
mfaEnabled,
premiumType,
publicFlags,
...user
} = transformUser(bot, payload)
// RETURN ONLY USEFUL PROPS WE NEED TO USE AND CACHE IF NECESSARY
return user as DiscordenoUser
}
return bot
}
```
First we override the internal transformer for the `user` object. What's cool is the typings will be automatically
provided :) Next, we use the `transformUser` function from the lib itself to make it create the internal user version.
The reason I do this is so when I update the library and a new property is added or removed i can simply update and get
it. Should you desire maximum control you can remove this entirely and only have what you want no matter what discord
sends. Discordeno gives you the ability to stay in control.
This method can be applied to any transformer, helper function, gateway event handler, util function or any part of the
library. Anything and everything is possible to override. You do NOT need to fork and modify the library ever and give
yourself a headache trying to maintain your fork with updates.
## Handling Incoming Gateway Events
Remember, this is a separate process we need to make sure we are listening to incoming events from our gateway
instances. Since we used http in our Gateway step, we can create an http listener here as well.
Create a file in a path like `src/bot/gatewayEventsListener.ts`
Now we should create a http listener, check for authorization in headers, run `bot.events.raw` and `bot.handlers[event]`
```ts
import { DiscordGatewayPayload } from 'discordeno'
import { EVENT_HANDLER_PORT, REST_AUTHORIZATION } from '../../configs.ts'
const server = Deno.listen({ port: EVENT_HANDLER_PORT })
// Connections to the server will be yielded up as an async iterable.
for await (const conn of server) {
// In order to not be blocking, we need to handle each connection individually
// in its own async function.
handleRequest(conn)
}
async function handleRequest(conn: Deno.Conn) {
// This "upgrades" a network connection into an HTTP connection.
const httpConn = Deno.serveHttp(conn)
// Each request sent over the HTTP connection will be yielded as an async
// iterator from the HTTP connection.
for await (const requestEvent of httpConn) {
if (
!REST_AUTHORIZATION ||
REST_AUTHORIZATION !== requestEvent.request.headers.get('AUTHORIZATION')
) {
return requestEvent.respondWith(
new Response(JSON.stringify({ error: 'Invalid authorization key.' }), {
status: 401,
}),
)
}
const json = (await requestEvent.request.json()) as {
message: DiscordGatewayPayload
shardId: number
}
// Run raw event.
bot.events.raw(bot, json.message, json.shardId)
if (json.message.t && json.message.t !== 'RESUMED') {
// When a guild or something isn't in cache this will fetch it before doing anything else.
if (!['READY', 'GUILD_LOADED_DD'].includes(json.message.t)) {
await bot.events.dispatchRequirements(bot, json.message, json.shardId)
}
// Run event function provided in bot.events
bot.handlers[json.message.t]?.(bot, json.message, json.shardId)
}
new Response(undefined, { status: 200 })
}
}
```
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
---
sidebar_position: 2
sidebar_label: Step 1 - REST
---
# Creating A Standalone REST Process
The first thing we want to make is our standalone REST process. This process will be used by almost every other process,
so it is going to be the foundation of the bot.
Before, we dive into how, here is a quick summary of why you will want a standalone REST process.
## Why Use Standalone REST Process?
- Easily host on any serverless infrastructure.
- Freedom from global rate limit errors
- As your bot grows, you want to handle global rate limits better. Shards don't communicate fast enough to truly
handle it properly so this allows 1 rest handler across the entire bot.
- In fact, you can host multiple instances of your bot and all connect to the same rest server.
- REST does not rest!
- Separate rest means if your bot for whatever reason crashes, your requests that are queued will still keep going and
will not be lost.
- Seamless updates! When you want to update and reboot the bot, you could potentially lose tons of messages or
responses that are in queue. Using this you could restart your bot without ever worrying about losing any responses.
- Single source of contact to Discord API
- This will allow you to make requests to discord from anywhere including a bot dashboard. You no longer need to have
to communicate to your bot processes just to make a request or anything. Free up your bot process for processing bot
events.
- Scalability! Scalability! Scalability!
## Preparations
Before going further, you should have already made the following pieces:
- rest/mod.ts
- deps.ts (Make sure to import discordeno)
- configs.ts
- Deno extension(if you are using deno, this is required)
- TabNine extension to make your life so much better. (Optional)
## Creating Rest Manager
Now let's open up that rest file and start coding.
```ts
import { DISCORD_TOKEN, REST_AUTHORIZATION, REST_PORT } from '../../configs.ts'
import { BASE_URL, createRestManager } from '../../deps.ts'
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: `http://localhost:${REST_PORT}`,
})
```
- `createRestManager` is imported from your deps file which should have exported everything from discordeno.
- `DISCORD_TOKEN` is the bot's token itself.
- `REST_AUTHORIZATION` is a special password you want to use to authenticate that requests being sent to your port are
indeed from you.
- `customUrl` the url where this rest process will be running. This can be localhost which we are using in this guide if
you want all processes on same VPS or separate them to different servers for horizontal scaling. `REST_PORT` is just
the port where you want the process hosted.
Now you have an entire Rest manager ready and waiting. Only thing you need now, is to listen for requests.
## Creating HTTP Listener
Since this is not a beginner guide, I am assuming you know already how to create a HTTP listener. There are enough
guides on this out there. I will only cover the rough functionality.
```ts
// START LISTENING TO THE URL(localhost)
const server = Deno.listen({ port: REST_PORT })
console.info(
`HTTP webserver running. Access it at: http://localhost:${REST_PORT}/`,
)
// Connections to the server will be yielded up as an async iterable.
for await (const conn of server) {
// In order to not be blocking, we need to handle each connection individually
// in its own async function.
handleRequest(conn)
}
async function handleRequest(conn: Deno.Conn) {
// This "upgrades" a network connection into an HTTP connection.
const httpConn = Deno.serveHttp(conn)
// Each request sent over the HTTP connection will be yielded as an async
// iterator from the HTTP connection.
for await (const requestEvent of httpConn) {
if (
!REST_AUTHORIZATION ||
REST_AUTHORIZATION !== requestEvent.request.headers.get('AUTHORIZATION')
) {
return requestEvent.respondWith(
new Response(JSON.stringify({ error: 'Invalid authorization key.' }), {
status: 401,
}),
)
}
const json = (await requestEvent.request.json()) as any
// IMPLEMENT ANY ERROR HANDLING HERE IF YOU WOULD LIKE BY WRAPPING THIS IN A CATCH
// MAKE THE REQUEST TO DISCORD
const result = await rest.runMethod(
rest,
// USE THE SAME METHOD THAT CAME IN. IF DELETE CAME IN WE SEND DELETE OUT
requestEvent.request.method as any,
// OVERWRITE THE CUSTOM URL WITH DISCORDS BASE URL
`${BASE_URL}/v${rest.version}${requestEvent.request.url.substring(
rest.customUrl.length,
)}`,
json,
)
// RETURN DISCORDS RESPONSE BACK TO THE PROCESS MAKING THE REQUEST
if (result) {
requestEvent.respondWith(
new Response(JSON.stringify(result), {
status: 200,
}),
)
} else {
requestEvent.respondWith(
new Response(undefined, {
status: 204,
}),
)
}
}
}
```
@@ -0,0 +1,40 @@
---
sidebar_position: 1
---
# Step By Step Guide
THIS IS A WORK IN PROGRESS GUIDE USING THE NEW v16 OF DISCORDENO.
## Understanding The Goals of This Guide
This guide is a quick-paced walkthrough meant for big bot developers. It is expected that you have a decent amount of
understanding of how to code your bots.
## Is This Guide Meant For You?
If your goal is not to have a bot in millions of discord servers, please find another guide/library. Discordeno is
heavily opinionated towards optimizing for bots at scale. If you do not know what a Map or a Set is without having to
google it, you are at the wrong place.
## Why You Should Use Discordeno?
The best way I can describe why you should use Discordeno, is from the words of the biggest bot developers themselves.
After speaking to some of the developers of the biggest JS/TS bots, you begin to see a pattern of users unhappy with the
current state of JS/TS libraries. They are no longer able to help them scale easily and are starting to move away to
other libraries or having to make their own libraries because they need to be able to make their bot distributed.
The following quotes are from developers who have bot's in atleast 1 million+ discord servers.
- Flexibility like no other library.
- One of the big bot developers found that when their bot got too big, Eris was just very painful to optimize.
- "A pretty large hassle, I had to fork eris and modify it. There was a lot of interdependency on the values from
caches that made it difficult to remove properties "safely" without searching the entire codebase"
- When discovering how easy it was to do the same thing in Discordeno:
- "the convenience of being able to do so puts confidence in me that the lib is versatile so it'd certainly draw me
towards it"
- Scalability: Standalone Gateway, Rest, Event Handler, Commands, Cache and much more.
- "All this sound like a dream (especially when you currently use eris)"
Discordeno provides you all the tools that you need to make bot development really easy. As the old saying goes, the
best way to learn to ride a bicycle is to actually try riding a bicycle. So let's try out Discordeno.
@@ -0,0 +1,107 @@
---
sidebar_position: 5
---
# Frequently Asked Questions
## Does Discordeno Support TypeScript?
TypeScript is supported to the highest standard by Discordeno. TypeScript is included in Discordeno since Deno supports it. This implies that before using TypeScript, you do not need to compile it. But this isn't the main reason Discordeno is the ideal library for TypeScript programmers. I was experimenting with a lot of various things when I created this library, and automatic typings was one of them.
When I utilised other libraries, I frequently observed incorrect or troublesome typings. This is so that TypeScript won't alert the library developers because most of the Discord API typings aren't utilised by the libraries themselves.
It is quite unlikely that these typings would become wrong or outdated as a result of minor errors like forgetting to update typings because Discordeno utilises them as part of the rest process. Libraries occasionally add a property without also adding it to their typings. Because of this, TypeScript developers cannot use it, only JavaScript developers can. Typings are crucial for TypeScript developers. Typings are treated as a component of the code by Discordeno! A breaking change in typings is a breaking change for the library!
## How Stable Is Discordeno?
Stability is one of the main problems with practically every library (I have used). None of the libraries showed TypeScript developers the love and care that they deserve. Because breaking changes to typings occasionally occurred without producing a MAJOR bump, TypeScript projects would occasionally fail. As a result, production TypeScript bots would fail. At times, I was the only one keeping the typings up to date for that library. Some libraries that were older than 1.0 didn't even have a stable branch or version, so I didn't have to worry about them undergoing breaking changes.
The finest stability for TypeScript developers is one of my basic goals for this library.
No matter how little, a change that impacts the public API qualifies as a breaking change. I don't care whether we reach version 500. As a library maintainer, you should never be scared to bump a MAJOR because it just involves a tiny modification or a type change because doing so will ruin the end user's experience.
## Why Doesn't Discordeno Use Classes or EventEmitter?
This is a design decision for the library itself. You can still use class on your bot if you want. In fact, I hope someone
makes a framework/templates for this lib one day using classes so that devs have a choice on which style they prefer.
Without trying to write an entire thesis statement on the reasons why I avoided Classes in this library, I will just link to
the best resources that I believe help explain it.
- [Really good article](https://dannyfritz.wordpress.com/2014/10/11/class-free-object-oriented-programming/)
- [Lecture by one of the developers who makes JavaScript](https://www.youtube.com/watch?v=PSGEjv3Tqo0)
In regards to EventEmitter, I believe a functional event API was a much better choice. EventEmitter at its core, is simply a set of functions that run when a certain event is emitted. In Discordeno, that function is executed instead of emitting some event to trigger it.
```typescript
// EventEmitter Example
EventEmitter.emit('guildCreate', guild)
// Discordeno Example
bot.events.guildCreate?.(bot, guild)
```
There isn't really any difference especially for users when they use it. One bad thing about EventEmitter is that if
misused it can easily cause memory leaks. It is very easy to open yourself up to these memory leak issues. It has
happened to me when I started coding as well. This is why I wanted Discordeno's implementation to help devs avoid the
issues I had. It prevents anyone from having this as a potential issue. Another issue with EventEmitter is trying to
update the code in those functions without having to deal with headaches left and right of removing and adding
listeners. You don't need to worry about binding or not binding events. They are just pure functions
In Discordeno, this is extremely simple; you just simply give it the new event handlers. For example:
```typescript
bot.events.guildCreate = newGuildCreateEventHandler
```
## Why Do You Have A Class for Collection If Classes Are Bad?
The Collection class is an exception in the library where a class was allowed. This is because Collection extends Map.
The Map class is provided by JavaScript itself and is extremely fast. You can perform millions of operations a second
with a Map. Maps are too useful to avoid and don't have downsides like EventEmitters do. The Collection class simply
adds on other functionality that Discordeno users felt they needed. Although I am against using classes whenever
possible, I am also a big supporter of providing the best developer experience.
## Why Are there no options in Discordeno?
Discordeno is not a library that handles code in the exact way every person wants it to. It is opinionated. Discordeno
defaults to the Discord recommended options or the best options for majority of developers needs. For example, there is
no option of fetching all members startup. This is a practice that Discord does not recommend or want users doing. By
default, we don't support stuff like this. In Discordeno, we follow Discords recommended solution and it just works
internally. The End! No fuss! No Muss! Just good stuff!
Now, I understand that there are times when it's necessary to be able to customize this and fetch them all. If you are
advanced enough to need these options, you should be able to simply do it yourself. For most users, this is just an
unnecessary option. The main module should remain minimalistic and easy to use for 99% of users.
## Why Do I See errors Like "MISSING_VIEW_CHANNEL" or "BOTS_HIGHEST_ROLE_TOO_LOW"?
Discordeno is the only library(that I have used), that has built in permission handling. A lot of bots get automatically
banned by Discord because they forget to handle permissions. When bots don't check permissions and continue to send
requests to the API, this leads to bots being banned. I have tried to request adding this feature into libraries but
they were reluctant to do so because it would require the devs to maintain the library whenever an update was made by
Discord.
Discordeno provides you specific keywords that you can use to send a clean response to the end user of your choosing. I
have even seen some bots have hundreds of thousands of Missing Permission or Missing Access errors because libraries
don't handle it. IMO, this is a crucial part of any good library as much as it is to handle rate limiting.
```typescript
import {
Bot,
Errors,
Message,
} from 'https://deno.land/x/discordeno@16.0.0/mod.ts'
export function handleCommandError(bot: Bot, message: Message, type: Errors) {
switch (type) {
case Errors.MISSING_MANAGE_NICKNAMES:
return bot.helpers.sendMessage(message.channelId, {
content:
"The bot does not have the necessary permission to manage/edit other user's nicknames. Grant the **MANAGE_NICKNAME** permission to the bot and try again.",
})
case Errors.MISSING_MANAGE_ROLES:
// Note: i18n is not part of the library. This is just an example of how you could use i18n for custom error responses.
return bot.helpers.sendMessage(message.channelId, {
content: i18n.translate(type),
})
}
}
```
@@ -0,0 +1,107 @@
---
sidebar_position: 5
---
# Frequently Asked Questions
## Does Discordeno Support TypeScript?
TypeScript is supported to the highest standard by Discordeno. TypeScript is included in Discordeno since Deno supports it. This implies that before using TypeScript, you do not need to compile it. But this isn't the main reason Discordeno is the ideal library for TypeScript programmers. I was experimenting with a lot of various things when I created this library, and automatic typings was one of them.
When I utilised other libraries, I frequently observed incorrect or troublesome typings. This is so that TypeScript won't alert the library developers because most of the Discord API typings aren't utilised by the libraries themselves.
It is quite unlikely that these typings would become wrong or outdated as a result of minor errors like forgetting to update typings because Discordeno utilises them as part of the rest process. Libraries occasionally add a property without also adding it to their typings. Because of this, TypeScript developers cannot use it, only JavaScript developers can. Typings are crucial for TypeScript developers. Typings are treated as a component of the code by Discordeno! A breaking change in typings is a breaking change for the library!
## How Stable Is Discordeno?
Stability is one of the main problems with practically every library (I have used). None of the libraries showed TypeScript developers the love and care that they deserve. Because breaking changes to typings occasionally occurred without producing a MAJOR bump, TypeScript projects would occasionally fail. As a result, production TypeScript bots would fail. At times, I was the only one keeping the typings up to date for that library. Some libraries that were older than 1.0 didn't even have a stable branch or version, so I didn't have to worry about them undergoing breaking changes.
The finest stability for TypeScript developers is one of my basic goals for this library.
No matter how little, a change that impacts the public API qualifies as a breaking change. I don't care whether we reach version 500. As a library maintainer, you should never be scared to bump a MAJOR because it just involves a tiny modification or a type change because doing so will ruin the end user's experience.
## Why Doesn't Discordeno Use Classes or EventEmitter?
This is a design decision for the library itself. You can still use class on your bot if you want. In fact, I hope someone
makes a framework/templates for this lib one day using classes so that devs have a choice on which style they prefer.
Without trying to write an entire thesis statement on the reasons why I avoided Classes in this library, I will just link to
the best resources that I believe help explain it.
- [Really good article](https://dannyfritz.wordpress.com/2014/10/11/class-free-object-oriented-programming/)
- [Lecture by one of the developers who makes JavaScript](https://www.youtube.com/watch?v=PSGEjv3Tqo0)
In regards to EventEmitter, I believe a functional event API was a much better choice. EventEmitter at its core, is simply a set of functions that run when a certain event is emitted. In Discordeno, that function is executed instead of emitting some event to trigger it.
```typescript
// EventEmitter Example
EventEmitter.emit('guildCreate', guild)
// Discordeno Example
bot.events.guildCreate?.(bot, guild)
```
There isn't really any difference especially for users when they use it. One bad thing about EventEmitter is that if
misused it can easily cause memory leaks. It is very easy to open yourself up to these memory leak issues. It has
happened to me when I started coding as well. This is why I wanted Discordeno's implementation to help devs avoid the
issues I had. It prevents anyone from having this as a potential issue. Another issue with EventEmitter is trying to
update the code in those functions without having to deal with headaches left and right of removing and adding
listeners. You don't need to worry about binding or not binding events. They are just pure functions
In Discordeno, this is extremely simple; you just simply give it the new event handlers. For example:
```typescript
bot.events.guildCreate = newGuildCreateEventHandler
```
## Why Do You Have A Class for Collection If Classes Are Bad?
The Collection class is an exception in the library where a class was allowed. This is because Collection extends Map.
The Map class is provided by JavaScript itself and is extremely fast. You can perform millions of operations a second
with a Map. Maps are too useful to avoid and don't have downsides like EventEmitters do. The Collection class simply
adds on other functionality that Discordeno users felt they needed. Although I am against using classes whenever
possible, I am also a big supporter of providing the best developer experience.
## Why Are there no options in Discordeno?
Discordeno is not a library that handles code in the exact way every person wants it to. It is opinionated. Discordeno
defaults to the Discord recommended options or the best options for majority of developers needs. For example, there is
no option of fetching all members startup. This is a practice that Discord does not recommend or want users doing. By
default, we don't support stuff like this. In Discordeno, we follow Discords recommended solution and it just works
internally. The End! No fuss! No Muss! Just good stuff!
Now, I understand that there are times when it's necessary to be able to customize this and fetch them all. If you are
advanced enough to need these options, you should be able to simply do it yourself. For most users, this is just an
unnecessary option. The main module should remain minimalistic and easy to use for 99% of users.
## Why Do I See errors Like "MISSING_VIEW_CHANNEL" or "BOTS_HIGHEST_ROLE_TOO_LOW"?
Discordeno is the only library(that I have used), that has built in permission handling. A lot of bots get automatically
banned by Discord because they forget to handle permissions. When bots don't check permissions and continue to send
requests to the API, this leads to bots being banned. I have tried to request adding this feature into libraries but
they were reluctant to do so because it would require the devs to maintain the library whenever an update was made by
Discord.
Discordeno provides you specific keywords that you can use to send a clean response to the end user of your choosing. I
have even seen some bots have hundreds of thousands of Missing Permission or Missing Access errors because libraries
don't handle it. IMO, this is a crucial part of any good library as much as it is to handle rate limiting.
```typescript
import {
Bot,
Errors,
Message,
} from 'https://deno.land/x/discordeno@16.0.0/mod.ts'
export function handleCommandError(bot: Bot, message: Message, type: Errors) {
switch (type) {
case Errors.MISSING_MANAGE_NICKNAMES:
return bot.helpers.sendMessage(message.channelId, {
content:
"The bot does not have the necessary permission to manage/edit other user's nicknames. Grant the **MANAGE_NICKNAME** permission to the bot and try again.",
})
case Errors.MISSING_MANAGE_ROLES:
// Note: i18n is not part of the library. This is just an example of how you could use i18n for custom error responses.
return bot.helpers.sendMessage(message.channelId, {
content: i18n.translate(type),
})
}
}
```
@@ -0,0 +1,80 @@
---
sidebar_position: 3
---
# Getting Started
Discordeno aims for a simple, easy and stress-free interaction with the Discord API. Always supporting the latest
version to ensure stability, consistency and the best developer experience. This guide serves as the purpose for
introducing Discordeno to developers.
## Requirements
- **Deno 1.0** or higher
## Creating your First Discord Bot Application
Plenty of guides are available on how to create a Discord Bot Application.
1. [Creating an Application](https://discord.com/developers/applications) on the Developer Portal, name something cool
and pick a sweet icon!
2. After creating an application. Save the **Client ID.** Thats the unique identifier for a Discord Bot.
3. Now, go and create a bot by clicking the **Bot** tab. You will see a **Token** section and thats the Discord Bot's
token. **Make sure you don't share that token with anyone!!!**
4. Invite the bot to the server, you can use the
**[Discord Permissions Calculator](https://discordapi.com/permissions.html#0)** for creating the invite link with
custom permissions. By default, `0` means no permissions and `8` means Administrator.
Now you've created an Application but it will need some code in order for it to be online. Thats when Discordeno comes
in handy!
> Make sure you store your tokens in a file that is NOT deployed by adding it to the .gitignore file. **Don't share your
> bot token with anybody.**
## Installation
You can install Discordeno by importing:
```ts
import { startBot } from 'https://deno.land/x/discordeno@16.0.0/mod.ts'
```
## Example Usage
Starting with Discordeno is very simple, you can start from scratch without any templates/frameworks: Add this snippet
of code into a new TypeScript file:
```ts
import {
createBot,
Intents,
startBot,
} from 'https://deno.land/x/discordeno/mod.ts'
startBot(
createBot({
token: 'BOT TOKEN',
intents: Intents.Guilds | Intents.GuildMessages,
events: {
ready() {
console.log('Successfully connected to gateway')
},
messageCreate(bot, message) {
if (message.content === '!ping') {
bot.helpers.sendMessage(message.channelId, {
content: 'Pong using Discordeno!',
})
}
},
},
}),
)
```
## Tutorials
Below you will find youtube playlists that display channels using Discordeno for their tutorials.
- [Making a Discord bot with Deno and Discordeno](https://web-mystery.com/articles/making-discord-bot-deno-and-discordeno)
- [Running a Discord bot written using Deno in Docker](https://web-mystery.com/articles/running-discord-bot-written-deno-docker)
- [Discordeno Bot Tutorials (YouTube)](https://youtu.be/rIph9-BGsuQ)
+487
View File
@@ -0,0 +1,487 @@
---
sidebar_position: 4
---
# Migrating
## Migrating from Discord.js
This migration guide is not intended to discredit Discord.js authors/maintainers or Discord.js itself. In fact,
Discord.js is the most popular Node.js library, admired and praised by a lot of JavaScript developers.
## Finding an Open-Source Discord Bot
For the purposes of this guide, I wanted to find a moderation bot that is totally open source to show an example of how
to convert the bot to Discordeno. Trying to find one was not easy as most bot's were not using the latest Discord.JS
version 12. Trying to find one that was using TypeScript made it even more difficult. My next best solution was to find
a moderation bot that was recently updated(showing it is maintained or recently built). The best one I could find was
[Zodiac Bot](https://github.com/Nukestye/Zodiac).
For the purposes of this guide, I will be using the current
[latest commit](https://github.com/Nukestye/Zodiac/tree/213891a38af1b7ecbd068b661ef9062ab58cc818)
## Preparations
- First, create a Discordeno Bot using the [Generator Template](https://github.com/discordeno/template) I will name it
Zodiac.
- Then `git clone https://github.com/Skillz4Killz/Zodiac.git`
Now that I had the repository cloned, I could begin. Note that although the bot we are converting is built in
JavaScript, I converted all code to TypeScript in this Guide as Discordeno is designed to be the best lib for TypeScript
developers.
Time to get started!
## Converting main.js (index file)
The first thing is to convert the `main.js` file which would be the app.js or index.js file. This is the file that is
run to start your bot. In this case, the bot developer chose `main.js`. In Deno, the initial file is named `mod.ts` so
we can go ahead and opt for the Deno pattern. Note: there is already a `mod.ts` file created and prebuilt entirely using
the Generator.
Current Discord.JS Code:
```js
/* Keeping this to shoutout/credit the original author <3
* @author: nukestye
*/
const config = require('./config.json')
const fs = require('fs')
const log = console.log
// Setting up the way to get commands
const { CommandoClient } = require('discord.js-commando')
const path = require('path')
// reading events
fs.readdir('./src/events/', (err, files) => {
if (err) return console.error(err)
files.forEach(file => {
const eventFunction = require(`./src/events/${file}`)
if (eventFunction.disabled) return
const event = eventFunction.event || file.split('.')[0]
const emitter =
(typeof eventFunction.emitter === 'string'
? client[eventFunction.emitter]
: eventFunction.emitter) || client
const { once } = eventFunction
try {
emitter[once ? 'once' : 'on'](event, (...args) =>
eventFunction.run(...args),
)
} catch (error) {
console.error(error.stack)
}
})
})
const client = (global.client = new CommandoClient({
commandPrefix: `${config.prefix}`,
owner: `${config.owner}`,
invite: `${config.discord}`,
unknownCommandResponse: false,
}))
// Registing the commands
client.registry
.registerDefaultTypes()
// The different fields for cmds
.registerGroups([
['mod', 'Moderation Commands'],
['public', 'Public Commands'],
])
.registerDefaultGroups()
// Basic cmds can be disabled like {"cmd: false"}
.registerDefaultCommands()
// commands in "/src/commands" will be counted
.registerCommandsIn(path.join(__dirname, '/src/commands'))
// list of activities that the bot goes through
const activityArray = [`${config.prefix}help | `]
// Bot lanuch code
client.once('ready', () => {
log(`Logged in as ${client.user.tag} in ${client.guilds.size} guild(s)!`)
setInterval(() => {
const index = Math.floor(Math.random() * activityArray.length) // generates a random number between 1 and the length of the activities array list
client.user.setActivity(activityArray[index], {
type: 'PLAYING',
}) // sets bot"s activities to one of the phrases in the arraylist.
}, 5000) // updates every 10000ms = 10s
})
// If an error print it out
client.on('error', console.error)
// Login in using the token in config
client.login(config.env.TOKEN)
```
Discordeno Version:
```ts
import { botCache, Intents } from './deps.ts'
import { configs } from './configs.ts'
import { importDirectory } from './src/utils/helpers.ts'
import { loadLanguages } from './src/utils/i18next.ts'
console.info(
'Beginning Bot Startup Process. This can take a little bit depending on your system. Loading now...',
)
// Always require these files be processed before anything else
await Promise.all(
['./src/customizations/structures'].map(path =>
importDirectory(Deno.realPathSync(path)),
),
)
// Forces deno to read all the files which will fill the commands/inhibitors cache etc.
await Promise.all(
[
'./src/commands',
'./src/inhibitors',
'./src/events',
'./src/arguments',
'./src/monitors',
'./src/tasks',
'./src/permissionLevels',
'./src/events',
].map(path => importDirectory(Deno.realPathSync(path))),
)
// Loads languages
await loadLanguages()
await import('./src/database/database.ts')
startBot({
token: configs.token,
// Pick the intents you wish to have for your bot.
// For instance, to work with guild message reactions, you will have to pass the Intents.GUILD_MESSAGE_REACTIONS intent to the array.
intents: Intents.Guilds | Intents.GuildMessages,
// These are all your event handler functions. Imported from the events folder
events: botCache.events,
})
```
Something we haven't converted yet from the `main.js` files is the event listeners. To do that, we will open up the
events folder and find the corresponding event or create it if necessary. In this case, we have the `ready` event and
there is already a `ready.ts` file. We can just use that.
In our `ready.ts` file we can add the `ready` event listener.
```ts
import {
ActivityTypes,
botCache,
cache,
chooseRandom,
editBotStatus,
StatusTypes,
} from '../../deps.ts'
import { registerTasks } from './../utils/taskHelper.ts'
botCache.events.ready = function () {
editBotStatus(
StatusTypes.DoNotDisturb,
'Discordeno Best Lib',
ActivityTypes.Game,
)
console.log(`Loaded ${botCache.arguments.size} Argument(s)`)
console.log(`Loaded ${botCache.commands.size} Command(s)`)
console.log(`Loaded ${Object.keys(botCache.events).length} Event(s)`)
console.log(`Loaded ${botCache.inhibitors.size} Inhibitor(s)`)
console.log(`Loaded ${botCache.monitors.size} Monitor(s)`)
console.log(`Loaded ${botCache.tasks.size} Task(s)`)
registerTasks()
console.log(
`[READY] Bot is online and ready in ${cache.guilds.size} guild(s)!`,
)
// list of activities that the bot goes through
const activityArray = [`${configs.prefix}help | `]
setInterval(() => {
const randomActivity =
activityArray[Math.floor(Math.random() * activityArray.length)]
editBotStatus(botCache, {
activities: [
{
name: randomActivity,
type: ActivityTypes.Game,
createdAt: Date.now(),
},
],
status: 'online',
})
}, 5000)
}
```
To understand this code, we are setting a function to be run when the bot is `ready`. Then the bot will edit the bot's
status every 5 seconds. Notice that you also have beautiful enums provided that prevents you from making any
typos/mistakes.
We have now converted the entire `main.js` file, in a matter of seconds. The Discordeno official generator took care of
the majority of workload and we just modified the `ready.ts` file.
`Note:` I did remove some generally well known "bad practices" such as global vars and such. Overall, you will see the
functionality of the project will not change as we progress through this guide.
## Converting Commands
The first command in the commands folder is the `addRole` command.
This is the code from the bot:
```ts
// Getting the 'Command' features from Commando
const { Command } = require('discord.js-commando')
// Code for the command
module.exports = class addRoleCommand extends Command {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: 'addrole',
// other ways to call the command, must be in lowercase
aliases: ['role'],
// command group its part of
group: 'mod',
// name within the command group, must be in lowercase
memberName: 'addrole',
// Is the description used for 'help' command
description: 'Adds mentioned role to mentioned user.',
// Prevents it from being used in dms
guildOnly: true,
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
clientPermissions: ['ADMINISTRATOR', 'MANAGE_ROLES'],
userPermissions: ['MANAGE_ROLES'],
// Prevents anyone other than owner to use the command
ownerOnly: false,
})
}
// Run code goes here
run(message) {
const user = message.mentions.members.first()
const roleToAdd = message.mentions.roles.first()
// checking to see if the user has the role or not
if (!user.roles.find(r => r.name === roleToAdd.name)) {
user.addRole(roleToAdd)
message.channel
.send(`${user} has been given the role: ${roleToAdd.name}`)
.then(msg => {
msg.delete(5000)
})
} else {
message.channel.send(`${user} already has the role: ${roleToAdd.name}`)
}
// console.error(user, roleToAdd, message.member.roles.find(r => r.name === roleToAdd));
}
}
```
This is how to do it with Discordeno:
```ts
import { createCommand } from './../../utils/helpers.ts'
createCommand({
name: 'role',
// Oher ways to call the command
aliases: ['addrole'],
// Is the description used for 'help' command
description: 'Adds mentioned role to mentioned user.',
// Prevents it from being used in dms
guildOnly: true,
botServerPermissions: ['ADMINISTRATOR', 'MANAGE_ROLES'],
userServerPermissions: ['MANAGE_ROLES'],
arguments: [
{ name: 'member', type: 'member' },
{ name: 'role', type: 'role' },
],
execute: (bot, message, args) => {
// checking to see if the user has the role or not
if (!args.member.roles.includes(args.role.id)) {
bot.helpers.addRole(message.guildId, args.member.id, args.role.id)
bot.helpers.sendMessage(message.channelId, {
content: `${args.member.mention} has been given the role: ${args.role.name}`,
})
} else {
bot.helpers.sendMessage(message.channelId, {
content: `${args.member.mention} already has the role: ${args.role.name}`,
})
}
},
})
```
Awesome, that is a full command converted from Discord.JS to Discordeno. See how easy it is! Let's convert one more
command to see how to really take full advantage of Discordeno template and have something amazing.
Discord.JS Kick Command Version
```js
// Getting the 'Command' features from Commando
const { Command } = require('discord.js-commando')
const { RichEmbed } = require('discord.js')
const chalk = require('chalk')
const log = console.log
// Code for the command
module.exports = class kickCommand extends Command {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: 'kick',
// other ways to call the command, must be in lowercase
aliases: ['boot', 'tempban'],
// command group its part of
group: 'mod',
// name within the command group, must be in lowercase
memberName: 'kick',
// Is the description used for 'help' command
description: 'Kick command.',
// adds cooldowns to the command
throttling: {
// usages in certain time x
usages: 1,
// the cooldown
duration: 10,
},
// Prevents it from being used in dms
guildOnly: true,
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
clientPermissions: ['ADMINISTRATOR'],
userPermissions: ['KICK_MEMBERS'],
// Prevents anyone other than owner to use the command
ownerOnly: false,
})
}
// Run code goes here
run(message) {
const messageArry = message.content.split(' ')
const args = messageArry.slice(1)
const kUser = message.guild.member(
message.mentions.users.first() || message.guild.get(args[0]),
)
if (!kUser) return message.channel.send('User cannot be found!')
const kreason = args.join(' ').slice(22)
// setting up the embed for report/log
const kickEmbed = new RichEmbed()
.setDescription(`Report: ${kUser} Kick`)
.addField('Reason >', `${kreason}`)
.addField('Time', message.createdAt)
const reportchannel = message.guild.channels.find('name', 'report')
if (!reportchannel) {
return message.channel.send('*`Report channel cannot be found!`*')
}
// Delete the message command
// eslint-disable-next-line camelcase
message.delete().catch(O_o => {})
// Kick the user with reason
message.guild.member(kUser).kick(kreason)
// sends the kick report into log/report
reportchannel.send(kickEmbed)
// Logs the kick into the terminal
log(chalk.red('KICK', chalk.underline.bgBlue(kUser) + '!'))
}
}
```
Discordeno Version
```ts
import { createCommand } from './../../utils/helpers.ts'
createCommand({
name: `kick`,
aliases: ['boot', 'tempban'],
description: 'Kick command.',
// adds cooldowns to the command
cooldown: {
// usages in certain duration of seconds below
allowedUses: 1,
// the cooldown
seconds: 10,
},
// Prevents it from being used in dms
guildOnly: true,
botServerPermissions: ['ADMINISTRATOR'],
userServerPermissions: ['KICK_MEMBERS'],
arguments: [
{
name: 'member',
type: 'member',
missing: function (message) {
message.reply(`User cannot be found.`)
},
// By default this is true but for the purpose of the guide so you can see this exists.
required: true,
},
{
name: 'reason',
// The leftover string provided by the user that was not used by previous args.
type: '...string',
defaultValue: 'No reason provided.',
// It is silly to lowercase this but for the purpose of the guide you can see that this is also available to you.
lowercase: true,
},
],
execute: function (bot, message, args: KickArgs) {
// setting up the embed for report/log
const embed = new Embed()
.setDescription(`Report: ${args.member.mention} Kick`)
.addField('Reason >', args.reason)
.addField('Time', message.timestamp.toString())
const reportchannel = message.guild?.channels.find(
channel => channel.name === 'report',
)
if (!reportchannel) {
return bot.helpers.sendMessage(message.channelId, {
content: '*`Report channel cannot be found!`*',
})
}
// Delete the message command
bot.helpers.deleteMessage(message.channelId, {
content: 'Remove kick command trigger.',
})
// Kick the user with reason
bot.helpers.kickMember(message.guildId, args.member.id, args.reason)
// sends the kick report into log/report
bot.helpers.sendMessage(message.channelId, { embeds: [embed] })
},
})
interface KickArgs {
member: Member
reason: string
}
```
Let's take a minute and explain the differences here. The first thing you will probably notice is different is the
`arguments` property. Discordeno provides the `arguments` property because it provides argument
handling/parsing/validating internally. You don't need to be splitting the message content or going through and
validating it yourself. All you do is tell Discordeno that you want a member and a reason. It will do the magic and hard
work to get you that data before you even run the command. You just do `args.member` and you have access to the full
member object. There are a lot more powerful aspects to Discordeno like arguments. Keep diving in and you will find all
the wonderful tools available to give you the best developer experience possible.
### Need More Examples/Help
If you still need more help converting other aspects of your bot please contact me at
[Discord](https://discord.com/invite/5vBgXk3UcZ). I will continue adding more examples to this guide as more people
request them.
+80
View File
@@ -0,0 +1,80 @@
---
sidebar_position: 3
---
# Getting Started
Discordeno aims for a simple, easy and stress-free interaction with the Discord API. Always supporting the latest
version to ensure stability, consistency and the best developer experience. This guide serves as the purpose for
introducing Discordeno to developers.
## Requirements
- **Deno 1.0** or higher
## Creating your First Discord Bot Application
Plenty of guides are available on how to create a Discord Bot Application.
1. [Creating an Application](https://discord.com/developers/applications) on the Developer Portal, name something cool
and pick a sweet icon!
2. After creating an application. Save the **Client ID.** Thats the unique identifier for a Discord Bot.
3. Now, go and create a bot by clicking the **Bot** tab. You will see a **Token** section and thats the Discord Bot's
token. **Make sure you don't share that token with anyone!!!**
4. Invite the bot to the server, you can use the
**[Discord Permissions Calculator](https://discordapi.com/permissions.html#0)** for creating the invite link with
custom permissions. By default, `0` means no permissions and `8` means Administrator.
Now you've created an Application but it will need some code in order for it to be online. Thats when Discordeno comes
in handy!
> Make sure you store your tokens in a file that is NOT deployed by adding it to the .gitignore file. **Don't share your
> bot token with anybody.**
## Installation
You can install Discordeno by importing:
```ts
import { startBot } from 'https://deno.land/x/discordeno@16.0.0/mod.ts'
```
## Example Usage
Starting with Discordeno is very simple, you can start from scratch without any templates/frameworks: Add this snippet
of code into a new TypeScript file:
```ts
import {
createBot,
Intents,
startBot,
} from 'https://deno.land/x/discordeno/mod.ts'
startBot(
createBot({
token: 'BOT TOKEN',
intents: Intents.Guilds | Intents.GuildMessages,
events: {
ready() {
console.log('Successfully connected to gateway')
},
messageCreate(bot, message) {
if (message.content === '!ping') {
bot.helpers.sendMessage(message.channelId, {
content: 'Pong using Discordeno!',
})
}
},
},
}),
)
```
## Tutorials
Below you will find youtube playlists that display channels using Discordeno for their tutorials.
- [Making a Discord bot with Deno and Discordeno](https://web-mystery.com/articles/making-discord-bot-deno-and-discordeno)
- [Running a Discord bot written using Deno in Docker](https://web-mystery.com/articles/running-discord-bot-written-deno-docker)
- [Discordeno Bot Tutorials (YouTube)](https://youtu.be/rIph9-BGsuQ)
+23
View File
@@ -0,0 +1,23 @@
---
sidebar_position: 1
---
# Discordeno
> Discord API library for [Deno](https://deno.land)
- [Documentation](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
- [Discord](https://discord.gg/ddeno)
## Features
- **Secure & stable**: Discordeno is secure and stable. One of the greatest issues with almost every library is
stability; types are outdated, less (or minimal) parity with the API, core maintainers have quit or no longer actively
maintain the library, and whatnot. Discordeno, on the other hand, is actively maintained to ensure great performance
and convenience. Moreover, it internally checks all missing permissions before forwarding a request to the Discord API
so that the client does not get globally-banned by Discord.
- **Simple, Efficient, & Lightweight**: Discordeno is simplistic, easy-to-use, versatile while being efficient and
lightweight.
- [**Functional API**](https://en.wikipedia.org/wiki/Functional_programming): Functional API ensures an overall concise
yet performant code while removing the difficulties of extending built-in classes and inheritance.
[Learn more about class-free JavaScript](https://dannyfritz.wordpress.com/2014/10/11/class-free-object-oriented-programming/)
+487
View File
@@ -0,0 +1,487 @@
---
sidebar_position: 4
---
# Migrating
## Migrating from Discord.js
This migration guide is not intended to discredit Discord.js authors/maintainers or Discord.js itself. In fact,
Discord.js is the most popular Node.js library, admired and praised by a lot of JavaScript developers.
## Finding an Open-Source Discord Bot
For the purposes of this guide, I wanted to find a moderation bot that is totally open source to show an example of how
to convert the bot to Discordeno. Trying to find one was not easy as most bot's were not using the latest Discord.JS
version 12. Trying to find one that was using TypeScript made it even more difficult. My next best solution was to find
a moderation bot that was recently updated(showing it is maintained or recently built). The best one I could find was
[Zodiac Bot](https://github.com/Nukestye/Zodiac).
For the purposes of this guide, I will be using the current
[latest commit](https://github.com/Nukestye/Zodiac/tree/213891a38af1b7ecbd068b661ef9062ab58cc818)
## Preparations
- First, create a Discordeno Bot using the [Generator Template](https://github.com/discordeno/template) I will name it
Zodiac.
- Then `git clone https://github.com/Skillz4Killz/Zodiac.git`
Now that I had the repository cloned, I could begin. Note that although the bot we are converting is built in
JavaScript, I converted all code to TypeScript in this Guide as Discordeno is designed to be the best lib for TypeScript
developers.
Time to get started!
## Converting main.js (index file)
The first thing is to convert the `main.js` file which would be the app.js or index.js file. This is the file that is
run to start your bot. In this case, the bot developer chose `main.js`. In Deno, the initial file is named `mod.ts` so
we can go ahead and opt for the Deno pattern. Note: there is already a `mod.ts` file created and prebuilt entirely using
the Generator.
Current Discord.JS Code:
```js
/* Keeping this to shoutout/credit the original author <3
* @author: nukestye
*/
const config = require('./config.json')
const fs = require('fs')
const log = console.log
// Setting up the way to get commands
const { CommandoClient } = require('discord.js-commando')
const path = require('path')
// reading events
fs.readdir('./src/events/', (err, files) => {
if (err) return console.error(err)
files.forEach(file => {
const eventFunction = require(`./src/events/${file}`)
if (eventFunction.disabled) return
const event = eventFunction.event || file.split('.')[0]
const emitter =
(typeof eventFunction.emitter === 'string'
? client[eventFunction.emitter]
: eventFunction.emitter) || client
const { once } = eventFunction
try {
emitter[once ? 'once' : 'on'](event, (...args) =>
eventFunction.run(...args),
)
} catch (error) {
console.error(error.stack)
}
})
})
const client = (global.client = new CommandoClient({
commandPrefix: `${config.prefix}`,
owner: `${config.owner}`,
invite: `${config.discord}`,
unknownCommandResponse: false,
}))
// Registing the commands
client.registry
.registerDefaultTypes()
// The different fields for cmds
.registerGroups([
['mod', 'Moderation Commands'],
['public', 'Public Commands'],
])
.registerDefaultGroups()
// Basic cmds can be disabled like {"cmd: false"}
.registerDefaultCommands()
// commands in "/src/commands" will be counted
.registerCommandsIn(path.join(__dirname, '/src/commands'))
// list of activities that the bot goes through
const activityArray = [`${config.prefix}help | `]
// Bot lanuch code
client.once('ready', () => {
log(`Logged in as ${client.user.tag} in ${client.guilds.size} guild(s)!`)
setInterval(() => {
const index = Math.floor(Math.random() * activityArray.length) // generates a random number between 1 and the length of the activities array list
client.user.setActivity(activityArray[index], {
type: 'PLAYING',
}) // sets bot"s activities to one of the phrases in the arraylist.
}, 5000) // updates every 10000ms = 10s
})
// If an error print it out
client.on('error', console.error)
// Login in using the token in config
client.login(config.env.TOKEN)
```
Discordeno Version:
```ts
import { botCache, Intents } from './deps.ts'
import { configs } from './configs.ts'
import { importDirectory } from './src/utils/helpers.ts'
import { loadLanguages } from './src/utils/i18next.ts'
console.info(
'Beginning Bot Startup Process. This can take a little bit depending on your system. Loading now...',
)
// Always require these files be processed before anything else
await Promise.all(
['./src/customizations/structures'].map(path =>
importDirectory(Deno.realPathSync(path)),
),
)
// Forces deno to read all the files which will fill the commands/inhibitors cache etc.
await Promise.all(
[
'./src/commands',
'./src/inhibitors',
'./src/events',
'./src/arguments',
'./src/monitors',
'./src/tasks',
'./src/permissionLevels',
'./src/events',
].map(path => importDirectory(Deno.realPathSync(path))),
)
// Loads languages
await loadLanguages()
await import('./src/database/database.ts')
startBot({
token: configs.token,
// Pick the intents you wish to have for your bot.
// For instance, to work with guild message reactions, you will have to pass the Intents.GUILD_MESSAGE_REACTIONS intent to the array.
intents: Intents.Guilds | Intents.GuildMessages,
// These are all your event handler functions. Imported from the events folder
events: botCache.events,
})
```
Something we haven't converted yet from the `main.js` files is the event listeners. To do that, we will open up the
events folder and find the corresponding event or create it if necessary. In this case, we have the `ready` event and
there is already a `ready.ts` file. We can just use that.
In our `ready.ts` file we can add the `ready` event listener.
```ts
import {
ActivityTypes,
botCache,
cache,
chooseRandom,
editBotStatus,
StatusTypes,
} from '../../deps.ts'
import { registerTasks } from './../utils/taskHelper.ts'
botCache.events.ready = function () {
editBotStatus(
StatusTypes.DoNotDisturb,
'Discordeno Best Lib',
ActivityTypes.Game,
)
console.log(`Loaded ${botCache.arguments.size} Argument(s)`)
console.log(`Loaded ${botCache.commands.size} Command(s)`)
console.log(`Loaded ${Object.keys(botCache.events).length} Event(s)`)
console.log(`Loaded ${botCache.inhibitors.size} Inhibitor(s)`)
console.log(`Loaded ${botCache.monitors.size} Monitor(s)`)
console.log(`Loaded ${botCache.tasks.size} Task(s)`)
registerTasks()
console.log(
`[READY] Bot is online and ready in ${cache.guilds.size} guild(s)!`,
)
// list of activities that the bot goes through
const activityArray = [`${configs.prefix}help | `]
setInterval(() => {
const randomActivity =
activityArray[Math.floor(Math.random() * activityArray.length)]
editBotStatus(botCache, {
activities: [
{
name: randomActivity,
type: ActivityTypes.Game,
createdAt: Date.now(),
},
],
status: 'online',
})
}, 5000)
}
```
To understand this code, we are setting a function to be run when the bot is `ready`. Then the bot will edit the bot's
status every 5 seconds. Notice that you also have beautiful enums provided that prevents you from making any
typos/mistakes.
We have now converted the entire `main.js` file, in a matter of seconds. The Discordeno official generator took care of
the majority of workload and we just modified the `ready.ts` file.
`Note:` I did remove some generally well known "bad practices" such as global vars and such. Overall, you will see the
functionality of the project will not change as we progress through this guide.
## Converting Commands
The first command in the commands folder is the `addRole` command.
This is the code from the bot:
```ts
// Getting the 'Command' features from Commando
const { Command } = require('discord.js-commando')
// Code for the command
module.exports = class addRoleCommand extends Command {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: 'addrole',
// other ways to call the command, must be in lowercase
aliases: ['role'],
// command group its part of
group: 'mod',
// name within the command group, must be in lowercase
memberName: 'addrole',
// Is the description used for 'help' command
description: 'Adds mentioned role to mentioned user.',
// Prevents it from being used in dms
guildOnly: true,
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
clientPermissions: ['ADMINISTRATOR', 'MANAGE_ROLES'],
userPermissions: ['MANAGE_ROLES'],
// Prevents anyone other than owner to use the command
ownerOnly: false,
})
}
// Run code goes here
run(message) {
const user = message.mentions.members.first()
const roleToAdd = message.mentions.roles.first()
// checking to see if the user has the role or not
if (!user.roles.find(r => r.name === roleToAdd.name)) {
user.addRole(roleToAdd)
message.channel
.send(`${user} has been given the role: ${roleToAdd.name}`)
.then(msg => {
msg.delete(5000)
})
} else {
message.channel.send(`${user} already has the role: ${roleToAdd.name}`)
}
// console.error(user, roleToAdd, message.member.roles.find(r => r.name === roleToAdd));
}
}
```
This is how to do it with Discordeno:
```ts
import { createCommand } from './../../utils/helpers.ts'
createCommand({
name: 'role',
// Oher ways to call the command
aliases: ['addrole'],
// Is the description used for 'help' command
description: 'Adds mentioned role to mentioned user.',
// Prevents it from being used in dms
guildOnly: true,
botServerPermissions: ['ADMINISTRATOR', 'MANAGE_ROLES'],
userServerPermissions: ['MANAGE_ROLES'],
arguments: [
{ name: 'member', type: 'member' },
{ name: 'role', type: 'role' },
],
execute: (bot, message, args) => {
// checking to see if the user has the role or not
if (!args.member.roles.includes(args.role.id)) {
bot.helpers.addRole(message.guildId, args.member.id, args.role.id)
bot.helpers.sendMessage(message.channelId, {
content: `${args.member.mention} has been given the role: ${args.role.name}`,
})
} else {
bot.helpers.sendMessage(message.channelId, {
content: `${args.member.mention} already has the role: ${args.role.name}`,
})
}
},
})
```
Awesome, that is a full command converted from Discord.JS to Discordeno. See how easy it is! Let's convert one more
command to see how to really take full advantage of Discordeno template and have something amazing.
Discord.JS Kick Command Version
```js
// Getting the 'Command' features from Commando
const { Command } = require('discord.js-commando')
const { RichEmbed } = require('discord.js')
const chalk = require('chalk')
const log = console.log
// Code for the command
module.exports = class kickCommand extends Command {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: 'kick',
// other ways to call the command, must be in lowercase
aliases: ['boot', 'tempban'],
// command group its part of
group: 'mod',
// name within the command group, must be in lowercase
memberName: 'kick',
// Is the description used for 'help' command
description: 'Kick command.',
// adds cooldowns to the command
throttling: {
// usages in certain time x
usages: 1,
// the cooldown
duration: 10,
},
// Prevents it from being used in dms
guildOnly: true,
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
clientPermissions: ['ADMINISTRATOR'],
userPermissions: ['KICK_MEMBERS'],
// Prevents anyone other than owner to use the command
ownerOnly: false,
})
}
// Run code goes here
run(message) {
const messageArry = message.content.split(' ')
const args = messageArry.slice(1)
const kUser = message.guild.member(
message.mentions.users.first() || message.guild.get(args[0]),
)
if (!kUser) return message.channel.send('User cannot be found!')
const kreason = args.join(' ').slice(22)
// setting up the embed for report/log
const kickEmbed = new RichEmbed()
.setDescription(`Report: ${kUser} Kick`)
.addField('Reason >', `${kreason}`)
.addField('Time', message.createdAt)
const reportchannel = message.guild.channels.find('name', 'report')
if (!reportchannel) {
return message.channel.send('*`Report channel cannot be found!`*')
}
// Delete the message command
// eslint-disable-next-line camelcase
message.delete().catch(O_o => {})
// Kick the user with reason
message.guild.member(kUser).kick(kreason)
// sends the kick report into log/report
reportchannel.send(kickEmbed)
// Logs the kick into the terminal
log(chalk.red('KICK', chalk.underline.bgBlue(kUser) + '!'))
}
}
```
Discordeno Version
```ts
import { createCommand } from './../../utils/helpers.ts'
createCommand({
name: `kick`,
aliases: ['boot', 'tempban'],
description: 'Kick command.',
// adds cooldowns to the command
cooldown: {
// usages in certain duration of seconds below
allowedUses: 1,
// the cooldown
seconds: 10,
},
// Prevents it from being used in dms
guildOnly: true,
botServerPermissions: ['ADMINISTRATOR'],
userServerPermissions: ['KICK_MEMBERS'],
arguments: [
{
name: 'member',
type: 'member',
missing: function (message) {
message.reply(`User cannot be found.`)
},
// By default this is true but for the purpose of the guide so you can see this exists.
required: true,
},
{
name: 'reason',
// The leftover string provided by the user that was not used by previous args.
type: '...string',
defaultValue: 'No reason provided.',
// It is silly to lowercase this but for the purpose of the guide you can see that this is also available to you.
lowercase: true,
},
],
execute: function (bot, message, args: KickArgs) {
// setting up the embed for report/log
const embed = new Embed()
.setDescription(`Report: ${args.member.mention} Kick`)
.addField('Reason >', args.reason)
.addField('Time', message.timestamp.toString())
const reportchannel = message.guild?.channels.find(
channel => channel.name === 'report',
)
if (!reportchannel) {
return bot.helpers.sendMessage(message.channelId, {
content: '*`Report channel cannot be found!`*',
})
}
// Delete the message command
bot.helpers.deleteMessage(message.channelId, {
content: 'Remove kick command trigger.',
})
// Kick the user with reason
bot.helpers.kickMember(message.guildId, args.member.id, args.reason)
// sends the kick report into log/report
bot.helpers.sendMessage(message.channelId, { embeds: [embed] })
},
})
interface KickArgs {
member: Member
reason: string
}
```
Let's take a minute and explain the differences here. The first thing you will probably notice is different is the
`arguments` property. Discordeno provides the `arguments` property because it provides argument
handling/parsing/validating internally. You don't need to be splitting the message content or going through and
validating it yourself. All you do is tell Discordeno that you want a member and a reason. It will do the magic and hard
work to get you that data before you even run the command. You just do `args.member` and you have access to the full
member object. There are a lot more powerful aspects to Discordeno like arguments. Keep diving in and you will find all
the wonderful tools available to give you the best developer experience possible.
### Need More Examples/Help
If you still need more help converting other aspects of your bot please contact me at
[Discord](https://discord.com/invite/5vBgXk3UcZ). I will continue adding more examples to this guide as more people
request them.
@@ -0,0 +1,4 @@
{
"label": "Command Handler",
"position": 9
}
@@ -0,0 +1,92 @@
---
sidebar_position: 2
---
# Command Manager
Currently, you probably have something like this in your code:
```js
const Discord = require('discordeno.js')
// Ideally you should move to an `.env` file
const config = require('./config.json')
const bot = Discord.createBot({
events: {
messageCreate(client, message) {
if (message.content === '!ping') {
client.helpers.sendMessage(message.channelId, { content: 'pong' })
}
},
},
intents: Discord.Intents.Guilds | Discord.Intents.GuildMessages,
token: config.token,
})
const client = Discord.enableCachePlugin(bot, {})
Discord.startBot(client)
```
Of course, if you add more and more commands and as your code base grows, you can lose track very quickly.
To avoid this, it is recommended to store the commands in separate folders divided into different categories.
[Previously, we introduced you to our plugin structure, which has a lot of advantages.](../design.md)
```root
├Plugins/
├── General/
│ ├── commands/
│ │ ├── ping.js
│ │ └── ...
├── Developer/
│ ├── commands/
│ │ ├── eval.js
│ │ └── ...
└── ...
```
**Get [this file](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/CommandManager.js) from
the [nodejs template](https://github.com/discordeno/discordeno/tree/main/template)**
```js
const CommandManager = require('./template/Managers/CommandManager.js')
const manager = new CommandManager({})
manager.load({ plugin: true }) // Load the commands
client.commands = manager
client.commands.cache.get('ping') // Get the `ping` command
```
The Manager will automatically iterate through all files in the folder and then load them into the cache property, which
is mapped on the command name.
**Take a look at [Create Command](./create-command.md) to learn how to create a command.**
## Handle Command
The manager also contains a handler for executing the command when a message is received.
:::important
Currently checks for permissions, cooldowns, and rate limits are not covered, but these will be added soon.
:::
### Message Create Event:
```js
module.exports = async (client, message) => {
client.commands.isCommand(message)
}
```
### Interaction Create Event:
```js
module.exports = async (client, interaction) => {
client.commands.isInteraction(interaction)
}
```
You can also customize the `isCommand` function to your use case.
@@ -0,0 +1,61 @@
---
sidebar_position: 3
---
# Create Command
One of the most important features we wanted in our template, was that you can use the same code for handling
`slash commands` and `message based commands`.
This can be done by saving the static class in the command cache, creating a constructor and passing the desired data.
Moreover the `BaseCommand` is extended with the `Response Command` class, so you can take advantage of functions such as
`.reply()`
**Copy the [`BaseCommand`](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Structures/BaseCommand.js)
&
[`CommandResponses`](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Structures/CommandResponses.js)
code from the template**
### Creating a Ping Command:
```js
const BaseCommand = require("../../../Structures/BaseCommand.js");
const Embed = require("../../../Structures/Embed.js");
class pingCommand extends BaseCommand {
static name = "ping";
static description = "See if the bot latency is okay";
static usage = "";
static category = "General";
static slash = { name: "ping", category: "info" };
constructor(data) {
super(data);
}
async execute() {
const msg = await this.reply({content: `Pinging...`});
// Assign properties to the response
const ping = msg.timestamp - this.message.timestamp;
const embed = new Embed()
.setTitle(`The Bots ping is ${ping} ms`)
.toJSON();
// Edit Message with the Embed
return await msg.edit({embeds: [embed] });
});
}
}
module.exports = pingCommand;
```
- The `BaseCommand` is extended with the `CommandResponses` class.
- The ping command class is extended with the `BaseCommand` class.
- Some static properties are assigned to the ping command class, in order to access it in the cache, such as `name`,
`description`, `usage`, `category` and `slash`...
- The `execute()` function will be called, when the command has been run by the user.
- The constructor allows to access data, such as `this.message`, `this.args`, `this.client`...
You can customize the `CommandManager` file, in order to pass arguments in the `execute()` function.
@@ -0,0 +1,22 @@
---
sidebar_position: 1
---
# Getting Started with the Command Manager
One of the most important characteristics of bots is that they have commands that can be used to interact with the bot.
Hard coding your commands in an event function is not the best code practice and should be strictly prevented.
In the following we will show you how to create a command manager, which is compatible with Discordeno's Client.
- Load Commands
- Handle Commands
- Reload Commands
:::info template
You can also copy the
[`CommandManager` from the template repo](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/CommandManager.js).
:::
@@ -0,0 +1,4 @@
{
"label": "Event Handler",
"position": 8
}
@@ -0,0 +1,123 @@
---
sidebar_position: 2
---
# Create Event Manager
In order to process certain events, you must provide the Discordeno client with functions for these events.
```js
const Discord = require('discordeno')
const config = require('./config.json')
const client = Discord.createBot({
events: {
ready(client, payload) {
console.log(
`Successfully connected Shard ${payload.shardId} to the gateway`,
)
},
async messageCreate(client, message) {
if (message.content === '!ping') {
await client.helpers.sendMessage(message.channelId, { content: 'pong' })
}
console.log(`Received message: ${message.content || message.embeds}`)
},
},
intents: ['Guilds', 'GuildMessages'],
token: config.token,
})
Discord.startBot(client)
```
As you listen to more and more events, the functions code grows along with them, so you can quickly lose track.
To avoid this, we recommend storing the event functions divided into files in a separate folder.
## Create Event Folder
Create a folder called `events` in your project folder.
:::info note
The event files have to be named using camelCase so that they can be understood by the client. e.g `message` ->
`messageCreate.js`. You can check the typings see how the events are called.
:::
Ready Event:
```js
module.exports = (client, payload) => {
if (payload.shardId + 1 === client.gateway.maxShards) {
// All Shards are ready
console.log(
`Successfully connected to the gateway as ${payload.user.username}#${payload.user.discriminator}`,
)
}
}
```
## Load your Events
```js
const fs = require('fs')
const path = require('path')
const resolveFolder = folderName => path.resolve(__dirname, '.', folderName)
class EventManager {
constructor(client) {
this.cache = new Map()
this._events = {}
}
load(options = {}) {
const eventsFolder = resolveFolder('../events')
fs.readdirSync(eventsFolder).map(async file => {
if (!file.endsWith('.js')) return
const fileName = path.join(eventsFolder, file)
const event = require(fileName)
const eventName = file.split('.')[0]
this._events[`${eventName}`] = event
})
return this._events
}
}
module.exports = EventManager
```
The code above, which can also be found in the
[template repo](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/EventManager.js) will loop
through all the files in the `events` folder and load the functions into the `_events` object.
In order to let the client know which events should be processed, you need to pass the functions in the
`createBot<options>.events` object.
```js
const Discord = require('discordeno')
const config = require('./config.json')
const EventManager = require('./Managers/EventManager.js')
const events = new EventManager({})
const client = Discord.createBot({
events: events.load({}),
intents: ['Guilds', 'GuildMessages'],
token: config.token,
})
Discord.startBot(client)
```
Moreover, you can customize the `EventManager` and add more functionality to it and make it exactly fit your needs or
even emit events, by extending it.
Of course you wonder what you can do with all of this now. We will explain this further on the next page.
@@ -0,0 +1,29 @@
---
sidebar_position: 1
---
# Getting Started with the Event Handler
An event handler is essential to process the data, which Discord sends to you.
With a good implementation, you will have a nice code structure and thus have a good overview in long term.
Since the `EventEmitter` class is commonly used you probably already know it from other libraries.
Discordeno decided against it as it comes with several downsides which are mentioned below.
- It's easy to create memory leaks, when you add too many listeners or go carelessly with it.
- Many fragmented parts of event code complicate maintenance.
- ErrorHandling is difficult and debugging is harder when many listeners are open for the same events.
Performance plays a more important role than handling, however this event management system can be easily implemented
since it only needs a few changes in your code.
In the following we will show you, how to create an event manager, which is compatible with Discordeno's Client.
:::info template
You can also copy the
[`EventManager` from the template repo](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/EventManager.js).
:::
@@ -0,0 +1,76 @@
---
sidebar_position: 3
---
# Handle Events
When an event is fired, Discordeno sends two important things: the `client` instance and the `payload`.
As mentioned in the `Structure` section, the `payload` object does not contain any functions as it's a plain json
object.
In order to take use of our nice built structures, we need to transform the payload into a structure.
:::info
The Structures can be found [here](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Structures)
:::
Sometimes it's important to listen to events, in order to get informed of changes and updating the cache based on it.
### Message Event
This file should be called `messageCreate.js`.
```js
const Message = require('./structures/Message')
module.exports = async (client, payload) => {
const message = client.messages.forge(payload)
if (message.author.bot) return
if (message.content === '!ping') return await message.reply('pong')
}
```
### Interaction Event
This file should be called `interactionCreate.js`.
```js
const Interaction = require('./structures/Interaction')
module.exports = async (client, payload) => {
const interaction = client.interactions.forge(payload)
if (interaction.data.name === 'ping')
return await interaction.reply({ content: 'pong' })
}
```
### Ready Event
This file should be called `ready.js`.
:::tip
There is a small difference with the `ready` Event. The Event is fired `shard` wise, in other words it fires every time
a `shard` becomes ready.
:::
In order to fire the "real event" a small code snippet has to be added to the `ready` Event.
```js
const User = require('../Structures/User')
module.exports = async (client, payload) => {
client.user = client.users.forge(payload.user)
if (payload.shardId + 1 === client.gateway.maxShards) {
// All Shards are ready
console.log(`Successfully connected to the gateway as ${client.user.tag}`)
}
}
```
@@ -0,0 +1,4 @@
{
"label": "Structures",
"position": 7
}
@@ -0,0 +1,56 @@
---
sidebar_position: 5
---
# Create Collectors
Some of your commands or features are sometimes based on user interactions. E.g. if a user presses a button and you want
to know whether it was pressed. This is actually done by listening to the `interactionCreate` event.
But sometimes you need to access locale variables or don't want to "hardcode" the part.
That's why it's sometimes recommended to create collectors.
Collectors are listeners that listen to a specific event. In addition, you can provide a filter, so you only receive
certain interactions.
## Use a Collector
:::note Template The template code is used below. You must have the EventManager part to use the collector feature. :::
We have a pre-made class for collectors which you can find
[here](https://github.com/meister03/discordeno.js/blob/master/Util/Collectors.js).
```js
const Discord = require('discordeno.js')
const filter = m =>
m.data?.customId === 'warn_modal' && m.user.id === interaction.user.id
const listener = client.eventListener // When the eventListener property is named different
const collector = new Discord.Collector('interactionCreate', {
client: client,
timeout: 60000,
filter,
max: 20,
listener,
})
collector.on('collect', m => {
const interaction = client.interactions.forge(m)
// Stop Collector
// collector.stop();
})
// Fires on a timeout, when the collector has reached the max amount of interactions or when it has been closed
collector.on('end', collected => {
// Map of Collected Interactions
console.log(collected)
})
```
As you can see, this opens up many possibilities. You can listen to any event and get the interaction you need.
### Collector Options
`filter`: Function, just fire the event if the filter returns true. `timeout`: Number, the time in milliseconds until
the collector times out. `max`: Number, the max amount of interactions the collector can collect. `listener`: Function,
the listener that will be fired when the collector collects an interaction. Just required when client property is named
differently.
@@ -0,0 +1,229 @@
---
sidebar_position: 4
---
# Create Components
Since Discord has decided to make message content accessible only to privileged bots, components will play an
increasingly important role in the future. Discord has released some components already and many more will follow. Of
course, this opens up completely new possibilities. On the one hand, it improves the user experience and on the other
hand, the interactions can be easily handled by the developer.
To take advantage of this, we'll go into more detail on how to use them.
:::note Runtime Overhead
Constructor classes are nice to use and make your code look better, but they incur a slight runtime overhead compared to
just using raw data because they still execute methods, which takes more time to process.
:::
We already have a Template for `Components`, which can be found
[here](https://github.com/meister03/discordeno.js/tree/master/Structures/Component.js).
## Different Components:
There are many different components, which you can quickly read about here:
### Action Row (`type: 1`):
This is a top level component, which contains a limited amount of other components. It can be described as container.
An Action Row ...
- can not include an action row
- can maximal have 5 Buttons
- can have 1 SelectMenu
- can have 1 Text Input (only available in modal responses)
### Button (`type: 2`):
Buttons are interactive components, are bound to a message and they sent an interaction payload, when a user clicks on
it.
![Different Button Styles](https://i.imgur.com/jUE2Kp0.png)
- Needs a customId, except the Link Button
- An Action Row can have maximal 5 Buttons
There are different styles of buttons, which can be used:
- `1` - PRIMARY - blurple - customId required
- `2` - DEFAULT - grey - customId required
- `3` - SUCCESS - green - customId required
- `4` - DANGER - red - customId required
- `5` - LINK - grey - url required
### Select Menu (`type: 3`):
Select Menus are a simple drop-down with selectable options. They accept a set of allowed selects, which sends an
interaction payload, when a user selects sth. from the menu.
![Select Menu](https://i.imgur.com/42Hwiuw.png)
- You can specify a range of allowed selects (`minValue` and `maxValue`)
- Every Select Item can have an `emoji` and has a `value`, in order to identify the selected item
- A default Select Item can be set
- An Action Row can have maximal 1 Select Menu
### Text Input (`type: 4`):
Text Inputs are interactive components, which can just be sent with a modal response.
- You can specify a range of text length (`minLength` and `maxLength`)
- You can add a placeholder, a pre-filled value and specify whether the text input is required
- An Action Row can have maximal 1 Text Input
## Send Components
As mentioned above there are different types of components. This requires to define a type, so that Discord knows, which
component you want to use.
```js
class ActionRow {
constructor(options = {}) {
this.type = 1
}
setComponents(...components) {
this.components = components
return this
}
}
```
```js
const button = new Button()
const button2 = new Button()
const actionRow = new ActionRow().setComponents(button, button2)
```
This code will obviously not work because it's a missing a lot required of data. The other reason is that we can't send
a class to Discord, we need sth. to transform it to a json object.
We have a pre-made class for components which you can find
[here](https://github.com/meister03/discordeno.js/tree/master/Structures/Component.js).
### Button
```js
const Discord = require('discordeno.js')
const message = client.messages.forge(rawMessage)
const button = new Discord.Component()
.setType('BUTTON')
.setStyle('LINK')
.setLabel('Click me!')
.setUrl('https://google.com')
.toJSON()
// Button with raw types
const button2 = new Discord.Component()
.setType(2)
.setStyle(4)
.setLabel('DO NOT CLICK')
.setCustomId('12345')
.toJSON()
const actionRow = new Discord.Component()
.setType('ACTION_ROW')
.setComponents(button, button2)
.toJSON()
// Message to send
const messageOptions = { content: 'hello', components: [actionRow] }
// await client.helpers.sendMessage(channelId, messageOptions); // Do it the raw way
message.channel.send(messageOptions) // Do it with the structure
```
As you can see, for simplicity you can use strings instead of numbers (types), which are hard to remember.
### Select Menu
```js
const Discord = require('discordeno.js')
const message = client.messages.forge(rawMessage)
const selectMenu = new Discord.Component()
.setType('SELECT_MENU')
.setCustomId('12345')
.setOptions([
{
label: 'Option 1',
value: '1',
description: `This is option 1`,
},
{
label: 'Option 2',
value: '2',
description: `This is option 2`,
},
{
label: 'Default Option',
value: '3',
description: `Default option...`,
default: true,
},
])
.setPlaceholder('Select an option')
.toJSON()
const actionRow = new Discord.Component()
.setType('ACTION_ROW')
.setComponents(selectMenu)
.toJSON()
const messageOptions = { content: 'hello', components: [actionRow] }
// await client.helpers.sendMessage(channelId, messageOptions); // Do it the raw way
message.channel.send(messageOptions) // Do it with the structure
```
### Text Input
```js
const Discord = require('discordeno.js')
const interaction = client.messages.forge(rawInteraction)
const textInput = new Component()
.setType('TEXT_INPUT')
.setStyle('SHORT')
.setCustomId('t1')
.setLabel('User ID')
.setPlaceholder('User ID')
.setRequired(true)
.setMaxLength(20)
.setMinLength(1)
.toJSON()
const textInput2 = new Component()
.setType('TEXT_INPUT')
.setStyle('PARAGRAPH')
.setCustomId('t2')
.setLabel('Reason')
.setPlaceholder('Reason for Ban')
.setRequired(false)
.setMaxLength(300)
.toJSON()
const actionRow = new Component()
.setType('ACTION_ROW')
.setComponents(textInput)
.toJSON()
const actionRow2 = new Component()
.setType('ACTION_ROW')
.setComponents(textInput2)
.toJSON()
interaction.popupModal({
customId: 'ban_modal',
title: 'Ban User',
components: [actionRow, actionRow2],
})
```
### Receive Interactions
When a user clicks a button or selects an option from a Select Menu, Discord sends an `interactionCreate` event, which
contains the information necessary to process it.
@@ -0,0 +1,100 @@
---
sidebar_position: 2
---
# Create Structure
Structures are often used to transform data and add methods to existing objects. To make it easier to work with them.
Imagine you have a channel object to which you want to send a message.
```js
const data = {
id: 806947972004839444n,
name: 'spam-and-bots',
}
```
The recommended way would be:
```js
await client.helpers.sendMessage(data.id, { content: 'hello' })
```
However, you probably want to use something shorter, such as the following:
```js
class Channel {
constructor(client, data) {
this.client = client
this.id = data.id
this.name = data.name
}
async send(options) {
return await this.client.helpers.sendMessage(this.id, options)
}
}
```
Now you can use the `.send()` method on the channel object without using such a long code:
```js
const channel = new Channel(client, data)
await channel.send({ content: 'hello' })
```
Moreover, you can modify the `.send()` method to better suit your use case e.g not send the message if the channel is
blacklisted.
This naturally opens a lot of opportunities and makes coding a lot easier. Because you decide what you want to do with
the data, how the methods are named and how you want to process the request.
## Using Template Structures:
When you are migrating from another library and you want to utilize the djs-like wrapper, you'll likely choose to
continue using special structures. Therefore we have ready-made structures for the wrapper `Discordeno.js`.
- [Guild](https://github.com/meister03/discordeno.js/tree/master/Structures/Guild.js)
- [Channel](https://github.com/meister03/discordeno.js/tree/master/Structures/Channel.js)
- [Role](https://github.com/meister03/discordeno.js/tree/master/Structures/Role.js)
- [Member](https://github.com/meister03/discordeno.js/tree/master/Structures/Member.js)
- [User](https://github.com/meister03/discordeno.js/tree/master/Structures/User.js)
- [Message](https://github.com/meister03/discordeno.js/tree/master/Structures/Message.js)
- [Interaction](https://github.com/meister03/discordeno.js/tree/master/Structures/Interaction.js)
- [Emoji](https://github.com/meister03/discordeno.js/tree/master/Structures/Emoji.js)
- [Webhook](https://github.com/meister03/discordeno.js/tree/master/Structures/Webhook.js)
- [Embed](https://github.com/meister03/discordeno.js/tree/master/Structures/Embed.js)
- [Component](https://github.com/meister03/discordeno.js/tree/master/Structures/Component.js)
- [Collection](https://github.com/meister03/discordeno.js/tree/master/Structures/Collection.js)
We recommend that you check the wrappers [Readme](https://github.com/meister03/discordeno.js#discordclient) in order to
construct the client for following the Guide
**Using the Structures:**
```js
const Discord = require('discordeno.js')
const client = new Discord.Client(clientOptions, cacheOptions) //See the Readme above
Discord.startBot(client)
const guild = client.guilds.forge(guildData)
const channel = guild.channels.forge(channelData)
const role = guild.roles.forge(roleData)
const member = guild.members.forge(memberData)
const user = guild.users.forge(userData)
const message = guild.messages.forge(messageData)
const interaction = guild.interactions.forge(interactionData)
const emoji = guild.emojis.forge(emojiData)
const webhook = new Discord.Webhook(client, webhookData)
const embed = new Discord.Embed(embedData) // embedData is optional
const component = new Discord.Component(componentData) // componentData is optional
const collection = new Discord.Collection()
```
Some popular methods have been added to the structures so that you can use them without having to come up with your own.
In order to use the Structures from the Wrapper, you need to invoke the `.forge` method with the raw discord data,
whereas it will construct the structure for you.
Next we're going to give a better insight into how create [`Embeds`](embeds) and [`Components`](components) with the
wrappers structures.
@@ -0,0 +1,112 @@
---
sidebar_position: 3
---
# Create Embeds
Embeds are widely used by bots in order to display messages in a fancy way.
Unfortunately, the Discord API does not accept funky classes such as `new MessageEmbed().setTitle("hello")`, instead it
takes a json object, e.g. `{ title: "hello" }`. Therefore, we need to create an embed Structure that converts the
user-supplied data into the format which Discord uses.
:::note Runtime Overhead
Constructor classes are nice to use and make your code look better, but they incur a slight runtime overhead compared to
just using raw data because they still execute methods, which takes more time to process.
:::
```js
class Embed() {
constructor() {}
setTitle(title) {
this.title = title;
}
}
```
Now we have created a class which we can use to create embeds. But we can't just send this to Discord.
So we need an additional method which will convert the data from the class to the correct format.
```js
class Embed(){
constructor() {}
setTitle(title) {
this.title = title;
}
toJSON() {
return {
title: this.title
}
}
}
```
Wow, now you can create a embed and send it to Discord.
```js
const Channel = require('./structures/Channel') // Path to structure
const channel = new Channel(client, data)
await channel.send({ embeds: [embed] })
```
You probably want more methods which you can use to create embeds.
[Here is how the Embed Structure looks like](https://github.com/meister03/discordeno.js/blob/master/Structures/Embed.js)
### Using the Embed Structure:
```js
const Discord = require('discordeno.js')
const channel = client.channels.forge(channelData)
const showCaseEmbed = new Discord.Embed()
.setColor(0x00ae86)
.setTitle('A Random Title')
.setURL('https://github.com/discordeno')
.setAuthor({
name: 'Author name',
iconUrl:
'https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png',
url: 'https://github.com/discordeno',
})
.setDescription('A Random Description')
.setThumbnail(
'https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png',
)
.addFields(
{ name: 'Field 1 Name', value: 'Normal Field Value' },
{ name: '\u200B', value: '\u200B' },
{ name: 'Field 2 Name', value: 'Inline Field Value', inline: true },
{ name: 'Field 3 Name', value: 'Inline Field Value', inline: true },
)
.addField({ name: 'Field 4', value: 'Field Value' })
.setImage(
'https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png',
)
.setTimestamp()
.setFooter({
text: 'A Footer Text',
iconUrl:
'https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png',
})
.toJSON()
await channel.send({ embeds: [showCaseEmbed] })
```
### Embed Limits:
- Title: 256 characters
- Description: 4096 characters
- Field Name: 256 characters
- Field Value: 1024 characters
- Footer Text: 2048 characters
- Author Name: 256 characters
- 10 Embeds per message
- In total over all 10 Embeds not more than 6000 characters
@@ -0,0 +1,30 @@
---
sidebar_position: 1
---
# Getting Started with Structures
As previously mentioned, Discordeno was built with as few classes as possible, this is in favor of performance.
For example, you cannot execute functions on objects.
```diff
- message.channel.send({content: "hello"})
+ client.helpers.sendMessage(message.channel.id, {content: "hello"})
```
This seems to be more complicated at first, but has many advantages:
- You get full control over the actions
- Errors are easier to debug
- A validation by classes does not have to take place
One of the disadvantages is that you have to change a lot in your code.
Of course, we recommend that you try out the upper way, but we will introduce structures in this guide because they are
used by many users who eventually want to migrate.
For example, if you want to get correctly formatted objects, structures are obviously beneficial, because they support
the readability of the code by their ease of use
In the following, we will introduce how to create your own structures and how to use the ones available in the template.
+4
View File
@@ -0,0 +1,4 @@
{
"label": "Nodejs",
"position": 3
}
@@ -0,0 +1,29 @@
---
sidebar_position: 3
---
# Create Application
1. Go to the [Developer Portal](https://discord.com/developers/applications) and create a new application.
2. Navigate to the Section `Bot` and confirm with "Yes, do it!"
3. Now copy your token and save it under a safe environment.
:::caution Token Security
Keep your token safe, because it is like a password that grants access to your bot, which then can be used for mass
DMing, mass banning or any other kind of malicious activity.
:::
## Add your Bot to your Server
In order to use your Bot, it should be in a server where you can interact with it.
1. Go to the [Developer Portal](https://discord.com/developers/applications) and click on your previously created bot.
2. Click on `OAuth2` and there go to the `URL Generator`.
3. Select the `bot` and the `applications.commands` scope.
4. Scroll down and select the `Administrator` permission.
5. Copy the generated URL and open it in your browser.
6. Select your Server and click the invite button.
The bot should now have been added to your server and show as an offline user.
+203
View File
@@ -0,0 +1,203 @@
---
sidebar_position: 6
---
# Design
In order to ensure long-term scalability and maintainability, the code structure is of enormous importance. In the
following, we show how such a code structure could look like.
The essential parts are a `CommandHandler/CommandManager`, `EventHandler/EventManager`, lots of `Structures` in order to
code faster and `Plugins`, where your different features will be, such as `Commands`, `DB Stuff`...
## Code Structure
We recommend following structure for your code:
```root
├index.js
├─Structures/
├─Managers/
├─events/
├─Plugins/
├── General/
│ ├── commands/
│ │ ├── ping.js
│ │ └── ...
├── Developer/
│ ├── commands/
│ │ ├── eval.js
│ │ └── ...
├─Util/
└── ...
```
The following explains why this structure is suitable. If you want to follow this guide further, you should create these
folders.
In the `Managers` folder the Managers will be added e.g. `CommandManager.js`, `EventManager.js`. Generally codes, which
manage the system.
While in the `Structures` folder mainly classes are added like `BaseCommand.js`, `CommandResponse.js`, `Embed.js`,
`Components.js`, which make it easier to add methods to objects.
The `events` folder will contain the event handlers such as `messageCreate.js`, `debug.js`
Your many useful features and categories end up in the `Plugins` folder, where they should be categorically divided into
many folders.
The `Util` folder contains functions or classes that help you convert certain things, such as timestamps, into a
human-readable format.
## CommandHandler & BaseCommand
The `CommandHandler` is the main class of the bot, which will handle all the commands and the events received from
Discord.
The `BaseCommand` is the base class of all commands, which will be extended with the`CommandResponse` class.
### Steps showed in the following Guide
- Loading commands from different plugins
- Deploying slash commands
- Handling `messageCreate` & `interactionCreate` events
- Command rate limit handling
- Handle `Interaction` & `Message` commands with the same code
- Validating user provided arguments
- Correct permission and error handling
- Hot reloading commands
- Creating message and interaction collectors
## EventHandler
You probably realized that Discordeno does not use an `EventEmitter` to fire the events, but your own event function is
fired.
There are ways to adapt to an `EventEmitter`, but we decided against it for the following reasons:
- It's easy to create memory leaks, when you add too many listeners or go carelessly with it.
- Many fragmented parts of event code complicate maintenance.
- ErrorHandling is difficult and debugging is harder when many listeners are open for the same events.
## Structures
Structures are essential to abstract larger parts of code in smaller ready-made methods and to modify them if necessary.
Example:
```js
class Command {
static name = 'ping'
static aliases = ['pong']
static botPermission = ['SEND_EMBED_LINKS']
run(message, args) {
// do something
}
}
```
It would be annoying adding everytime the `botPermission` property to the class Command, when the Permission is used
from every Command, then it is unnecessary to add it, when you can extend the class.
It would be annoying to add the `botPermission` property to the command class every time the same permissions are used
by each command. Extending the class makes this extra step obsolete.
```js
class BaseCommand {
constructor(client) {
this.client = client
this.basePermission = ['SEND_EMBED_LINKS']
}
}
class Command extends BaseCommand {
static name = 'ping'
static aliases = ['pong']
constructor(data) {
super(data)
}
run(message, args) {
// do something
}
}
```
## Plugins
The plugins folder helps you categorize your code into many parts to give some structure.
Of course, this has many advantages, you have a much clearer code, you can debug problems much easier.
This also opens possibilities for open source contributions, since not all parts of the code have to be published in
order to add new plugins, since they are "independent".
There will be the main `Plugins` folder, which by default contains a `General` folder for all your base commands. The
`Plugins` folder will also contain all your other plugins.
## Error Handling
One of the most important things is how to handle errors. This is done to provide a user-friendly experience and to find
errors faster.
You should catch errors and log them in your logger so you can fix them later. There are several open source `Sentry`'s
that give you a good overview of the latest errors through a website.
Sometimes errors have a positive effect on maintainability and scalability.
In addition, handling errors caused by users is very important to increase transparency. If they don't know why the
error happened, then they'll be very surprised with what they did wrong and might even remove your bot from their
server.
## Caching
Normally libraries cache all the info they get, which can of course be helpful at the beginning to discover all
functionalities but later it turns out to be a resource-consuming method. Therefore, this way should be avoided.
Discordeno allows `Custom Caching` and even `Custom Property Caching` which gives you fine-grained control over the
caching of data. Normally you only need 20% of the data received by Discord, which makes caching unnecessary in most
cases.
There are also some `Filter` and `Sweeper` methods which help you to empty unused cache values.
## Cross Communication & Scaling
If you are running many different processes, such as a Welcomer API, communication is of central importance in order to
send or receive data, with which you can then perform certain actions.
Cross communication can be easily done with sockets or a TCP client.
This brings up this Structure:
```js
Bridge (Heart)
- Machine 1
- Cluster [0-9]
- Machine 2
- Cluster [10-18]
- Machine 3 -> Welcomer Api
- Machine 4 -> Dashboard
```
It's important to use something fast to have a proper "real time" communication.
Discordeno already offers many internal options for scaling bots, no matter what size.
As you scale, you will likely separate many parts of your bot and put them in separate processes, such as a
`RestManager`, a `Gateway Manager` etc.
This of course opens up a lot of possibilities:
- Zero downtime updates
- Global cache
- Synced rate limits
[Check the Github Readme for more information](https://github.com/discordeno/discordeno#features)
:::tip congratulations
You just learned how to design a scalable bot, let's get into implementing it with the next pages.
:::
@@ -0,0 +1,48 @@
---
sidebar_position: 1
---
# Getting Started
If you are reading this, you probably want to create a Discord bot with Discordeno or migrate from popular libraries like Discord.js.
If this is going to be your first time making a bot, you should use Deno instead of Node.js. Although in some cases, Deno might not be suitable for you because of missing packages or a code base that is too large to migrate to a slightly different language.
This guide will help you make your first Discord bot using Node.js or even migrate your bot from another library.
Moreover this guide will utilize two different options. One option to use the Discordeno package without any frameworks
and one, which uses the wrapper called `Discordeno.js`, which aims to achieve a djs-like interface.
:::important Disclaimer
Some features are not documented yet. If you want to know more about them, kindly ask for help in the
[Discord Server](https://discord.gg/ddeno).
:::
## Why should I switch?
Discordeno was built with the purpose of being scalable, flexible and easy to use.
Libraries like `Discord.js` and `Eris` often have excessive caching behavior that can only be changed slightly without
breaking the entire library. There is a lack of customization and many nested classes, which makes it almost impossible
to edit the code without having unwanted side effects. Moreover scalability is only possible on a limited extend.
Discordeno has been kept plain and simple, which opens up a lot of opportunities for customization such as
`custom-caching (custom-property-caching)`, [`Standalone Rest`](../big-bot-guide/rest.md),
[`Gateway`](../big-bot-guide/gateway.md), [`Cache`](../big-bot-guide/cache.md) and more. Check the detailed advantages
[here](https://github.com/discordeno/discordeno#features).
This guide will also help you making your code more scalable and easier to maintain with bringing you closer to the
Discord API.
# Before you start
Before you start digging in this guide, you should have a solid understanding of `javascript`. If you are not familiar
with it, then you should take a look at some popular resources.
- [W3Schools Course](https://www.w3schools.com/js/DEFAULT.asp)
- [Mozilla Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- [JavaScript.Info](https://javascript.info)
A basic understanding is of great importance in order to solve problems skillfully.
+48
View File
@@ -0,0 +1,48 @@
---
sidebar_position: 4
---
# Initial Setup
## Config File
Ideally, you should save your configs in an `.env` file. Out of simplicity for this guide, we are saving it in a
`config.json` file.
Create a file named `config.json` in your project folder and insert the following content:
```json
{
"token": "YOUR_TOKEN_HERE",
"prefix": "!"
}
```
## Edit the main file
Open the `index.js` file which you have created earlier and then insert the following content:
```js
const Discord = require('discordeno')
const config = require('./config.json')
const client = Discord.createBot({
events: {
ready(client, payload) {
console.log(
`Successfully connected Shard ${payload.shardId} to the gateway`,
)
},
},
intents: ['Guilds', 'GuildMessages'],
token: config.token,
})
Discord.startBot(client)
```
Now you can start your bot by running the following command in your terminal:
```cli
$ node index.js
```
+30
View File
@@ -0,0 +1,30 @@
---
sidebar_position: 2
---
# Installing Node.js and Discordeno
To use the Discordeno library you first need to install Node.js and then Discordeno from NPM.
Go to [nodejs.org](https://nodejs.org/en/) and download the latest version of Node.js. Open the downloaded file and
follow the instructions of the installer to install Node.js.
## Create a Folder
Open your file manager and create a new folder (e.g.: `discordbot`) in your desired directory. Then open the code editor
of your choice and create a new file (e.g.: `index.js`) in the folder you just have created.
### Initalize NPM & Install Discordeno
In order to keep track of the dependencies, you need to initialize NPM, which generates a `package.json` file.
```cli
$ npm init --yes
```
Then you need to install Discordeno. When you want to go along with the wrapper named `Discordeno.js`, then install it
too. Go to your terminal and run the following command:
```cli
$ npm install discordeno
```
+70
View File
@@ -0,0 +1,70 @@
---
sidebar_position: 5
---
# Slash Commands
Since Discord has decided to make message content accessible only to privileged bots, message commands will play a
subordinate role in the future. Discord users will be more used to slash commands. That's why it's essential that every
bot offers them.
In the following we will show you how to create slash commands:
## Deploying Slash Commands
There is a difference between global and guild commands. Global commands take a while to appear in all guilds. Guild
commands show up directly.
For this reason, we will now show how to create guild commands, in order to test them immediately.
```js
const guildId = BigInt('YOUR_GUILD_ID')
const command = {
name: 'ping',
description: 'Retrieves the Bot latency',
options: [],
}
client.helpers.createApplicationCommand(command, guildId)
```
This is just very simple example, you can also add sub commands, select options and much more.
## Handling Slash Commands
Discord sends a WebSocket Event when a user runs a slash command. You can listen to this event by adding the
`interactionCreate` function in the client.
```js
const Discord = require('discordeno')
const config = require('./config.json')
const client = Discord.createBot({
events: {
ready(client, payload) {
console.log(
`Successfully connected Shard ${payload.shardId} to the gateway`,
)
},
async interactionCreate(client, interaction) {
if (interaction.data?.name === 'ping') {
return await client.helpers.sendInteractionResponse(
interaction.id,
interaction.token,
{
type: Discord.InteractionResponseTypes.ChannelMessageWithSource,
data: { content: '🏓 Pong!' },
},
)
}
},
},
intents: Discord.Intents.Guilds | Discord.Intents.GuildMessages,
token: config.token,
})
Discord.startBot(client)
```
The handling may see complicated in the beginning, but as mentioned before, we will introduce structures to make it
easier.