docs: move to docusaurus (#1984)
* new site start * ci: test docusaurus * we use npm * Update site_tests.yml * Update site_tests.yml * Update site_tests.yml * do not ignore package.json * Update package-lock.json * Create deploy.yml * Update docusaurus.config.js * update authors * Update docusaurus.config.js * Update deploy.yml * remove blog posts * Update README.md * delete old docs * Update .gitignore * delete template images * Create 2022-02-03-welcome.md
@@ -0,0 +1,23 @@
|
|||||||
|
name: Test Docusaurus build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "docs/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-deploy:
|
||||||
|
name: Test deployment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 14.x
|
||||||
|
cache: npm
|
||||||
|
cache-dependency-path: site/package-lock.json
|
||||||
|
- name: Test build
|
||||||
|
working-directory: site
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
@@ -4,5 +4,4 @@ configs.ts
|
|||||||
|
|
||||||
# npm stuff
|
# npm stuff
|
||||||
.npmignore
|
.npmignore
|
||||||
package.json
|
|
||||||
npm/
|
npm/
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
name: Deploy
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: main
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
- name: Build documentation
|
|
||||||
run: |
|
|
||||||
yarn install
|
|
||||||
yarn docs:build
|
|
||||||
- name: Deploy documentation
|
|
||||||
if: success()
|
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
publish_dir: ./src/.vuepress/dist
|
|
||||||
commit_message: ${{ github.event.head_commit.message }}
|
|
||||||
cname: discordeno.mod.land
|
|
||||||
keep_files: true
|
|
||||||
enable_jekyll: false
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Discordeno Documentation
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
const { name, description, repository: repo } = require("../../package");
|
|
||||||
const nav = require("./navbar");
|
|
||||||
const sidebar = require("./sidebar");
|
|
||||||
|
|
||||||
const title = name[0].toUpperCase() + name.slice(1);
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
base: `/`,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
head: [
|
|
||||||
["link", { rel: "icon", href: "/favicon.png" }],
|
|
||||||
["meta", { name: "theme-color", content: "#7289DA" }],
|
|
||||||
["meta", { name: "apple-mobile-web-app-capable", content: "yes" }],
|
|
||||||
[
|
|
||||||
"meta",
|
|
||||||
{ name: "apple-mobile-web-app-status-bar-style", content: "black" },
|
|
||||||
],
|
|
||||||
["meta", { name: "og:title", content: title }],
|
|
||||||
[
|
|
||||||
"meta",
|
|
||||||
{
|
|
||||||
name: "og:description",
|
|
||||||
content: description,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
["meta", { name: "og:type", content: "website" }],
|
|
||||||
["meta", { name: "og:url", content: "https://discordeno.mod.land" }],
|
|
||||||
["meta", { name: "og:image", content: "/logo.png" }],
|
|
||||||
],
|
|
||||||
theme: "yuu",
|
|
||||||
themeConfig: {
|
|
||||||
repo,
|
|
||||||
docsDir: "src",
|
|
||||||
docsBranch: "main",
|
|
||||||
editLinks: true,
|
|
||||||
lastUpdated: true,
|
|
||||||
sidebarDepth: 0,
|
|
||||||
nav,
|
|
||||||
sidebar,
|
|
||||||
yuu: {
|
|
||||||
defaultDarkTheme: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
"@vuepress/plugin-back-to-top",
|
|
||||||
"@vuepress/plugin-medium-zoom",
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
module.exports = [
|
|
||||||
{
|
|
||||||
text: "Home",
|
|
||||||
link: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Docs",
|
|
||||||
link: "https://doc.deno.land/https/deno.land/x/discordeno/mod.ts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Discord",
|
|
||||||
link: "https://discord.gg/5vBgXk3UcZ",
|
|
||||||
target: "_blank",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"/": [
|
|
||||||
{
|
|
||||||
title: "Home",
|
|
||||||
children: ["/", "faq", "gettingstarted", "migrating"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Big Bots Guide",
|
|
||||||
children: [
|
|
||||||
"/bigbots/",
|
|
||||||
"/bigbots/rest",
|
|
||||||
"/bigbots/gateway",
|
|
||||||
"/bigbots/cache",
|
|
||||||
"/bigbots/events",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Step By Step Guide",
|
|
||||||
children: [
|
|
||||||
"/stepbystep/",
|
|
||||||
"/stepbystep/createbot",
|
|
||||||
"/stepbystep/createcommand",
|
|
||||||
"/stepbystep/createevent",
|
|
||||||
"/stepbystep/createlanguage",
|
|
||||||
"/stepbystep/createmonitor",
|
|
||||||
"/stepbystep/createinhibitor",
|
|
||||||
"/stepbystep/createtask",
|
|
||||||
"/stepbystep/hostingbot",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Advanced Guide",
|
|
||||||
children: [
|
|
||||||
"/advanced/",
|
|
||||||
"/advanced/arguments",
|
|
||||||
"/advanced/collectors",
|
|
||||||
"/advanced/customizations",
|
|
||||||
"/advanced/dockerhosting",
|
|
||||||
"/advanced/dynamiccommands",
|
|
||||||
"/advanced/permlevels",
|
|
||||||
"/advanced/subcommands",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Advanced Guide
|
|
||||||
|
|
||||||
## Understanding The Goals of This Guide
|
|
||||||
|
|
||||||
This guide is for advanced Discordeno features.
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
### Channels
|
|
||||||
|
|
||||||
- `categorychannel` Must be a category channel id or name or mention.
|
|
||||||
- `newschannel` Must be a news channel id or name or mention.
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
## 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:
|
|
||||||
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.arguments.set("boolean", {
|
|
||||||
name: "boolean",
|
|
||||||
execute: function (_argument, parameters) {
|
|
||||||
const [boolean] = parameters;
|
|
||||||
|
|
||||||
const valid = ["true", "false", "on", "off"].includes(boolean);
|
|
||||||
if (valid) return ["true", "on"].includes(boolean);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Simply update the code so it looks like the following:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.arguments.set("boolean", {
|
|
||||||
name: "boolean",
|
|
||||||
execute: function (_argument, parameters) {
|
|
||||||
const [boolean] = parameters;
|
|
||||||
|
|
||||||
const valid = ["true", "false", "on", "off", "yes", "no"].includes(boolean);
|
|
||||||
if (valid) return ["true", "on", "yes"].includes(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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.arguments.set("argumentname", {
|
|
||||||
name: "argumentname",
|
|
||||||
execute: async function (argument, parameters, message) {
|
|
||||||
// Your code goes here
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
First let's change the `argumentname` to be `url`. Then we can start adding the
|
|
||||||
pseudo-code.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.arguments.set("url", {
|
|
||||||
name: "url",
|
|
||||||
execute: async function (argument, parameters, message) {
|
|
||||||
// Get the provided parameter
|
|
||||||
|
|
||||||
// The regex we will use to test if it's a valid url
|
|
||||||
|
|
||||||
// Use the regex to test if it is a valid url
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can get started.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.arguments.set("url", {
|
|
||||||
name: "url",
|
|
||||||
execute: async function (argument, parameters, message) {
|
|
||||||
// Get the provided parameter
|
|
||||||
const [url] = parameters;
|
|
||||||
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,})?$/;
|
|
||||||
// Use the regex to test if it is a valid url
|
|
||||||
const validURL = urlRegex.test(url);
|
|
||||||
if (!validURL) return;
|
|
||||||
|
|
||||||
return 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)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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.
|
|
||||||
name: "url",
|
|
||||||
// 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) {
|
|
||||||
// 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.
|
|
||||||
required: true,
|
|
||||||
// This is optional. The default is false. If the type was a string or ...string, this can forcibly lowercase the string.
|
|
||||||
lowercase: true,
|
|
||||||
// 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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
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. 🎉
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# Message And Reaction Collectors!
|
|
||||||
|
|
||||||
Collectors are a powerful feature in Discordeno that allow you to await a response from a user using a message, a reaction or a button. For example, if you want the user to provide you a follow-up answer to the command, you can use collectors to get a response of your choosing.
|
|
||||||
|
|
||||||
## Message Collectors
|
|
||||||
|
|
||||||
When you need another message from the user, it is very simple:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
execute: (message, args) {
|
|
||||||
const userResponse = await needMessage(message.author.id, message.channelId);
|
|
||||||
// userResponse is a full message object you can now use.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
There are a few things here working by default.
|
|
||||||
|
|
||||||
1. The collector will only activate for a message which is sent by the message.author.id
|
|
||||||
2. It will collect only 1 message.
|
|
||||||
3. It will only listen for 5 minutes and then expire if no message was sent by the user.
|
|
||||||
|
|
||||||
If you wish to customize any of these three, it is as simple as follows:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
execute: (message, args) {
|
|
||||||
const userResponse = await needMessage(message.author.id, message.channelId, {
|
|
||||||
// You can customize the filter function to decide which messages should be excepted. For example, this will only accept a valid number
|
|
||||||
filter: (msg) => msg.author.id === message.author.id && !isNaN(msg.content),
|
|
||||||
// If you want it to last a different amount of time. For example, we can make this never expire
|
|
||||||
duration: Infinity,
|
|
||||||
// If you want to collect more than 1 message
|
|
||||||
amount: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
// userResponse is a full message object you can now use. If amount is > 1 it will be an array of messages
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reaction/Button Collectors
|
|
||||||
|
|
||||||
The same process applies to reactions. You just replace `needMessage` with `needReaction`. For buttons, simply use `needButton`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
execute: (message, args) {
|
|
||||||
const userResponse = await needReaction(message.author.id, message.id, {
|
|
||||||
// You can customize the filter function to decide which messages should be excepted.
|
|
||||||
filter: (userId) => userId === message.author.id,
|
|
||||||
// If you want it to last a different amount of time. For example, we can make this never expire
|
|
||||||
duration: Infinity,
|
|
||||||
// If you want to collect more than 1 reaction
|
|
||||||
amount: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
// userResponse is the reaction string or an array of strings if more than 1 amount.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,402 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
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 createMemberStruct() {
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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 createMemberStruct function
|
|
||||||
takes 2 arguments. `data: MemberCreatePayload, guildID: string`
|
|
||||||
|
|
||||||
```ts
|
|
||||||
async function createMemberStruct(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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
|
||||||
const {
|
|
||||||
joined_at: joinedAt,
|
|
||||||
premium_since: premiumSince,
|
|
||||||
...rest
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
const {
|
|
||||||
mfa_enabled: mfaEnabled,
|
|
||||||
premium_type: premiumType,
|
|
||||||
...user
|
|
||||||
} = data.user || {};
|
|
||||||
|
|
||||||
const member = {
|
|
||||||
...rest,
|
|
||||||
// Only use those that we have not removed above
|
|
||||||
user,
|
|
||||||
/** When the user joined the guild */
|
|
||||||
joinedAt: Date.parse(joinedAt),
|
|
||||||
/** When the user used their nitro boost on the server. */
|
|
||||||
premiumSince: premiumSince ? Date.parse(premiumSince) : undefined,
|
|
||||||
/** The guild id where this member exists */
|
|
||||||
guildID,
|
|
||||||
/** Whether or not this user has 2FA enabled. */
|
|
||||||
mfaEnabled,
|
|
||||||
/** The premium type for this user */
|
|
||||||
premiumType,
|
|
||||||
};
|
|
||||||
|
|
||||||
return 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";
|
|
||||||
|
|
||||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
|
||||||
// Hidden code here to make it easier to see the changes
|
|
||||||
|
|
||||||
const member = {
|
|
||||||
...rest,
|
|
||||||
// Hidden code here to make it easier to see the changes
|
|
||||||
|
|
||||||
/** The premium type for this user */
|
|
||||||
premiumType,
|
|
||||||
/** The username#discriminator tag for this member. */
|
|
||||||
tag: `${user.username}#${user.discriminator}`,
|
|
||||||
/** The avatarURL made easily accessible */
|
|
||||||
avatarURL: rawAvatarURL(data.id, user.discriminator, user.avatar),
|
|
||||||
/** The guild object for where this member is located */
|
|
||||||
guild: await cacheHandler.get("guilds", guildID),
|
|
||||||
/** Easily mention the member */
|
|
||||||
mention: `<@${data.user.id}>`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we need to use this function and telling Discordeno to override the internal
|
|
||||||
createMemberStruct function. To do this, we will modify the internal functions.
|
|
||||||
This is where we reassign the value of the function.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
structures.createMemberStruct = createMemberStruct;
|
|
||||||
```
|
|
||||||
|
|
||||||
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" {
|
|
||||||
interface Member {
|
|
||||||
tag: string;
|
|
||||||
avatarURL: string;
|
|
||||||
guild: Guild;
|
|
||||||
mention: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
|
||||||
const {
|
|
||||||
joined_at: joinedAt,
|
|
||||||
premium_since: premiumSince,
|
|
||||||
...rest
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
const {
|
|
||||||
mfa_enabled: mfaEnabled,
|
|
||||||
premium_type: premiumType,
|
|
||||||
...user
|
|
||||||
} = data.user || {};
|
|
||||||
|
|
||||||
const member = {
|
|
||||||
...rest,
|
|
||||||
// Only use those that we have not removed above
|
|
||||||
user,
|
|
||||||
/** When the user joined the guild */
|
|
||||||
joinedAt: Date.parse(joinedAt),
|
|
||||||
/** When the user used their nitro boost on the server. */
|
|
||||||
premiumSince: premiumSince ? Date.parse(premiumSince) : undefined,
|
|
||||||
/** The guild id where this member exists */
|
|
||||||
guildID,
|
|
||||||
/** Whether or not this user has 2FA enabled. */
|
|
||||||
mfaEnabled,
|
|
||||||
/** The premium type for this user */
|
|
||||||
premiumType,
|
|
||||||
/** The username#discriminator tag for this member. */
|
|
||||||
tag: `${user.username}#${user.discriminator}`,
|
|
||||||
/** The avatarURL made easily accessible */
|
|
||||||
avatarURL: rawAvatarURL(data.id, user.discriminator, user.avatar),
|
|
||||||
/** The guild object for where this member is located */
|
|
||||||
guild: await cacheHandler.get("guilds", guildID),
|
|
||||||
/** Easily mention the member */
|
|
||||||
mention: `<@${data.user.id}>`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
|
|
||||||
structures.createMemberStruct = createMemberStruct;
|
|
||||||
|
|
||||||
declare module "../../deps.ts" {
|
|
||||||
interface Member {
|
|
||||||
tag: string;
|
|
||||||
avatarURL: string;
|
|
||||||
guild: Guild;
|
|
||||||
mention: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
The following is the current base data available to us when we create the
|
|
||||||
member.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
user: {
|
|
||||||
/** The user's id */
|
|
||||||
id: string;
|
|
||||||
/** the user's username, not unique across the platform */
|
|
||||||
username: string;
|
|
||||||
/** The user's 4 digit discord tag */
|
|
||||||
discriminator: string;
|
|
||||||
/** The user's avatar hash */
|
|
||||||
avatar: string | null;
|
|
||||||
/** Whether the user is a bot */
|
|
||||||
bot?: boolean;
|
|
||||||
/** Whether the user is an official discord system user (part of the urgent message system.) */
|
|
||||||
system?: boolean;
|
|
||||||
/** Whether the user has two factor enabled on their account */
|
|
||||||
mfa_enabled?: boolean;
|
|
||||||
/** the user's chosen language option */
|
|
||||||
locale?: string;
|
|
||||||
/** Whether the email on this account has been verified */
|
|
||||||
verified?: boolean;
|
|
||||||
/** The user's email */
|
|
||||||
email?: string;
|
|
||||||
/** The flags on a user's account. */
|
|
||||||
flags?: number;
|
|
||||||
/** The type of Nitro subscription on a user's account. */
|
|
||||||
premium_type?: number;
|
|
||||||
},
|
|
||||||
/** The user's guild nickname if one is set. */
|
|
||||||
nick?: string;
|
|
||||||
/** Array of role ids that the member has */
|
|
||||||
roles: string[];
|
|
||||||
/** When the user joined the guild. */
|
|
||||||
joined_at: string;
|
|
||||||
/** When the user used their nitro boost on the server. */
|
|
||||||
premium_since?: string;
|
|
||||||
/** Whether the user is deafened in voice channels */
|
|
||||||
deaf: boolean;
|
|
||||||
/** Whether the user is muted in voice channels */
|
|
||||||
mute: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
bot,
|
|
||||||
username,
|
|
||||||
discriminator,
|
|
||||||
avatar,
|
|
||||||
...user
|
|
||||||
} = data.user || {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
bot,
|
|
||||||
nick: data.nick,
|
|
||||||
roles: data.roles,
|
|
||||||
joinedAt: Date.parse(data.joined_at),
|
|
||||||
tag: `${username}#${discriminator}`,
|
|
||||||
avatarURL: rawAvatarURL(id, discriminator, avatar),
|
|
||||||
guild: await cacheHandlers.get("guilds", guildID),
|
|
||||||
mention: `<@${id}>`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
structures.createMemberStruct = createMemberStruct;
|
|
||||||
|
|
||||||
declare module "../../deps.ts" {
|
|
||||||
interface Member {
|
|
||||||
bot: boolean;
|
|
||||||
tag: string;
|
|
||||||
avatarURL: string;
|
|
||||||
guild: Guild;
|
|
||||||
mention: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You might be seeing an error on `structures.createMemberStruct`. 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
|
|
||||||
structures.createMemberStruct = createMemberStruct;
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { cacheHandlers } from "../../../deps.ts";
|
|
||||||
|
|
||||||
cacheHandlers.has = async function (table, key) {
|
|
||||||
return Redis.tables(table).has(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.
|
|
||||||
|
|
||||||
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
|
|
||||||
- delete
|
|
||||||
- clear
|
|
||||||
- set
|
|
||||||
- forEach
|
|
||||||
|
|
||||||
## Custom Gateway Payload Handling (Handlers)
|
|
||||||
|
|
||||||
Handlers 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:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { eventHandlers, handlers, TypingStartPayload } from "../../../deps.ts";
|
|
||||||
|
|
||||||
const typingUsers = new Map<String, number>();
|
|
||||||
|
|
||||||
function createTimeout(userID: String) {
|
|
||||||
return setTimeout(() => {
|
|
||||||
typingUsers.delete(userID);
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
handlers.TYPING_START = function (data) {
|
|
||||||
const payload = data.d as TypingStartPayload;
|
|
||||||
eventHandlers.typingStart?.(payload);
|
|
||||||
|
|
||||||
if (typingUsers.has(payload.user_id)) {
|
|
||||||
clearTimeout(typingUsers.get(payload.user_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
typingUsers.set(payload.user_id, createTimeout(payload.user_id));
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Something worth noting about why Discordeno handlers 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 handlers, 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 handlers, you never have to fork or
|
|
||||||
anything. Just take control!
|
|
||||||
|
|
||||||
Remember with great power comes great bugs!
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
# 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 Docker’s
|
|
||||||
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.
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
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)
|
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker start CONTAINER_NAME
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker stop CONTAINER_NAME
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker restart CONTAINER_NAME
|
|
||||||
```
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
- Create a file in the commands folder called `nekos.ts` which will create all
|
|
||||||
our fun commands.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache, sendMessage } from "../../deps.ts";
|
|
||||||
|
|
||||||
const nekosEndpoints = [
|
|
||||||
{ name: "tickle", path: "/img/tickle", nsfw: false },
|
|
||||||
{ name: "slap", path: "/img/slap", nsfw: false },
|
|
||||||
{ name: "poke", path: "/img/poke", nsfw: false },
|
|
||||||
{ name: "pat", path: "/img/pat", nsfw: false },
|
|
||||||
{ name: "neko", path: "/img/neko", nsfw: false },
|
|
||||||
{ name: "meow", path: "/img/meow", nsfw: false },
|
|
||||||
{ name: "lizard", path: "/img/lizard", nsfw: false },
|
|
||||||
{ name: "kiss", path: "/img/kiss", nsfw: false },
|
|
||||||
{ name: "hug", path: "/img/hug", nsfw: false },
|
|
||||||
{ name: "foxGirl", path: "/img/fox_girl", nsfw: false },
|
|
||||||
{ name: "feed", path: "/img/feed", nsfw: false },
|
|
||||||
{ name: "cuddle", path: "/img/cuddle", nsfw: false },
|
|
||||||
{ name: "why", path: "/why", nsfw: false },
|
|
||||||
{ name: "catText", path: "/cat", nsfw: false },
|
|
||||||
{ name: "OwOify", path: "/owoify", nsfw: false },
|
|
||||||
{ name: "8Ball", path: "/8ball", nsfw: false },
|
|
||||||
{ name: "fact", path: "/fact", nsfw: false },
|
|
||||||
{ name: "nekoGif", path: "/img/ngif", nsfw: false },
|
|
||||||
{ name: "kemonomimi", path: "/img/kemonomimi", nsfw: false },
|
|
||||||
{ name: "holo", path: "/img/holo", nsfw: false },
|
|
||||||
{ name: "smug", path: "/img/smug", nsfw: false },
|
|
||||||
{ name: "baka", path: "/img/baka", nsfw: false },
|
|
||||||
{ name: "woof", path: "/img/woof", nsfw: false },
|
|
||||||
{ name: "spoiler", path: "/spoiler", nsfw: false },
|
|
||||||
{ name: "wallpaper", path: "/img/wallpaper", nsfw: false },
|
|
||||||
{ name: "goose", path: "/img/goose", nsfw: false },
|
|
||||||
{ name: "gecg", path: "/img/gecg", nsfw: false },
|
|
||||||
{ name: "avatar", path: "/img/avatar", nsfw: false },
|
|
||||||
{ name: "waifu", path: "/img/waifu", nsfw: false },
|
|
||||||
// All other paths have been removed for this guide as they are NSFW.
|
|
||||||
{ name: "nsfw_endpoint", path: "/img/nsfw_example", nsfw: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
nekosEndpoints.forEach((endpoint) => {
|
|
||||||
botCache.commands.set(endpoint.name, {
|
|
||||||
name: endpoint.name,
|
|
||||||
nsfw: endpoint.nsfw,
|
|
||||||
botChannelPermissions: ["SEND_MESSAGES", "EMBED_LINKS"],
|
|
||||||
execute: async function (message) {
|
|
||||||
const url = `https://nekos.life/api/v2${endpoint.path}`;
|
|
||||||
const result = await fetch(url).then((res) => res.json());
|
|
||||||
sendMessage(message.channelID, result?.url || result[endpoint.name]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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.
|
|
||||||
|
|
||||||
**That ladies and gentleman is the power and magic of Discordeno!**
|
|
||||||
|
|
||||||
## Understanding The Concept
|
|
||||||
|
|
||||||
If your still a little confused, don't worry. Let's break it down.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
botCache.commands.set(endpoint.name, {
|
|
||||||
name: endpoint.name,
|
|
||||||
nsfw: endpoint.nsfw,
|
|
||||||
botChannelPermissions: ["SEND_MESSAGES", "EMBED_LINKS"],
|
|
||||||
execute: async function (message) {
|
|
||||||
const url = `https://nekos.life/api/v2${endpoint.path}`;
|
|
||||||
const result = await fetch(url).then((res) => res.json());
|
|
||||||
sendMessage(message.channelID, result?.url || result[endpoint.name]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Take a minute and try out some of the commands and how they function.
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
# 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
|
|
||||||
- `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_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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
import { PermissionLevels } from "../types/commands.ts";
|
|
||||||
import { configs } from "../../configs.ts";
|
|
||||||
|
|
||||||
// The member using the command must be one of the bots support team
|
|
||||||
botCache.permissionLevels.set(
|
|
||||||
PermissionLevels.BOT_SUPPORT,
|
|
||||||
async (message) => configs.userIDs.botSupporters.includes(message.author.id),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Let's add some pseudo-code first.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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;
|
|
||||||
|
|
||||||
// The users id was not in the configs, check if they have the role in bot server
|
|
||||||
|
|
||||||
// Get the bots support server
|
|
||||||
|
|
||||||
// If the user is not a member of the support server they can't be one of the support staff.
|
|
||||||
|
|
||||||
// If they have the role allow the command otherwise it will be false and block the command.
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Awesome, now that the plan is in place, let's add the code.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
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");
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
First, we need to update the Permission Levels enum in `src/types/commands.ts`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export enum PermissionLevels {
|
|
||||||
MEMBER,
|
|
||||||
MODERATOR,
|
|
||||||
ADMIN,
|
|
||||||
SERVER_OWNER,
|
|
||||||
BOT_SUPPORT,
|
|
||||||
BOT_DEVS,
|
|
||||||
BOT_OWNER,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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 {
|
|
||||||
MEMBER,
|
|
||||||
NITRO_BOOSTER,
|
|
||||||
MODERATOR,
|
|
||||||
ADMIN,
|
|
||||||
SERVER_OWNER,
|
|
||||||
BOT_SUPPORT,
|
|
||||||
BOT_DEVS,
|
|
||||||
BOT_OWNER,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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";
|
|
||||||
import { PermissionLevels } from "../types/commands.ts";
|
|
||||||
import { configs } from "../../configs.ts";
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
PermissionLevels.NITRO_BOOSTER;
|
|
||||||
```
|
|
||||||
|
|
||||||
Next we can add the code in place.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
import { PermissionLevels } from "../types/commands.ts";
|
|
||||||
import { configs } from "../../configs.ts";
|
|
||||||
|
|
||||||
botCache.permissionLevels.set(
|
|
||||||
PermissionLevels.NITRO_BOOSTER,
|
|
||||||
(message) => {
|
|
||||||
const guild = message.guild();
|
|
||||||
if (!guild) return false;
|
|
||||||
|
|
||||||
const member = message.member();
|
|
||||||
if (!member) return false;
|
|
||||||
|
|
||||||
const boosterRole = guild.roles.find((role) =>
|
|
||||||
role.name.toLowerCase() === "nitro booster"
|
|
||||||
);
|
|
||||||
if (!boosterRole) return false;
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
createCommand({
|
|
||||||
name: `reload`,
|
|
||||||
permissionLevels: [PermissionLevels.BOT_OWNER],
|
|
||||||
botChannelPermissions: ["SEND_MESSAGES"],
|
|
||||||
```
|
|
||||||
|
|
||||||
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];
|
|
||||||
```
|
|
||||||
|
|
||||||
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,
|
|
||||||
|
|
||||||
```ts
|
|
||||||
createCommand({
|
|
||||||
name: `example`,
|
|
||||||
permissionLevels: (message, command) => {
|
|
||||||
// Anything you'd like to check here and return a boolean. Must return true or false.
|
|
||||||
},
|
|
||||||
botChannelPermissions: ["SEND_MESSAGES"],
|
|
||||||
```
|
|
||||||
|
|
||||||
The function is able to take 2 arguments. `message`, `command`.
|
|
||||||
|
|
||||||
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. 🎉
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
# 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!
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
import { PermissionLevels } from "../types/commands.ts";
|
|
||||||
import {
|
|
||||||
createCommand,
|
|
||||||
createSubcommand,
|
|
||||||
sendEmbed,
|
|
||||||
sendResponse,
|
|
||||||
} from "../utils/helpers.ts";
|
|
||||||
import { parsePrefix } from "../monitors/commandHandler.ts";
|
|
||||||
import { Embed } from "../utils/Embed.ts";
|
|
||||||
|
|
||||||
// This command will only execute if there was no valid sub command: !prefix
|
|
||||||
createCommand({
|
|
||||||
name: "prefix",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "sub commmand",
|
|
||||||
type: "subcommand",
|
|
||||||
literals: ["set"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
guildOnly: true,
|
|
||||||
permissionLevels: [PermissionLevels.MEMBER],
|
|
||||||
execute: (message, args) => {
|
|
||||||
const embed = new Embed()
|
|
||||||
.setTitle("Prefix Information")
|
|
||||||
.setDescription(`
|
|
||||||
**Guild**: \`${message.guild?.name}\`
|
|
||||||
**Current Prefix**: \`${parsePrefix(message.guildId)}\`
|
|
||||||
`)
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
sendEmbed(message.channelID, embed);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a subcommand for when users do !prefix set $
|
|
||||||
createSubcommand("prefix", {
|
|
||||||
name: "set",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "prefix",
|
|
||||||
type: "string",
|
|
||||||
required: true,
|
|
||||||
missing: (message) => {
|
|
||||||
sendResponse(message, `${message.member} please provid a prefix`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
permissionLevels: [PermissionLevels.ADMIN],
|
|
||||||
execute: (message, args) => {
|
|
||||||
if (args.prefix.length > 3) {
|
|
||||||
return sendResponse(message, "Prefix input too long");
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldPrefix = parsePrefix(message.guildId);
|
|
||||||
botCache.guildPrefixes.set(message.guildId, args.prefix);
|
|
||||||
|
|
||||||
const embed = new Embed()
|
|
||||||
.setTitle("Success, prefix was changed")
|
|
||||||
.setDescription(`
|
|
||||||
**Old Prefix**: \`${oldPrefix}\`
|
|
||||||
**New Prefix**: \`${args.prefix}\`
|
|
||||||
`)
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
sendEmbed(message.channelID, embed);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
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({
|
|
||||||
name: "prefix",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "sub commmand",
|
|
||||||
type: "subcommand",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
guildOnly: true,
|
|
||||||
permissionLevels: [PermissionLevels.MEMBER],
|
|
||||||
execute: (message, args) => {
|
|
||||||
const embed = new Embed()
|
|
||||||
.setTitle("Prefix Information")
|
|
||||||
.setDescription(`
|
|
||||||
**Guild**: \`${message.guild?.name}\`
|
|
||||||
**Current Prefix**: \`${parsePrefix(message.guildId)}\`
|
|
||||||
`)
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
sendEmbed(message.channelID, embed);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
That was just a basic command. Now, we can create our subcommands:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Create a subcommand for when users do !prefix set $
|
|
||||||
createSubcommand("prefix", {
|
|
||||||
name: "set",
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "prefix",
|
|
||||||
type: "string",
|
|
||||||
required: true,
|
|
||||||
missing: (message) => {
|
|
||||||
sendResponse(message, `${message.member} please provid a prefix`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
permissionLevels: [PermissionLevels.ADMIN],
|
|
||||||
execute: (message, args) => {
|
|
||||||
if (args.prefix.length > 3) {
|
|
||||||
return sendResponse(message, "Prefix input too long");
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldPrefix = parsePrefix(message.guildId);
|
|
||||||
botCache.guildPrefixes.set(message.guildId, args.prefix);
|
|
||||||
|
|
||||||
const embed = new Embed()
|
|
||||||
.setTitle("Success, prefix was changed")
|
|
||||||
.setDescription(`
|
|
||||||
**Old Prefix**: \`${oldPrefix}\`
|
|
||||||
**New Prefix**: \`${args.prefix}\`
|
|
||||||
`)
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
sendEmbed(message.channelID, embed);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
`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.
|
|
||||||
|
|
||||||
_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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
!reactionroles create
|
|
||||||
!reactionroles delete
|
|
||||||
!reactionroles edit
|
|
||||||
!reactionroles list
|
|
||||||
```
|
|
||||||
|
|
||||||
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!!!**
|
|
||||||
|
|
||||||
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
|
|
||||||
!settings feedback suggestions channel #channel
|
|
||||||
!settings welcome channel #channel
|
|
||||||
!settings welcome message Welcome To The Server! %user%
|
|
||||||
!settings gooodbye message %user% has left
|
|
||||||
!settings welcome channel #channel
|
|
||||||
!settings gooodbye channel #channel
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a subcommand inside a subcommand, `name` in the createSubcommand()
|
|
||||||
function must be as follows:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
createSubcommand("settings", { name: "feedback" })
|
|
||||||
createSubcommand("settings-feedback" { name: "suggestions" })
|
|
||||||
createSubccomand("settings-feedback", { name: "channel" })
|
|
||||||
|
|
||||||
createSubcommand("settings", { name: "welcome" })
|
|
||||||
createSubcommand("settings-welcome", { name: "channel" })
|
|
||||||
createSubcommand("settings-welcome", { name: "message" })
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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!
|
|
||||||
|
|
||||||
```ts
|
|
||||||
createSubcommand("settings-feedback", {
|
|
||||||
name: "suggestions",
|
|
||||||
aliases: ["ideas", "sugg", "s"],
|
|
||||||
// The rest of the command stuff below here!
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**THE POWER AND MAGIC OF DISCORDENO AT YOUR FINGERTIPS!**
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Step By Step Guide
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## 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!
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
- First, log into your Github account and create a repository for your Discordeno Bot using the
|
|
||||||
[Generator Template](https://github.com/discordeno/template/generate).
|
|
||||||
Give it any name you like. For the purpose of this guide we will call it,
|
|
||||||
Stargate. Following the link prior to logging in might lead to an error page.
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
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!
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Step 1: Remove the `.example` from this file name so it is called `configs.ts`
|
|
||||||
// Step 2: Add all your bot's information below. The only required one is token and prefix. NOTE: As long as `.gitignore` file is ignoring configs.ts your configurations will be kept private!
|
|
||||||
// Step 3: Remove these comments if you like.
|
|
||||||
|
|
||||||
export const configs = {
|
|
||||||
// Your bot token goes here
|
|
||||||
token: "",
|
|
||||||
// The default prefix for your bot. Don't worry guilds can change this later.
|
|
||||||
prefix: "!",
|
|
||||||
// This isn't required but you can add bot list api keys here.
|
|
||||||
botListTokens: {
|
|
||||||
DISCORD_BOT_ORG: "",
|
|
||||||
BOTS_ON_DISCORD: "",
|
|
||||||
DISCORD_BOT_LIST: "",
|
|
||||||
BOTS_FOR_DISCORD: "",
|
|
||||||
DISCORD_BOATS: "",
|
|
||||||
DISCORD_BOTS_GG: "",
|
|
||||||
DISCORD_BOTS_GROUP: "",
|
|
||||||
},
|
|
||||||
// This is the server id for your bot's main server where users can get help/support
|
|
||||||
supportServerID: "",
|
|
||||||
// These are channel ids that will enable some functionality
|
|
||||||
channelIDs: {
|
|
||||||
// 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: "",
|
|
||||||
},
|
|
||||||
// These are the role ids that will enable some functionality.
|
|
||||||
roleIDs: {
|
|
||||||
// If you have a patreon set up you can add the patreon vip role id here.
|
|
||||||
patreonVIPRoleID: "",
|
|
||||||
},
|
|
||||||
// These are the user ids that will enable some functionality.
|
|
||||||
userIDs: {
|
|
||||||
// The user ids for the support team
|
|
||||||
botSupporters: [],
|
|
||||||
// The user ids for the other devs on your team
|
|
||||||
botDevs: [],
|
|
||||||
// The user ids who have complete 100% access to your bot
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
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 `--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:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
We will dive into these deeper in this guide. Let's take it step by step.
|
|
||||||
@@ -1,770 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
## 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:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
import { createCommand } from "../utils/helpers.ts";
|
|
||||||
|
|
||||||
createCommand({
|
|
||||||
name: "invite",
|
|
||||||
execute: function (message) {
|
|
||||||
// Replace the permission number at the end to request the permissions you wish to request. By default, this will request Admin perms.
|
|
||||||
message.reply(
|
|
||||||
`https://discordapp.com/oauth2/authorize?client_id=${botID}&scope=bot&permissions=8`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
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`,
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
> 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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.
|
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Let's add in the two aliases we wanted.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
name: "invite",
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
import { PermissionLevels } from "../types/commands.ts";
|
|
||||||
import { createCommand } from "../utils/helpers.ts";
|
|
||||||
|
|
||||||
createCommand({
|
|
||||||
name: "commandname",
|
|
||||||
aliases: [],
|
|
||||||
dmOnly: false,
|
|
||||||
guildOnly: false,
|
|
||||||
nsfw: false,
|
|
||||||
permissionLevels: [PermissionLevels.MEMBER],
|
|
||||||
botServerPermissions: [],
|
|
||||||
botChannelPermissions: [],
|
|
||||||
userServerPermissions: [],
|
|
||||||
userChannelPermissions: [],
|
|
||||||
description: "description",
|
|
||||||
cooldown: {
|
|
||||||
seconds: 0,
|
|
||||||
allowedUses: 0,
|
|
||||||
},
|
|
||||||
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.
|
|
||||||
|
|
||||||
> **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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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` command 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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];
|
|
||||||
```
|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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: {
|
|
||||||
seconds: 60,
|
|
||||||
allowedUses: 5
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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.
|
|
||||||
|
|
||||||
1. `!reload commands`
|
|
||||||
2. Spam the `!role` very quickly 6 times in under a minute.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
For our role command, we need a `member` and a `role`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
arguments:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: "member",
|
|
||||||
type: "member",
|
|
||||||
missing: (message) => {
|
|
||||||
message.reply(
|
|
||||||
`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.reply(
|
|
||||||
`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 2 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
execute: function (message, args) {
|
|
||||||
// The code that is to be executed goes here
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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) => {
|
|
||||||
> // 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).
|
|
||||||
|
|
||||||
```ts
|
|
||||||
execute: function (message, args) {
|
|
||||||
// If this was the everyone role alert with a silly error
|
|
||||||
|
|
||||||
// If this is a managed role (some bots role) we can't give/remove it alert with silly error
|
|
||||||
|
|
||||||
// Get the bots highest role
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// Check the command author's highest role
|
|
||||||
|
|
||||||
// If the author does not have a role high enough to give this role alert
|
|
||||||
|
|
||||||
// If the user has this role already we should remove it
|
|
||||||
|
|
||||||
// Add the role to the user.
|
|
||||||
|
|
||||||
// Alert the user that used the command that the user has been give the role.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
> **Note:** The everyone role ID is the same as the server guild ID.
|
|
||||||
|
|
||||||
To do this, we are going to want something like this:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
if (args.role.id === message.guildId) {
|
|
||||||
return message.reply(
|
|
||||||
"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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { Role, Member } from "../../deps.ts";
|
|
||||||
|
|
||||||
// Lots of code here hidden so you can see the changes easily.
|
|
||||||
|
|
||||||
execute: function (message, args: RoleArgs, guild) {
|
|
||||||
// If this was the everyone role alert with a silly error
|
|
||||||
if (args.role.id === message.guildId) {
|
|
||||||
return message.reply("Are you trying to make this person a super hero? Everyone has the everyone role. I can't give the everyone role to another user.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lots of comments here hidden so you can see the changes easily.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put this at the end of the file.
|
|
||||||
interface RoleArgs {
|
|
||||||
role: Role;
|
|
||||||
member: Member;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Awesome! Let's keep going.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Make the function asynchronous
|
|
||||||
execute: async function (message, args: RoleArgs, guild) {
|
|
||||||
// If this was the everyone role alert with a silly error
|
|
||||||
if (args.role.id === message.guildId) {
|
|
||||||
return message.reply("I don't know if you noticed or not but I'm an extremely arrogant bot who tends to think all of his plans will work. But I can't give the everyone role to someone.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a managed role(some bots role) we can't give/remove alert with silly error
|
|
||||||
if (args.role.managed) {
|
|
||||||
return message.reply("Dammit man, just 'cause I'm Scottish doesn't mean I can give your people managed roles.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the bots highest role
|
|
||||||
const botsHighestRole = await highestRole(message.guildId, botID);
|
|
||||||
|
|
||||||
// Check if the bot has a role higher than the role that it will try to give.
|
|
||||||
const botIsHigher = await higherRolePosition(message.guildId, botsHighestRole.id, args.role.id)
|
|
||||||
|
|
||||||
// If the role is too high alert the user.
|
|
||||||
if (!botIsHigher) {
|
|
||||||
return message.reply("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);
|
|
||||||
|
|
||||||
// If the author does not have a role high enough to give this role alert
|
|
||||||
if (!(await higherRolePosition(message.guildId, membersHighestRole.id, args.role.id))) {
|
|
||||||
return message.reply("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.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user has this role already we should remove it
|
|
||||||
if (message.member?.guilds.get(message.guildId)?.roles.includes(args.role.id)) {
|
|
||||||
message.member.removeRole(message.guildId, args.role.id, `${message.author.tag} used the role command to remove this role.`)
|
|
||||||
// Alert the user that used the command that the user has lost the role.
|
|
||||||
return message.reply(`The role **${args.role.name}** has been removed from **${args.member.tag}**.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the role to the user.
|
|
||||||
message.member?.addRole(message.guildId, args.role.id)
|
|
||||||
|
|
||||||
// Alert the user that used the command that the user has been give the role.
|
|
||||||
return message.reply(`The role **${args.role.name}** has been added to **${args.member.tag}**.`)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The final version of the command should look something like this:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import {
|
|
||||||
botCache,
|
|
||||||
botID,
|
|
||||||
higherRolePosition,
|
|
||||||
highestRole,
|
|
||||||
Member,
|
|
||||||
Role,
|
|
||||||
} from "../../deps.ts";
|
|
||||||
import { PermissionLevels } from "../types/commands.ts";
|
|
||||||
import { createCommand } from "../utils/helpers.ts";
|
|
||||||
|
|
||||||
createCommand({
|
|
||||||
name: "role",
|
|
||||||
permissionLevels: [PermissionLevels.MODERATOR, PermissionLevels.ADMIN],
|
|
||||||
botServerPermissions: ["MANAGE_ROLES"],
|
|
||||||
botChannelPermissions: ["SEND_MESSAGES"],
|
|
||||||
cooldown: {
|
|
||||||
seconds: 60,
|
|
||||||
allowedUses: 5,
|
|
||||||
},
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "member",
|
|
||||||
type: "member",
|
|
||||||
missing: (message) => {
|
|
||||||
message.reply(
|
|
||||||
`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.reply(
|
|
||||||
`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
|
|
||||||
if (args.role.id === message.guildId) {
|
|
||||||
return message.reply(
|
|
||||||
"I don't know if you noticed or not but I'm an extremely arrogant bot who tends to think all of his plans will work. But I can't give the everyone role to someone.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a managed role(some bots role) we can't give/remove alert with silly error
|
|
||||||
if (args.role.managed) {
|
|
||||||
return message.reply(
|
|
||||||
"Dammit man, just 'cause I'm Scottish doesn't mean I can give your people managed roles.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the bots highest role
|
|
||||||
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(
|
|
||||||
message.guildId,
|
|
||||||
botsHighestRole.id,
|
|
||||||
args.role.id,
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
return message.reply(
|
|
||||||
"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,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
return message.reply(
|
|
||||||
"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.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user has this role already we should remove it
|
|
||||||
if (
|
|
||||||
message.member?.guilds.get(message.guildId)?.roles.includes(args.role.id)
|
|
||||||
) {
|
|
||||||
message.member.removeRole(
|
|
||||||
message.guildId,
|
|
||||||
args.role.id,
|
|
||||||
`${message.author.username} used the role command to remove this role.`,
|
|
||||||
);
|
|
||||||
// Alert the user that used the command that the user has lost the role.
|
|
||||||
return message.reply(
|
|
||||||
`The role **${args.role.name}** has been removed from **${args.member.tag}**.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Alert the user that used the command that the user has been give the role.
|
|
||||||
return message.reply(
|
|
||||||
`The role **${args.role.name}** has been added to **${args.member.tag}**.`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
interface RoleArgs {
|
|
||||||
role: Role;
|
|
||||||
member: Member;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```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";
|
|
||||||
|
|
||||||
const funCommandData = [
|
|
||||||
{ name: "bite", gifs: configs.gifs.bite },
|
|
||||||
{ name: "cuddle", gifs: configs.gifs.cuddle },
|
|
||||||
{ name: "dance", gifs: configs.gifs.dance },
|
|
||||||
{ name: "hug", gifs: configs.gifs.hug },
|
|
||||||
{ name: "kiss", gifs: configs.gifs.kiss },
|
|
||||||
{ name: "kanna", gifs: configs.gifs.kanna },
|
|
||||||
{ name: "kiss", gifs: configs.gifs.kiss },
|
|
||||||
{ name: "kitten", gifs: configs.gifs.kitten },
|
|
||||||
{ name: "lmao", gifs: configs.gifs.lmao, aliases: ["lol"] },
|
|
||||||
{ name: "pat", gifs: configs.gifs.pat },
|
|
||||||
{ name: "poke", gifs: configs.gifs.poke },
|
|
||||||
{ name: "pony", gifs: configs.gifs.pony },
|
|
||||||
{ name: "puppy", gifs: configs.gifs.puppy },
|
|
||||||
{ name: "raphtalia", gifs: configs.gifs.raphtalia },
|
|
||||||
{ name: "stargate", gifs: configs.gifs.stargate },
|
|
||||||
{ name: "supernatural", gifs: configs.gifs.supernatural },
|
|
||||||
{ name: "tickle", gifs: configs.gifs.tickle },
|
|
||||||
{ name: "zerotwo", gifs: configs.gifs.zerotwo },
|
|
||||||
];
|
|
||||||
|
|
||||||
funCommandData.forEach((data) => {
|
|
||||||
botCache.commands.set({
|
|
||||||
name: data.name,
|
|
||||||
aliases: data.aliases,
|
|
||||||
botChannelPermissions: ["SEND_MESSAGES", "EMBED_LINKS"],
|
|
||||||
cooldown: {
|
|
||||||
seconds: 2,
|
|
||||||
},
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: "member",
|
|
||||||
type: "member",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
execute: function (message, args: FunArgs) {
|
|
||||||
// If a member is provided use that otherwise set the member themself
|
|
||||||
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";
|
|
||||||
|
|
||||||
// Create an embed
|
|
||||||
const embed = new Embed()
|
|
||||||
.setAuthor(member.tag, avatarURL(member))
|
|
||||||
.setDescription(
|
|
||||||
translate(
|
|
||||||
message.guildId,
|
|
||||||
`commands/fun/${data.name}:${type}`,
|
|
||||||
{ mention: message.member!.mention, user: member.mention },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.setImage(chooseRandom(data.gifs));
|
|
||||||
|
|
||||||
return sendEmbed(message.channelID, embed);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
interface FunArgs {
|
|
||||||
member?: Member;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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.
|
|
||||||
|
|
||||||
**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!!!!
|
|
||||||
|
|
||||||
> **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.
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache, cache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.eventHandlers.ready = function () {
|
|
||||||
console.log(`Loaded ${botCache.arguments.size} Argument(s)`);
|
|
||||||
console.log(`Loaded ${botCache.commands.size} Command(s)`);
|
|
||||||
console.log(`Loaded ${Object.keys(botCache.eventHandlers).length} Event(s)`);
|
|
||||||
console.log(`Loaded ${botCache.inhibitors.size} Inhibitor(s)`);
|
|
||||||
console.log(`Loaded ${botCache.monitors.size} Monitor(s)`);
|
|
||||||
console.log(`Loaded ${botCache.tasks.size} Task(s)`);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[READY] Bot is online and ready in ${cache.guilds.size} guild(s)!`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
> **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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.eventHandlers.eventname = function () {
|
|
||||||
// Your code goes here
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
- Change the event name to `discordLog`
|
|
||||||
- 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.
|
|
||||||
export interface CustomEvents extends EventHandlers {
|
|
||||||
discordLog: (error: Error) => unknown;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Awesome, now we can get started on adding the code.
|
|
||||||
|
|
||||||
````ts
|
|
||||||
import { botCache, cache, sendMessage } from "../../deps.ts";
|
|
||||||
import { Embed } from "../utils/Embed.ts";
|
|
||||||
import { configs } from "../../configs.ts";
|
|
||||||
import { sendEmbed } from "../utils/helpers.ts";
|
|
||||||
|
|
||||||
botCache.eventHandlers.discordLog = function (error) {
|
|
||||||
const embed = new Embed()
|
|
||||||
.setDescription([
|
|
||||||
"```ts",
|
|
||||||
error,
|
|
||||||
"```",
|
|
||||||
].join("\n"))
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
// If the channel is not found cancel out
|
|
||||||
if (!configs.channelIDs.errorChannelID) return;
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
Once, you are ready, let's jump into creating some command inhibitors.
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# 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?
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Creating the File
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// Your code goes here
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Inhibitors can take up to 3 arguments.
|
|
||||||
|
|
||||||
- `message`: The message object that triggered the command.
|
|
||||||
- `command`: The command object itself that was triggered.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// If the command is not VIP only we can allow this command to execute
|
|
||||||
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);
|
|
||||||
// Member doesn't exist so cancel the command
|
|
||||||
if (!member) {
|
|
||||||
message.reply(
|
|
||||||
`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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alert the user they don't have vip and can't use the command
|
|
||||||
message.reply(
|
|
||||||
`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
|
|
||||||
|
|
||||||
```ts
|
|
||||||
vipOnly?: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Next we will try our hands on creating tasks.
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
# Creating Languages!
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## 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`.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
- Create the `hug.json` file in the `src/languages/en_US/commands/fun/` folder.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"DESCRIPTION": "Hug yourself or another user."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"DESCRIPTION": "Hug yourself or another user.",
|
|
||||||
"SELF": "If you had no one to hug you could have hugged me. Years from now, when you're thinking about me, you're gonna say: 'How did I ever get along without that wonderful, constant companion?' *Woof.*",
|
|
||||||
"OTHER": "{{user}} was hugged by {{mention}}"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
```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`
|
|
||||||
|
|
||||||
## Variables
|
|
||||||
|
|
||||||
::: v-pre Variables in i18next use the `{{}}` format. So the variable `mention`
|
|
||||||
would be used by doing `{{mention}}` :::
|
|
||||||
|
|
||||||
## Key Rules
|
|
||||||
|
|
||||||
When you create keys in the files there are a couple rules to follow.
|
|
||||||
|
|
||||||
- Never use `:` in your key name. **REQUIRED**
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## Spanish Version
|
|
||||||
|
|
||||||
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/`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"DESCRIPTION": "Abrázate a ti mismo oa otro usuario",
|
|
||||||
"SELF": "Si no tuvieras a nadie a quien abrazar, podrías haberme abrazado. Años a partir de ahora, cuando estés pensando en mí, dirás: '¿Cómo me las arreglé sin esa maravillosa y constante compañera?' *Guau.*",
|
|
||||||
"OTHER": "{{user}} fue abrazado por {{mention}}"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
- [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!
|
|
||||||
|
|
||||||
Once you are ready, let's go make our first monitor.
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## Command Handler Monitor
|
|
||||||
|
|
||||||
Discordeno comes with a built-in 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
|
|
||||||
botCache.monitors.set("monitorname", {
|
|
||||||
name: "monitorname",
|
|
||||||
ignoreBots: true,
|
|
||||||
ignoreOthers: false,
|
|
||||||
ignoreEdits: false,
|
|
||||||
ignoreDM: true,
|
|
||||||
userServerPermissions: [],
|
|
||||||
userChannelPermissions: [],
|
|
||||||
botServerPermissions: [],
|
|
||||||
botChannelPermissions: [],
|
|
||||||
execute: async function (message) {
|
|
||||||
// Your code goes here
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Understanding Monitor Options
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
```ts
|
|
||||||
botCache.monitors.set("inviteFilter", {
|
|
||||||
name: "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.
|
|
||||||
|
|
||||||
- **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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
botChannelPermissions:
|
|
||||||
["MANAGE_MESSAGES"];
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding The Code
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache, deleteMessage } from "../../deps.ts";
|
|
||||||
import { translate } from "../utils/i18next.ts";
|
|
||||||
|
|
||||||
botCache.monitors.set("inviteFilter", {
|
|
||||||
name: "inviteFilter",
|
|
||||||
ignoreBots: false,
|
|
||||||
botChannelPermissions: ["MANAGE_MESSAGES"],
|
|
||||||
execute: async function (message) {
|
|
||||||
// Use a regex to test if the content of the message has a valid discord invite link
|
|
||||||
const hasInviteLink =
|
|
||||||
/(https?:\/\/)?(www\.)?(discord\.(gg|li|me|io)|discordapp\.com\/invite)\/.+/
|
|
||||||
.test(message.content);
|
|
||||||
// If the message does not have an invite link cancel out.
|
|
||||||
if (!hasInviteLink) return;
|
|
||||||
|
|
||||||
// This message has an invite link, so delete the message.
|
|
||||||
try {
|
|
||||||
// Delete the invite link
|
|
||||||
message.delete(
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
} catch (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.
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
# Creating Tasks
|
|
||||||
|
|
||||||
Phenomenal! Now that you have mastered monitors, 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:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
import { Milliseconds } from "../utils/constants/time.ts";
|
|
||||||
|
|
||||||
botCache.tasks.set(`taskname`, {
|
|
||||||
name: `taskname`,
|
|
||||||
interval: Milliseconds.HOUR,
|
|
||||||
execute: async function () {
|
|
||||||
// This is where you put your code you want to run.
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
- `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.
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
- Removes any messages sent over 10 minutes ago.
|
|
||||||
|
|
||||||
## Adding Pseudo Code
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../deps.ts";
|
|
||||||
import { Milliseconds } from "../utils/constants/time.ts";
|
|
||||||
|
|
||||||
botCache.tasks.set(`taskname`, {
|
|
||||||
name: `taskname`,
|
|
||||||
interval: Milliseconds.HOUR,
|
|
||||||
execute: async function () {
|
|
||||||
// For every server, we will clean the cache
|
|
||||||
|
|
||||||
// Delete presences from the bots cache and server caches.
|
|
||||||
|
|
||||||
// Delete any member who has not been active in the last 30 minutes and is not currently in a voice channel
|
|
||||||
|
|
||||||
// Delete any messages over 10 minutes old
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding The Code
|
|
||||||
|
|
||||||
Now that we have a plan in place, let's add the code.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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;
|
|
||||||
|
|
||||||
botCache.tasks.set(`sweeper`, {
|
|
||||||
name: `sweeper`,
|
|
||||||
interval: Milliseconds.MINUTE * 5,
|
|
||||||
execute: async function () {
|
|
||||||
const now = Date.now();
|
|
||||||
// Delete presences from the bots cache.
|
|
||||||
cache.presences.clear();
|
|
||||||
|
|
||||||
// For every server, we will clean the cache
|
|
||||||
cache.guilds.forEach((guild) => {
|
|
||||||
// Delete presences from the server caches.
|
|
||||||
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) => {
|
|
||||||
// Delete any messages over 10 minutes old
|
|
||||||
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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
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);
|
|
||||||
```
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { botCache } from "../../mod.ts";
|
|
||||||
import { Milliseconds } from "../utils/constants/time.ts";
|
|
||||||
import { cache } from "../../deps.ts";
|
|
||||||
|
|
||||||
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();
|
|
||||||
// Delete presences from the bots cache.
|
|
||||||
cache.presences.clear();
|
|
||||||
|
|
||||||
// For every guild, we will clean the cache
|
|
||||||
cache.guilds.forEach((guild) => {
|
|
||||||
// Delete presences from the guild caches.
|
|
||||||
guild.presences.clear();
|
|
||||||
// Delete any member who has not been active in the last 30 minutes and is not currently in a voice channel
|
|
||||||
guild.members.forEach((member) => {
|
|
||||||
// The user is currently active in a voice channel
|
|
||||||
if (guild.voiceStates.has(member.user.id)) return;
|
|
||||||
|
|
||||||
const lastActive = botCache.memberLastActive.get(member.user.id);
|
|
||||||
// If the user is active recently
|
|
||||||
if (lastActive && now - lastActive < MEMBER_LIFETIME) return;
|
|
||||||
|
|
||||||
guild.members.delete(member.user.id);
|
|
||||||
botCache.memberLastActive.delete(member.user.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// For ever, message we will delete if necessary
|
|
||||||
cache.messages.forEach((message) => {
|
|
||||||
// Delete any messages over 10 minutes old
|
|
||||||
if (now - message.timestamp > MESSAGE_LIFETIME) {
|
|
||||||
cache.messages.delete(message.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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!
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
- 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!!!**
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## Virtual Private Server
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Setting Up The VPS
|
|
||||||
|
|
||||||
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!
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```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!
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
deno install --allow-read --allow-run --allow-write --allow-net -f -q --unstable https://deno.land/x/denon/denon.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Once, that is done you can simply run the bot.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
denon run --allow-net --allow-read --no-check mod.ts
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 14.x
|
||||||
|
cache: npm
|
||||||
|
- name: Build website
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Popular action to deploy to GitHub Pages:
|
||||||
|
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# Build output to publish to the `gh-pages` branch:
|
||||||
|
publish_dir: ./build
|
||||||
|
# Assign commit authorship to the official GH-Actions bot for deploys to `gh-pages` branch:
|
||||||
|
# https://github.com/actions/checkout/issues/13#issuecomment-724415212
|
||||||
|
# The GH actions bot is used by default if you didn't specify the two fields.
|
||||||
|
# You can swap them out with your own user credentials.
|
||||||
|
user_name: github-actions[bot]
|
||||||
|
user_email: 41898282+github-actions[bot]@users.noreply.github.com
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.docusaurus
|
||||||
|
.cache-loader
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Discordeno Site
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
slug: welcome
|
||||||
|
title: Welcome
|
||||||
|
authors: [itoh, skillz]
|
||||||
|
tags: [discordeno, hello]
|
||||||
|
---
|
||||||
|
|
||||||
|
Hello and Welcome to Discordeno, a highly scalable Discord library for Deno and Node.js using TypeScript.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
itoh:
|
||||||
|
name: ITOH
|
||||||
|
title: Maintainer of Discordeno
|
||||||
|
url: https://github.com/itohatweb
|
||||||
|
image_url: https://github.com/itohatweb.png
|
||||||
|
|
||||||
|
skillz:
|
||||||
|
name: Skillz
|
||||||
|
title: Maintainer of Discordeno
|
||||||
|
url: https://github.com/Skillz4Killz
|
||||||
|
image_url: https://github.com/Skillz4Killz.png
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"label": "Big Bot Guide",
|
||||||
|
"position": 2
|
||||||
|
}
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 4
|
||||||
|
sidebar_label: Step 3 - Cache
|
||||||
|
---
|
||||||
|
|
||||||
# Step 3: Standalone Cache Process
|
# Step 3: Standalone Cache Process
|
||||||
|
|
||||||
The next part of this is going to be about making a standalone cache process. By now, you should have both a REST and a Gateway process ready. Before, we start handling events we should build a Cache handler.
|
The next part of this is going to be about making a standalone cache process. By now, you should have both a REST and a Gateway process ready. Before, we start handling events we should build a Cache handler.
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 5
|
||||||
|
sidebar_label: Step 4 - Event Handler
|
||||||
|
---
|
||||||
|
|
||||||
# Step 4: Creating Standalone Event Handler
|
# Step 4: Creating Standalone Event Handler
|
||||||
|
|
||||||
Now we are about to start working on the bot code itself. The last 3 steps should be completed by the time you reach this. The event handler process will be listening for events from any number of gateway instances and be ready to handle them.
|
Now we are about to start working on the bot code itself. The last 3 steps should be completed by the time you reach this. The event handler process will be listening for events from any number of gateway instances and be ready to handle them.
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 3
|
||||||
|
sidebar_label: Step 2 - Gateway
|
||||||
|
---
|
||||||
|
|
||||||
# Step 2: Creating A Standalone Gateway Process
|
# Step 2: Creating A Standalone Gateway Process
|
||||||
|
|
||||||
If you are reading this, you should have your REST process completed. We are going to need it here. This process will be connecting to discord's websockets which will send you all the events.
|
If you are reading this, you should have your REST process completed. We are going to need it here. This process will be connecting to discord's websockets which will send you all the events.
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
# Step 1: Creating A Standalone REST Process
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
sidebar_label: Step 1 - REST
|
||||||
|
---
|
||||||
|
|
||||||
|
# Creating A Standalone REST Process
|
||||||
|
|
||||||
The first thing we want to make is our standalone REST process. This process will be used by almost every other process, so it is going to be the foundation of the bot.
|
The first thing we want to make is our standalone REST process. This process will be used by almost every other process, so it is going to be the foundation of the bot.
|
||||||
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
# Step By Step Guide
|
# Step By Step Guide
|
||||||
|
|
||||||
THIS IS A WORK IN PROGRESS GUIDE USING THE NEW v13 OF DISCORDENO.
|
THIS IS A WORK IN PROGRESS GUIDE USING THE NEW v13 OF DISCORDENO.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"label": "General",
|
||||||
|
"position": 1
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
|
|
||||||
## Does Discordeno Support TypeScript?
|
## Does Discordeno Support TypeScript?
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 2
|
||||||
|
---
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
Discordeno aims for a simple, easy and stress-free interaction with the Discord
|
Discordeno aims for a simple, easy and stress-free interaction with the Discord
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 3
|
||||||
|
---
|
||||||
# Migrating
|
# Migrating
|
||||||
|
|
||||||
## Migrating from Discord.js
|
## Migrating from Discord.js
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
# Discordeno
|
# Discordeno
|
||||||
|
|
||||||
> Discord API library for [Deno](https://deno.land)
|
> Discord API library for [Deno](https://deno.land)
|
||||||
|
|
||||||
- [Documentation](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
|
- [Documentation](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
|
||||||
- [Discord](https://discord.com/invite/5vBgXk3UcZ)
|
- [Discord](https://discord.gg/ddeno)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -15,10 +19,7 @@
|
|||||||
internally checks all missing permissions before forwarding a request to the
|
internally checks all missing permissions before forwarding a request to the
|
||||||
Discord API so that the client does not get globally-banned by Discord.
|
Discord API so that the client does not get globally-banned by Discord.
|
||||||
- **Simple, Efficient, & Lightweight**: Discordeno is simplistic, easy-to-use,
|
- **Simple, Efficient, & Lightweight**: Discordeno is simplistic, easy-to-use,
|
||||||
versatile while being efficient and lightweight. Follows
|
versatile while being efficient and lightweight.
|
||||||
[Convention Over Configuration](https://en.wikipedia.org/wiki/Convention_over_configuration)
|
|
||||||
design paradigm ― prefers defaults options or values that are recommended by
|
|
||||||
Discord or the best configuration for the majority of the users.
|
|
||||||
- [**Functional API**](https://en.wikipedia.org/wiki/Functional_programming):
|
- [**Functional API**](https://en.wikipedia.org/wiki/Functional_programming):
|
||||||
Functional API ensures an overall concise yet performant code while removing
|
Functional API ensures an overall concise yet performant code while removing
|
||||||
the difficulties of extending built-in classes and inheritance.
|
the difficulties of extending built-in classes and inheritance.
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Note: type annotations allow type checking and IDEs autocompletion
|
||||||
|
|
||||||
|
const lightCodeTheme = require("prism-react-renderer/themes/github");
|
||||||
|
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
|
||||||
|
|
||||||
|
/** @type {import('@docusaurus/types').Config} */
|
||||||
|
const config = {
|
||||||
|
title: "Discordeno",
|
||||||
|
tagline: "A Highly scalable Discord Library",
|
||||||
|
url: "https://discordeno.mod.land",
|
||||||
|
baseUrl: "/",
|
||||||
|
onBrokenLinks: "throw",
|
||||||
|
onBrokenMarkdownLinks: "warn",
|
||||||
|
favicon: "img/favicon.png",
|
||||||
|
organizationName: "discordeno",
|
||||||
|
projectName: "discordeno",
|
||||||
|
deploymentBranch: "gh-pages",
|
||||||
|
trailingSlash: false,
|
||||||
|
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
"classic",
|
||||||
|
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||||
|
({
|
||||||
|
docs: {
|
||||||
|
sidebarPath: require.resolve("./sidebars.js"),
|
||||||
|
editUrl: "https://github.com/discordeno/discordeno/tree/main/site/",
|
||||||
|
},
|
||||||
|
blog: {
|
||||||
|
showReadingTime: true,
|
||||||
|
editUrl: "https://github.com/discordeno/discordeno/tree/main/site/",
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
customCss: require.resolve("./src/css/custom.css"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
themeConfig:
|
||||||
|
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||||
|
({
|
||||||
|
navbar: {
|
||||||
|
title: "Discordeno",
|
||||||
|
logo: {
|
||||||
|
alt: "My Site Logo",
|
||||||
|
src: "img/logo.svg",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: "doc",
|
||||||
|
docId: "intro",
|
||||||
|
position: "left",
|
||||||
|
label: "Tutorial",
|
||||||
|
},
|
||||||
|
{ to: "/blog", label: "Blog", position: "left" },
|
||||||
|
{
|
||||||
|
href: "https://github.com/discordeno/discordeno",
|
||||||
|
label: "GitHub",
|
||||||
|
position: "right",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
style: "dark",
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
title: "Docs",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Tutorial",
|
||||||
|
to: "/docs/intro",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Community",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Discord",
|
||||||
|
href: "https://discord.gg/ddeno",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "More",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "Blog",
|
||||||
|
to: "/blog",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "GitHub",
|
||||||
|
href: "https://github.com/discordeno/discordeno",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
copyright: `Copyright © 2021 - ${new Date().getFullYear()} Discordeno.`,
|
||||||
|
},
|
||||||
|
prism: {
|
||||||
|
theme: lightCodeTheme,
|
||||||
|
darkTheme: darkCodeTheme,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
// ... Your other plugins.
|
||||||
|
[
|
||||||
|
require.resolve("@easyops-cn/docusaurus-search-local"),
|
||||||
|
{
|
||||||
|
indexDocs: true,
|
||||||
|
indexBlog: true,
|
||||||
|
indexPages: true,
|
||||||
|
docsRouteBasePath: ["/docs"],
|
||||||
|
blogRouteBasePath: ["/blog"],
|
||||||
|
language: ["en"],
|
||||||
|
hashed: true,
|
||||||
|
docsDir: ["docs"],
|
||||||
|
blogDir: ["blog"],
|
||||||
|
removeDefaultStopWordFilter: true,
|
||||||
|
highlightSearchTermsOnTargetPage: true,
|
||||||
|
searchResultLimits: 8,
|
||||||
|
searchResultContextMaxLength: 50,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "site",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": false,
|
||||||
|
"license": "Apache 2.0",
|
||||||
|
"scripts": {
|
||||||
|
"docusaurus": "docusaurus",
|
||||||
|
"start": "docusaurus start",
|
||||||
|
"build": "docusaurus build",
|
||||||
|
"swizzle": "docusaurus swizzle",
|
||||||
|
"deploy": "docusaurus deploy",
|
||||||
|
"clear": "docusaurus clear",
|
||||||
|
"serve": "docusaurus serve",
|
||||||
|
"write-translations": "docusaurus write-translations",
|
||||||
|
"write-heading-ids": "docusaurus write-heading-ids",
|
||||||
|
"typecheck": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@docusaurus/core": "2.0.0-beta.15",
|
||||||
|
"@docusaurus/preset-classic": "2.0.0-beta.15",
|
||||||
|
"@easyops-cn/docusaurus-search-local": "^0.21.4",
|
||||||
|
"@mdx-js/react": "^1.6.21",
|
||||||
|
"clsx": "^1.1.1",
|
||||||
|
"prism-react-renderer": "^1.2.1",
|
||||||
|
"react": "^17.0.1",
|
||||||
|
"react-dom": "^17.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@docusaurus/module-type-aliases": "2.0.0-beta.15",
|
||||||
|
"@tsconfig/docusaurus": "^1.0.4",
|
||||||
|
"typescript": "^4.5.2"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.5%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Creating a sidebar enables you to:
|
||||||
|
- create an ordered group of docs
|
||||||
|
- render a sidebar for each doc of that group
|
||||||
|
- provide next/previous navigation
|
||||||
|
|
||||||
|
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||||
|
|
||||||
|
Create as many sidebars as you want.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||||
|
const sidebars = {
|
||||||
|
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||||
|
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||||
|
|
||||||
|
// But you can create a sidebar manually
|
||||||
|
/*
|
||||||
|
tutorialSidebar: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Tutorial',
|
||||||
|
items: ['hello'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = sidebars;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
.features {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featureSvg {
|
||||||
|
height: 200px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||||
|
import React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import styles from './HomepageFeatures.module.css';
|
||||||
|
|
||||||
|
type FeatureItem = {
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
description: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FeatureList: FeatureItem[] = [
|
||||||
|
{
|
||||||
|
title: 'Easy to Use',
|
||||||
|
image: '/img/undraw_docusaurus_mountain.svg',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Docusaurus was designed from the ground up to be easily installed and
|
||||||
|
used to get your website up and running quickly.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Focus on What Matters',
|
||||||
|
image: '/img/undraw_docusaurus_tree.svg',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Docusaurus lets you focus on your docs, and we'll do the chores. Go
|
||||||
|
ahead and move your docs into the <code>docs</code> directory.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Powered by React',
|
||||||
|
image: '/img/undraw_docusaurus_react.svg',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Extend or customize your website layout by reusing React. Docusaurus can
|
||||||
|
be extended while reusing the same header and footer.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function Feature({title, image, description}: FeatureItem) {
|
||||||
|
return (
|
||||||
|
<div className={clsx('col col--4')}>
|
||||||
|
<div className="text--center">
|
||||||
|
<img
|
||||||
|
className={styles.featureSvg}
|
||||||
|
alt={title}
|
||||||
|
src={useBaseUrl(image)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text--center padding-horiz--md">
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<p>{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HomepageFeatures(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<section className={styles.features}>
|
||||||
|
<div className="container">
|
||||||
|
<div className="row">
|
||||||
|
{FeatureList.map((props, idx) => (
|
||||||
|
<Feature key={idx} {...props} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Any CSS included here will be global. The classic template
|
||||||
|
* bundles Infima by default. Infima is a CSS framework designed to
|
||||||
|
* work well for content-centric websites.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* You can override the default Infima variables here. */
|
||||||
|
:root {
|
||||||
|
--ifm-color-primary: #2e8555;
|
||||||
|
--ifm-color-primary-dark: #29784c;
|
||||||
|
--ifm-color-primary-darker: #277148;
|
||||||
|
--ifm-color-primary-darkest: #205d3b;
|
||||||
|
--ifm-color-primary-light: #33925d;
|
||||||
|
--ifm-color-primary-lighter: #359962;
|
||||||
|
--ifm-color-primary-lightest: #3cad6e;
|
||||||
|
--ifm-code-font-size: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||||
|
html[data-theme='dark'] {
|
||||||
|
--ifm-color-primary: #25c2a0;
|
||||||
|
--ifm-color-primary-dark: #21af90;
|
||||||
|
--ifm-color-primary-darker: #1fa588;
|
||||||
|
--ifm-color-primary-darkest: #1a8870;
|
||||||
|
--ifm-color-primary-light: #29d5b0;
|
||||||
|
--ifm-color-primary-lighter: #32d8b4;
|
||||||
|
--ifm-color-primary-lightest: #4fddbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docusaurus-highlight-code-line {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
display: block;
|
||||||
|
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||||
|
padding: 0 var(--ifm-pre-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] .docusaurus-highlight-code-line {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||||
|
* and scoped locally.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.heroBanner {
|
||||||
|
padding: 4rem 0;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 966px) {
|
||||||
|
.heroBanner {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import Layout from '@theme/Layout';
|
||||||
|
import Link from '@docusaurus/Link';
|
||||||
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
|
import styles from './index.module.css';
|
||||||
|
import HomepageFeatures from '../components/HomepageFeatures';
|
||||||
|
|
||||||
|
function HomepageHeader() {
|
||||||
|
const {siteConfig} = useDocusaurusContext();
|
||||||
|
return (
|
||||||
|
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
||||||
|
<div className="container">
|
||||||
|
<h1 className="hero__title">{siteConfig.title}</h1>
|
||||||
|
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<Link
|
||||||
|
className="button button--secondary button--lg"
|
||||||
|
to="/docs/intro">
|
||||||
|
Discordeno Tutorial
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Home(): JSX.Element {
|
||||||
|
const {siteConfig} = useDocusaurusContext();
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
title={`Hello from ${siteConfig.title}`}
|
||||||
|
description="Description will go into a meta tag in <head />">
|
||||||
|
<HomepageHeader />
|
||||||
|
<main>
|
||||||
|
<HomepageFeatures />
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Markdown page example
|
||||||
|
---
|
||||||
|
|
||||||
|
# Markdown page example
|
||||||
|
|
||||||
|
You don't need React to write simple standalone pages.
|
||||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,170 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
|
||||||
|
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
|
||||||
|
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
|
||||||
|
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
|
||||||
|
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
|
||||||
|
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||||
|
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
|
||||||
|
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||||
|
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
|
||||||
|
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
|
||||||
|
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
|
||||||
|
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
|
||||||
|
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
|
||||||
|
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
|
||||||
|
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
|
||||||
|
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
|
||||||
|
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
|
||||||
|
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||||
|
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
|
||||||
|
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||||
|
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
|
||||||
|
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
|
||||||
|
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||||
|
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||||
|
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||||
|
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||||
|
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||||
|
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||||
|
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||||
|
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||||
|
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||||
|
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||||
|
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||||
|
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||||
|
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||||
|
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||||
|
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||||
|
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||||
|
</g>
|
||||||
|
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
|
||||||
|
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
|
||||||
|
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
|
||||||
|
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
|
||||||
|
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
|
||||||
|
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
|
||||||
|
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
|
||||||
|
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
|
||||||
|
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
|
||||||
|
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
</g>
|
||||||
|
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,169 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
|
||||||
|
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
|
||||||
|
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
|
||||||
|
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
|
||||||
|
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
|
||||||
|
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
|
||||||
|
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||||
|
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
|
||||||
|
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
|
||||||
|
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
|
||||||
|
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
||||||
|
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
|
||||||
|
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
|
||||||
|
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
||||||
|
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
|
||||||
|
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
||||||
|
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
|
||||||
|
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
|
||||||
|
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
|
||||||
|
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
|
||||||
|
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
|
||||||
|
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||||
|
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
|
||||||
|
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
|
||||||
|
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
|
||||||
|
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||||
|
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
|
||||||
|
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
|
||||||
|
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||||
|
</g>
|
||||||
|
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
|
||||||
|
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
|
||||||
|
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
|
||||||
|
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
|
||||||
|
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
|
||||||
|
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
|
||||||
|
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
|
||||||
|
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
|
||||||
|
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
|
||||||
|
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||||
|
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
|
||||||
|
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
|
||||||
|
</g>
|
||||||
|
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
|
||||||
|
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||||
|
</g>
|
||||||
|
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
|
||||||
|
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
|
||||||
|
</g>
|
||||||
|
<g id="React-icon" transform="translate(906.3 541.56)">
|
||||||
|
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
|
||||||
|
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
|
||||||
|
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
|
||||||
|
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
// This file is not used in compilation. It is here just for a nice editor experience.
|
||||||
|
"extends": "@tsconfig/docusaurus/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "."
|
||||||
|
}
|
||||||
|
}
|
||||||