style: format files

This commit is contained in:
ayntee
2021-01-20 17:02:01 +04:00
parent 7f67b45a09
commit b3f9287183
25 changed files with 1569 additions and 673 deletions
+40 -14
View File
@@ -1,43 +1,69 @@
# Fundamental Design Goals
This document serves to outline the overall design goals of the project. Please see below a list of these fundamentals.
This document serves to outline the overall design goals of the project. Please
see below a list of these fundamentals.
## Do not allow anything Discord does not permit
Prevent any and all attempts of making user bots. If someone connects and the client.user is not a bot user then throw an error immediately.
Prevent any and all attempts of making user bots. If someone connects and the
client.user is not a bot user then throw an error immediately.
Do not support non-bot features like Group DMs or dm calls etc...
## Prettier Philosophy Regarding Options
Avoid options/customizable whenever possible. Always enforce default values. Except in cases like intents where the user should be able to pick which intents to listen for.
Avoid options/customizable whenever possible. Always enforce default values.
Except in cases like intents where the user should be able to pick which intents
to listen for.
## Security
Permission checks should be done by the library! We can throw a custom error that shows which permissions are missing in order to run this request and save an API call. This will also prevent bots from getting banned due to Missing Access errors.
Permission checks should be done by the library! We can throw a custom error
that shows which permissions are missing in order to run this request and save
an API call. This will also prevent bots from getting banned due to Missing
Access errors.
Typescript 3.8 provides **TRUE** private props and methods that no one can access. We will use this to our advantage to truly make a proper API. This isn't a silly `_` to mark it as a private but the user should NEVER be able to access it no matter what.
Typescript 3.8 provides **TRUE** private props and methods that no one can
access. We will use this to our advantage to truly make a proper API. This isn't
a silly `_` to mark it as a private but the user should NEVER be able to access
it no matter what.
## Functional API
Events emitted by the client, for example the message creation event, should not emit a `Message` class instance, but instead a *POJO* (Plain Ol' JavaScript Object). This will overall make a cleaner and more performant API, while removing the headaches of extending built-in classes, and inheritance.
Events emitted by the client, for example the message creation event, should not
emit a `Message` class instance, but instead a _POJO_ (Plain Ol' JavaScript
Object). This will overall make a cleaner and more performant API, while
removing the headaches of extending built-in classes, and inheritance.
Use functions when possible instead of an event emitter to prevent emitter related memory leak issues or a number of other headaches that arise.
Use functions when possible instead of an event emitter to prevent emitter
related memory leak issues or a number of other headaches that arise.
TLDR: Avoid `classes` whenever possible. Avoid `loops` whenever possible(opt for iterations like .forEach, map reduce, some find etc...)
TLDR: Avoid `classes` whenever possible. Avoid `loops` whenever possible(opt for
iterations like .forEach, map reduce, some find etc...)
## Documentation
Use `/** Description here */` comments above all properties and methods to describe it so that VSC and other good IDE's with intellisense can pick it up and provide the documentation right inside the IDE preventing a developer from needing Discord API docs or even Deno documentation.
Use `/** Description here */` comments above all properties and methods to
describe it so that VSC and other good IDE's with intellisense can pick it up
and provide the documentation right inside the IDE preventing a developer from
needing Discord API docs or even Deno documentation.
We should have a step by step guide nonetheless but this is a POST v1 launch.
We should have a template repo to creating a boilerplate bot.
We should have a step by step guide nonetheless but this is a POST v1 launch. We
should have a template repo to creating a boilerplate bot.
## Backwards Compatibility BS
Backwards compatibility is the death of code. It causes clutter and uglyness to pile up and makes developers lazier. There will be no such thing as backwards compatibility reasons in Discordeno. We will always support the latest and greatest of JS. The end! Users can fork the lib at any commit to keep older versions until they are ready to update.
Backwards compatibility is the death of code. It causes clutter and uglyness to
pile up and makes developers lazier. There will be no such thing as backwards
compatibility reasons in Discordeno. We will always support the latest and
greatest of JS. The end! Users can fork the lib at any commit to keep older
versions until they are ready to update.
That said, we don't expect many things to be changing drastically after v1. As you can imagine Typescript allows the latest and greatest of JS so we will be ahead of the curve for years to come.
That said, we don't expect many things to be changing drastically after v1. As
you can imagine Typescript allows the latest and greatest of JS so we will be
ahead of the curve for years to come.
## Style Guide
Prettier is our style guide. No discussions around styling ever. The options are set and that is all. When you code let prettier handle the styling. PERIOD!
Prettier is our style guide. No discussions around styling ever. The options are
set and that is all. When you code let prettier handle the styling. PERIOD!
+6 -3
View File
@@ -2,10 +2,13 @@
**Status**
- [ ] Code changes have been tested against the Discord API, or there are no code changes
- [ ] Code changes have been tested against the Discord API, or there are no
code changes
**Semantic versioning classification:**
- [ ] This PR changes the library's interface (methods or parameters added)
- [ ] This PR includes breaking changes (methods removed or renamed, parameters moved or removed)
- [ ] This PR **only** includes non-code changes, like changes to documentation, README, etc.
- [ ] This PR includes breaking changes (methods removed or renamed,
parameters moved or removed)
- [ ] This PR **only** includes non-code changes, like changes to documentation,
README, etc.
+23 -6
View File
@@ -6,16 +6,32 @@
![Lint](https://github.com/discordeno/discordeno/workflows/Lint/badge.svg)
![Test](https://github.com/discordeno/discordeno/workflows/Test/badge.svg)
- **Secure & stable**: Discordeno is comparatively more stable than the other libraries. 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. Discordeno internally checks all missing permissions before forwarding a request to the API so that the client does not get globally-banned by Discord.
- **Efficient & lightweight**: Discordeno is simplistic and easy-to-use. Always prefer defaults that Discord recommends or the best configuration for the majority―if necessary, it is remarkably customizable, versatile, and efficient.
- **Functional API**: This will produce a cleaner and more performant code while removing the difficulties of extending built-in classes and inheritance. Avoid potential memory leaks or crashes because of too many listeners or other silly issues.
- **Secure & stable**: Discordeno is comparatively more stable than the other
libraries. 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. Discordeno internally checks all missing permissions before
forwarding a request to the API so that the client does not get
globally-banned by Discord.
- **Efficient & lightweight**: Discordeno is simplistic and easy-to-use. Always
prefer defaults that Discord recommends or the best configuration for the
majority―if necessary, it is remarkably customizable, versatile, and
efficient.
- **Functional API**: This will produce a cleaner and more performant code while
removing the difficulties of extending built-in classes and inheritance. Avoid
potential memory leaks or crashes because of too many listeners or other silly
issues.
## Usage
### Beginner Developers
Don't worry a lot of developers start out coding their first projects as a Discord bot (I did 😉) and it is not so easy to do so. Discordeno is built considering all the issues with pre-existing libraries and issues that I had when I first started out coding bots.
If you are a beginner developer, you may check out these awesome official and unofficial boilerplates:
Don't worry a lot of developers start out coding their first projects as a
Discord bot (I did 😉) and it is not so easy to do so. Discordeno is built
considering all the issues with pre-existing libraries and issues that I had
when I first started out coding bots. If you are a beginner developer, you may
check out these awesome official and unofficial boilerplates:
- Official Discordeno Boilerplate
- [GitHub](https://github.com/Skillz4Killz/Discordeno-bot-template)
@@ -56,7 +72,8 @@ startBot({
We appreciate your help!
Before contributing, please read the [Contributing Guide](https://github.com/discordeno/discordeno/blob/master/.github/CONTRIBUTING.md).
Before contributing, please read the
[Contributing Guide](https://github.com/discordeno/discordeno/blob/master/.github/CONTRIBUTING.md).
### License
+30 -8
View File
@@ -6,9 +6,22 @@
![Lint](https://github.com/discordeno/discordeno/workflows/Lint/badge.svg)
![Test](https://github.com/discordeno/discordeno/workflows/Test/badge.svg)
- **Secure & stable**: Discordeno is comparatively more stable than the other libraries. 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. Discordeno internally checks all missing permissions before forwarding a request to the API so that the client does not get globally-banned by Discord.
- **Efficient & lightweight**: Discordeno is simplistic and easy-to-use. Always prefer defaults that Discord recommends or the best configuration for the majority―if necessary, it is remarkably customizable, versatile, and efficient.
- **Functional API**: This will produce a cleaner and more performant code while removing the difficulties of extending built-in classes and inheritance. Avoid potential memory leaks or crashes because of too many listeners or other silly issues.
- **Secure & stable**: Discordeno is comparatively more stable than the other
libraries. 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. Discordeno internally checks all missing permissions before
forwarding a request to the API so that the client does not get
globally-banned by Discord.
- **Efficient & lightweight**: Discordeno is simplistic and easy-to-use. Always
prefer defaults that Discord recommends or the best configuration for the
majority―if necessary, it is remarkably customizable, versatile, and
efficient.
- **Functional API**: This will produce a cleaner and more performant code while
removing the difficulties of extending built-in classes and inheritance. Avoid
potential memory leaks or crashes because of too many listeners or other silly
issues.
## Useful Links
@@ -18,17 +31,26 @@
## Read me first...
Discordeno is cool right? You could make the next big bot! Who knows, but before we get right into developing our Bot. We want to get started with learning the basics...
Discordeno is cool right? You could make the next big bot! Who knows, but before
we get right into developing our Bot. We want to get started with learning the
basics...
You've seen how amazing Discord Bots are built and functioned! So beginning with Discordeno always starts with learning the TypeScript and/or JavaScript programming languages first. Making a Discord bot with very little knowledge is possible, it can be a challenge! You may end up dealing with Console errors or just syntax typographical errors...
You've seen how amazing Discord Bots are built and functioned! So beginning with
Discordeno always starts with learning the TypeScript and/or JavaScript
programming languages first. Making a Discord bot with very little knowledge is
possible, it can be a challenge! You may end up dealing with Console errors or
just syntax typographical errors...
If you are new to Discordeno, TypeScript or JavaScript, here are some great resources:
If you are new to Discordeno, TypeScript or JavaScript, here are some great
resources:
- [Official TypeScript Documentation](https://www.typescriptlang.org/docs/home.html)
- [JavaScript Documentation from Devdocs](https://devdocs.io/javascript/)
- [Codecademy](https://www.codecademy.com/)
- [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
- [Deno Crash Course by Traversy Media](https://www.youtube.com/watch?v=NHHhiqwcfRM)
- [TypeScript Crash Course by Traversy Media](https://www.youtube.com/watch?v=rAy_3SIqT-E)
- [TypeScript Crash Course by Traversy
Media](https://www.youtube.com/watch?v=rAy_3SIqT-E)
There is always more resources... Take your time and don't fret! Come back when you are ready, we can't wait to see what your Discordeno created bot does!
There is always more resources... Take your time and don't fret! Come back when
you are ready, we can't wait to see what your Discordeno created bot does!
+74 -37
View File
@@ -1,25 +1,41 @@
# Command Arguments
Command Arguments is a really cool and powerful feature that will parse, validate and handle arguments for your commands. Sometimes, you want certain commands to have arguments provided. For example, in a `ban` command, you will need a `member` and an optional reason to ban them. Discordeno will handle this for you even before the code reaches your commands execution.
Command Arguments is a really cool and powerful feature that will parse,
validate and handle arguments for your commands. Sometimes, you want certain
commands to have arguments provided. For example, in a `ban` command, you will
need a `member` and an optional reason to ban them. Discordeno will handle this
for you even before the code reaches your commands execution.
## Built-In Arguments
Discordeno comes with the most useful command arguments already built for you. The command arguments can be found in the `src/arguments` folder.
Discordeno comes with the most useful command arguments already built for you.
The command arguments can be found in the `src/arguments` folder.
### General
- `string` A valid **1 word** string with atleast 1 character. This is the default argument type. If you don't provide a type, it will use string
- `...string` Sometimes you will want strings but with more than 1 word. For example, in our ban command we will need a longer reason argument. For that we use `...string`. Note, this should always be your FINAL argument.
- `boolean` When you want the user to provide `true` or `false`. This also supports `on` or `off`.
- `command` When you want a command name or an command alias.
- `duration` When you want the user to provide a string like `12d2h5m`. This will be converted to milliseconds for you.
- `guild` When you want a guild object and the user provides a guild id.
- `member` When you want a member object and the user can provide a member id, @mention or their username/nickname.
- `number` When you want a number.
- `role` When you want a role object. The user can provide the role id, mention or role name.
- `snowflake` When you just want a snowflake. A snowflake is an ID generation format from Twitter that Discord uses to make their IDs unique. These IDs do never change and are used to identify users, guilds, emojis, and more.
- `...snowflakes` When you want to check if the mentioned ID is a valid snowflake. Note that this is similar to ...string, it will take all coming arguments and check them. This should always be your FINAL argument.
- `subcommand` When your command has subcommands.
- `string` A valid **1 word** string with atleast 1 character. This is the
default argument type. If you don't provide a type, it will use string
- `...string` Sometimes you will want strings but with more than 1 word. For
example, in our ban command we will need a longer reason argument. For that we
use `...string`. Note, this should always be your FINAL argument.
- `boolean` When you want the user to provide `true` or `false`. This also
supports `on` or `off`.
- `command` When you want a command name or an command alias.
- `duration` When you want the user to provide a string like `12d2h5m`. This
will be converted to milliseconds for you.
- `guild` When you want a guild object and the user provides a guild id.
- `member` When you want a member object and the user can provide a member id,
@mention or their username/nickname.
- `number` When you want a number.
- `role` When you want a role object. The user can provide the role id, mention
or role name.
- `snowflake` When you just want a snowflake. A snowflake is an ID generation
format from Twitter that Discord uses to make their IDs unique. These IDs do
never change and are used to identify users, guilds, emojis, and more.
- `...snowflakes` When you want to check if the mentioned ID is a valid
snowflake. Note that this is similar to ...string, it will take all coming
arguments and check them. This should always be your FINAL argument.
- `subcommand` When your command has subcommands.
### Channels
@@ -28,17 +44,19 @@ Discordeno comes with the most useful command arguments already built for you. T
- `textchannel` Must be a text channel id or name or mention.
- `voicechannel` Must be a voice channel id or name or mention.
Any of these can be easily modified, in their files. Let's go ahead and try and modify one of the command arguments as an example.
Any of these can be easily modified, in their files. Let's go ahead and try and
modify one of the command arguments as an example.
## Modifying Existing Command Arguments
Suppose you wanted to make it possible so that the boolean argument could accept other options as well. By default, it supports:
Suppose you wanted to make it possible so that the boolean argument could accept
other options as well. By default, it supports:
- `true` and `on` which will be true
- `false` and `off` which will be false
What if we also wanted to support, `yes` and `no`? Let's open up the `boolean.ts` file in the arguments folder and get started.
What if we also wanted to support, `yes` and `no`? Let's open up the
`boolean.ts` file in the arguments folder and get started.
```ts
import { botCache } from "../../deps.ts";
@@ -70,16 +88,23 @@ botCache.arguments.set("boolean", {
});
```
Awesome. You just managed to modify one of the existing command arguments. But what if we wanted to create our own custom command argument. Let's do that next.
Awesome. You just managed to modify one of the existing command arguments. But
what if we wanted to create our own custom command argument. Let's do that next.
## Creating A Command Argument
Let's create a command argument for the purposes of this guide so we can learn how to create one. For example, let's say we wanted a command argument for urls.
Let's create a command argument for the purposes of this guide so we can learn
how to create one. For example, let's say we wanted a command argument for urls.
First, we need to update the **CommandArgument** interface in `src/types/commands.ts`.
First, we need to update the **CommandArgument** interface in
`src/types/commands.ts`.
- Add your argument type to the list of types in the interface. In this case we will add **"url"**. You can add it in any order here you like. The specific order does not matter.
- Once that's done, we can go and create the code for it. Now, lets create a new file in `src/arguments` folder called `url.ts` and paste the base snippet for a command argument below.
- Add your argument type to the list of types in the interface. In this case we
will add **"url"**. You can add it in any order here you like. The specific
order does not matter.
- Once that's done, we can go and create the code for it. Now, lets create a new
file in `src/arguments` folder called `url.ts` and paste the base snippet for
a command argument below.
```ts
import { botCache } from "../../deps.ts";
@@ -92,7 +117,8 @@ botCache.arguments.set("argumentname", {
});
```
First let's change the `argumentname` to be `url`. Then we can start adding the pseudo-code.
First let's change the `argumentname` to be `url`. Then we can start adding the
pseudo-code.
```ts
import { botCache } from "../../deps.ts";
@@ -105,7 +131,6 @@ botCache.arguments.set("url", {
// The regex we will use to test if it's a valid url
// Use the regex to test if it is a valid url
},
});
```
@@ -123,9 +148,10 @@ botCache.arguments.set("url", {
if (!url) return;
// The regex we will use to test if it's a valid url
const urlRegex = /^((https?):\/\/)?([w|W]{3}\.)*[a-zA-Z0-9\-\.]{3,}\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/
const urlRegex =
/^((https?):\/\/)?([w|W]{3}\.)*[a-zA-Z0-9\-\.]{3,}\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/;
// Use the regex to test if it is a valid url
const validURL = urlRegex.test(url)
const validURL = urlRegex.test(url);
if (!validURL) return;
return url;
@@ -133,16 +159,22 @@ botCache.arguments.set("url", {
});
```
Feel free to use any other regex you prefer. There are so many URL regexes out there. Use the one that fits you best. [URL Regex Comparison](https://mathiasbynens.be/demo/url-regex)
Feel free to use any other regex you prefer. There are so many URL regexes out
there. Use the one that fits you best.
[URL Regex Comparison](https://mathiasbynens.be/demo/url-regex)
Awesome! You just created your very own command argument. Now let's check out how to use command arguments.
Awesome! You just created your very own command argument. Now let's check out
how to use command arguments.
## Using Command Arguments
When you create a command, you have the option to provide an array of command arguments using the `arguments` property on a command. A CommandArgument must follow the following rules.
When you create a command, you have the option to provide an array of command
arguments using the `arguments` property on a command. A CommandArgument must
follow the following rules.
```ts
arguments: [
arguments:
[
// Each argument goes here
{
// This is required. You can name it anything you like. This will be used when you want to access the properties. For example, `args.url` will be done to use this argument when the command code is written.
@@ -150,7 +182,7 @@ arguments: [
// This is optional. It will default to string. There are various types available and will be shown to you through auto-completion. For our case, let's use the `url` argument we just created.
type: "url",
// This is optional. A function can be provided which is executed when the user does not provide a valid argument.
missing: function(message) {
missing: function (message) {
// Useful for sending a response to the user saying x thing wasnt provided properly.
},
// This is optional. By default this is set to true. If this is true and a command was NOT provided, the command will not be executed UNLESS a defaultValue was provided.
@@ -160,11 +192,16 @@ arguments: [
// This is optional BUT it is required if the type is subcommand. When you have a type of string or subcommand you can sometimes want very specific keywords like `add` or `remove`.
literals: ["add", "remove"],
// This is optional. If a argument is not provided it can be given a default argument. Useful for a default subcommand if you wish.
defaultValue: "my default value here"
}
]
defaultValue: "my default value here",
},
];
```
We already covered using the arguments in a command in our guide when we created the role command, so we can skip that here. To re-read that you can check it out [here](https://discordeno.mod.land/stepbystep/createcommand.html#arguments).
We already covered using the arguments in a command in our guide when we created
the role command, so we can skip that here. To re-read that you can check it out
[here](https://discordeno.mod.land/stepbystep/createcommand.html#arguments).
Command arguments are an extremely powerful feature that can help make creating bots a lot easier. Discordeno provides extremely flexible command arguments. As a side benefit, command arguments are designed to be hot reloadable from the reload command. 🎉
Command arguments are an extremely powerful feature that can help make creating
bots a lot easier. Discordeno provides extremely flexible command arguments. As
a side benefit, command arguments are designed to be hot reloadable from the
reload command. 🎉
+110 -33
View File
@@ -1,33 +1,56 @@
# Customizing Discordeno
This guide is only for extremely advanced developers. This feature is extremely powerful but comes with huge risks. When I was building Discordeno, there was always one goal I had in my mind that I wanted because when I used other libraries I always felt this was missing. I hated forking a library just to customize it as then it made staying up to date a much larger hassle. Discordeno was designed with customizability in mind. A vast majority of the library can be overridden allowing you to make it work however you would like for your bot.
This guide is only for extremely advanced developers. This feature is extremely
powerful but comes with huge risks. When I was building Discordeno, there was
always one goal I had in my mind that I wanted because when I used other
libraries I always felt this was missing. I hated forking a library just to
customize it as then it made staying up to date a much larger hassle. Discordeno
was designed with customizability in mind. A vast majority of the library can be
overridden allowing you to make it work however you would like for your bot.
## Customizing Structures
Discordeno provides the ability to customize structures. You have the ability to override the function that creates the structures inside Discordeno. **THIS DOES NOT MODIFY THE PROTOTYPE!** I wanted to make sure that Discordeno handled this without encouraging bad practices such as prototype pollution which can make your bots inherintly unstable and unpredictable. Discordeno allows you to take control over the functions that create the structures.
Discordeno provides the ability to customize structures. You have the ability to
override the function that creates the structures inside Discordeno. **THIS DOES
NOT MODIFY THE PROTOTYPE!** I wanted to make sure that Discordeno handled this
without encouraging bad practices such as prototype pollution which can make
your bots inherintly unstable and unpredictable. Discordeno allows you to take
control over the functions that create the structures.
### Customizing Member Structure
Let's take into consideration that you wanted to add some custom properties to the member structure. For instance, let's add a couple of properties that could help make working with the bot easier such as `member.tag`, `member.guild`, `member.avatarURL`. We are going to add these, simple as an example for the purpose of this guide. Please note, that these don't exist in the library itself because they take up quite a bit of RAM/memory since as your bot grows you can hit millions of members which adds up to GB of memory. Keeping this minimal can help allow your bots to grow larger without increasing the cost. However, if you are advanced enough and feel adding/customizing the structures is necessary then this feature allows you to do that.
Let's take into consideration that you wanted to add some custom properties to
the member structure. For instance, let's add a couple of properties that could
help make working with the bot easier such as `member.tag`, `member.guild`,
`member.avatarURL`. We are going to add these, simple as an example for the
purpose of this guide. Please note, that these don't exist in the library itself
because they take up quite a bit of RAM/memory since as your bot grows you can
hit millions of members which adds up to GB of memory. Keeping this minimal can
help allow your bots to grow larger without increasing the cost. However, if you
are advanced enough and feel adding/customizing the structures is necessary then
this feature allows you to do that.
To begin customizing, create a file in the structures folder called `member.ts`. The name of the file is not important at all.
To begin customizing, create a file in the structures folder called `member.ts`.
The name of the file is not important at all.
```ts
async function createMember() {
}
```
We start by declaring a function that will be run to create the structure. Once again the name here is not important. The function must take the same arguments that the internal function takes. In this case the createMember function takes 2 arguments. `data: MemberCreatePayload, guildID: string`
We start by declaring a function that will be run to create the structure. Once
again the name here is not important. The function must take the same arguments
that the internal function takes. In this case the createMember function takes 2
arguments. `data: MemberCreatePayload, guildID: string`
```ts
async function createMember(data: MemberCreatePayload, guildID: string) {
}
```
The next step is to fill in this function. You can make this do whatever you want. My recommendation is to start by copying the current code from the internal libraries structure.
The next step is to fill in this function. You can make this do whatever you
want. My recommendation is to start by copying the current code from the
internal libraries structure.
```ts
async function createMember(data: MemberCreatePayload, guildID: string) {
@@ -63,7 +86,8 @@ async function createMember(data: MemberCreatePayload, guildID: string) {
}
```
Now we have a base to work with. We can now add a `tag`, `avatarURL`, `mention`, and `guild` properties to the member.
Now we have a base to work with. We can now add a `tag`, `avatarURL`, `mention`,
and `guild` properties to the member.
```ts
import { rawAvatarURL } from "../../deps.ts";
@@ -84,20 +108,24 @@ async function createMember(data: MemberCreatePayload, guildID: string) {
/** The guild object for where this member is located */
guild: await cacheHandler.get("guilds", guildID),
/** Easily mention the member */
mention: `<@${data.user.id}>`
mention: `<@${data.user.id}>`,
};
return member;
}
```
Now we need to use this function and telling Discordeno to override the internal createMember function. To do this, we will modify the internal functions. This is where we reassign the value of the function.
Now we need to use this function and telling Discordeno to override the internal
createMember function. To do this, we will modify the internal functions. This
is where we reassign the value of the function.
```ts
structures.createMember = createMember;
```
Awesome. Now, we have one more step to complete which is to declare these new properties on our typings for the member structure. At the bottom, of the file we will write the following code.
Awesome. Now, we have one more step to complete which is to declare these new
properties on our typings for the member structure. At the bottom, of the file
we will write the following code.
```ts
declare module "../../deps.ts" {
@@ -110,8 +138,8 @@ declare module "../../deps.ts" {
}
```
> **Important:** when you modify structures, it is important to restart the bot so it takes effect on all members that have already be constructed.
> **Important:** when you modify structures, it is important to restart the bot
> so it takes effect on all members that have already be constructed.
The code should look like this right now:
@@ -150,7 +178,7 @@ async function createMember(data: MemberCreatePayload, guildID: string) {
/** The guild object for where this member is located */
guild: await cacheHandler.get("guilds", guildID),
/** Easily mention the member */
mention: `<@${data.user.id}>`
mention: `<@${data.user.id}>`,
};
return member;
@@ -170,16 +198,26 @@ declare module "../../deps.ts" {
#### Removing Properties
Now, that we have added the properties we want, we can discuss how to remove properties we don't want. Remember every property that exists on member can become very expensive as your bot grows.
Now, that we have added the properties we want, we can discuss how to remove
properties we don't want. Remember every property that exists on member can
become very expensive as your bot grows.
To do a little easy math, let's suppose we had 1,000,000 member objects. Each one of them could possibly store an avatar string which can take up about 32 bytes. Now that comes to a total of around 32MB for every 1 million members.
To do a little easy math, let's suppose we had 1,000,000 member objects. Each
one of them could possibly store an avatar string which can take up about 32
bytes. Now that comes to a total of around 32MB for every 1 million members.
Now let's go ahead and delete the `avatar`, `username` and `discriminator` strings since we already used them and don't really want them to be duplicated as we will never need their raw counterparts in our bot(Note: You may in your bot and this is why this is an advanced feature. Decide carefully what you wish to add or remove). In fact, for the purposes of this guide let's go a little crazy and remove everything we don't specifically want to have.
Now let's go ahead and delete the `avatar`, `username` and `discriminator`
strings since we already used them and don't really want them to be duplicated
as we will never need their raw counterparts in our bot(Note: You may in your
bot and this is why this is an advanced feature. Decide carefully what you wish
to add or remove). In fact, for the purposes of this guide let's go a little
crazy and remove everything we don't specifically want to have.
The following is the current base data available to us when we create the member.
The following is the current base data available to us when we create the
member.
```ts
user: {
user: {
/** The user's id */
id: string;
/** the user's username, not unique across the platform */
@@ -219,10 +257,16 @@ The following is the current base data available to us when we create the member
mute: boolean;
```
Let's just keep `nick`, `roles`, `joinedAt`, `bot` and `id` plus the new stuff we have added above.
Let's just keep `nick`, `roles`, `joinedAt`, `bot` and `id` plus the new stuff
we have added above.
```ts
import { cacheHandlers, Guild, MemberCreatePayload, rawAvatarURL } from "../../deps.ts";
import {
cacheHandlers,
Guild,
MemberCreatePayload,
rawAvatarURL,
} from "../../deps.ts";
async function createMember(data: MemberCreatePayload, guildID: string) {
const {
@@ -260,7 +304,11 @@ declare module "../../deps.ts" {
}
```
You might be seeing an error on `structures.createMember`. This is happening because our new member structures is modifying/removing existing properties that the lib internally said it would have. To solve this, simply just add a ts-ignore above it as you know better than TS that the typings are being overwritten.
You might be seeing an error on `structures.createMember`. This is happening
because our new member structures is modifying/removing existing properties that
the lib internally said it would have. To solve this, simply just add a
ts-ignore above it as you know better than TS that the typings are being
overwritten.
```ts
// @ts-ignore
@@ -269,8 +317,11 @@ structures.createMember = createMember;
## Custom Cache
One of the features that Discordeno has is the ability to customize the cache. In order to do this, we will simply override the cache handlers. For the purposes of this guide, we are going to use Redis for our custom cache solution. You can use anything you like, this guide purpose is just to teach the basics of how to customize. We will not be discussing how to use Redis.
One of the features that Discordeno has is the ability to customize the cache.
In order to do this, we will simply override the cache handlers. For the
purposes of this guide, we are going to use Redis for our custom cache solution.
You can use anything you like, this guide purpose is just to teach the basics of
how to customize. We will not be discussing how to use Redis.
```ts
import { cacheHandlers } from "../../../deps.ts";
@@ -280,9 +331,14 @@ cacheHandlers.has = async function (table, key) {
};
```
This code now overrides the `has` method which will check from Redis. The basic concept is that the function `has` takes a table name and a key. The `has` method returns whether or not a value exists. You can do anything you like inside this function, like connecting to a database or any other solution you would like to use.
This code now overrides the `has` method which will check from Redis. The basic
concept is that the function `has` takes a table name and a key. The `has`
method returns whether or not a value exists. You can do anything you like
inside this function, like connecting to a database or any other solution you
would like to use.
Similarily, since we want to use Redis we would want to customize all the methods on the cacheHandlers. The current list of methods available are:
Similarily, since we want to use Redis we would want to customize all the
methods on the cacheHandlers. The current list of methods available are:
- has
- get
@@ -293,9 +349,20 @@ Similarily, since we want to use Redis we would want to customize all the method
## Custom Gateway Payload Handling (Controllers)
Controllers are one of the most powerful features of Discordeno. They allow you to take control of how Discordeno handles the Discord payloads from the gateway. When an event comes in, you can override and control how you want it to work. For example, if your bot does not use emojis at all, you could simply just take control over the GUILD_EMOJIS_UPDATE event and prevent anyone from caching any emojis at all. Another example, is if you are building a custom module/framework around Discordeno and you want to provide more custom events such as when someone boosts the server or some other custom event this is possible as well.
Controllers are one of the most powerful features of Discordeno. They allow you
to take control of how Discordeno handles the Discord payloads from the gateway.
When an event comes in, you can override and control how you want it to work.
For example, if your bot does not use emojis at all, you could simply just take
control over the GUILD_EMOJIS_UPDATE event and prevent anyone from caching any
emojis at all. Another example, is if you are building a custom module/framework
around Discordeno and you want to provide more custom events such as when
someone boosts the server or some other custom event this is possible as well.
Someone once asked if it was possible to make Discordeno, show the number of users currently typing in the server. He had managed to build this himself in his bot, but he wanted to do it inside the library itself. In order to keep Discordeno minimalistic and memory efficient I avoided adding this. So let's see how we could achieve this same thing with Controllers.
Someone once asked if it was possible to make Discordeno, show the number of
users currently typing in the server. He had managed to build this himself in
his bot, but he wanted to do it inside the library itself. In order to keep
Discordeno minimalistic and memory efficient I avoided adding this. So let's see
how we could achieve this same thing with Controllers.
```ts
import {
@@ -326,8 +393,18 @@ controllers.TYPING_START = function (data) {
};
```
Controllers are amazing in so many ways. This is just a basic example but it's true potential is only limited by your imagination. I would love to see what you all can create with controllers.
Controllers are amazing in so many ways. This is just a basic example but it's
true potential is only limited by your imagination. I would love to see what you
all can create with controllers.
Something worth noting about why Discordeno controllers are so amazing is that it allows you to never depend on me. When Discord releases something new, you don't need to wait for me to update the library to access it. Without controllers, if you wanted access to a feature you would need to wait for the library to be updated or have to fork it, modify it and modify your code for it. Then when the library does get updated, you need to switch back to it and modify your code again possibly to how the lib designed it. With controllers, you never have to fork or anything. Just take control!
Something worth noting about why Discordeno controllers are so amazing is that
it allows you to never depend on me. When Discord releases something new, you
don't need to wait for me to update the library to access it. Without
controllers, if you wanted access to a feature you would need to wait for the
library to be updated or have to fork it, modify it and modify your code for it.
Then when the library does get updated, you need to switch back to it and modify
your code again possibly to how the lib designed it. With controllers, you never
have to fork or anything. Just take control!
Controllers are extremely powerful. **Remember with great power comes great bugs!**
Controllers are extremely powerful. **Remember with great power comes great
bugs!**
+24 -6
View File
@@ -1,16 +1,27 @@
# Docker Hosting
Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications. By taking advantage of Dockers methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.
Docker is an open platform for developing, shipping, and running applications.
Docker enables you to separate your applications from your infrastructure so you
can deliver software quickly. With Docker, you can manage your infrastructure in
the same ways you manage your applications. By taking advantage of Dockers
methodologies for shipping, testing, and deploying code quickly, you can
significantly reduce the delay between writing code and running it in
production.
Learn more [here](https://docs.docker.com/get-started/)
### Installing Docker
Installing Docker is very simple and supported on nearly every major operating system! Just follow the instructions [here](https://docs.docker.com/get-docker/) to get started.
Installing Docker is very simple and supported on nearly every major operating
system! Just follow the instructions [here](https://docs.docker.com/get-docker/)
to get started.
### Building The Bot's Docker Image
Now it is time to build the image that will be running the bot! Simply run this command, replacing `IMAGE_NAME` with whatever you want to name the image. Make sure to run this command from the root of the bot directory! This command could take a while depending on how powerful your device is.
Now it is time to build the image that will be running the bot! Simply run this
command, replacing `IMAGE_NAME` with whatever you want to name the image. Make
sure to run this command from the root of the bot directory! This command could
take a while depending on how powerful your device is.
```bash
docker build -t IMAGE_NAME .
@@ -18,17 +29,24 @@ docker build -t IMAGE_NAME .
### Running The Bot For The First Time
With the bot's image built, it is time to create a container and run the bot for the first time! Remember to replace `IMAGE_NAME` with the same name you chose in the last step, and replace `CONTAINER_NAME` with what you want to name the container. (RECOMMENDATION: Name the container with the name of your bot)
With the bot's image built, it is time to create a container and run the bot for
the first time! Remember to replace `IMAGE_NAME` with the same name you chose in
the last step, and replace `CONTAINER_NAME` with what you want to name the
container. (RECOMMENDATION: Name the container with the name of your bot)
```bash
docker run -it --init -v $PWD:/bot --name CONTAINER_NAME IMAGE_NAME
```
This command should create a new container linked to the directory containing your bot's code. Once your bot finishes loading, you should be able to use the bot just like normal.
This command should create a new container linked to the directory containing
your bot's code. Once your bot finishes loading, you should be able to use the
bot just like normal.
### Starting, Stopping, And Restarting The Bot
Now that the container is created, you should only need three main commands to manage the bot. Remember to replace CONTAINER_NAME with whatever you chose in the last step.
Now that the container is created, you should only need three main commands to
manage the bot. Remember to replace CONTAINER_NAME with whatever you chose in
the last step.
```bash
docker start CONTAINER_NAME
+37 -15
View File
@@ -1,21 +1,32 @@
# Dynamic Command Creation!
Discordeno has one of the coolest ways to create commands for advanced level developers. I call it Dynamic command creation. This allows you to basically create a bunch of commands that are similar instantly.
Discordeno has one of the coolest ways to create commands for advanced level
developers. I call it Dynamic command creation. This allows you to basically
create a bunch of commands that are similar instantly.
This is some advanced level super magical command creation skills. Discordeno gives you the power to dynamically create commands. What that means is you can write the code for one command but dynamically create like 50 commands using that same code.
This is some advanced level super magical command creation skills. Discordeno
gives you the power to dynamically create commands. What that means is you can
write the code for one command but dynamically create like 50 commands using
that same code.
## Nekos.Life API Commands
A lot of bot's have entertainment commands. Usually they have to create like 100 different files and each file is it's own code. Some might just take the functionality and move it to a separate file and reuse that function in all those files.
A lot of bot's have entertainment commands. Usually they have to create like 100
different files and each file is it's own code. Some might just take the
functionality and move it to a separate file and reuse that function in all
those files.
In Discordeno, there is a better way. Dynamically create the commands based on the command name.
In Discordeno, there is a better way. Dynamically create the commands based on
the command name.
Let's create all the commands for the entire Nekos.Life API in a very short time.
Let's create all the commands for the entire Nekos.Life API in a very short
time.
- Create a file in the commands folder called `nekos.ts` which will create all our fun commands.
- Create a file in the commands folder called `nekos.ts` which will create all
our fun commands.
```ts
import { botCache,sendMessage } from "../../deps.ts";
import { botCache, sendMessage } from "../../deps.ts";
const nekosEndpoints = [
{ name: "tickle", path: "/img/tickle", nsfw: false },
@@ -65,9 +76,12 @@ nekosEndpoints.forEach((endpoint) => {
});
```
> **Note:** We have removed the endpoints that were leading to NSFW content. With them, we would just have created 68 different commands.
> **Note:** We have removed the endpoints that were leading to NSFW content.
> With them, we would just have created 68 different commands.
Take a minute to realize what just happened. This has made 29 different unique commands dynamically. In 1 file, using the same piece of code, we created so many commands. You can easily add more commands to this.
Take a minute to realize what just happened. This has made 29 different unique
commands dynamically. In 1 file, using the same piece of code, we created so
many commands. You can easily add more commands to this.
**That ladies and gentleman is the power and magic of Discordeno!**
@@ -79,10 +93,11 @@ If your still a little confused, don't worry. Let's break it down.
const nekosEndpoints = [
{ name: "tickle", path: "/img/tickle", nsfw: false },
// ...
]
];
```
This is just an array of all the endpoints on the API giving a name, a path, and a boolean to say if it is NSFW or not.
This is just an array of all the endpoints on the API giving a name, a path, and
a boolean to say if it is NSFW or not.
```ts
botCache.commands.set(endpoint.name, {
@@ -97,12 +112,19 @@ botCache.commands.set(endpoint.name, {
});
```
This is the part where we are doing the magical stuff. So let's take a look at this a bit.
This is the part where we are doing the magical stuff. So let's take a look at
this a bit.
We ran a loop `nekosEndpoints.forEach()` on that array and for each item in that array, we created a command.
We ran a loop `nekosEndpoints.forEach()` on that array and for each item in that
array, we created a command.
For the command name, we used the `endpoint.name` property. For the `nsfw` property we used the `endpoint.nsfw` property. Since all of these commands, send a message that is a URL we simply required those permissions in all 29 of these commands.
For the command name, we used the `endpoint.name` property. For the `nsfw`
property we used the `endpoint.nsfw` property. Since all of these commands, send
a message that is a URL we simply required those permissions in all 29 of these
commands.
Then we simply write the code for the commands and it all just works. If you were to reload/restart your bot now with this. You will see that you have access to 68 new commands.
Then we simply write the code for the commands and it all just works. If you
were to reload/restart your bot now with this. You will see that you have access
to 68 new commands.
Take a minute and try out some of the commands and how they function.
+59 -30
View File
@@ -1,24 +1,34 @@
# Permission Levels!
Permission levels is a really cool concept that will let you easily control which users can use your commands. For example, if you have commands like `warn` or `ban`, you only want users that are Moderators or Admins of a server to be able to use these commands. By default, a command is set so any member can use the command. To raise the requirement, you can use Permission Levels.
Permission levels is a really cool concept that will let you easily control
which users can use your commands. For example, if you have commands like `warn`
or `ban`, you only want users that are Moderators or Admins of a server to be
able to use these commands. By default, a command is set so any member can use
the command. To raise the requirement, you can use Permission Levels.
## Default Permission Levels
The default permission levels can be found in the `src/permissionLevels` folder.
- `MEMBER` Anyone can use this command.
- `MODERATOR` User needs **MANAGE SERVER** permission in order to use this command
- `MODERATOR` User needs **MANAGE SERVER** permission in order to use this
command
- `ADMIN` User needs **ADMINISTRATOR** permission in order to use this command.
- `SERVER_OWNER` Only a **SERVER OWNER** can use this command in their server.
- `BOT_SUPPORT` Requires the user ID be present in **configs.userIDs.botSupporters**
- `BOT_SUPPORT` Requires the user ID be present in
**configs.userIDs.botSupporters**
- `BOT_DEVS` Requires the user ID be present in **configs.userIDs.botDevs**
- `BOT_OWNERS` Requires the user ID be present in **configs.userIDs.botOwners**
Any of these can be easily modified, in their files. Let's go ahead and try and modify one of the permission levels as an example.
Any of these can be easily modified, in their files. Let's go ahead and try and
modify one of the permission levels as an example.
## Modifying Existing Permission Levels
If you have a bot support server, you might also have a role setup for your bot's support team. So to make life easier, it would be nice if someone with that role could use the command without having to modify the configs file every time.
If you have a bot support server, you might also have a role setup for your
bot's support team. So to make life easier, it would be nice if someone with
that role could use the command without having to modify the configs file every
time.
```ts
import { botCache } from "../../deps.ts";
@@ -39,7 +49,7 @@ botCache.permissionLevels.set(
PermissionLevels.BOT_SUPPORT,
async (message) => {
// If the user id exists in the configs allow the command
if (configs.userIDs.botSupporters.includes(message.author.id)) return true
if (configs.userIDs.botSupporters.includes(message.author.id)) return true;
// The users id was not in the configs, check if they have the role in bot server
@@ -59,29 +69,33 @@ botCache.permissionLevels.set(
PermissionLevels.BOT_SUPPORT,
async (message) => {
// If the user id exists in the configs allow the command
if (configs.userIDs.botSupporters.includes(message.author.id)) return true
if (configs.userIDs.botSupporters.includes(message.author.id)) return true;
// The users id was not in the configs, check if they have the role in bot server
// Get the bots support server
const guild = cache.guilds.get(configs.supportServerID)
if (!guild) return false
const guild = cache.guilds.get(configs.supportServerID);
if (!guild) return false;
// If the user is not a member of the support server they can't be one of the support staff.
const member = guild.members.get(message.author.id)
const member = guild.members.get(message.author.id);
if (!member) return false;
// If they have the role allow the command otherwise it will be false and block the command.
return member.roles.includes('BOT_SUPPORT_ROLE_ID_HERE')
return member.roles.includes("BOT_SUPPORT_ROLE_ID_HERE");
},
);
```
Awesome. You just managed to modify one of the existing permission levels. But what if we wanted to create our own custom permission level. Let's do that next.
Awesome. You just managed to modify one of the existing permission levels. But
what if we wanted to create our own custom permission level. Let's do that next.
## Creating A Permission Level
Let's create a permission level for the purposes of this guide so we can learn how to create one. For example, let's say we wanted a permission level for users that have boosted the server. We want it to check if the user has a role called Nitro Booster.
Let's create a permission level for the purposes of this guide so we can learn
how to create one. For example, let's say we wanted a permission level for users
that have boosted the server. We want it to check if the user has a role called
Nitro Booster.
First, we need to update the Permission Levels enum in `src/types/commands.ts`.
@@ -97,7 +111,8 @@ export enum PermissionLevels {
}
```
Let's add a new one for a Nitro Booster role. You can add it in any order here you like. The specific order does not matter.
Let's add a new one for a Nitro Booster role. You can add it in any order here
you like. The specific order does not matter.
```ts
export enum PermissionLevels {
@@ -112,7 +127,9 @@ export enum PermissionLevels {
}
```
Once that's done, we can go and create the code for it. Now, lets create a new file in `permissionLevels` folder called `booster.ts` and paste the base snippet for a permission level below.
Once that's done, we can go and create the code for it. Now, lets create a new
file in `permissionLevels` folder called `booster.ts` and paste the base snippet
for a permission level below.
```ts
import { botCache } from "../../deps.ts";
@@ -123,14 +140,16 @@ botCache.permissionLevels.set(
PermissionLevels,
async (message) => {
// Code goes here
}
},
);
```
> **NOTE:** You will see an error in the `PermissionLevels,` line because we need to select one of the permission levels. In this case we want the NITRO_BOOSTER level we just created above.
> **NOTE:** You will see an error in the `PermissionLevels,` line because we
> need to select one of the permission levels. In this case we want the
> NITRO_BOOSTER level we just created above.
```ts
PermissionLevels.NITRO_BOOSTER
PermissionLevels.NITRO_BOOSTER;
```
Next we can add the code in place.
@@ -146,22 +165,26 @@ botCache.permissionLevels.set(
const guild = message.guild();
if (!guild) return false;
const member = message.member()
const member = message.member();
if (!member) return false;
const boosterRole = guild.roles.find(role => role.name.toLowerCase() === 'nitro booster')
const boosterRole = guild.roles.find((role) =>
role.name.toLowerCase() === "nitro booster"
);
if (!boosterRole) return false;
return member.roles.includes(boosterRole.id)
}
return member.roles.includes(boosterRole.id);
},
);
```
Awesome! You just created your very own permission level. Now let's check out how to use permission levels.
Awesome! You just created your very own permission level. Now let's check out
how to use permission levels.
## Using Permission Levels
There are two ways to use permission levels. You can provide an array of PermissionLevels or you can provide a custom function.
There are two ways to use permission levels. You can provide an array of
PermissionLevels or you can provide a custom function.
```ts
createCommand({
@@ -170,19 +193,23 @@ createCommand({
botChannelPermissions: ["SEND_MESSAGES"],
```
As an example the `reload` command requires the user to be a bot owner to use this command.
As an example the `reload` command requires the user to be a bot owner to use
this command.
**ONLY ONE LEVEL IS NECESSARY TO ALLOW THE COMMAND.**
```ts
[PermissionLevels.MODERATOR, PermissionLevels.ADMIN]
[PermissionLevels.MODERATOR, PermissionLevels.ADMIN];
```
If you provide an array of permission levels, only one of these is necessary to run the command. For example, if the user passes MODERATOR, they do not need ADMIN to run it.
If you provide an array of permission levels, only one of these is necessary to
run the command. For example, if the user passes MODERATOR, they do not need
ADMIN to run it.
### Advanced Perm Levels
There is another way to use permission levels. You can provide a custom function that must return a boolean. For example,
There is another way to use permission levels. You can provide a custom function
that must return a boolean. For example,
```ts
createCommand({
@@ -193,11 +220,13 @@ createCommand({
botChannelPermissions: ["SEND_MESSAGES"],
```
The function is able to take 3 arguments. `message`, `command` and `guild`.
You can use an async function if you like as well.
---
Permission levels are an extremely powerful feature that can help make creating bots a lot easier. Discordeno provides extremely flexible permission levels. As a side benefit, Permission Levels are designed to be hot reloadable from the reload command. 🎉
Permission levels are an extremely powerful feature that can help make creating
bots a lot easier. Discordeno provides extremely flexible permission levels. As
a side benefit, Permission Levels are designed to be hot reloadable from the
reload command. 🎉
+80 -23
View File
@@ -1,17 +1,33 @@
# Subcommands Inside Subcommands!
One of the most powerful things about Discordeno is it's ability to have infinite subcommands. A command can have a subcommand which can have it's own subcommands... Mind not blown yet? Wait for it.... A `subcommand` is a full `command`! What that means is, every subcommand has the full power and flexibility of any command. BOOM! Mind blown!
One of the most powerful things about Discordeno is it's ability to have
infinite subcommands. A command can have a subcommand which can have it's own
subcommands... Mind not blown yet? Wait for it.... A `subcommand` is a full
`command`! What that means is, every subcommand has the full power and
flexibility of any command. BOOM! Mind blown!
A subcommand can have it's own settings for example, you can allow 1 subcommand inside a guild and the other in dm. You can allow 1 subcommand to be NSFW and the other to be SFW. You can have different permissions requirements for each. You can set custom permission levels for each so that certain subcommands could only be done by mods/admins. Each subcommand also has it's own arguments meaning you can require different things for each and have the best experience possible when making your bot.
A subcommand can have it's own settings for example, you can allow 1 subcommand
inside a guild and the other in dm. You can allow 1 subcommand to be NSFW and
the other to be SFW. You can have different permissions requirements for each.
You can set custom permission levels for each so that certain subcommands could
only be done by mods/admins. Each subcommand also has it's own arguments meaning
you can require different things for each and have the best experience possible
when making your bot.
## Basic Example
Let's start by understanding the `prefix` command which uses subcommands. It is a good basic example, to help us understand how to create a subcommand.
Let's start by understanding the `prefix` command which uses subcommands. It is
a good basic example, to help us understand how to create a subcommand.
```ts
import { botCache } from "../../deps.ts";
import { PermissionLevels } from "../types/commands.ts";
import { sendResponse, sendEmbed, createSubcommand, createCommand } from "../utils/helpers.ts";
import {
createCommand,
createSubcommand,
sendEmbed,
sendResponse,
} from "../utils/helpers.ts";
import { parsePrefix } from "../monitors/commandHandler.ts";
import { Embed } from "../utils/Embed.ts";
@@ -75,7 +91,9 @@ createSubcommand("prefix", {
});
```
Let's separate this to understand what is happening here. The first half of the code is the main command. This is like any other command. However, the important thing is in the arguments we requested a subcommand!
Let's separate this to understand what is happening here. The first half of the
code is the main command. This is like any other command. However, the important
thing is in the arguments we requested a subcommand!
```ts
createCommand({
@@ -84,7 +102,7 @@ createCommand({
{
name: "sub commmand",
type: "subcommand",
required: false
required: false,
},
],
guildOnly: true,
@@ -141,26 +159,46 @@ createSubcommand("prefix", {
});
```
`createSubcommand` takes 2 arguments. The first is the command name and the second is a full Command object.
The command is just like any other command you will make. You can customize this as you like as you would any other.
`createSubcommand` takes 2 arguments. The first is the command name and the
second is a full Command object. The command is just like any other command you
will make. You can customize this as you like as you would any other.
What this does is it creates a subcommand in the `prefix` command. This subcommand will be called `set` so the user will have to type `!prefix set` in order to trigger this command. This subcommand also takes a required argument that will be a string. In this case, that means the user has to type something like `!prefix set $` to change their prefix.
What this does is it creates a subcommand in the `prefix` command. This
subcommand will be called `set` so the user will have to type `!prefix set` in
order to trigger this command. This subcommand also takes a required argument
that will be a string. In this case, that means the user has to type something
like `!prefix set $` to change their prefix.
*POP QUIZ TIME*
_POP QUIZ TIME_
Alright, that sounds good so far. Now let's take a step back. What would happen if the user typed `!prefix` without the `set` keyword? This would trigger the main commands execute. Did you notice that in the main command the subcommand argument has `required: false`. This makes it so that the user can execute a command without a subcommand.
Alright, that sounds good so far. Now let's take a step back. What would happen
if the user typed `!prefix` without the `set` keyword? This would trigger the
main commands execute. Did you notice that in the main command the subcommand
argument has `required: false`. This makes it so that the user can execute a
command without a subcommand.
## Default Subcommands
On occassion you may want to trigger a default subcommand. This is very simple. In your subcommand argument, just set the defaultValue as the default subcommand you wish to trigger.
On occassion you may want to trigger a default subcommand. This is very simple.
In your subcommand argument, just set the defaultValue as the default subcommand
you wish to trigger.
```ts
arguments: [
{ name: 'subcommand', type: 'subcommand', defaultValue: 'set', required: false }
]
arguments:
[
{
name: "subcommand",
type: "subcommand",
defaultValue: "set",
required: false,
},
];
```
If the user now typed `!prefix`, it would automatically execute the `prefix set` command. Using a default subcommand isn't so helpful in the prefix command but imagine other commands where you may want to list the items to the user. For example, if you had a reactiorole command.
If the user now typed `!prefix`, it would automatically execute the `prefix set`
command. Using a default subcommand isn't so helpful in the prefix command but
imagine other commands where you may want to list the items to the user. For
example, if you had a reactiorole command.
```shell
!reactionroles create
@@ -169,13 +207,19 @@ If the user now typed `!prefix`, it would automatically execute the `prefix set`
!reactionroles list
```
This is where the default subcommands can shine, making it easier for your users to trigger the commands they want.
This is where the default subcommands can shine, making it easier for your users
to trigger the commands they want.
## Subcommand In A Subcommand
Now, let's see how crazy we can get with this. What if we wanted a subcommand inside our subcommand. Is that possible? What did Bob The Builder always say? **YES WE CAN!!!**
Now, let's see how crazy we can get with this. What if we wanted a subcommand
inside our subcommand. Is that possible? What did Bob The Builder always say?
**YES WE CAN!!!**
This is really helpful when you are creating a `settings` command which can help users configure your bot. You might have a bot like mine with 300 different features and 1000 different settings. For this, you will want the power and flexibility of Discordeno!
This is really helpful when you are creating a `settings` command which can help
users configure your bot. You might have a bot like mine with 300 different
features and 1000 different settings. For this, you will want the power and
flexibility of Discordeno!
```shell
!settings feedback suggestions on
@@ -187,7 +231,8 @@ This is really helpful when you are creating a `settings` command which can help
!settings gooodbye channel #channel
```
To create a subcommand inside a subcommand, `name` in the createSubcommand() function must be as follows:
To create a subcommand inside a subcommand, `name` in the createSubcommand()
function must be as follows:
```ts
createSubcommand("settings", { name: "feedback" })
@@ -201,18 +246,30 @@ createSubcommand("settings-goodbye", { name: "channel" })
createSubcommand("settings-goodbye", { name: "message" })
```
You can nest subcommands inside subcommands to provide the functionality to the user. Before, I was able to do this, my settings command was literally thousands of lines long and I had headaches just wanting to open it. Trying to handle 1000s of different settings was a huge pain. So now, I have subcommands inside subcommands. Why? Because you can **separate subcommands into separate files**!!! That's right, those thousands of lines of code now are modularized making maintainability much better. There are a lot less chances of bugs arising. Adding/removing/modifying settings is also incredibly simpler.
You can nest subcommands inside subcommands to provide the functionality to the
user. Before, I was able to do this, my settings command was literally thousands
of lines long and I had headaches just wanting to open it. Trying to handle
1000s of different settings was a huge pain. So now, I have subcommands inside
subcommands. Why? Because you can **separate subcommands into separate
files**!!! That's right, those thousands of lines of code now are modularized
making maintainability much better. There are a lot less chances of bugs
arising. Adding/removing/modifying settings is also incredibly simpler.
## Subcommands Also Have Aliases
Another powerful part about subcommands is that they also have their own aliases. This means you can create different ways to trigger a subcommand. For example, earlier we saw a subcommand `suggestions` of another subcommand `feedback` of a command `settings`. What if we wanted to allow users to type `ideas` for `suggestions`? Let's add some aliases to the subcommand to see the magic!
Another powerful part about subcommands is that they also have their own
aliases. This means you can create different ways to trigger a subcommand. For
example, earlier we saw a subcommand `suggestions` of another subcommand
`feedback` of a command `settings`. What if we wanted to allow users to type
`ideas` for `suggestions`? Let's add some aliases to the subcommand to see the
magic!
```ts
createSubcommand("settings-feedback", {
name: "suggestions",
aliases: ["ideas", "sugg", "s"],
// The rest of the command stuff below here!
})
});
```
---
+178 -109
View File
@@ -2,99 +2,126 @@
## Understanding The Goals of This Guide
This guide is not intended to trash or hate on Discord.JS. In fact, Discord.JS is the most popular Node.JS library which is why most users wanting to use Discordeno come from Discord.JS. Today, I had a user ask me for a guide to convert a Discord.JS bot to Discordeno. That was the start of this guide.
This guide is not intended to trash or hate on Discord.JS. In fact, Discord.JS
is the most popular Node.JS library which is why most users wanting to use
Discordeno come from Discord.JS. Today, I had a user ask me for a guide to
convert a Discord.JS bot to Discordeno. That was the start of this guide.
## Finding A Open Source 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 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)
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 Boilerplate](https://github.com/discordeno/discordeno-bot-template) I will name it Zodiac.
- First, create a Discordeno Bot using the
[Generator Boilerplate](https://github.com/discordeno/discordeno-bot-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.
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.
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
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')
const { CommandoClient } = require("discord.js-commando");
const path = require("path");
// reading events
fs.readdir('./src/events/', (err, files) => {
if (err) return console.error(err)
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
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))
emitter[
once
? "once"
: "on"
](event, (...args) => eventFunction.run(...args));
} catch (error) {
console.error(error.stack)
console.error(error.stack);
}
})
})
});
});
const client = global.client = new CommandoClient({
commandPrefix: `${config.prefix}`,
owner: `${config.owner}`,
invite: `${config.discord}`,
unknownCommandResponse: false
})
unknownCommandResponse: false,
});
// Registing the commands
client.registry
.registerDefaultTypes()
// The different fields for cmds
// The different fields for cmds
.registerGroups([
['mod', 'Moderation Commands'],
['public', 'Public Commands']
["mod", "Moderation Commands"],
["public", "Public Commands"],
])
.registerDefaultGroups()
// Basic cmds can be disabled like {"cmd: false"}
// Basic cmds can be disabled like {"cmd: false"}
.registerDefaultCommands()
// commands in "/src/commands" will be counted
.registerCommandsIn(path.join(__dirname, '/src/commands'))
// 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 | `]
const activityArray = [`${config.prefix}help | `];
// Bot lanuch code
client.once('ready', () => {
log(`Logged in as ${client.user.tag} in ${client.guilds.size} guild(s)!`)
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
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
})
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)
client.on("error", console.error);
// Login in using the token in config
client.login(config.env.TOKEN)
client.login(config.env.TOKEN);
```
Discordeno Version:
@@ -146,18 +173,21 @@ startBot({
});
```
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.
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 {
ActivityType,
botCache,
cache,
chooseRandom,
editBotsStatus,
StatusTypes,
ActivityType,
chooseRandom
} from "../../deps.ts";
import { registerTasks } from "./../utils/taskHelper.ts";
@@ -165,7 +195,7 @@ botCache.eventHandlers.ready = function () {
editBotsStatus(
StatusTypes.DoNotDisturb,
"Discordeno Best Lib",
ActivityType.Game
ActivityType.Game,
);
console.log(`Loaded ${botCache.arguments.size} Argument(s)`);
@@ -178,78 +208,92 @@ botCache.eventHandlers.ready = function () {
registerTasks();
console.log(
`[READY] Bot is online and ready in ${cache.guilds.size} guild(s)!`
`[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(() => {
editBotsStatus(StatusType.Online, chooseRandom(activityArray), ActivityType.Game)
}, 5000)
editBotsStatus(
StatusType.Online,
chooseRandom(activityArray),
ActivityType.Game,
);
}, 5000);
};
```
To understand this code, we are setting a function to be run when the bot is `ready`. Then the bot will edit the bots status every 5 seconds. Notice, that Discordeno provides a nice clean util function to choose a random item from an array. You also have beautiful enums provided that prevent you from making any typos/mistakes.
To understand this code, we are setting a function to be run when the bot is
`ready`. Then the bot will edit the bots status every 5 seconds. Notice, that
Discordeno provides a nice clean util function to choose a random item from an
array. You also have beautiful enums provided that prevent 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.
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.
`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')
const { Command } = require("discord.js-commando");
// Code for the command
module.exports = class addRoleCommand extends Command {
constructor (client) {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: 'addrole',
name: "addrole",
// other ways to call the command, must be in lowercase
aliases: ['role'],
aliases: ["role"],
// command group its part of
group: 'mod',
group: "mod",
// name within the command group, must be in lowercase
memberName: 'addrole',
memberName: "addrole",
// Is the description used for 'help' command
description: 'Adds mentioned role to mentioned user.',
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'],
clientPermissions: ["ADMINISTRATOR", "MANAGE_ROLES"],
userPermissions: ["MANAGE_ROLES"],
// Prevents anyone other than owner to use the command
ownerOnly: false
})
ownerOnly: false,
});
}
// Run code goes here
run (message) {
const user = message.mentions.members.first()
const roleToAdd = message.mentions.roles.first()
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)
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)
})
.then((msg) => {
msg.delete(5000);
});
} else {
message.channel.send(`${user} already has the role: ${roleToAdd.name}`)
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";
@@ -265,93 +309,106 @@ createCommand({
userServerPermissions: ["MANAGE_ROLES"],
arguments: [
{ name: "member", type: "member" },
{ name: "role", type: "role" }
{ name: "role", type: "role" },
],
execute: (message, args) => {
// checking to see if the user has the role or not
if (!args.member.roles.includes(args.role.id)) {
args.member.addRole(message.guildID, args.role.id)
message.sendResponse(`${args.member.mention} has been given the role: ${args.role.name}`, 5);
args.member.addRole(message.guildID, args.role.id);
message.sendResponse(
`${args.member.mention} has been given the role: ${args.role.name}`,
5,
);
} else {
message.sendResponse(`${args.member.mention} already has the role: ${args.role.name}`)
}
message.sendResponse(
`${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 boilerplate and have something amazing.
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 boilerplate 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
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) {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: 'kick',
name: "kick",
// other ways to call the command, must be in lowercase
aliases: ['boot', 'tempban'],
aliases: ["boot", "tempban"],
// command group its part of
group: 'mod',
group: "mod",
// name within the command group, must be in lowercase
memberName: 'kick',
memberName: "kick",
// Is the description used for 'help' command
description: 'Kick command.',
description: "Kick command.",
// adds cooldowns to the command
throttling: {
// usages in certain time x
usages: 1,
// the cooldown
duration: 10
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'],
clientPermissions: ["ADMINISTRATOR"],
userPermissions: ["KICK_MEMBERS"],
// Prevents anyone other than owner to use the command
ownerOnly: false
})
ownerOnly: false,
});
}
// Run code goes here
run (message) {
const messageArry = message.content.split(' ')
const args = messageArry.slice(1)
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)
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)
.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!`*')
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 => {})
message.delete().catch((O_o) => {});
// Kick the user with reason
message.guild.member(kUser).kick(kreason)
message.guild.member(kUser).kick(kreason);
// sends the kick report into log/report
reportchannel.send(kickEmbed)
reportchannel.send(kickEmbed);
// Logs the kick into the terminal
log(chalk.red('KICK', chalk.underline.bgBlue(kUser) + '!'))
log(chalk.red("KICK", chalk.underline.bgBlue(kUser) + "!"));
}
}
};
```
Discordeno Version
```ts
import { createCommand } from "./../../utils/helpers.ts";
@@ -418,8 +475,20 @@ interface KickArgs {
}
```
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.
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.
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.
+88 -17
View File
@@ -2,54 +2,123 @@
## Does Discordeno Support TypeScript?
Discordeno provides first class support for TypeScript! Since Deno provides support for TypeScript, that also comes into Discordeno. This means you don't need to compile TypeScript before you use it. However, this isn't really why Discordeno is the best library for TypeScript developers. When I developed this library, I was experimenting with a lot of different things and one of them was automated typings.
Discordeno provides first class support for TypeScript! Since Deno provides
support for TypeScript, that also comes into Discordeno. This means you don't
need to compile TypeScript before you use it. However, this isn't really why
Discordeno is the best library for TypeScript developers. When I developed this
library, I was experimenting with a lot of different things and one of them was
automated typings.
Whenever I used other libraries, I was always seeing typings being inaccurate or problematic. This is because in any Discord API library, the majority is not used by the library itself so TypeScript doesn't warn the library developers. This makes it extremely likely that those typings become inaccurate or out of date because of simple mistakes like forgetting to update typings. Sometimes libraries will add a property and forget to add that on their typings. This makes it usable for JavaScript developers but not for TypeScript devs. For TypeScript developers, typings are everything! Discordeno treats typings as part of it's code! A breaking change in typings is a breaking change for the library!
Whenever I used other libraries, I was always seeing typings being inaccurate or
problematic. This is because in any Discord API library, the majority is not
used by the library itself so TypeScript doesn't warn the library developers.
This makes it extremely likely that those typings become inaccurate or out of
date because of simple mistakes like forgetting to update typings. Sometimes
libraries will add a property and forget to add that on their typings. This
makes it usable for JavaScript developers but not for TypeScript devs. For
TypeScript developers, typings are everything! Discordeno treats typings as part
of it's code! A breaking change in typings is a breaking change for the library!
## How Stable Is Discordeno?
One of the biggest issues with almost every library(I have used) is stability. None of the libraries gave much love and attention to TypeScript developers the way it deserves. Sometimes TypeScript projects would break because breaking changes to typings did not make a MAJOR bump so TypeScript bots in production would break. Sometimes I was personally maintaing the typings because no one else was for that lib. Some libs were pre 1.0 and didn't even have a stable branch/version where I would not have to worry about breaking changes.
One of the biggest issues with almost every library(I have used) is stability.
None of the libraries gave much love and attention to TypeScript developers the
way it deserves. Sometimes TypeScript projects would break because breaking
changes to typings did not make a MAJOR bump so TypeScript bots in production
would break. Sometimes I was personally maintaing the typings because no one
else was for that lib. Some libs were pre 1.0 and didn't even have a stable
branch/version where I would not have to worry about breaking changes.
This is why I made it one of my foundational goals of this library to have the best stability for TypeScript developers. No matter how small, a breaking change is a breaking change when it affects the public API. I could care less if we end up at version 500. Being afraid to bump a MAJOR because it's a small change or a typing change is a terrible decision as a library maintainer and destroys the experience for end users.
This is why I made it one of my foundational goals of this library to have the
best stability for TypeScript developers. No matter how small, a breaking change
is a breaking change when it affects the public API. I could care less if we end
up at version 500. Being afraid to bump a MAJOR because it's a small change or a
typing change is a terrible decision as a library maintainer and destroys the
experience for end users.
## Why Doesn't Discordeno Use Classes or EventEmitter?
This is a design decision for the lib itself. You can still use class if you want on your bot. In fact, I hope someone makes a framework/boilerplate 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 lib, I will just link to the best resources I believe help explain it.
This is a design decision for the lib itself. You can still use class if you
want on your bot. In fact, I hope someone makes a framework/boilerplate 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 lib, I will just link to the best resources 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)
- [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 it's core is simply just functions that run when a certain event is emitted. In Discordeno, that function is executed instead of emitting some event to trigger that function.
In regards to EventEmitter, I believe a functional event API was a much better
choice. EventEmitter at it's core is simply just functions that run when a
certain event is emitted. In Discordeno, that function is executed instead of
emitting some event to trigger that function.
```typescript
// EventEmitter Example
EventEmitter.emit('guildCreate', guild);
EventEmitter.emit("guildCreate", guild);
// Discordeno Example
eventHandlers.guildCreate?.(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.
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.
```typescript
updateEventHandlers(newEventHandlers)
updateEventHandlers(newEventHandlers);
```
## 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.
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!
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.
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 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.
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 { Errors, Message } from "https://deno.land/x/discordeno@10.0.0/mod.ts";
@@ -57,7 +126,9 @@ import { Errors, Message } from "https://deno.land/x/discordeno@10.0.0/mod.ts";
export function handleCommandError(message: Message, type: Errors) {
switch (type) {
case Errors.MISSING_MANAGE_NICKNAMES:
return message.channel.sendMessage("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.");
return message.channel.sendMessage(
"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 message.channel.sendMessage(i18n.translate(type));
+38 -15
View File
@@ -1,12 +1,17 @@
# 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.
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 website serves as the purpose for introducing Discordeno to developers. The full documentation for all the functions and methods can be visited by clicking the link below:
This website serves as the purpose for introducing Discordeno to developers. The
full documentation for all the functions and methods can be visited by clicking
the link below:
[View Documentation on Deno](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
## Useful Links
- [GitHub Repository](https://github.com/discordeno/discordeno)
- [Deno Page](https://deno.land/x/discordeno)
- [Website](https://discordeno.mod.land)
@@ -19,28 +24,42 @@ This website serves as the purpose for introducing Discordeno to developers. The
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.
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!
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.**
> 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@10.0.0/mod.ts";
```
## Example Usage
Starting with Discordeno is very simple, you can start from scratch without any boilerplates/frameworks: Add this snippet of code into a new TypeScript file:
Starting with Discordeno is very simple, you can start from scratch without any
boilerplates/frameworks: Add this snippet of code into a new TypeScript file:
```ts
import { startBot, Intents } from "https://deno.land/x/discordeno@10.0.0/mod.ts";
import {
Intents,
startBot,
} from "https://deno.land/x/discordeno@10.0.0/mod.ts";
import config from "./config.ts";
startBot({
@@ -54,17 +73,21 @@ startBot({
if (message.content === "!ping") {
message.reply("Pong");
}
}
}
},
},
});
```
## Tutorials
Below you will find youtube playlists that display channels using Discordeno for their tutorials.
Below you will find youtube playlists that display channels using Discordeno for
their tutorials.
Web-Mystery 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 in Deno in Docker](https://web-mystery.com/articles/running-discord-bot-written-deno-docker)
- [Making a Discord bot with Deno and
Discordeno](https://web-mystery.com/articles/making-discord-bot-deno-and-discordeno)
- [Running a Discord bot written in Deno in
Docker](https://web-mystery.com/articles/running-discord-bot-written-deno-docker)
- YouTube Tutorials:
- [Discordeno Bot Tutorials YouTube series](https://youtu.be/rIph9-BGsuQ)
+10 -4
View File
@@ -2,14 +2,20 @@
## Understanding The Goals of This Guide
This guide is a step by step simple walkthrough meant for beginner developers but also can be quite useful for advanced developers to learn about Discordeno.
This guide is a step by step simple walkthrough meant for beginner developers
but also can be quite useful for advanced developers to learn about Discordeno.
## Who Made Discordeno?
Skillz4Killz. However, there are a lot of other contributors to the project as well. Join the discord server to meet and learn more about the wonderful team!
Skillz4Killz. However, there are a lot of other contributors to the project as
well. Join the discord server to meet and learn more about the wonderful team!
## Why You Should Use Discordeno?
Discordeno provides you all the tools that you need to make bot development really easy. All the tools are already made for you to just simply copy and use for yourself. We will go through each and every tool in other sections of this guide.
Discordeno provides you all the tools that you need to make bot development
really easy. All the tools are already made for you to just simply copy and use
for yourself. We will go through each and every tool in other sections of this
guide.
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.
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.
+63 -26
View File
@@ -1,22 +1,32 @@
# Creating The Bot!
Discordeno will help make Discord bot development much easier. Don't worry, as you go through this guide it will make a lot more sense.
Discordeno will help make Discord bot development much easier. Don't worry, as
you go through this guide it will make a lot more sense.
## Creating The Bot!
> This guide is going to assume you already have the basic requirements to make a bot ready. This includes github, git, a code editor like Visual Studio Code. If you don't have these yet please prepare them first before going forward.
> This guide is going to assume you already have the basic requirements to make
> a bot ready. This includes github, git, a code editor like Visual Studio Code.
> If you don't have these yet please prepare them first before going forward.
- First, create a Discordeno Bot using the [Generator Boilerplate](https://github.com/discordeno/discordeno-bot-template/generate). Give it any name you like. For the purpose of this guide we will call it, Stargate.
- First, create a Discordeno Bot using the
[Generator Boilerplate](https://github.com/discordeno/discordeno-bot-template/generate).
Give it any name you like. For the purpose of this guide we will call it,
Stargate.
- Then `git clone https://github.com/Skillz4Killz/Stargate.git` Replace **Stargate** with the name you chose.
- Then `git clone https://github.com/Skillz4Killz/Stargate.git` Replace
**Stargate** with the name you chose.
- When that is done, go ahead and open up the folder with VSC.
- Create a new file called `configs.ts`. Open the `configs.example.ts` file and copy everything over.
- Create a new file called `configs.ts`. Open the `configs.example.ts` file and
copy everything over.
Let's take a minute to review all the options we have available to us.
### Configs File
The `configs.ts` file is where you will keep all your secret info you don't want to share with anyone else. As long as `.gitignore` file is ignoring configs.ts your configurations will be kept private!
The `configs.ts` file is where you will keep all your secret info you don't want
to share with anyone else. As long as `.gitignore` file is ignoring configs.ts
your configurations will be kept private!
```ts
// Step 1: Remove the `.example` from this file name so it is called `configs.ts`
@@ -45,7 +55,7 @@ export const configs = {
// When a translation is missing this is the channel you will be alerted in.
missingTranslation: "",
// When an error occurs, we will try and log it to this channel
errorChannelID: ""
errorChannelID: "",
},
// These are the role ids that will enable some functionality.
roleIDs: {
@@ -59,68 +69,95 @@ export const configs = {
// The user ids for the other devs on your team
botDevs: [],
// The user ids who have complete 100% access to your bot
botOwners: []
}
botOwners: [],
},
};
```
#### Token
First, add your bot token. This is **required** for the bot to start. Review the [instructions](https://discordeno.mod.land/gettingstarted.html#creating-your-first-discord-bot-application) if you have not made your token yet.
First, add your bot token. This is **required** for the bot to start. Review the
[instructions](https://discordeno.mod.land/gettingstarted.html#creating-your-first-discord-bot-application)
if you have not made your token yet.
#### Prefix
The prefix is where you will set the default prefix for your bot. Don't worry, every server will be able to choose their own prefix but we need a default prefix to start.
The prefix is where you will set the default prefix for your bot. Don't worry,
every server will be able to choose their own prefix but we need a default
prefix to start.
#### Bot Lists Tokens
This section of the file is so you can easily have your bot's statistics updated on all the bot lists out there to help you grow your bot. The code is already written to handle this but you will need to do 3 things for each bot list.
This section of the file is so you can easily have your bot's statistics updated
on all the bot lists out there to help you grow your bot. The code is already
written to handle this but you will need to do 3 things for each bot list.
1. Go to the bot list and add your bot to their website. (Each site has it's own instructions)
1. Go to the bot list and add your bot to their website. (Each site has it's own
instructions)
2. Create a token for yourself on each website and paste it in your configs.
3. Enjoy!
If you wish to customize the code, you will find the bot list tasks in the tasks folder. Don't worry we will discuss this when we get to the tasks section of the guide.
If you wish to customize the code, you will find the bot list tasks in the tasks
folder. Don't worry we will discuss this when we get to the tasks section of the
guide.
For now just remember, that Discordeno provides you a built in way to update most discord bot lists.
For now just remember, that Discordeno provides you a built in way to update
most discord bot lists.
#### Channel IDs
The channelIDs section holds the channel IDs that are useful for specific features to help give you alerts/notifications. For example, the `missingTranslation` channel will be where messages are sent alerting you that somewhere in your code you are trying to use a translation key that you never created.
The channelIDs section holds the channel IDs that are useful for specific
features to help give you alerts/notifications. For example, the
`missingTranslation` channel will be where messages are sent alerting you that
somewhere in your code you are trying to use a translation key that you never
created.
When you get to around 25,000 Discord servers on your bot, you may want to convert these channel IDs to webhooks.
When you get to around 25,000 Discord servers on your bot, you may want to
convert these channel IDs to webhooks.
#### Role IDs
The roleIDs section holds the role IDs that are useful for specific features. For example, the `patreonVIPRoleID` role can be used easily to enable vip features later in this guide.
The roleIDs section holds the role IDs that are useful for specific features.
For example, the `patreonVIPRoleID` role can be used easily to enable vip
features later in this guide.
#### User IDs
The userIDs section holds the user IDs that are useful for specific features. For example, the `botDevs` and such are useful for permission levels as you will see in the Permission Level part of the guide.
The userIDs section holds the user IDs that are useful for specific features.
For example, the `botDevs` and such are useful for permission levels as you will
see in the Permission Level part of the guide.
## It's Alive!
Oh my god! You now have a bot with a bunch of features already! You don't believe me? Well, seeing is believing, so start the bot.
Oh my god! You now have a bot with a bunch of features already! You don't
believe me? Well, seeing is believing, so start the bot.
1. Invite your bot to a discord server.
2. Open up the integrated terminal in VSC using **CTRL + `**. If you use a Mac, replace CTRL with CMD.
2. Open up the integrated terminal in VSC using **CTRL + `**. If you use a Mac,
replace CTRL with CMD.
3. Run the script below:
```shell
deno run -A --no-check mod.ts
```
> The `-A` flag will grant it all permissions to run the bot. If you would like to specify specific ones you may do so!
> The `-A` flag will grant it all permissions to run the bot. If you would like
> to specify specific ones you may do so!
> The `--no-check` flag is used for module augmentation support as Deno still does not provide a clean way to have it. If you don't use custom structures, this is not needed.
> The `--no-check` flag is used for module augmentation support as Deno still
> does not provide a clean way to have it. If you don't use custom structures,
> this is not needed.
The first time you run it, you may see a lot of files being loaded. This is preparing all the magic behind the scene. Once it is ready, you will see something like this:
The first time you run it, you may see a lot of files being loaded. This is
preparing all the magic behind the scene. Once it is ready, you will see
something like this:
![image](https://i.imgur.com/TOXjLgh.png)
## Understanding What Discordeno Did
Discordeno includes these commands/folders as they are essential for any discord bot to have in order to meet the Discord Bot Best Practices. It also adds a few things that will help make some things easier to build a bot.
Discordeno includes these commands/folders as they are essential for any discord
bot to have in order to meet the Discord Bot Best Practices. It also adds a few
things that will help make some things easier to build a bot.
We will dive into these deeper in this guide. Let's take it step by step.
+278 -97
View File
@@ -1,14 +1,17 @@
# Creating A Command
Really great job. Now, lets dive into trying to use some of the commands and try to make our very own command.
Really great job. Now, lets dive into trying to use some of the commands and try
to make our very own command.
## Editing Invite Command
Let's first start by taking an existing command and slightly modifying it to your needs. Let's use the `Invite` command as our example. When you open the command, you will see something like this:
Let's first start by taking an existing command and slightly modifying it to
your needs. Let's use the `Invite` command as our example. When you open the
command, you will see something like this:
```ts
import { botCache } from "../../deps.ts";
import { createCommand } from "../utils/helpers.ts"
import { createCommand } from "../utils/helpers.ts";
createCommand({
name: "invite",
@@ -21,20 +24,32 @@ createCommand({
});
```
Let's break this down. The first two lines are importing the necessary things from their files so we can use them in this command. Don't worry if this doesn't make sense, most of the time, this will all be done automatically for you if you use a good code editor like Visual Studio Code. We create a command by doing:
Let's break this down. The first two lines are importing the necessary things
from their files so we can use them in this command. Don't worry if this doesn't
make sense, most of the time, this will all be done automatically for you if you
use a good code editor like Visual Studio Code. We create a command by doing:
```ts
createCommand({
name: "commandname",
})
});
```
The command name here must be unique. If it is not unique, your commands will not work properly as you wish. For example, you can't have 2 commands with the `ping` name. In this case, our command is called `invite`.
The command name here must be unique. If it is not unique, your commands will
not work properly as you wish. For example, you can't have 2 commands with the
`ping` name. In this case, our command is called `invite`.
Next is the `execute`. This is where the magic happens. This is the function that is triggered when the command is ran by a user. In this case, the bot simply sends a message to a channel where the command was ran and sends an invite link. Let's take a minute and optimize this for our case. We don't **NEED** admin permissions for our bot. Let's go to the [permission calculator](https://discordapi.com/permissions.html) and figure out the permissions we need for our bot.
Once you have decided which permissions you want to request, you can copy the number and replace it here. For example, if we want `68640` as our permissions. We can modify our command to be:
Next is the `execute`. This is where the magic happens. This is the function
that is triggered when the command is ran by a user. In this case, the bot
simply sends a message to a channel where the command was ran and sends an
invite link. Let's take a minute and optimize this for our case. We don't
**NEED** admin permissions for our bot. Let's go to the
[permission calculator](https://discordapi.com/permissions.html) and figure out
the permissions we need for our bot.
Once you have decided which permissions you want to request, you can copy the
number and replace it here. For example, if we want `68640` as our permissions.
We can modify our command to be:
```ts
`https://discordapp.com/oauth2/authorize?client_id=${botID}&scope=bot&permissions=68640`,
@@ -44,9 +59,18 @@ Lastly, let's get rid of the comment there as now it is customized to our needs.
### Command Descriptions
Right now, if you were to go to Discord and type `!help invite`, you would see quite a bit of information. But where does all that text come from? It's not in this file. The description option tells Discordeno what to show the user when they use the help command. In that help message, each command has a small description next to it. The magic is happening because of Discordeno's translations feature.
Right now, if you were to go to Discord and type `!help invite`, you would see
quite a bit of information. But where does all that text come from? It's not in
this file. The description option tells Discordeno what to show the user when
they use the help command. In that help message, each command has a small
description next to it. The magic is happening because of Discordeno's
translations feature.
> Discordeno has built in multi-lingual support. You can support as many languages as you wish. We will learn more about languages when we create our own language but for now we just want to see how to customize the command. What we are going to do below isn't the correct way but it's the easy way to learn. We will see the correct way to do it when we get to translations.
> Discordeno has built in multi-lingual support. You can support as many
> languages as you wish. We will learn more about languages when we create our
> own language but for now we just want to see how to customize the command.
> What we are going to do below isn't the correct way but it's the easy way to
> learn. We will see the correct way to do it when we get to translations.
Let's add a custom description to our invite command.
@@ -55,19 +79,26 @@ name: "invite",
description: "Like the bot? Use this link to add it to your server!",
```
🎉 It's that simple. So let's restart the bot and see how it changed. Use **CTRL + C** to shut down the bot. Then run the command from earlier.
🎉 It's that simple. So let's restart the bot and see how it changed. Use
**CTRL + C** to shut down the bot. Then run the command from earlier.
```shell
deno run -A --no-check mod.ts
```
To access this easily, most likely all you need to do is press the **UP ARROW** key. Feel free to copy paste this if it doesn't work.
To access this easily, most likely all you need to do is press the **UP ARROW**
key. Feel free to copy paste this if it doesn't work.
Once the bot is started try `!help invite` again and you will see the change! 🎉
## Command Aliases
Now, let's make this a little bit easier. What if we wanted this command to also work with other names besides `invite`. For example, as a shortcut what if users could just type `!inv` or as an alias type `!join`. Let's go ahead and set that up. Add a `aliases` property, I usually recommend under the name property. Aliases takes an array of strings that will be the names you would like to use as aliases.
Now, let's make this a little bit easier. What if we wanted this command to also
work with other names besides `invite`. For example, as a shortcut what if users
could just type `!inv` or as an alias type `!join`. Let's go ahead and set that
up. Add a `aliases` property, I usually recommend under the name property.
Aliases takes an array of strings that will be the names you would like to use
as aliases.
Let's add in the two aliases we wanted.
@@ -77,35 +108,55 @@ aliases: ["inv", "join"],
description: "Like the bot? Use this link to add it to your server!",
```
Notice, I added 2 aliases here. You can add as many aliases as you like. If you see a lot of users typing it wrong by accident you can add those typos as aliases as well.
Notice, I added 2 aliases here. You can add as many aliases as you like. If you
see a lot of users typing it wrong by accident you can add those typos as
aliases as well.
Let's start testing this out. But first, let's understand something important.
## Reloading
Okay, we need to first understand a core concept of developing discord bots. If you had to restart your bot every time you made a tiny change, it would get really annoying/tedious to do. Also, if you had restarted your bot 1000 times, you would be in big trouble because Discord would ban your bot for 24 hours. The bigger your bot gets, the faster it is to hit this limit of 1000 identify calls to the Discord API.
Okay, we need to first understand a core concept of developing discord bots. If
you had to restart your bot every time you made a tiny change, it would get
really annoying/tedious to do. Also, if you had restarted your bot 1000 times,
you would be in big trouble because Discord would ban your bot for 24 hours. The
bigger your bot gets, the faster it is to hit this limit of 1000 identify calls
to the Discord API.
To solve this issue, Discordeno provides you with a really cool `reload` command. Whenever you make a change to any command, event, monitor, inhibitor, task, language or argument you can simply reload it. You don't need to restart your bot every time.
To solve this issue, Discordeno provides you with a really cool `reload`
command. Whenever you make a change to any command, event, monitor, inhibitor,
task, language or argument you can simply reload it. You don't need to restart
your bot every time.
Now since we added the aliases above, let's test it out.
1. `!inv` this will not work
2. `!reload commands` this will reload the files and the aliases will be created.
2. `!reload commands` this will reload the files and the aliases will be
created.
3. `!inv` This will now work. 🎉
---
Perfect! You can take some time and customize the invite command to your needs if you wish. For example, this is my bot's invite command.
Perfect! You can take some time and customize the invite command to your needs
if you wish. For example, this is my bot's invite command.
![image](https://i.imgur.com/4WRFMtR.png)
Nice! You can now customize any of the commands from Discordeno as you wish. Then we will continue to making our very own command so we can learn about all the options that Discordeno gives us when it comes to making commands. As you learn more in this guide, you can keep improving it.
Nice! You can now customize any of the commands from Discordeno as you wish.
Then we will continue to making our very own command so we can learn about all
the options that Discordeno gives us when it comes to making commands. As you
learn more in this guide, you can keep improving it.
If you get stuck, don't worry. When you are ready, let's continue to the next step.
If you get stuck, don't worry. When you are ready, let's continue to the next
step.
## Creating A Command
Let's make a command that will allow guild admins to give or take roles from a member. Since, we are creating a `Moderation` command to give or take roles, let's go ahead and create a category folder called `Moderation` and then create a file called `role.ts`. Once the file is made, you can paste this following base snippet to make our first command.
Let's make a command that will allow guild admins to give or take roles from a
member. Since, we are creating a `Moderation` command to give or take roles,
let's go ahead and create a category folder called `Moderation` and then create
a file called `role.ts`. Once the file is made, you can paste this following
base snippet to make our first command.
```ts
import { botCache } from "../../deps.ts";
@@ -131,82 +182,125 @@ createCommand({
arguments: [],
execute: function (message, args, guild) {
// The code for your command goes here
}
},
});
```
## Understanding Command Options
Woah! We just added a massive file but most of that stuff is new to us. Don't worry, let's break everything down step by step.
Woah! We just added a massive file but most of that stuff is new to us. Don't
worry, let's break everything down step by step.
> **Note:** Any options that are not changed from the snippet above can actually be deleted as Discordeno will use the default option if you do not provide anything for that option. This can help keep your files cleaner.
> **Note:** Any options that are not changed from the snippet above can actually
> be deleted as Discordeno will use the default option if you do not provide
> anything for that option. This can help keep your files cleaner.
Before we start, quickly update the command name and description.
## dmOnly & guildOnly Options
The `dmOnly` option is available if you want to make sure this command only runs in a direct message. If this command is ran in a server, the command will immediately exit out.
The `dmOnly` option is available if you want to make sure this command only runs
in a direct message. If this command is ran in a server, the command will
immediately exit out.
On the other hand, `guildOnly` is the opposite. To easily make sure that this command is never allowed in a private message you can simple enable this.
On the other hand, `guildOnly` is the opposite. To easily make sure that this
command is never allowed in a private message you can simple enable this.
If you want to allow a command to be able to run both in a server and in a direct message, simply set them both to false.
If you want to allow a command to be able to run both in a server and in a
direct message, simply set them both to false.
Remember if you want either of them as false, you can simply delete them and it will default to `false` allowing your bot to be cleaner and easier to read. However, if your prefer keeping it you can.
Remember if you want either of them as false, you can simply delete them and it
will default to `false` allowing your bot to be cleaner and easier to read.
However, if your prefer keeping it you can.
For the purpose of this guide, we want our `role` comamnd to only be run in a server, so we can set `guildOnly` to be **true** and delete the `dmOnly` option.
For the purpose of this guide, we want our `role` comamnd to only be run in a
server, so we can set `guildOnly` to be **true** and delete the `dmOnly` option.
## NSFW Option
NSFW stands for **Not Safe For Work**. One of Discord's rules is that you enforce that NSFW content is sent only in NSFW channels. Discordeno has this built in. You simply tell Discordeno, that you want a command to be considered `nsfw` or not.
NSFW stands for **Not Safe For Work**. One of Discord's rules is that you
enforce that NSFW content is sent only in NSFW channels. Discordeno has this
built in. You simply tell Discordeno, that you want a command to be considered
`nsfw` or not.
If this option is enabled, this command will only be able to be used in a `nsfw` channel on a server.
If this option is enabled, this command will only be able to be used in a `nsfw`
channel on a server.
> Discord does not consider Direct Messages as nsfw safe!
## Permission Level Option
Permission levels are a very powerful feature that easily allow you to set what permission is required to run a command. We will cover this in detail, in a separate section dedicated to permission levels. For now, just understand that this will restrict certain users from using a command.
Permission levels are a very powerful feature that easily allow you to set what
permission is required to run a command. We will cover this in detail, in a
separate section dedicated to permission levels. For now, just understand that
this will restrict certain users from using a command.
For example, the role command we only want to be used by moderators or server admins.
For example, the role command we only want to be used by moderators or server
admins.
Discordeno makes this pretty simple for us.
```ts
permissionLevels: [PermissionLevels.MODERATOR, PermissionLevels.ADMIN]
permissionLevels:
[PermissionLevels.MODERATOR, PermissionLevels.ADMIN];
```
We're not going to cover this in detail here because we will have an entire in depth guide for permission levels. If you want to pause and learn it now, feel free: [Permission Levels Advanced Guide](https://discordeno.mod.land/advanced/permlevels.html)
We're not going to cover this in detail here because we will have an entire in
depth guide for permission levels. If you want to pause and learn it now, feel
free:
[Permission Levels Advanced Guide](https://discordeno.mod.land/advanced/permlevels.html)
## Permissions Check Options
Discordeno provides you 4 different types of permission handling that will be done before the command function is executed. This allows you to make sure that the permissions are available before running a command. For example, the role command will require the bot to have **MANAGE ROLES** permission. This permission comes from the server settings so we can require this in the `botServerPermissions`.
Discordeno provides you 4 different types of permission handling that will be
done before the command function is executed. This allows you to make sure that
the permissions are available before running a command. For example, the role
command will require the bot to have **MANAGE ROLES** permission. This
permission comes from the server settings so we can require this in the
`botServerPermissions`.
```ts
botServerPermissions: ["MANAGE_ROLES"],
```
Discordeno will provide you easy to use auto-completion as soon as you type a single letter or two. This will help make it easier to use and will warn you when you make a typo and provide a wrong permission by accident.
Discordeno will provide you easy to use auto-completion as soon as you type a
single letter or two. This will help make it easier to use and will warn you
when you make a typo and provide a wrong permission by accident.
We also want to make sure that the bot can send a message in this channel so that we can respond to the user. This will check the correct permission heirarchy to make sure that the bot can indeed send a message in this channel.
We also want to make sure that the bot can send a message in this channel so
that we can respond to the user. This will check the correct permission
heirarchy to make sure that the bot can indeed send a message in this channel.
```ts
botChannelPermissions: ["SEND_MESSAGES"],
```
> **Note:** If you want to send an embed response, you should also make sure it has the `EMBED_LINKS` permission.
> **Note:** If you want to send an embed response, you should also make sure it
> has the `EMBED_LINKS` permission.
Already we have prevented two of the largest errors that can get our bot banned if it is not checked. Discordeno handles this internally so you don't have to worry about it.
Already we have prevented two of the largest errors that can get our bot banned
if it is not checked. Discordeno handles this internally so you don't have to
worry about it.
Sometimes, you may also want to make sure that the user using the command has a certain permission to run the command. To do this, you can use the `userServerPermissions` or the `userChannelPermissions`.
Sometimes, you may also want to make sure that the user using the command has a
certain permission to run the command. To do this, you can use the
`userServerPermissions` or the `userChannelPermissions`.
## Cooldown Options
The cooldown options go hand in hand together.
- `seconds` is how long to make the user wait in seconds before they can use the command again. By default, there is no wait time aka 0 seconds.
- `allowedUses` is how many times a user is allowed to use a command before they are placed on cooldown.
- `seconds` is how long to make the user wait in seconds before they can use the
command again. By default, there is no wait time aka 0 seconds.
- `allowedUses` is how many times a user is allowed to use a command before they
are placed on cooldown.
Let's use our command as an example. We don't want someone to start spamming the role command so we can add a cooldown of 60 seconds. However, this would mean every time you give a role to someone, you would need to wait a whole minute to give a role to someone else. This is where `allowedUses` shines! Let's set it to 5 so that a user can use this command 5 times in a row before being asked to wait for 60 seconds.
Let's use our command as an example. We don't want someone to start spamming the
role command so we can add a cooldown of 60 seconds. However, this would mean
every time you give a role to someone, you would need to wait a whole minute to
give a role to someone else. This is where `allowedUses` shines! Let's set it to
5 so that a user can use this command 5 times in a row before being asked to
wait for 60 seconds.
```ts
cooldown: {
@@ -215,7 +309,10 @@ cooldown: {
},
```
> **Note:** It is important to understand the way these two interact. The cooldown starts as soon as the first command is used. So, if a user uses the role command 5 times in 59 seconds, they only have to wait 1 second to do another 5 times.
> **Note:** It is important to understand the way these two interact. The
> cooldown starts as soon as the first command is used. So, if a user uses the
> role command 5 times in 59 seconds, they only have to wait 1 second to do
> another 5 times.
Let's give this a try shall we.
@@ -224,40 +321,58 @@ Let's give this a try shall we.
## Arguments
Arguments is an incredibly powerful feature in Discordeno. Like Permission Levels we will have an in depth look at arguments later in the guide. For now, let's try and understand this in a simple manner.
Arguments is an incredibly powerful feature in Discordeno. Like Permission
Levels we will have an in depth look at arguments later in the guide. For now,
let's try and understand this in a simple manner.
- `name`: The name of the argument. Useful for when you need to alert the user X arg is missing or when you want to use the arg in your command.
- `type`: The type of the argument you would like. Defaults to string. Some of the ones available by default are "number", "string", "...string", "boolean", "subcommand", "member".
- `missing`: a function that will be run when this argument is not provided by the user or if the provided argument is not valid. By default, it does nothing.
- `required`: Whether this argument is required or optional. By default, this is true.
- `name`: The name of the argument. Useful for when you need to alert the user X
arg is missing or when you want to use the arg in your command.
- `type`: The type of the argument you would like. Defaults to string. Some of
the ones available by default are "number", "string", "...string", "boolean",
"subcommand", "member".
- `missing`: a function that will be run when this argument is not provided by
the user or if the provided argument is not valid. By default, it does
nothing.
- `required`: Whether this argument is required or optional. By default, this is
true.
- `lowercase`: If the type is a string, this can forcibly lowercase the string.
- `literals`: If the type is string or subcommand you can provide literals. The argument MUST be exactly the same as the literals to be accepted. For example, if you want to make sure the argument is `a` or `b` only and not `c`.
- `defaultValue`: The default value for this argument/subcommand. If the user does not provide an argument or it is invalid, it can be defaulted to a certain value.
- `literals`: If the type is string or subcommand you can provide literals. The
argument MUST be exactly the same as the literals to be accepted. For example,
if you want to make sure the argument is `a` or `b` only and not `c`.
- `defaultValue`: The default value for this argument/subcommand. If the user
does not provide an argument or it is invalid, it can be defaulted to a
certain value.
For our role command, we need a `member` and a `role`.
```ts
arguments: [
arguments:
[
{
name: "member",
type: "member",
missing: (message) => {
message.sendResponse(`You did not provide a member to give the role to. You can provide a @member mention, a member ID, or try using their nickname/username. The nickname/username will only work if they have been active in your server recently.`)
}
message.sendResponse(
`You did not provide a member to give the role to. You can provide a @member mention, a member ID, or try using their nickname/username. The nickname/username will only work if they have been active in your server recently.`,
);
},
},
{
name: "role",
type: "role",
missing: (message) => {
message.sendResponse(`You did not provide a role to give. You can provide a @role mention, a role ID, or it's name. If the role name did not work, try to use the **roleinfo** command to get the role ID.`)
}
}
]
message.sendResponse(
`You did not provide a role to give. You can provide a @role mention, a role ID, or it's name. If the role name did not work, try to use the **roleinfo** command to get the role ID.`,
);
},
},
];
```
## Execute Option
The execute option is the code that will run when the command is triggered. The execute function is passed 3 parameters that you can use in your command.
The execute option is the code that will run when the command is triggered. The
execute function is passed 3 parameters that you can use in your command.
- `message`: The message object itself that triggered this command.
- `args`: The args that were provided by the user.
@@ -269,14 +384,18 @@ execute: function (message, args, guild) {
}
```
> **Note:** I prefer writing code using the function keyword, but you can easily change to the fat arrow function if you prefer.
> **Note:** I prefer writing code using the function keyword, but you can easily
> change to the fat arrow function if you prefer.
> ```ts
> execute: (message, args, guild) => {
> execute:
> (message, args, guild) => {
> // The code that is to be executed goes here
> }
> };
> ```
Let's start out by writing some pseudo-code (comments that will help us plan the code).
Let's start out by writing some pseudo-code (comments that will help us plan the
code).
```ts
execute: function (message, args, guild) {
@@ -304,7 +423,8 @@ execute: function (message, args, guild) {
Nice! The plan for the code is complete. Now, let's add in the code.
The first thing we are going to try is to check if the role the user provided is the same as the everyone role.
The first thing we are going to try is to check if the role the user provided is
the same as the everyone role.
> **Note:** The everyone role ID is the same as the server guild ID.
@@ -312,11 +432,16 @@ To do this, we are going to want something like this:
```ts
if (args.role.id === message.guildID) {
return message.sendResponse("The everyone role can not be given to anyone because everyone has the everyone role already. *Keep calm and let Carter figure it out*!");
return message.sendResponse(
"The everyone role can not be given to anyone because everyone has the everyone role already. *Keep calm and let Carter figure it out*!",
);
}
```
You might be seeing an error since we didn't provide the accurate typings for the `args` parameter. The message and guild parameter is automated but the args is dynamic depending on your `arguments` option for your command. So let's do this real quick.
You might be seeing an error since we didn't provide the accurate typings for
the `args` parameter. The message and guild parameter is automated but the args
is dynamic depending on your `arguments` option for your command. So let's do
this real quick.
```ts
import { Role, Member } from "../../deps.ts";
@@ -341,7 +466,6 @@ interface RoleArgs {
Awesome! Let's keep going.
```ts
// Make the function asynchronous
execute: async function (message, args: RoleArgs, guild) {
@@ -392,7 +516,14 @@ execute: async function (message, args: RoleArgs, guild) {
The final version of the command should look something like this:
```ts
import { botCache, highestRole, higherRolePosition, botID, Role, Member } from "../../deps.ts";
import {
botCache,
botID,
higherRolePosition,
highestRole,
Member,
Role,
} from "../../deps.ts";
import { PermissionLevels } from "../types/commands.ts";
import { createCommand } from "../utils/helpers.ts";
@@ -410,16 +541,20 @@ createCommand({
name: "member",
type: "member",
missing: (message) => {
message.sendResponse(`You did not provide a member to give the role to. You can provide a @member mention, a member ID, or try using their nickname/username. The nickname/username will only work if they have been active in your server recently.`)
}
message.sendResponse(
`You did not provide a member to give the role to. You can provide a @member mention, a member ID, or try using their nickname/username. The nickname/username will only work if they have been active in your server recently.`,
);
},
},
{
name: "role",
type: "role",
missing: (message) => {
message.sendResponse(`You did not provide a role to give. You can provide a @role mention, a role ID, or it's name. If the role name did not work, try to use the **roleinfo** command to get the role ID.`)
}
}
message.sendResponse(
`You did not provide a role to give. You can provide a @role mention, a role ID, or it's name. If the role name did not work, try to use the **roleinfo** command to get the role ID.`,
);
},
},
],
execute: async function (message, args: RoleArgs) {
// If this was the everyone role alert with a silly error
@@ -440,22 +575,32 @@ createCommand({
const botsHighestRole = await highestRole(message.guildID, botID);
// Check if the bot has a role higher than the role that it will try to give. If the role is too high alert the user.
if (!botsHighestRole || !(await higherRolePosition(
if (
!botsHighestRole || !(await higherRolePosition(
message.guildID,
botsHighestRole.id,
args.role.id,
))) {
))
) {
return message.sendResponse(
"Okay look, asking me give a role that is higher than my highest role is ridiculous! I am the first bot to admit I don't know who these people are nor do I care to. Look, if you'd like I could take you down the hall and just point at the people who annoy me more than the rest. But that's about as useful as I get.",
);
}
// Check the command author's highest role
const membersHighestRole = await highestRole(message.guildID, message.author.id);
const membersHighestRole = await highestRole(
message.guildID,
message.author.id,
);
// If the author does not have a role high enough to give this role alert
if (!membersHighestRole ||
!(await higherRolePosition(message.guildID, membersHighestRole.id, args.role.id))
if (
!membersHighestRole ||
!(await higherRolePosition(
message.guildID,
membersHighestRole.id,
args.role.id,
))
) {
return message.sendResponse(
"In my culture, whenever someone tries to give a role that is higher than their highest role, I would be well within my rights to dismember you.",
@@ -463,7 +608,9 @@ createCommand({
}
// If the user has this role already we should remove it
if (message.member?.guilds.get(message.guildID)?.roles.includes(args.role.id)) {
if (
message.member?.guilds.get(message.guildID)?.roles.includes(args.role.id)
) {
message.member.removeRole(
message.guildID,
args.role.id,
@@ -476,7 +623,11 @@ createCommand({
}
// Add the role to the user.
message.member?.addRole(message.guildID, args.role.id, `${message.author.username} used the role command to give this role.`);
message.member?.addRole(
message.guildID,
args.role.id,
`${message.author.username} used the role command to give this role.`,
);
// Alert the user that used the command that the user has been give the role.
return message.sendResponse(
@@ -491,19 +642,38 @@ interface RoleArgs {
}
```
> **Note:** The response strings are a play on some funny Stargate (my favorite tv show) moments. Feel free to change as you wish to fit your bot's personality.
> **Note:** The response strings are a play on some funny Stargate (my favorite
> tv show) moments. Feel free to change as you wish to fit your bot's
> personality.
## Dynamic (Advanced Level) Command Creation
This is some advanced level super magical command creation skills. Discordeno gives you the power to dynamically create commands. What that means is you can write the code for one command but dynamically create like 50 commands using that same code.
This is some advanced level super magical command creation skills. Discordeno
gives you the power to dynamically create commands. What that means is you can
write the code for one command but dynamically create like 50 commands using
that same code.
For example, in my main bot I have a lot of "fun" commands like `.hug` or `.kiss` etc... All of these files have the exact same code except for 1 word being the command name. Would it not be better to simply write the code once and dynamically create every single command. Let me show you how that works.
For example, in my main bot I have a lot of "fun" commands like `.hug` or
`.kiss` etc... All of these files have the exact same code except for 1 word
being the command name. Would it not be better to simply write the code once and
dynamically create every single command. Let me show you how that works.
- Create a file in the commands folder called `fun.ts` which will create all our fun commands.
- Create a file in the commands folder called `fun.ts` which will create all our
fun commands.
```ts
import { botCache, Member, sendMessage, chooseRandom, avatarURL } from "../../deps.ts";
import { createCommand, createCommandAliases, sendEmbed } from "../utils/helpers.ts";
import {
avatarURL,
botCache,
chooseRandom,
Member,
sendMessage,
} from "../../deps.ts";
import {
createCommand,
createCommandAliases,
sendEmbed,
} from "../utils/helpers.ts";
import { configs } from "../../configs.ts";
import { Embed } from "../utils/Embed.ts";
import { translate } from "../utils/i18next.ts";
@@ -549,11 +719,10 @@ funCommandData.forEach((data) => {
const member = args.member || message.member!;
const type = member.user.id === message.author.id
// Silly response like if user tries to hug themself
? "SELF"
// Response for when user tries to hug another user
: "OTHER";
? // Silly response like if user tries to hug themself
"SELF"
: // Response for when user tries to hug another user
"OTHER";
// Create an embed
const embed = new Embed()
@@ -577,14 +746,26 @@ interface FunArgs {
}
```
> **Note:** This is only an example of dynamic command creation and won't work if you try using this code since you won't have the configs necessary. Since this is an advanced topic we're not going to cover this in more detail here, because we will have an entire in depth guide for dynamic command creation. If you want to pause and learn it now, feel free: [Dynamic Command Creation Advanced Guide](https://discordeno.mod.land/advanced/dynamiccommands.html)
> **Note:** This is only an example of dynamic command creation and won't work
> if you try using this code since you won't have the configs necessary. Since
> this is an advanced topic we're not going to cover this in more detail here,
> because we will have an entire in depth guide for dynamic command creation. If
> you want to pause and learn it now, feel free:
> [Dynamic Command Creation Advanced Guide](https://discordeno.mod.land/advanced/dynamiccommands.html)
Take a minute to realize what just happened. This has made 18 different unique commands dynamically. In 1 file, using the same piece of code, we created so many commands. You can easily add more commands to this. For example, if you wanted to add weeb (animated) versions of these commands. Then you are at 36 commands with 1 simple command file.
Take a minute to realize what just happened. This has made 18 different unique
commands dynamically. In 1 file, using the same piece of code, we created so
many commands. You can easily add more commands to this. For example, if you
wanted to add weeb (animated) versions of these commands. Then you are at 36
commands with 1 simple command file.
**That ladies and gentleman is the power and magic of Discordeno!**
Take a minute to stand up, step back and make some room around you. Now start dancing because you just acheived one of the most complex things in bot development. You have already created about 36 commands!!!!
Take a minute to stand up, step back and make some room around you. Now start
dancing because you just acheived one of the most complex things in bot
development. You have already created about 36 commands!!!!
> **Note:** The command above uses translations which we will cover in depth in a later section of this guide.
> **Note:** The command above uses translations which we will cover in depth in
> a later section of this guide.
Once you are ready, let's proceed to making our events.
+33 -12
View File
@@ -1,14 +1,24 @@
# Creating Events!
Woah! You are almost half way done with understanding all of Discordeno. Amazing isn't it? Something you may have noticed in the last section was when the bot started up you could see a bunch of messages like `Loaded X Commands` and such. Let's jump into this part now and understand how event handling works in Discordeno.
Woah! You are almost half way done with understanding all of Discordeno. Amazing
isn't it? Something you may have noticed in the last section was when the bot
started up you could see a bunch of messages like `Loaded X Commands` and such.
Let's jump into this part now and understand how event handling works in
Discordeno.
## What Is An Event?
An event in Discordeno is a function that can be called when a specific event occurs. For the most part, events will usually be the ones that are available from Discordeno. However, you can create your own custom events as well if you wish.
An event in Discordeno is a function that can be called when a specific event
occurs. For the most part, events will usually be the ones that are available
from Discordeno. However, you can create your own custom events as well if you
wish.
## Understanding The Events
Go ahead and open up the `src/events/ready.ts` file. When you open this file, you will see the code that is triggered on the `ready` event. Whenever the bot completely starts up, Discordeno emits the `ready` event. This is when this code will be run allowing you to log these messages.
Go ahead and open up the `src/events/ready.ts` file. When you open this file,
you will see the code that is triggered on the `ready` event. Whenever the bot
completely starts up, Discordeno emits the `ready` event. This is when this code
will be run allowing you to log these messages.
```ts
import { botCache, cache } from "../../deps.ts";
@@ -27,13 +37,18 @@ botCache.eventHandlers.ready = function () {
};
```
> **Note:** Some of the code from the ready.ts file was removed here to make it easier to understand.
> **Note:** Some of the code from the ready.ts file was removed here to make it
> easier to understand.
Overall, this code is pretty self-explanatory. When the bot is ready, it logs all these things to the console for you.
Overall, this code is pretty self-explanatory. When the bot is ready, it logs
all these things to the console for you.
## Creating A Custom Event
Make a new file in the events folder called `discordLog.ts` that will send a message to a discord channel whenever we get an error so we don't need to always be watching the console to see errors. Once you made the file, go ahead and paste the base event snippet below.
Make a new file in the events folder called `discordLog.ts` that will send a
message to a discord channel whenever we get an error so we don't need to always
be watching the console to see errors. Once you made the file, go ahead and
paste the base event snippet below.
```ts
import { botCache } from "../../deps.ts";
@@ -44,7 +59,8 @@ botCache.eventHandlers.eventname = function () {
```
- Change the event name to `discordLog`
- Go to `src/types/events.ts` and add in the following code so it looks like this:
- Go to `src/types/events.ts` and add in the following code so it looks like
this:
```ts
// This interface is a placeholder that allows you to easily add on custom events for your need.
@@ -55,7 +71,7 @@ export interface CustomEvents extends EventHandlers {
Awesome, now we can get started on adding the code.
```ts
````ts
import { botCache, cache, sendMessage } from "../../deps.ts";
import { Embed } from "../utils/Embed.ts";
import { configs } from "../../configs.ts";
@@ -66,7 +82,7 @@ botCache.eventHandlers.discordLog = function (error) {
.setDescription([
"```ts",
error,
"```"
"```",
].join("\n"))
.setTimestamp();
@@ -76,12 +92,17 @@ botCache.eventHandlers.discordLog = function (error) {
// Send the message
return sendEmbed(errorChannel, embed);
};
```
````
Now that we have fully covered events, it would be a good time to get some practice here. Feel free to make more events that you would like in your bot. The following is a list of all the events available to you by the library at the time of writing this guide. There may be more or some may have been removed. I'll try to keep this updated but either way, VSC will let you know through autocompletion what is and isn't available.
Now that we have fully covered events, it would be a good time to get some
practice here. Feel free to make more events that you would like in your bot.
The following is a list of all the events available to you by the library at the
time of writing this guide. There may be more or some may have been removed.
I'll try to keep this updated but either way, VSC will let you know through
autocompletion what is and isn't available.
```ts
botUpdate?: (user: UserPayload) => unknown;
botUpdate?: (user: UserPayload) => unknown;
channelCreate?: (channel: Channel) => unknown;
channelUpdate?: (channel: Channel, cachedChannel: Channel) => unknown;
channelDelete?: (channel: Channel) => unknown;
+53 -19
View File
@@ -1,25 +1,41 @@
# Creating Inhibitors!
Woah! You are almost half way done with understanding all of Discordeno. Amazing isn't it? Something you may have noticed in the last section was there were some options that prevented some commands from running like `dmOnly` or the permission options. We created a setting to prevent the monitor from running in certain channels. What if we wanted to do prevent commands from happening? How would we prevent commands from running?
Woah! You are almost half way done with understanding all of Discordeno. Amazing
isn't it? Something you may have noticed in the last section was there were some
options that prevented some commands from running like `dmOnly` or the
permission options. We created a setting to prevent the monitor from running in
certain channels. What if we wanted to do prevent commands from happening? How
would we prevent commands from running?
## What is an Inhibitor?
Inhibitors are very similar to how monitors work. A monitor runs on every message but an inhibitor runs on every command. Remember all those command options like cooldown, permissions, permissionLevel, nsfw, etc... each and every one of these options has an inhibitor that checks commands for these settings.
Inhibitors are very similar to how monitors work. A monitor runs on every
message but an inhibitor runs on every command. Remember all those command
options like cooldown, permissions, permissionLevel, nsfw, etc... each and every
one of these options has an inhibitor that checks commands for these settings.
Let's create our own inhibitor that would prevent commands from being used if the user is not a VIP user.
Let's create our own inhibitor that would prevent commands from being used if
the user is not a VIP user.
> **Note:** It is important to remember that everything below can be done with a simple permission level as well. We will create our own custom permission levels but for the purposes of this guide and to be able to learn about Inhibitors, we will be using an inhibitor.
> **Note:** It is important to remember that everything below can be done with a
> simple permission level as well. We will create our own custom permission
> levels but for the purposes of this guide and to be able to learn about
> Inhibitors, we will be using an inhibitor.
## Creating the File
Let's start by creating a file inside the inhibitors folder called `boosted.ts` and paste the following snippet of code:
Let's start by creating a file inside the inhibitors folder called `boosted.ts`
and paste the following snippet of code:
```ts
import { botCache } from "../../deps.ts";
botCache.inhibitors.set("inhibitorname", async function (message, command, guild) {
botCache.inhibitors.set(
"inhibitorname",
async function (message, command, guild) {
// Your code goes here
});
},
);
```
Inhibitors can take up to 3 arguments.
@@ -31,10 +47,10 @@ Inhibitors can take up to 3 arguments.
## Understanding How Inhibitors Function
To block a command you have to return a truthy value.
- **return true;** If you return true the inhibitor will block the execution of the command.
To allow a command return a falsey value.
- **return false;** If you return false the inhibitor will not block the error.
- **return true;** If you return true the inhibitor will block the execution of
the command. To allow a command return a falsey value.
- **return false;** If you return false the inhibitor will not block the error.
```ts
// If the command is not VIP only we can allow this command to execute
@@ -43,38 +59,56 @@ if (!command.vipOnly) return false;
// The bot's support server
const guild = cache.guilds.get(configs.supportServerID);
// If the command author is not in the server they won't have the vip role
const member = message.member || await getMember(guild.id, message.author.id).catch(console.error);
const member = message.member ||
await getMember(guild.id, message.author.id).catch(console.error);
// Member doesn't exist so cancel the command
if (!member) {
message.sendResponse(`Sorry, but you can not use this command until you become VIP. **Close the IRIS!!!**`)
message.sendResponse(
`Sorry, but you can not use this command until you become VIP. **Close the IRIS!!!**`,
);
return true;
}
// If the user has the vip role on the support server given by patreon allow the command
if (member.guilds.get(message.guildID)?.roles.includes(configs.roleIDs.patreonVIPRoleID)) return false;
if (
member.guilds.get(message.guildID)?.roles.includes(
configs.roleIDs.patreonVIPRoleID,
)
) {
return false;
}
// Alert the user they don't have vip and can't use the command
message.sendResponse(`Sorry, but you can not use this command until you become a VIP. I'm sorry, Teal'c. We'll go to Disneyland next year. I promise.`)
message.sendResponse(
`Sorry, but you can not use this command until you become a VIP. I'm sorry, Teal'c. We'll go to Disneyland next year. I promise.`,
);
// Cancel the command since the user does not have vip
return true;
```
## Updating Command Typings
Since we need a new option on our commands we need to add that. Open the `src/types/commands.ts` file and add in the following
Since we need a new option on our commands we need to add that. Open the
`src/types/commands.ts` file and add in the following
```ts
vipOnly?: boolean;
```
Once that is added, you can go into any command and mark them as vip only commands.
Once that is added, you can go into any command and mark them as vip only
commands.
## Challenges
Hell yes! We just got through the entire inhibitor section. You have now mastered everything related to inhibitors.
Hell yes! We just got through the entire inhibitor section. You have now
mastered everything related to inhibitors.
You can now try and get a little more practice with inhibitors by trying to challenge yourself and make your own inhibitors. Don't worry if you can't think of any good use cases for inhibitors.
You can now try and get a little more practice with inhibitors by trying to
challenge yourself and make your own inhibitors. Don't worry if you can't think
of any good use cases for inhibitors.
Majority of Discordeno bots do not use many custom inhibitors because most of the necessary/important ones already come built for you in the inhibitors folder.
Majority of Discordeno bots do not use many custom inhibitors because most of
the necessary/important ones already come built for you in the inhibitors
folder.
Next we will try our hands on creating tasks.
+62 -27
View File
@@ -1,28 +1,45 @@
# Creating Languages!
Woot! You have mastered Discordeno events already. Now it's time to finally make our bot multi-lingual. Vàmanos!
Woot! You have mastered Discordeno events already. Now it's time to finally make
our bot multi-lingual. Vàmanos!
## What Is A Discordeno Language?
A Discordeno language is a folder that will hold all our responses that the bot sends. By having various different language files you can have a multi-lingual bot that can be used in different languages.
A Discordeno language is a folder that will hold all our responses that the bot
sends. By having various different language files you can have a multi-lingual
bot that can be used in different languages.
## i18next
By default, Discordeno comes built with support for i18next (one of if not the best localization libraries). If you want to learn more, go to [i18next website](https://www.i18next.com/). For now, there is probably not going to be anything you will need to learn there. As most of the functionality has already been created for you right here in Discordeno.
By default, Discordeno comes built with support for i18next (one of if not the
best localization libraries). If you want to learn more, go to
[i18next website](https://www.i18next.com/). For now, there is probably not
going to be anything you will need to learn there. As most of the functionality
has already been created for you right here in Discordeno.
## Default Language
The default language with Discordeno is American English which uses the name `en_US`. So when you open the `src/languages/` folder you will find a folder called `en_US`. This is where all the strings can be kept for your bot that can be easily translated by other translators.
The default language with Discordeno is American English which uses the name
`en_US`. So when you open the `src/languages/` folder you will find a folder
called `en_US`. This is where all the strings can be kept for your bot that can
be easily translated by other translators.
## Understanding The Folder Structure
The first folder inside the languages folder must be a language folder following the name pattern like `en_US`. So for example, if we wanted to add a Spanish (Spain) language to our bot we would create a new folder called `es_ES`.
The first folder inside the languages folder must be a language folder following
the name pattern like `en_US`. So for example, if we wanted to add a Spanish
(Spain) language to our bot we would create a new folder called `es_ES`.
You can have as many folder in here as you like. For example I can do something like `src/languages/en_US/commands/fun/hug.json`. Notice that I have created categories to help keep them categorized and easier to find. You can do it however you wish to have them. For now, just remember that files must always be `.json` files in these folders. **JSON is required.**
You can have as many folder in here as you like. For example I can do something
like `src/languages/en_US/commands/fun/hug.json`. Notice that I have created
categories to help keep them categorized and easier to find. You can do it
however you wish to have them. For now, just remember that files must always be
`.json` files in these folders. **JSON is required.**
## Adding Hug Strings
Earlier in the guide, we made a hug command. So let's make that commands translations work properly now.
Earlier in the guide, we made a hug command. So let's make that commands
translations work properly now.
- Create the `hug.json` file in the `src/languages/en_US/commands/fun/` folder.
@@ -32,9 +49,12 @@ Earlier in the guide, we made a hug command. So let's make that commands transla
}
```
Most of the time, you should start with this base. The `DESCRIPTION` key, is used in the help command and provides the description for the command. When someone types `!help hug` they would see this description you typed.
Most of the time, you should start with this base. The `DESCRIPTION` key, is
used in the help command and provides the description for the command. When
someone types `!help hug` they would see this description you typed.
In our hug command we also had 2 other keys we used. `SELF` and `OTHER` so let's add those in.
In our hug command we also had 2 other keys we used. `SELF` and `OTHER` so let's
add those in.
```json
{
@@ -44,36 +64,41 @@ In our hug command we also had 2 other keys we used. `SELF` and `OTHER` so let's
}
```
Now the `"SELF"` is pretty easy to understand but the `OTHER` has some interesting things in it so let's jump into that.
Now the `"SELF"` is pretty easy to understand but the `OTHER` has some
interesting things in it so let's jump into that.
## Translate Function
Discordeno provides you a built in function called `translate`. It takes in 3 different arguments.
Discordeno provides you a built in function called `translate`. It takes in 3
different arguments.
- `guildID` the id of the server. This is used to determine which language to use.
- `key` the unique folderpath:KEY string that will determine which string to translate.
- `guildID` the id of the server. This is used to determine which language to
use.
- `key` the unique folderpath:KEY string that will determine which string to
translate.
- `options` the variables that the strings have available to them.
i18next allows you to pass in variables that you can use when you want in your strings. If you recall from our guide ealier we passed in 2 variables.
i18next allows you to pass in variables that you can use when you want in your
strings. If you recall from our guide ealier we passed in 2 variables.
```ts
translate(
message.guildID,
`commands/fun/${data.name}:${type}`,
{ mention: message.member!.mention, user: member.mention },
)
);
```
Here we can see that we passed in:
- `mention`: The user mention who used this command. `!hug`
- `user`: The user mention of the member who was @ by the command author. `!hug @o'neill`
- `user`: The user mention of the member who was @ by the command author.
`!hug @o'neill`
## Variables
::: v-pre
Variables in i18next use the `{{}}` format. So the variable `mention` would be used by doing `{{mention}}`
:::
::: v-pre Variables in i18next use the `{{}}` format. So the variable `mention`
would be used by doing `{{mention}}` :::
## Key Rules
@@ -83,17 +108,22 @@ When you create keys in the files there are a couple rules to follow.
- ALWAYS USE UPPERCASE **OPTIONAL**
- Words are separated by `_` **OPTIONAL**
The first one is the only one that is mandatory. The other two are recommended for you.
The first one is the only one that is mandatory. The other two are recommended
for you.
## Missing Keys
Every developer forgets stuff sometimes. When you forget to translate a key, it will be marked as a missing key and you would be alerted if you have provided a channelID in the configs file, and it will also be logged in your console.
Every developer forgets stuff sometimes. When you forget to translate a key, it
will be marked as a missing key and you would be alerted if you have provided a
channelID in the configs file, and it will also be logged in your console.
## Spanish Version
Let's just create a spanish version of the hug command from above to see an example of different languages.
Let's just create a spanish version of the hug command from above to see an
example of different languages.
- Create a file called `hug.json` in the folder `src/languages/es_ES/commands/fun/`
- Create a file called `hug.json` in the folder
`src/languages/es_ES/commands/fun/`
```json
{
@@ -103,17 +133,22 @@ Let's just create a spanish version of the hug command from above to see an exam
}
```
Notice, that there are 2 thing that were **NOT** translated. The `KEY` names and the `VARIABLES`. These 2 things should never be translated. Anything else can be translated upon your needs.
Notice, that there are 2 thing that were **NOT** translated. The `KEY` names and
the `VARIABLES`. These 2 things should never be translated. Anything else can be
translated upon your needs.
## Localization Platform
i18next works perfectly with localization platforms. For example, you can easily plug in `crowdin` or `transifex` to your project.
i18next works perfectly with localization platforms. For example, you can easily
plug in `crowdin` or `transifex` to your project.
- [Transifex](https://www.transifex.com/) *This is the one I use in my bot but you can use anything you like.*
- [Transifex](https://www.transifex.com/) _This is the one I use in my bot but
you can use anything you like._
- [Crowdin](https://crowdin.com/)
## Challenge
Wow! You have even masted languages. Go ahead and jump back to the role command we made earlier and add translation support to it. Give it some love!
Wow! You have even masted languages. Go ahead and jump back to the role command
we made earlier and add translation support to it. Give it some love!
Once you are ready, let's go make our first monitor.
+57 -18
View File
@@ -1,20 +1,31 @@
# Creating Monitors!
Holy bananza! You even got the entire languages complete. You are well on your way to mastering 23 different languages. Now we are going to down and dirty with monitors.
Holy bananza! You even got the entire languages complete. You are well on your
way to mastering 23 different languages. Now we are going to down and dirty with
monitors.
## What is a monitor?
Monitors are functions that will run on every single message that is sent in every channel that your bot has permissions to read. When you want to do something on every single message that is sent, the best way to do that is to create a new monitor.
Monitors are functions that will run on every single message that is sent in
every channel that your bot has permissions to read. When you want to do
something on every single message that is sent, the best way to do that is to
create a new monitor.
## Command Handler Monitor
Discordeno come built with a monitor called `commandHandler`. This monitor runs on every message sent and figures out if it is a command and executes the command. If it is a valid command with a valid prefix, Discordeno runs that command.
Discordeno come built with a monitor called `commandHandler`. This monitor runs
on every message sent and figures out if it is a command and executes the
command. If it is a valid command with a valid prefix, Discordeno runs that
command.
Let's try and create our own monitor so we can understand it better. Suppose we wanted to build a filter that would delete any message which included a discord invite link.
Let's try and create our own monitor so we can understand it better. Suppose we
wanted to build a filter that would delete any message which included a discord
invite link.
## Creating Invite Filter Monitor
To start, we make a new file in the monitors folder called `inviteFilter.ts`. Then you can paste in the following base monitor snippet.
To start, we make a new file in the monitors folder called `inviteFilter.ts`.
Then you can paste in the following base monitor snippet.
```ts
import { botCache } from "../../deps.ts";
@@ -41,7 +52,8 @@ The monitor options are very similar in functionality with the command options.
### Monitor Name
Similar to the command name, we will specify a unique name for the monitors. In this case let's call it inviteFilter
Similar to the command name, we will specify a unique name for the monitors. In
this case let's call it inviteFilter
```ts
botCache.monitors.set("inviteFilter", {
@@ -50,21 +62,42 @@ botCache.monitors.set("inviteFilter", {
### Ignore Options
The ignore options are filters that you can use to enable/disable the monitor from running in those specific circumstances. The default option for each monitor is shown in the base snippet above.
The ignore options are filters that you can use to enable/disable the monitor
from running in those specific circumstances. The default option for each
monitor is shown in the base snippet above.
- **ignoreBots**: Should this monitor run on a message sent by a bot. In our example, if we set this false then no bot would be allowed to post links in the server. Since we don't want other bot's posting invite links either we can simply just set this to `false`.
- **ignoreOthers**: Should this monitor run on other USERS. If we set this as false, then any user will not be allowed to post discord links. Since the default option for this is already `false`, let's go ahead and just delete this line.
- **ignoreEdits**: Should this monitor run on edited messaged. If we set this as false, then it would also be filtering messages that are edited. It would be important to prevent users from editing a message and posting a discord invite link so let's keep this `false`. Since the default is false, we can just delete this line.
- **ignoreDM**: Should this monitor run on direct messages. If we set this as false, then this monitor would not run when the bot is sent a dm. Since we don't care if the users send our bot a dm with an invite link we can just simply set this to `true` Since the default is `true` for this option we can simple delete this line.
- **ignoreBots**: Should this monitor run on a message sent by a bot. In our
example, if we set this false then no bot would be allowed to post links in
the server. Since we don't want other bot's posting invite links either we can
simply just set this to `false`.
- **ignoreOthers**: Should this monitor run on other USERS. If we set this as
false, then any user will not be allowed to post discord links. Since the
default option for this is already `false`, let's go ahead and just delete
this line.
- **ignoreEdits**: Should this monitor run on edited messaged. If we set this as
false, then it would also be filtering messages that are edited. It would be
important to prevent users from editing a message and posting a discord invite
link so let's keep this `false`. Since the default is false, we can just
delete this line.
- **ignoreDM**: Should this monitor run on direct messages. If we set this as
false, then this monitor would not run when the bot is sent a dm. Since we
don't care if the users send our bot a dm with an invite link we can just
simply set this to `true` Since the default is `true` for this option we can
simple delete this line.
The default options were chosen for what the majority of monitors will use to help keep your code clean and clear.
The default options were chosen for what the majority of monitors will use to
help keep your code clean and clear.
## Permission Options
The permission options are the exact same from the commands guide. These options first make sure that either the bot or user has those necessary permissions to run this monitor. For example, our invite filter would mean we need **MANAGE MESSAGES** permission so we can delete messages sent with an invite URL.
The permission options are the exact same from the commands guide. These options
first make sure that either the bot or user has those necessary permissions to
run this monitor. For example, our invite filter would mean we need **MANAGE
MESSAGES** permission so we can delete messages sent with an invite URL.
```ts
botChannelPermissions: ["MANAGE_MESSAGES"]
botChannelPermissions:
["MANAGE_MESSAGES"];
```
## Adding The Code
@@ -92,13 +125,19 @@ botCache.monitors.set("inviteFilter", {
translate(message.guildID, `monitors/invitefilter:DELETE_REASON`),
);
// Send a message to the user so they know why the message was deleted. Then delete the response after 5 seconds to prevent spam.
message.alertResponse(translate(message.guildID, "monitors/invitefilter:DELETE_ALERT_MESSAGE"), 5)
message.alertResponse(
translate(
message.guildID,
"monitors/invitefilter:DELETE_ALERT_MESSAGE",
),
5,
);
} catch (error) {
return botCache.eventHandlers.discordLog(error)
return botCache.eventHandlers.discordLog(error);
}
},
});
```
Nice! Now take some time and add these translation keys to their appropriate files.
Once, you are ready, let's jump into creating some command inhibitors.
Nice! Now take some time and add these translation keys to their appropriate
files. Once, you are ready, let's jump into creating some command inhibitors.
+65 -25
View File
@@ -1,10 +1,21 @@
# Creating Tasks
Phenomenal! Now that you have mastered inhibitors, let's move on to one of the final parts, Tasks. A lot of times, you will want to do something over and over again. To do this, you can use tasks. You could technically just do this with `setInterval` but, with tasks you gain a little more advantage like having reloadability. The functions that are run can be easily reloaded meaning, you don't need to restart your whole bot just for little change.
Phenomenal! Now that you have mastered inhibitors, let's move on to one of the
final parts, Tasks. A lot of times, you will want to do something over and over
again. To do this, you can use tasks. You could technically just do this with
`setInterval` but, with tasks you gain a little more advantage like having
reloadability. The functions that are run can be easily reloaded meaning, you
don't need to restart your whole bot just for little change.
## What is a Task?
A task is used to conduct a function every so often. For example, Discordeno comes with a built in task called `botlists`. This task runs every so often and updates your data in all the bot lists apis. Let's create a new task to really understand the gist of it. Let's create a task that will run every 5 minutes which will keep our cache clean and minimal allowing your bot to grow and scale much larger without needing a much bigger server to host it on. Start by pasting the following snippet below:
A task is used to conduct a function every so often. For example, Discordeno
comes with a built in task called `botlists`. This task runs every so often and
updates your data in all the bot lists apis. Let's create a new task to really
understand the gist of it. Let's create a task that will run every 5 minutes
which will keep our cache clean and minimal allowing your bot to grow and scale
much larger without needing a much bigger server to host it on. Start by pasting
the following snippet below:
```ts
import { botCache } from "../../deps.ts";
@@ -21,16 +32,24 @@ botCache.tasks.set(`taskname`, {
## Understanding The Options
- `name`: The name property is just the name of the task. Useful for logs and such to tell you which task is running.
- `interval`: The amount of millisconds to wait before executing this function again.
- `name`: The name property is just the name of the task. Useful for logs and
such to tell you which task is running.
- `interval`: The amount of millisconds to wait before executing this function
again.
- `execute`: The function to execute.
## What is Cache and Why Should We Sweep It?
Your bot's cache is the memory it is holding on to since it started. To understand this slightly easier we have an example: when you buy a server to host your bot on you can check how much RAM (memory) it has. That amount of memory can be expensive $$$ so it is best to build a bot that does not use a ton of memory. You can use this task to tell your bot to delete any memory it is holding on to that is not necessary.
Your bot's cache is the memory it is holding on to since it started. To
understand this slightly easier we have an example: when you buy a server to
host your bot on you can check how much RAM (memory) it has. That amount of
memory can be expensive $$$ so it is best to build a bot that does not use a ton
of memory. You can use this task to tell your bot to delete any memory it is
holding on to that is not necessary.
- Suppose we don't want to keep any presence cached.
- Remove any members from cache that have not been active in the last 30 minutes.
- Remove any members from cache that have not been active in the last 30
minutes.
- Removes any messages sent over 10 minutes ago.
## Adding Pseudo Code
@@ -62,55 +81,64 @@ Now that we have a plan in place, let's add the code.
import { botCache, cache } from "../../deps.ts";
import { Milliseconds } from "../utils/constants/time.ts";
const MESSAGE_LIFETIME = Milliseconds.MINUTE * 10
const MEMBER_LIFETIME = Milliseconds.MINUTE * 30
const MESSAGE_LIFETIME = Milliseconds.MINUTE * 10;
const MEMBER_LIFETIME = Milliseconds.MINUTE * 30;
botCache.tasks.set(`sweeper`, {
name: `sweeper`,
interval: Milliseconds.MINUTE * 5,
execute: async function () {
const now = Date.now()
const now = Date.now();
// Delete presences from the bots cache.
cache.presences.clear()
cache.presences.clear();
// For every server, we will clean the cache
cache.guilds.forEach(guild => {
cache.guilds.forEach((guild) => {
// Delete presences from the server caches.
guild.presences.clear()
guild.presences.clear();
// Delete any member who has not been active in the last 30 minutes and is not currently in a voice channel
})
});
// For ever, message we will delete if necessary
cache.messages.forEach(message => {
cache.messages.forEach((message) => {
// Delete any messages over 10 minutes old
if (now - message.timestamp > MESSAGE_LIFETIME) cache.messages.delete(message.id)
})
if (now - message.timestamp > MESSAGE_LIFETIME) {
cache.messages.delete(message.id);
}
});
},
});
```
Alrighty, we managed to add most of the code. But we hit a small obstacle. There is no way to tell when the last time a member was active was. In order to accomplish this we will need to add this functionality. No problem, let's get cracking.
Alrighty, we managed to add most of the code. But we hit a small obstacle. There
is no way to tell when the last time a member was active was. In order to
accomplish this we will need to add this functionality. No problem, let's get
cracking.
## Adding Member Activity Tracker
Let's go to the botCache in `cache.ts` file and add a `new Collection` that will hold the member's id + server id as the key since a member can be in multiple servers. The value for the map will be a timestamp showing when they were last active. We will be able to use this check, how long ago they were last active.
Let's go to the botCache in `cache.ts` file and add a `new Collection` that will
hold the member's id + server id as the key since a member can be in multiple
servers. The value for the map will be a timestamp showing when they were last
active. We will be able to use this check, how long ago they were last active.
```ts
memberLastActive: new Collection<string, number>()
memberLastActive:
new Collection<string, number>();
```
Now, let's go to our `messageCreate`, event and add 1 line to update this value.
```ts
botCache.memberLastActive.set(message.author.id, message.timestamp)
botCache.memberLastActive.set(message.author.id, message.timestamp);
```
This will store the time when the message was sent by the member using their id.
## Cleaning Out Inactive Members
Now, we can get back to cleaning out the members from the cache. The final code should look something like this:
Now, we can get back to cleaning out the members from the cache. The final code
should look something like this:
```ts
import { botCache } from "../../mod.ts";
@@ -157,12 +185,24 @@ botCache.tasks.set(`sweeper`, {
});
```
If you reload/restart your bot now, you will begin to see massive improvements in cache/RAM usage. Feel free to take some time and customize this to your needs for your bot. For example, in this case, I opted to keep voice states but if your bot does not do anything voice related, you can save even more memory by deleting voice states.
If you reload/restart your bot now, you will begin to see massive improvements
in cache/RAM usage. Feel free to take some time and customize this to your needs
for your bot. For example, in this case, I opted to keep voice states but if
your bot does not do anything voice related, you can save even more memory by
deleting voice states.
---
Remember, optimizing cache is a very difficult thing to do so don't worry if it doesnt make too much sense. The main thing to take away from this guide is how to create a function that runs every X amount of time. When your bot grows enough where it reaches a point where you need to worry about optimzing the cache, join the Discord server and we can chat.
Remember, optimizing cache is a very difficult thing to do so don't worry if it
doesnt make too much sense. The main thing to take away from this guide is how
to create a function that runs every X amount of time. When your bot grows
enough where it reaches a point where you need to worry about optimzing the
cache, join the Discord server and we can chat.
Cheers 🎉
Thank you for reading this step by step guide to creating a Discord bot. There are many more powerful things in Discordeno, but they take a little more advanced skills to master. Once you have gone through all this, feel free to jump into the advanced section of the guide to become an advanced level Discord Bot Developer!
Thank you for reading this step by step guide to creating a Discord bot. There
are many more powerful things in Discordeno, but they take a little more
advanced skills to master. Once you have gone through all this, feel free to
jump into the advanced section of the guide to become an advanced level Discord
Bot Developer!
+35 -13
View File
@@ -1,15 +1,24 @@
# Hosting Your Bot Online 24/7!
Once you feel that you are ready to get your bot to stay online 24/7, follow the guide below. This guide, is going to use [Hyper Expert](https://p.hyper.expert/aff.php?aff=125) as our hosting provider. You can use any you wish, but I recommend using [Hyper Expert](https://p.hyper.expert/aff.php?aff=125) for the following reasons.
Once you feel that you are ready to get your bot to stay online 24/7, follow the
guide below. This guide, is going to use
[Hyper Expert](https://p.hyper.expert/aff.php?aff=125) as our hosting provider.
You can use any you wish, but I recommend using
[Hyper Expert](https://p.hyper.expert/aff.php?aff=125) for the following
reasons.
- I host all my bots on it. That's currently 6 bots with over 2 million users and 10K servers. They could grow to another 3-5 times their current size without issues.
- I host all my bots on it. That's currently 6 bots with over 2 million users
and 10K servers. They could grow to another 3-5 times their current size
without issues.
- Launch a high performance server in seconds.
- One of the cheapest VPS options, I have ever seen!
- The best part in my opinion is: **They have a Discord Server where you can get help if you need help with hosting your bot!!!**
- The best part in my opinion is: **They have a Discord Server where you can get
help if you need help with hosting your bot!!!**
## Local Computer 24/7
If you have a computer that you can keep running 24/7, you can actually run the bot on it. Just use the PM2 module to run the bot in the background.
If you have a computer that you can keep running 24/7, you can actually run the
bot on it. Just use the PM2 module to run the bot in the background.
## Virtual Private Server
@@ -17,45 +26,58 @@ For most people, this is where you are going to want to be.
### Buying The VPS
Go to the [Hyper Expert Website](https://p.hyper.expert/aff.php?aff=125) and get a VPS of your choice. Depending on your needs, you can choose a larger more powerful VPS, but I recommend starting out with the base model and then raise it when you need to.
Go to the [Hyper Expert Website](https://p.hyper.expert/aff.php?aff=125) and get
a VPS of your choice. Depending on your needs, you can choose a larger more
powerful VPS, but I recommend starting out with the base model and then raise it
when you need to.
### Setting Up The VPS
Once your ready and logged into the server, the first thing you should do is change your password!
Once your ready and logged into the server, the first thing you should do is
change your password!
```shell
sudo passwd
```
Enter the password twice and make sure you do not forget this password. Save it somewhere safe!
Enter the password twice and make sure you do not forget this password. Save it
somewhere safe!
Next, let's make sure you have the latest updates on your server so all bug fixes and features are available for your server.
Next, let's make sure you have the latest updates on your server so all bug
fixes and features are available for your server.
```shell
sudo apt update && sudo apt upgrade -y
```
In case there is some bug that let's someone access your machine, you always want to have an extra layer of protection. So let's create a separate user on your server where your bot will be.
In case there is some bug that let's someone access your machine, you always
want to have an extra layer of protection. So let's create a separate user on
your server where your bot will be.
```shell
adduser [USERNAME]
```
You will be prompted to enter a password. Use a different password for this for increased security. Save this password as well!
You will be prompted to enter a password. Use a different password for this for
increased security. Save this password as well!
Once your user has been created, you now should give this user sudo powers so it can do everything your root access could.
Once your user has been created, you now should give this user sudo powers so it
can do everything your root access could.
```shell
usermod -aG sudo [USERNAME]
```
Now let's install the required stuff for our bot. The following command will install Deno to your server.
Now let's install the required stuff for our bot. The following command will
install Deno to your server.
```shell
curl -fsSL https://deno.land/x/install/install.sh | sh
```
Now that we have Deno installed let's install Denon which will keep the process running even after you log out or close your VPS terminal. For Node.JS devs, Denon is like Nodemon/PM2 for Deno.
Now that we have Deno installed let's install Denon which will keep the process
running even after you log out or close your VPS terminal. For Node.JS devs,
Denon is like Nodemon/PM2 for Deno.
```shell
deno install --allow-read --allow-run --allow-write --allow-net -f -q --unstable https://deno.land/x/denon/denon.ts
+2 -1
View File
@@ -138,7 +138,8 @@ export async function createMessage(data: MessageCreateOptions) {
}
// Discord doesnt give guild id for getMessage() so this will fill it in
const guildIDFinal = guildID || (await cacheHandlers.get("channels", channelID))?.guildID || "";
const guildIDFinal = guildID ||
(await cacheHandlers.get("channels", channelID))?.guildID || "";
const message = Object.create(baseMessage, {
...restProps,
+3 -2
View File
@@ -1,7 +1,8 @@
# Discordeno Interactions
`interactions` is a standalone submodule that supports the webhooks through HTTP server to add support for Discord's interactions feature.
This is a barebones interface that will create and handle requests from Discord API.
`interactions` is a standalone submodule that supports the webhooks through HTTP
server to add support for Discord's interactions feature. This is a barebones
interface that will create and handle requests from Discord API.
- Complete and extremely fast security and verification checks
- First-class TypeScript Support
+14 -6
View File
@@ -1,13 +1,21 @@
# Discordeno Rest
A standalone and server-less REST module with functionality of REST, independently.
A standalone and server-less REST module with functionality of REST,
independently.
- Easily host on any serverless infrastructure.
- Easy to use and setup with Cloudflare Workers (FREE for 100K requests per day!)
- Easy to use and setup with Cloudflare Workers (FREE for 100K requests per
day!)
- 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.
- 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.
- 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.
- Scalability! Scalability! Scalability!