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
This commit is contained in:
ITOH
2022-02-03 17:57:13 +01:00
committed by GitHub
parent f57aec3895
commit 24239053f3
61 changed files with 23073 additions and 14322 deletions
+23
View File
@@ -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
-1
View File
@@ -4,5 +4,4 @@ configs.ts
# npm stuff # npm stuff
.npmignore .npmignore
package.json
npm/ npm/
-24
View File
@@ -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
View File
@@ -1 +0,0 @@
node_modules/
-1
View File
@@ -1 +0,0 @@
# Discordeno Documentation
-11048
View File
File diff suppressed because it is too large Load Diff
-49
View File
@@ -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",
],
};
-15
View File
@@ -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",
},
];
-45
View File
@@ -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",
],
},
],
};
-5
View File
@@ -1,5 +0,0 @@
# Advanced Guide
## Understanding The Goals of This Guide
This guide is for advanced Discordeno features.
-207
View File
@@ -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. 🎉
-56
View File
@@ -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.
}
```
-402
View File
@@ -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!
-61
View File
@@ -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 Dockers
methodologies for shipping, testing, and deploying code quickly, you can
significantly reduce the delay between writing code and running it in
production.
Learn more [here](https://docs.docker.com/get-started/)
### Installing Docker
Installing Docker is very simple and supported on nearly every major operating
system! Just follow the instructions [here](https://docs.docker.com/get-docker/)
to get started.
### 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
```
-130
View File
@@ -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.
-232
View File
@@ -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. 🎉
-277
View File
@@ -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!**
-21
View File
@@ -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.
-163
View File
@@ -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:
![image](https://i.imgur.com/TOXjLgh.png)
## Understanding What Discordeno Did
Discordeno includes these commands/folders as they are essential for any discord
bot to have in order to meet the Discord Bot Best Practices. It also adds a few
things that will help make some things easier to build a bot.
We will dive into these deeper in this guide. Let's take it step by step.
-770
View File
@@ -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.
![image](https://i.imgur.com/4WRFMtR.png)
Nice! You can now customize any of the commands from Discordeno as you wish.
Then we will continue to making our very own command so we can learn about all
the options that Discordeno gives us when it comes to making commands. As you
learn more in this guide, you can keep improving it.
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.
-100
View File
@@ -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.
-113
View File
@@ -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.
-154
View File
@@ -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.
-143
View File
@@ -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.
-208
View File
@@ -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!
-90
View File
@@ -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
```
+35
View File
@@ -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
+20
View File
@@ -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*
+1
View File
@@ -0,0 +1 @@
# Discordeno Site
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
+8
View File
@@ -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.
+11
View File
@@ -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
+4
View File
@@ -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.
+4
View File
@@ -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
+6 -5
View File
@@ -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.
+131
View File
@@ -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;
+22172
View File
File diff suppressed because it is too large Load Diff
+45
View File
@@ -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"
]
}
}
+31
View File
@@ -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;
}
+75
View File
@@ -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&apos;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>
);
}
+39
View File
@@ -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);
}
+23
View File
@@ -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;
}
+40
View File
@@ -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>
);
}
+7
View File
@@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.
View File

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

File diff suppressed because one or more lines are too long

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

+169
View File
@@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

+7
View File
@@ -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": "."
}
}