BREAKING: node migration + major improvements and style changes (#2783)

* Setup turborepo (#2610)

* chore: BREAKING move to monorepo structure

* chore: setup turborepo

* setup more eslint and tsconfig (#2612)

* fix packages setting lcoation

* add dist to ignore

* style

* fix: .vscode set prettier and remove deno configs (#2611)

* fix: rewrite to process.env

* chore: add node types

* style: run eslint --fix

* fix: tests import ext

* chore: fix formatter

* chore: add build script

* chore: remove site from turborepo

* chore: move to seperate packages

* chore: seperate util

* chore: rename to index

* fix: utils

* chore: bump typescript

* fix: in process of fixing rest

* fix: logger

* style

* chore: fix turbo script

* fix: types

* fix: types

* fix: shard

* fix: gateway

* fixing: bot

* fix: at least it can run now

* chore: fix turbo script

* chore: move plugins

* chore: add type and utils export

* chore: working bot type and cache plugin

* Add git hooks (#2618)

* CI: Setup ci (#2669)

* ci: fix test

* ci: fix typo

* fix: turbo script

* fixes: yarn and linter errors in embeds pkg

* chore: fix yarn gitignore

* Node migration - devcontainer (#2672)

*  - feat: devcontainer -> node

* 👷‍♂ - ci: devcontainer - add tabnine, prettier

* fix: ignore .env and debug.ts

* fix: couple of linter errors

* fix: linter error

* fix: gateway linter errors

* style: update style

* style: fix bot style

* fix: type

* ci: move all old workflow

* chore: close #2619

* chore: close #2671

* test: add mocha

* chore: add typescript plugin

* test: add mocha

* test: add test to utils

* test/ci: update ci and coverage

* chore: change script naming

* ci: update include test

* test: add coverage

* ci: fix cache

* ci: fix ci and codecov

* Discordeno Documentation  (#2673)

* Add git hooks

* Add documentation generatation

* Change Documentation Engine

* Add documentation

* Remove autogenerated docs

* combine lint staged action into one

* style: fix

Co-authored-by: H01001000 <heiheiho000@gmail.com>

* chore: new package client

* test: add rest test

* ci: enable codecov

* fix: type

* test: add test to all packages

* ci: add release to gh per commit

* fix: ci syntax

* fix: package version

* fix: publish script

* fix: remove private from gateway

* ci: add filter for changes

* fix: ci syntex

* ci: try fix path filter

* ci: try fix path filter

* test: add test

* ci: fix string and  boolean

* fix: package and ci

* chore: fix turbo type cache

* ci: also publish to npm

* ci: change to public

* fix: not publish to npm

* fix: dependencies

* chore: fix fmt script

* fix: better rest typecheck Closes #2621

* fix: run yarn install

* feat: add transformers to rest

* feat: add helpers to rest

* test: move bot utils test

* reverse change to release.yml

* chore: add clean build

* refactor: discordeno

* chore: add import type

* chore: remove bot

* fix: change deps from bot to dd

* chore: update yarn lock

* test: temp remove test from logger

* refactor: remove transformers in helpers/channel

* type: close #2622

* ci(fix): explicitly define coverage file

* refactor: remove transformers in helpers/emoji

* type: fix discord guild type

* feat: DiscordEditAutomoderationRule type

* fix: remove unused type

* feat: DiscordCreateGuildEmoji DiscordModifyGuildEmoji & DiscordModifyCHannel types

* feat: DiscordCreateChannel DiscordBuDeleteMessages DiscordCreateMessage DiscordEditMessage

* feat: DiscordCreateScheduledEvent EditScheduledEvent DiscordCreateInvite

* fix: types for guild stuff for rest

* feat: thread discord types for rest

* feat: channel rest types

* feat: member rest types

* feat: more discord rest types

* fix: type errors

* fix: docs bot param name should be rest

* type: fix type error

* ci(fix): codecov

* chore(client) :add export transformer

* fix: verifySignature

* test: fix types

* test: add test prevent #2683 #2678

* fix: export transformer twice

* ci: reuse cache

* test: add test:unit-noTextCoverage

* feat: add transform and constant package

* fix: half fix #2683 #2688

* fix: #2688 fix all transformer.spec.ts

* fix: transformer name

* ci: update style

* fix: dependencies

* fix: naming

* fix: yarn lock

* fix: remove validations from helpers. Closes #2700

* fix: rest routes as a constants pkg

* fix: esm import with .js

* test: add exception case

* feat: adding transformer

* chore: change script name

* chore: add path fixing to coverage file

* fix: camelize tuple bug

* fix: reverse transformers

* fix: transformers folder to camel

* fix: transformers as an object

* fix: linter error

* ci: run test with deno close #2701

* fix: test depends on build

* fix: deno import node:crypto

* fix: rest improvements

* fix: channel type

* fix: export user

* chore: move to unit dir

* test: moving bench

* fix: remove transformers from rest package

* fix: move toggle transformers to bot pkg

* fix: move out gw helpers to gw package

* test: add rest e2e test

* fix: syntex

* ci: add discord token

* fix: ci not passing secret

* test: add role test

* ci: fix secret inherit

* fix: role helpers transformer

* test: increase timeout to 10s

* fix: member helpers transformer

* test: add member test

* fix: name and type

* fix: guild ban

* test: fix test

* fix: test add await

* test: skip some test

* test: increase timeout

* chore: add transformer to import map

* fix: test

* test: add why-is-node-still-running

* test: add debug hook

* ci: add timeout incase any async running

* test: try fix

* test: turn on rest debug

* fix: if undefined

* test: reduce timeout

* fix: queue not running after some request

* fix: increase remaining after request without ratelimit header

* fix: partial webhook

* fix: fetch hooks not working if debug defined later

* fix: nickname null to undefined

* test: finish adding webhook test

* refactor(test): move rest to utils

* fix: add await

* fix: sticker

* feat: add message embed component transformer

* fix: test not done

* fix: arg type to bigString

* fix: sendMessage

* fix: add allowedMentions, interactionResponse

* test: add emoji e2e test

* test: add guild e2e test

* refactor(test): remove extra rest call

* fix: create emoji BINARY_TYPE_INVALID_DATA_URI

* fix: create guild rate limit

* fix: automode rule helpers and test

* fix: test run in only

* test: add some queue bucket test

* test: remove empty test

* refactor(test): use new guild

* test: use new guild for other test

* fix: guild not defind

* reactor(test): remove duplicated creat channel

* test: increase timeout to 30s

* test: add thread test

* fix: more transformers

* fix: gateway helpers use transformers

* fix: types belong in types pkg

* fix: helpers use transformers

* BREAKING: v19 rewrite to node + major improvements (#2703)

* fix: move all to old folder

* fix: cleanup types

* fix: more cleanup

* fix: more base cleanup

* fix: token dotenv

* fix: add base transformer

* fix: partial error handling

* fix: handle 429 rate limit

* test(rest): fix unit test

* fix(script): transform extension

* test: fix error and buffer

* feat: camelizer util

* fix: cleanup

* fix: rate limit queues and headers processing

* fix: rest exports

* fix: no more transformers

* fix: queue header null bug

* fix: add gateway package base

* fix: lint error

* fix: add prettier file

* fix: prettier is default fmtr

* fix: fmt shard file

* fix: fmt

* fix: types issue

* fix: remove unused consts

* fix: all import issues

* fix: import error

* fix: import ending with .js

* fix: remove transformers package

* Fix eslint (#2710)

* fix: typing of button component label to be optional (#2708)

* feat: add guild_connections to role tags and toggles (#2706)

Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>

* Add lint checks and autofixing workflow (#2702)

* Add lint checks and autofixing workflow

* Update lint.yml

* Fix: use yarn instead npm

* fix: add ts to eslint_extensions

* fix: update dir

* fix: lint.yml format

Co-authored-by: Jonathan Ho <heiheiho000@gmail.com>

* fix: unused deps

* ci: fix e2e test not running

* chore: run yarn install

* test: this should run test

* feat: getCHannel

* feat: createEMji helper

* feat: collection class in util package

* fix: gateway bugs

* why on earth is this needed change

* fix: cleanup docs on collection

* Emoji rest methods (node-migration-clean) (#2713)

* feat: all emoji rest methods

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* fix: stuff

* fix: bot types

* fix: remove logs

* fix: camelize all gateway payloads

* fix: remove todos

* fix: start deris

* fix: lint/ts errors except shard file

* thats 1 way to fix type errors

* yes (#2714)

Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>

* feat: rest channel helpers

* Update release.yml

* Update release.yml

* ci: add build filter

* ci: fail fast false

* fix: complete webhook related helpers

* fix: follow announcement helper

* add forum helper

* add stage helpers

* add thread related helpers

* alphabetize

* cleanup webhook routes

* automod helpers

* scheduled events

* integrations helpers

* invite stuff incomplete

* dm channel and avatar url

* chore: move ts-node into package

* fix: importing esm

* fix: tris message helpers

* test(rest): add simplifyUrl test

* test(rest): add checkRateLimits test

* test(rest): add processRateLimitedPaths test

* test(rest): fix missing beforeEach

* test(utils): enable old test

* interaction helpers

* perf(utils): optimize snake to camel case conversion (#2717)

* perf(utils): optimize snake to camel case conversion

* fmt

* wont change much but still faster

* actually this was a stupid idea

* shh

* Fix(client): Fix typings. (#2716)

Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>

* test: fix rest and utils test (#2723)

* test(utils): fix  cant import collection

* test(rest): await expect

* fix(utils): deno compactability

* test(utils): typing

* fix(utils): add return type

* Add rest helpers for templates (#2727)

* Add rest helpers for templates

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* fix: remove frozenAt

* fix: cleanup templates

* ci: release to npm (#2725)

* ci: release to npm

* Update release.yml

* 🐛 - fix: types - slashcommands - add nsfw prop (#2731)

* feat(rest helpers): Add template and member helpers. (#2728)

* feat(rest helpers): Add template and member helpers.

* format code

* feat(rest): add template routes

* fix(rest): routes and runMethod

* fix(rest): try to fix most of type

Co-authored-by: H01001000 <heiheiho000@gmail.com>
Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>

* ci: fix release (#2732)

* fix: rest type errors

* fix: v19 begin

* fix: yarn lock

* chore: fix deps and script (#2733)

* fix: bug camelizer deleting letters

* fix: falsy token check

* fix: add frozenat check for queue

* fix: max stack trace error do to infinite loop

* fix: type error

* test(rest): fix missing import (#2734)

Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>

* fix: file paths for imports

* fix: lastShardId should default to 0

* fix: use isomorphic ws

* test(rest): fix "TypeError: [Function] is not a thenable" (#2736)

* test(utils): add utils tests (#2737)

* test(utils): add urltobase64 test

* test(utils): add token test

* test(utils): fix missing import buffer

* test(utils): add casting test

* test(utils): add casting test

* test(utils): fix use correct function

* chore: make eslint error if missing .js extension (#2735)

* chore: eslint error if missing .js extension

* chore

* lint: fix missing .js error

Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>

* fix: readme runtime list (#2739)

* Bot pkg (#2740)

* fix: bot pkg test

* Fix code style issues with ESLint

* Update Guild.ts

* Update Guild.ts

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Jonathan Ho <heiheiho000@gmail.com>

* Update tsdoc.json (#2741)

Updates tsdoc.json to reflect the current packages.

* Interaction types - remove member,channel,role from value type (#2743)

https://discord.com/channels/785384884197392384/1067265182776176690/1068189883073572924

* Add missing types (#2742)

* Revert "fix: use isomorphic ws" (#2744)

This reverts commit ad306b0d0a.

* fix: interaction requests that sent without full url

* fix: lint issues

* fix: remote gateway test file

* fix: interaction response bug with body being invalid (#2746)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* fix: interaction followup type (#2747)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* fix: e2e exit bug (#2748)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* fix: rest sending attachments files (#2749)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

* fix: image file sending

* Fix code style issues with ESLint

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* fix: guild and role methods (#2751)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

* fix: image file sending

* Fix code style issues with ESLint

* guild and role methods

* Fix code style issues with ESLint

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* ci: add bot package (#2752)

* ci: add bot package

* ci: fix test

* e2e test stuff (#2754)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

* fix: image file sending

* Fix code style issues with ESLint

* guild and role methods

* Fix code style issues with ESLint

* fix: dont send heartbeat if socket is not open

* fix: remove logs

* fox: remove more logs

* fix some bugs in role tests

* Switch to after hook style

* hoti's speed snaker

* auto convert objects for discord

* Fix code style issues with ESLint

* fix: remove dup imports

* fix: i hate linters

* speeder

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* fix: delete guilds test (#2758)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

* fix: image file sending

* Fix code style issues with ESLint

* guild and role methods

* Fix code style issues with ESLint

* fix: dont send heartbeat if socket is not open

* fix: remove logs

* fox: remove more logs

* fix some bugs in role tests

* Switch to after hook style

* hoti's speed snaker

* auto convert objects for discord

* Fix code style issues with ESLint

* fix: remove dup imports

* fix: i hate linters

* speeder

* fix: tests delete guilds

* Fix code style issues with ESLint

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* chore: fix deps (#2757)

* ci/test: fix bot pkg e2e test (#2759)

* chore: fix script and update import map (#2761)

* chore: fix version script

* chore: update import map

* chore: fix ws import map

* chore: fix deno import map

* test: fix

* fix: more rest e2e tests (#2763)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

* fix: image file sending

* Fix code style issues with ESLint

* guild and role methods

* Fix code style issues with ESLint

* fix: dont send heartbeat if socket is not open

* fix: remove logs

* fox: remove more logs

* fix some bugs in role tests

* Switch to after hook style

* hoti's speed snaker

* auto convert objects for discord

* Fix code style issues with ESLint

* fix: remove dup imports

* fix: i hate linters

* speeder

* fix: tests delete guilds

* Fix code style issues with ESLint

* fix: easier to provide custom intents in bot

* fix: shutdown bot after test

* fix: add getGuild

* fix: multiple guild delete attempts

* fix: add emoji e2e tests

* fix: remaining old e2e rest tests

* Fix code style issues with ESLint

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* test: add gateway integration test (#2756)

* test: add gateway integration test

* test(gateway): fix connection test

* test(gateway): add heartbeat test

* ci: add integration test

* fix: add uWebSockets.js

* ci: add timeout

* test(utils): remove old test

* Revert "test(utils): remove old test"

This reverts commit 04fb6dd4b5.

* test(gateway): fix uws server

* test(gateway): fix type

* chore: update codecov flag

* test(gateway): remove dev code

---------

Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>

* Update release.yml (#2768)

* fix: bot logger (#2769)

* fix: readme runtime list

* Fix code style issues with ESLint

* node 18

* fix: websocket import type

* fix: body for interaction requests

* fix: perma fix for type error in ci

* fix: followupmessage option type

* fix: e2e tests exit bug

* fix: color console logger

* fix: image file sending

* Fix code style issues with ESLint

* guild and role methods

* Fix code style issues with ESLint

* fix: dont send heartbeat if socket is not open

* fix: remove logs

* fox: remove more logs

* fix some bugs in role tests

* Switch to after hook style

* hoti's speed snaker

* auto convert objects for discord

* Fix code style issues with ESLint

* fix: remove dup imports

* fix: i hate linters

* speeder

* fix: tests delete guilds

* Fix code style issues with ESLint

* fix: easier to provide custom intents in bot

* fix: shutdown bot after test

* fix: add getGuild

* fix: multiple guild delete attempts

* fix: add emoji e2e tests

* fix: remaining old e2e rest tests

* Fix code style issues with ESLint

* fix: add bot.logger

* fix: make logger name capital

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>

* chore: update readme (#2772)

* chore: add coverage per pkg

* chore: add npm version

* chore: add test status

* chore: fix ci

* chore: fix ci

* ci: fix ci needs

* chore: add only push event

* style: remove import logger

* 📚 - docs: fix README package links (#2773)

* test(all): add test importing index (#2774)

* test(all): add test importing index

* chore: remove old benchmark dir

* chore: disable coverage status fail

* test(client): add import test with try catch

* test(rest): fix narrow import scope

* test(utils): add test (#2764)

* test(utils): remove old test

* test(utils): add color test

* test(utils): fix import mocha

* test(utils): fix test type error

* test(utils): remove dev code

* fix(utils): bucket not export all function

* test(utils): add some test for bucket

* fix(utils): close #2775

* test(utils): add test for permissions.ts

* test(utils): fix missing mocha import

* fix(utils): better fix for #2775

* feat: addReaction & addReactions

* feat: connectToVoice

* fix: linters issues

* fix: remove aliases and add createGuildFromTemplate

* feat: deleteMessages

* fix: reaction related helpers

* mfa level

* voie states editing

* image urls

* fix: typos

* get message typeguards

* fix: more helpers

* fix: remaining helpers

* fix: add logs to gateway manager

* fix: rest resolve sends status and body

* fix: lots of errors

* fix: client errors

* fix: remove old pkg

* snaker

* fix: broken util import for image url

* fix: cleanup shard and circular deps

* fix: remove ThreadChannel from GuildChannel

* fix: generate interaction usage

* fix: more bugs

* fix: use node:events to import

* fix(rest): add interface RestRequestRejection (#2782)

* fix: remove invalid todo

* fix: timeout bug

---------

Co-authored-by: Skillz <skillz@discord.gg/ddeno>
Co-authored-by: Jonathan Ho <heiheiho000@gmail.com>
Co-authored-by: deepsarda <92147339+deepsarda@users.noreply.github.com>
Co-authored-by: Awesome Stickz <awesome@stickz.dev>
Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Lars_und_so <46791248+Larsundso@users.noreply.github.com>
Co-authored-by: ITOH <to@itoh.at>
Co-authored-by: Yaikava <83710104+Yaikava@users.noreply.github.com>
Co-authored-by: Andreas Fink <mail@afink.dev>

---------

Co-authored-by: Jonathan Ho <48591478+H01001000@users.noreply.github.com>
Co-authored-by: H01001000 <heiheiho000@gmail.com>
Co-authored-by: deepsarda <92147339+deepsarda@users.noreply.github.com>
Co-authored-by: Yaikava <83710104+Yaikava@users.noreply.github.com>
Co-authored-by: Skillz <skillz@discord.gg/ddeno>
Co-authored-by: Awesome Stickz <awesome@stickz.dev>
Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Lars_und_so <46791248+Larsundso@users.noreply.github.com>
Co-authored-by: ITOH <to@itoh.at>
Co-authored-by: Andreas Fink <mail@afink.dev>
This commit is contained in:
Skillz4Killz
2023-02-20 15:00:00 -06:00
committed by GitHub
co-authored by H01001000 Lint Action Skillz deepsarda Awesome Stickz Lars_und_so ITOH Yaikava Andreas Fink Jonathan Ho
parent 1a717ddc7d
commit 2714e1e7d1
1057 changed files with 53134 additions and 55520 deletions
-35
View File
@@ -1,35 +0,0 @@
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
@@ -1,20 +0,0 @@
# 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*
-3
View File
@@ -1,3 +0,0 @@
# Discordeno Site
This folder contains the code for the Discordeno Website: https://discordeno.mod.land.
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
};
-49
View File
@@ -1,49 +0,0 @@
---
sidebar_position: 2
---
import BrowserOnly from '@docusaurus/BrowserOnly';
# Architecture
## Overview
import FlowChart from '@site/src/components/architecture/FlowChart'
<BrowserOnly>
{() => <FlowChart />}
</BrowserOnly>
Discordeno have three main components/process, gateway, bot and rest. Websocket events from Discord, such as connecting, restarting, heartbeating, and transmitting websocket messages to Discord, are handled and maintained by the Gateway process. All Discord events are handled and converted by the Bot process, which also activates your code, such as the execute function upon message creation. All http requests to Discord, including proxying and ratelimiting, are handled by the Rest process.
## Gateway Process
import FlowChart2 from '@site/src/components/architecture/FlowChart2'
<BrowserOnly>
{() => <FlowChart2 />}
</BrowserOnly>
The Gateway process have two part the gateway manager and the gateway [shard](https://discord.com/developers/docs/topics/gateway#sharding), the gateway manager oversees the gateway shard.
### Gateway Manager
The gateway manager spawns the right amount of shard acording to data from discord's [getGatewayBot](https://discord.com/developers/docs/topics/gateway#get-gateway-bot) endpoint, user can override the value of gatewayBot by directly passing the value. The manager control the order or sequence of shard identifying base on the session start limit listed in the getGatewayBot to prevent hitting the ratelimiting. By default the manager will check the getGatewayBot endpoint every 8 hours and reshard if the number of shard changed shard. Check [here](/tutorial/big-bot-guide/gateway#understanding-gateway-manager) for more information about the gateway manager.
### Gateway Shard
Any event is passed to the handleMessage method by the gateway shard, which also establishes a websocket connection to Discord. The handleMessage method will examine the event and only deliver genuine events to the bot by intercepting and processing websocket-related events like hello, resume, heartbeat, and ready. You can modify the handleMessage to suit your needs, but it is not advised unless you are certain of your actions because the connection depends on it. After the function has finished processing the event, it will either pass the event directly (in the same process), via the rest api, a message queue, or another mechanism depending on the user's customization.
## Bot Process
import FlowChart3 from '@site/src/components/architecture/FlowChart3'
<BrowserOnly>
{() => <FlowChart3 handlerFilter={(handler) => handler.startsWith('handleChannel')}/>}
</BrowserOnly>
A simplified version of function used inside of the bot process, showing only the handlers, transformers and event related to channel event
### Bot
When an event arrives from the gateway, the bot process receives it and passes it all to the handleDiscordPayload method. The handleDiscordPayload method will invoke raw events for each event and route calls to handlers, transformers, and events at three different layers for processing. The handler will use transformers to alter the event's contents before calling the event. The data will be transformed into typescript after being stripped of pointless properties, given a new name, and having bitwise permission flags abstracted.
## Rest Process
TBC
-12
View File
@@ -1,12 +0,0 @@
---
sidebar_position: 10
---
# Benchmark
Benchmark runs on every commit pushed on the Discordeno's main branch
import Benchmark from '@site/src/components/Benchmark'
<Benchmark />
-101
View File
@@ -1,101 +0,0 @@
---
sidebar_position: 5
---
# Frequently Asked Questions
## Does Discordeno Support TypeScript?
TypeScript is supported to the highest standard by Discordeno. TypeScript is included in Discordeno since Deno supports it. This implies that before using TypeScript, you do not need to compile it. But this isn't the main reason Discordeno is the ideal library for TypeScript programmers. I was experimenting with a lot of various things when I created this library, and automatic typings was one of them.
When I utilised other libraries, I frequently observed incorrect or troublesome typings. This is so that TypeScript won't alert the library developers because most of the Discord API typings aren't utilised by the libraries themselves.
It is quite unlikely that these typings would become wrong or outdated as a result of minor errors like forgetting to update typings because Discordeno utilises them as part of the rest process. Libraries occasionally add a property without also adding it to their typings. Because of this, TypeScript developers cannot use it, only JavaScript developers can. Typings are crucial for TypeScript developers. Typings are treated as a component of the code by Discordeno! A breaking change in typings is a breaking change for the library!
## How Stable Is Discordeno?
Stability is one of the main problems with practically every library (I have used). None of the libraries showed TypeScript developers the love and care that they deserve. Because breaking changes to typings occasionally occurred without producing a MAJOR bump, TypeScript projects would occasionally fail. As a result, production TypeScript bots would fail. At times, I was the only one keeping the typings up to date for that library. Some libraries that were older than 1.0 didn't even have a stable branch or version, so I didn't have to worry about them undergoing breaking changes.
The finest stability for TypeScript developers is one of my basic goals for this library.
No matter how little, a change that impacts the public API qualifies as a breaking change. I don't care whether we reach version 500. As a library maintainer, you should never be scared to bump a MAJOR because it just involves a tiny modification or a type change because doing so will ruin the end user's experience.
## Why Doesn't Discordeno Use Classes or EventEmitter?
This is a design decision for the library itself. You can still use class on your bot if you want. In fact, I hope someone
makes a framework/templates for this lib one day using classes so that devs have a choice on which style they prefer.
Without trying to write an entire thesis statement on the reasons why I avoided Classes in this library, I will just link to
the best resources that I believe help explain it.
- [Really good article](https://dannyfritz.wordpress.com/2014/10/11/class-free-object-oriented-programming/)
- [Lecture by one of the developers who makes JavaScript](https://www.youtube.com/watch?v=PSGEjv3Tqo0)
In regards to EventEmitter, I believe a functional event API was a much better choice. EventEmitter at its core, is simply a set of functions that run when a certain event is emitted. In Discordeno, that function is executed instead of emitting some event to trigger it.
```typescript
// EventEmitter Example
EventEmitter.emit("guildCreate", guild);
// Discordeno Example
bot.events.guildCreate?.(bot, guild);
```
There isn't really any difference especially for users when they use it. One bad thing about EventEmitter is that if
misused it can easily cause memory leaks. It is very easy to open yourself up to these memory leak issues. It has
happened to me when I started coding as well. This is why I wanted Discordeno's implementation to help devs avoid the
issues I had. It prevents anyone from having this as a potential issue. Another issue with EventEmitter is trying to
update the code in those functions without having to deal with headaches left and right of removing and adding
listeners. You don't need to worry about binding or not binding events. They are just pure functions
In Discordeno, this is extremely simple; you just simply give it the new event handlers. For example:
```typescript
bot.events.guildCreate = newGuildCreateEventHandler;
```
## Why Do You Have A Class for Collection If Classes Are Bad?
The Collection class is an exception in the library where a class was allowed. This is because Collection extends Map.
The Map class is provided by JavaScript itself and is extremely fast. You can perform millions of operations a second
with a Map. Maps are too useful to avoid and don't have downsides like EventEmitters do. The Collection class simply
adds on other functionality that Discordeno users felt they needed. Although I am against using classes whenever
possible, I am also a big supporter of providing the best developer experience.
## Why Are there no options in Discordeno?
Discordeno is not a library that handles code in the exact way every person wants it to. It is opinionated. Discordeno
defaults to the Discord recommended options or the best options for majority of developers needs. For example, there is
no option of fetching all members startup. This is a practice that Discord does not recommend or want users doing. By
default, we don't support stuff like this. In Discordeno, we follow Discords recommended solution and it just works
internally. The End! No fuss! No Muss! Just good stuff!
Now, I understand that there are times when it's necessary to be able to customize this and fetch them all. If you are
advanced enough to need these options, you should be able to simply do it yourself. For most users, this is just an
unnecessary option. The main module should remain minimalistic and easy to use for 99% of users.
## Why Do I See errors Like "MISSING_VIEW_CHANNEL" or "BOTS_HIGHEST_ROLE_TOO_LOW"?
Discordeno is the only library(that I have used), that has built in permission handling. A lot of bots get automatically
banned by Discord because they forget to handle permissions. When bots don't check permissions and continue to send
requests to the API, this leads to bots being banned. I have tried to request adding this feature into libraries but
they were reluctant to do so because it would require the devs to maintain the library whenever an update was made by
Discord.
Discordeno provides you specific keywords that you can use to send a clean response to the end user of your choosing. I
have even seen some bots have hundreds of thousands of Missing Permission or Missing Access errors because libraries
don't handle it. IMO, this is a crucial part of any good library as much as it is to handle rate limiting.
```typescript
import { Bot, Errors, Message } from "https://deno.land/x/discordeno@16.0.0/mod.ts";
export function handleCommandError(bot: Bot, message: Message, type: Errors) {
switch (type) {
case Errors.MISSING_MANAGE_NICKNAMES:
return bot.helpers.sendMessage(message.channelId, {
content:
"The bot does not have the necessary permission to manage/edit other user's nicknames. Grant the **MANAGE_NICKNAME** permission to the bot and try again.",
});
case Errors.MISSING_MANAGE_ROLES:
// Note: i18n is not part of the library. This is just an example of how you could use i18n for custom error responses.
return bot.helpers.sendMessage(message.channelId, { content: i18n.translate(type) });
}
}
```
-74
View File
@@ -1,74 +0,0 @@
---
sidebar_position: 3
---
# Getting Started
Discordeno aims for a simple, easy and stress-free interaction with the Discord API. Always supporting the latest
version to ensure stability, consistency and the best developer experience. This guide serves as the purpose for
introducing Discordeno to developers.
## Requirements
- **Deno 1.0** or higher
## Creating your First Discord Bot Application
Plenty of guides are available on how to create a Discord Bot Application.
1. [Creating an Application](https://discord.com/developers/applications) on the Developer Portal, name something cool
and pick a sweet icon!
2. After creating an application. Save the **Client ID.** Thats the unique identifier for a Discord Bot.
3. Now, go and create a bot by clicking the **Bot** tab. You will see a **Token** section and thats the Discord Bot's
token. **Make sure you don't share that token with anyone!!!**
4. Invite the bot to the server, you can use the
**[Discord Permissions Calculator](https://discordapi.com/permissions.html#0)** for creating the invite link with
custom permissions. By default, `0` means no permissions and `8` means Administrator.
Now you've created an Application but it will need some code in order for it to be online. Thats when Discordeno comes
in handy!
> Make sure you store your tokens in a file that is NOT deployed by adding it to the .gitignore file. **Don't share your
> bot token with anybody.**
## Installation
You can install Discordeno by importing:
```ts
import { startBot } from "https://deno.land/x/discordeno@16.0.0/mod.ts";
```
## Example Usage
Starting with Discordeno is very simple, you can start from scratch without any templates/frameworks: Add this snippet
of code into a new TypeScript file:
```ts
import { createBot, Intents, startBot } from "https://deno.land/x/discordeno/mod.ts";
startBot(
createBot({
token: "BOT TOKEN",
intents: Intents.Guilds | Intents.GuildMessages,
events: {
ready() {
console.log("Successfully connected to gateway");
},
messageCreate(bot, message) {
if (message.content === "!ping") {
bot.helpers.sendMessage(message.channelId, { content: "Pong using Discordeno!" });
}
},
},
}),
);
```
## Tutorials
Below you will find youtube playlists that display channels using Discordeno for their tutorials.
- [Making a Discord bot with Deno and Discordeno](https://web-mystery.com/articles/making-discord-bot-deno-and-discordeno)
- [Running a Discord bot written using Deno in Docker](https://web-mystery.com/articles/running-discord-bot-written-deno-docker)
- [Discordeno Bot Tutorials (YouTube)](https://youtu.be/rIph9-BGsuQ)
-23
View File
@@ -1,23 +0,0 @@
---
sidebar_position: 1
---
# Discordeno
> Discord API library for [Deno](https://deno.land)
- [Documentation](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
- [Discord](https://discord.gg/ddeno)
## Features
- **Secure & stable**: Discordeno is secure and stable. One of the greatest issues with almost every library is
stability; types are outdated, less (or minimal) parity with the API, core maintainers have quit or no longer actively
maintain the library, and whatnot. Discordeno, on the other hand, is actively maintained to ensure great performance
and convenience. Moreover, it internally checks all missing permissions before forwarding a request to the Discord API
so that the client does not get globally-banned by Discord.
- **Simple, Efficient, & Lightweight**: Discordeno is simplistic, easy-to-use, versatile while being efficient and
lightweight.
- [**Functional API**](https://en.wikipedia.org/wiki/Functional_programming): Functional API ensures an overall concise
yet performant code while removing the difficulties of extending built-in classes and inheritance.
[Learn more about class-free JavaScript](https://dannyfritz.wordpress.com/2014/10/11/class-free-object-oriented-programming/)
-469
View File
@@ -1,469 +0,0 @@
---
sidebar_position: 4
---
# Migrating
## Migrating from Discord.js
This migration guide is not intended to discredit Discord.js authors/maintainers or Discord.js itself. In fact,
Discord.js is the most popular Node.js library, admired and praised by a lot of JavaScript developers.
## Finding an Open-Source Discord Bot
For the purposes of this guide, I wanted to find a moderation bot that is totally open source to show an example of how
to convert the bot to Discordeno. Trying to find one was not easy as most bot's were not using the latest Discord.JS
version 12. Trying to find one that was using TypeScript made it even more difficult. My next best solution was to find
a moderation bot that was recently updated(showing it is maintained or recently built). The best one I could find was
[Zodiac Bot](https://github.com/Nukestye/Zodiac).
For the purposes of this guide, I will be using the current
[latest commit](https://github.com/Nukestye/Zodiac/tree/213891a38af1b7ecbd068b661ef9062ab58cc818)
## Preparations
- First, create a Discordeno Bot using the [Generator Template](https://github.com/discordeno/template) I will name it
Zodiac.
- Then `git clone https://github.com/Skillz4Killz/Zodiac.git`
Now that I had the repository cloned, I could begin. Note that although the bot we are converting is built in
JavaScript, I converted all code to TypeScript in this Guide as Discordeno is designed to be the best lib for TypeScript
developers.
Time to get started!
## Converting main.js (index file)
The first thing is to convert the `main.js` file which would be the app.js or index.js file. This is the file that is
run to start your bot. In this case, the bot developer chose `main.js`. In Deno, the initial file is named `mod.ts` so
we can go ahead and opt for the Deno pattern. Note: there is already a `mod.ts` file created and prebuilt entirely using
the Generator.
Current Discord.JS Code:
```js
/* Keeping this to shoutout/credit the original author <3
* @author: nukestye
*/
const config = require("./config.json");
const fs = require("fs");
const log = console.log;
// Setting up the way to get commands
const { CommandoClient } = require("discord.js-commando");
const path = require("path");
// reading events
fs.readdir("./src/events/", (err, files) => {
if (err) return console.error(err);
files.forEach((file) => {
const eventFunction = require(`./src/events/${file}`);
if (eventFunction.disabled) return;
const event = eventFunction.event || file.split(".")[0];
const emitter =
(typeof eventFunction.emitter === "string" ? client[eventFunction.emitter] : eventFunction.emitter) || client;
const { once } = eventFunction;
try {
emitter[
once ? "once" : "on"
](event, (...args) => eventFunction.run(...args));
} catch (error) {
console.error(error.stack);
}
});
});
const client = global.client = new CommandoClient({
commandPrefix: `${config.prefix}`,
owner: `${config.owner}`,
invite: `${config.discord}`,
unknownCommandResponse: false,
});
// Registing the commands
client.registry
.registerDefaultTypes()
// The different fields for cmds
.registerGroups([
["mod", "Moderation Commands"],
["public", "Public Commands"],
])
.registerDefaultGroups()
// Basic cmds can be disabled like {"cmd: false"}
.registerDefaultCommands()
// commands in "/src/commands" will be counted
.registerCommandsIn(path.join(__dirname, "/src/commands"));
// list of activities that the bot goes through
const activityArray = [`${config.prefix}help | `];
// Bot lanuch code
client.once("ready", () => {
log(`Logged in as ${client.user.tag} in ${client.guilds.size} guild(s)!`);
setInterval(() => {
const index = Math.floor(Math.random() * (activityArray.length)); // generates a random number between 1 and the length of the activities array list
client.user.setActivity(
activityArray[index],
{
type: "PLAYING",
},
); // sets bot"s activities to one of the phrases in the arraylist.
}, 5000); // updates every 10000ms = 10s
});
// If an error print it out
client.on("error", console.error);
// Login in using the token in config
client.login(config.env.TOKEN);
```
Discordeno Version:
```ts
import { botCache, Intents } from "./deps.ts";
import { configs } from "./configs.ts";
import { importDirectory } from "./src/utils/helpers.ts";
import { loadLanguages } from "./src/utils/i18next.ts";
console.info(
"Beginning Bot Startup Process. This can take a little bit depending on your system. Loading now...",
);
// Always require these files be processed before anything else
await Promise.all([
"./src/customizations/structures",
].map(
(path) => importDirectory(Deno.realPathSync(path)),
));
// Forces deno to read all the files which will fill the commands/inhibitors cache etc.
await Promise.all(
[
"./src/commands",
"./src/inhibitors",
"./src/events",
"./src/arguments",
"./src/monitors",
"./src/tasks",
"./src/permissionLevels",
"./src/events",
].map(
(path) => importDirectory(Deno.realPathSync(path)),
),
);
// Loads languages
await loadLanguages();
await import("./src/database/database.ts");
startBot({
token: configs.token,
// Pick the intents you wish to have for your bot.
// For instance, to work with guild message reactions, you will have to pass the Intents.GUILD_MESSAGE_REACTIONS intent to the array.
intents: Intents.Guilds | Intents.GuildMessages,
// These are all your event handler functions. Imported from the events folder
events: botCache.events,
});
```
Something we haven't converted yet from the `main.js` files is the event listeners. To do that, we will open up the
events folder and find the corresponding event or create it if necessary. In this case, we have the `ready` event and
there is already a `ready.ts` file. We can just use that.
In our `ready.ts` file we can add the `ready` event listener.
```ts
import { ActivityTypes, botCache, cache, chooseRandom, editBotStatus, StatusTypes } from "../../deps.ts";
import { registerTasks } from "./../utils/taskHelper.ts";
botCache.events.ready = function () {
editBotStatus(
StatusTypes.DoNotDisturb,
"Discordeno Best Lib",
ActivityTypes.Game,
);
console.log(`Loaded ${botCache.arguments.size} Argument(s)`);
console.log(`Loaded ${botCache.commands.size} Command(s)`);
console.log(`Loaded ${Object.keys(botCache.events).length} Event(s)`);
console.log(`Loaded ${botCache.inhibitors.size} Inhibitor(s)`);
console.log(`Loaded ${botCache.monitors.size} Monitor(s)`);
console.log(`Loaded ${botCache.tasks.size} Task(s)`);
registerTasks();
console.log(
`[READY] Bot is online and ready in ${cache.guilds.size} guild(s)!`,
);
// list of activities that the bot goes through
const activityArray = [`${configs.prefix}help | `];
setInterval(() => {
const randomActivity = activityArray[Math.floor(Math.random() * activityArray.length)];
editBotStatus(botCache, {
activities: [{ name: randomActivity, type: ActivityTypes.Game, createdAt: Date.now() }],
status: "online",
});
}, 5000);
};
```
To understand this code, we are setting a function to be run when the bot is `ready`. Then the bot will edit the bot's
status every 5 seconds. Notice that you also have beautiful enums provided that prevents you from making any
typos/mistakes.
We have now converted the entire `main.js` file, in a matter of seconds. The Discordeno official generator took care of
the majority of workload and we just modified the `ready.ts` file.
`Note:` I did remove some generally well known "bad practices" such as global vars and such. Overall, you will see the
functionality of the project will not change as we progress through this guide.
## Converting Commands
The first command in the commands folder is the `addRole` command.
This is the code from the bot:
```ts
// Getting the 'Command' features from Commando
const { Command } = require("discord.js-commando");
// Code for the command
module.exports = class addRoleCommand extends Command {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: "addrole",
// other ways to call the command, must be in lowercase
aliases: ["role"],
// command group its part of
group: "mod",
// name within the command group, must be in lowercase
memberName: "addrole",
// Is the description used for 'help' command
description: "Adds mentioned role to mentioned user.",
// Prevents it from being used in dms
guildOnly: true,
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
clientPermissions: ["ADMINISTRATOR", "MANAGE_ROLES"],
userPermissions: ["MANAGE_ROLES"],
// Prevents anyone other than owner to use the command
ownerOnly: false,
});
}
// Run code goes here
run(message) {
const user = message.mentions.members.first();
const roleToAdd = message.mentions.roles.first();
// checking to see if the user has the role or not
if (!(user.roles.find((r) => r.name === roleToAdd.name))) {
user.addRole(roleToAdd);
message.channel.send(`${user} has been given the role: ${roleToAdd.name}`)
.then((msg) => {
msg.delete(5000);
});
} else {
message.channel.send(`${user} already has the role: ${roleToAdd.name}`);
}
// console.error(user, roleToAdd, message.member.roles.find(r => r.name === roleToAdd));
}
};
```
This is how to do it with Discordeno:
```ts
import { createCommand } from "./../../utils/helpers.ts";
createCommand({
name: "role",
// Oher ways to call the command
aliases: ["addrole"],
// Is the description used for 'help' command
description: "Adds mentioned role to mentioned user.",
// Prevents it from being used in dms
guildOnly: true,
botServerPermissions: ["ADMINISTRATOR", "MANAGE_ROLES"],
userServerPermissions: ["MANAGE_ROLES"],
arguments: [
{ name: "member", type: "member" },
{ name: "role", type: "role" },
],
execute: (bot, message, args) => {
// checking to see if the user has the role or not
if (!args.member.roles.includes(args.role.id)) {
bot.helpers.addRole(message.guildId, args.member.id, args.role.id);
bot.helpers.sendMessage(message.channelId, {
content: `${args.member.mention} has been given the role: ${args.role.name}`,
});
} else {
bot.helpers.sendMessage(message.channelId, {
content: `${args.member.mention} already has the role: ${args.role.name}`,
});
}
},
});
```
Awesome, that is a full command converted from Discord.JS to Discordeno. See how easy it is! Let's convert one more
command to see how to really take full advantage of Discordeno template and have something amazing.
Discord.JS Kick Command Version
```js
// Getting the 'Command' features from Commando
const { Command } = require("discord.js-commando");
const { RichEmbed } = require("discord.js");
const chalk = require("chalk");
const log = console.log;
// Code for the command
module.exports = class kickCommand extends Command {
constructor(client) {
super(client, {
// name of the command, must be in lowercase
name: "kick",
// other ways to call the command, must be in lowercase
aliases: ["boot", "tempban"],
// command group its part of
group: "mod",
// name within the command group, must be in lowercase
memberName: "kick",
// Is the description used for 'help' command
description: "Kick command.",
// adds cooldowns to the command
throttling: {
// usages in certain time x
usages: 1,
// the cooldown
duration: 10,
},
// Prevents it from being used in dms
guildOnly: true,
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
clientPermissions: ["ADMINISTRATOR"],
userPermissions: ["KICK_MEMBERS"],
// Prevents anyone other than owner to use the command
ownerOnly: false,
});
}
// Run code goes here
run(message) {
const messageArry = message.content.split(" ");
const args = messageArry.slice(1);
const kUser = message.guild.member(
message.mentions.users.first() || message.guild.get(args[0]),
);
if (!kUser) return message.channel.send("User cannot be found!");
const kreason = args.join(" ").slice(22);
// setting up the embed for report/log
const kickEmbed = new RichEmbed()
.setDescription(`Report: ${kUser} Kick`)
.addField("Reason >", `${kreason}`)
.addField("Time", message.createdAt);
const reportchannel = message.guild.channels.find("name", "report");
if (!reportchannel) {
return message.channel.send("*`Report channel cannot be found!`*");
}
// Delete the message command
// eslint-disable-next-line camelcase
message.delete().catch((O_o) => {});
// Kick the user with reason
message.guild.member(kUser).kick(kreason);
// sends the kick report into log/report
reportchannel.send(kickEmbed);
// Logs the kick into the terminal
log(chalk.red("KICK", chalk.underline.bgBlue(kUser) + "!"));
}
};
```
Discordeno Version
```ts
import { createCommand } from "./../../utils/helpers.ts";
createCommand({
name: `kick`,
aliases: ["boot", "tempban"],
description: "Kick command.",
// adds cooldowns to the command
cooldown: {
// usages in certain duration of seconds below
allowedUses: 1,
// the cooldown
seconds: 10,
},
// Prevents it from being used in dms
guildOnly: true,
botServerPermissions: ["ADMINISTRATOR"],
userServerPermissions: ["KICK_MEMBERS"],
arguments: [
{
name: "member",
type: "member",
missing: function (message) {
message.reply(`User cannot be found.`);
},
// By default this is true but for the purpose of the guide so you can see this exists.
required: true,
},
{
name: "reason",
// The leftover string provided by the user that was not used by previous args.
type: "...string",
defaultValue: "No reason provided.",
// It is silly to lowercase this but for the purpose of the guide you can see that this is also available to you.
lowercase: true,
},
],
execute: function (bot, message, args: KickArgs) {
// setting up the embed for report/log
const embed = new Embed()
.setDescription(`Report: ${args.member.mention} Kick`)
.addField("Reason >", args.reason)
.addField("Time", message.timestamp.toString());
const reportchannel = message.guild?.channels.find((channel) => channel.name === "report");
if (!reportchannel) {
return bot.helpers.sendMessage(message.channelId, { content: "*`Report channel cannot be found!`*" });
}
// Delete the message command
bot.helpers.deleteMessage(message.channelId, { content: "Remove kick command trigger." });
// Kick the user with reason
bot.helpers.kickMember(message.guildId, args.member.id, args.reason);
// sends the kick report into log/report
bot.helpers.sendMessage(message.channelId, { embeds: [embed] });
},
});
interface KickArgs {
member: Member;
reason: string;
}
```
Let's take a minute and explain the differences here. The first thing you will probably notice is different is the
`arguments` property. Discordeno provides the `arguments` property because it provides argument
handling/parsing/validating internally. You don't need to be splitting the message content or going through and
validating it yourself. All you do is tell Discordeno that you want a member and a reason. It will do the magic and hard
work to get you that data before you even run the command. You just do `args.member` and you have access to the full
member object. There are a lot more powerful aspects to Discordeno like arguments. Keep diving in and you will find all
the wonderful tools available to give you the best developer experience possible.
### Need More Examples/Help
If you still need more help converting other aspects of your bot please contact me at
[Discord](https://discord.com/invite/5vBgXk3UcZ). I will continue adding more examples to this guide as more people
request them.
-30
View File
@@ -1,30 +0,0 @@
/**
* 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
docs: [{ type: "autogenerated", dirName: "." }],
// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
};
module.exports = sidebars;
-200
View File
@@ -1,200 +0,0 @@
// @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: "Making Scalable Bots Easy!",
url: "https://discordeno.mod.land",
baseUrl: "/discordeno/",
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: false,
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: "docSidebar",
sidebarId: "docs",
position: "left",
label: "Docs",
docsPluginId: "docs",
},
{
type: "docSidebar",
sidebarId: "tutorial",
position: "left",
label: "Tutorial",
docsPluginId: "tutorial",
},
{
href: "https://github.com/discordeno/discordeno",
label: "GitHub",
position: "right",
},
],
},
footer: {
style: "dark",
links: [
{
title: "Docs",
items: [
{
label: "Introduction",
to: "/docs/intro",
},
{
label: "Getting Started",
to: "/docs/getting-started",
},
{
label: "FAQ",
to: "/docs/frequently-asked-questions",
},
{
label: "Benchmark",
to: "/docs/benchmark",
},
],
},
{
title: "Tutorial",
items: [
{
label: "Big Bot",
to: "/tutorial/big-bot-guide/step-by-step",
},
{
label: "Node.js",
to: "/tutorial/nodejs/getting-started",
},
{
label: "Amethyst",
to: "/tutorial/amethyst/intro",
},
],
},
{
title: "Community",
items: [
{
label: "Discord",
href: "https://discord.gg/ddeno",
},
],
},
{
title: "More",
items: [
{
label: "GitHub",
href: "https://github.com/discordeno/discordeno",
},
],
},
],
copyright: `Copyright © 2021 - ${new Date().getFullYear()} Discordeno.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
plugins: [
[
"@docusaurus/plugin-content-docs",
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
id: "docs",
path: "docs",
routeBasePath: "docs",
sidebarPath: require.resolve("./docsSidebars.js"),
editUrl: "https://github.com/discordeno/discordeno/tree/main/site/",
},
],
[
"@docusaurus/plugin-content-docs",
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
id: "tutorial",
path: "tutorial",
routeBasePath: "tutorial",
sidebarPath: require.resolve("./tutorialSidebars.js"),
editUrl: "https://github.com/discordeno/discordeno/tree/main/site/",
},
],
[
require.resolve("@easyops-cn/docusaurus-search-local"),
{
indexDocs: true,
indexPages: true,
docsRouteBasePath: ["/docs", "/tutorial"],
language: ["en"],
hashed: true,
docsDir: ["docs", "tutorial"],
blogDir: [],
removeDefaultStopWordFilter: true,
highlightSearchTermsOnTargetPage: true,
searchResultLimits: 8,
searchResultContextMaxLength: 50,
},
],
[
"client-redirects",
/** @type {import('@docusaurus/plugin-client-redirects').Options} */
{
createRedirects(existingPath) {
if (
existingPath.includes("/tutorial/big-bot-guide/") || existingPath.includes("/tutorial/nodejs/") ||
existingPath.includes("/tutorial/amethyst/")
) {
return [
existingPath.replace("/tutorial/", "/docs/"),
];
}
if (
existingPath.includes("/docs/docs/frequently-asked-questions") ||
existingPath.includes("/docs/docs/getting-started") || existingPath.includes("/docs/docs/migrating")
) {
return [
existingPath.replace("/docs/", "/docs/general/"),
];
}
return undefined; // Return a falsy value: no redirect created
},
},
],
],
};
module.exports = config;
-23437
View File
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
{
"name": "site",
"version": "0.0.0",
"private": false,
"license": "Apache-2.0",
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start --host 0.0.0.0",
"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.2.0",
"@docusaurus/plugin-client-redirects": "^2.2.0",
"@docusaurus/preset-classic": "2.2.0",
"@easyops-cn/docusaurus-search-local": "^0.21.4",
"@mdx-js/react": "^1.6.21",
"chart.js": "^3.9.1",
"clsx": "^1.1.1",
"prism-react-renderer": "^1.2.1",
"react": "^17.0.1",
"react-chartjs-2": "^4.3.1",
"react-dom": "^17.0.1",
"reactflow": "^11.2.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.2.0",
"@tsconfig/docusaurus": "^1.0.4",
"@types/chart.js": "^2.9.37",
"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"
]
}
}
-173
View File
@@ -1,173 +0,0 @@
import {
CategoryScale,
Chart as ChartJS,
Legend,
LinearScale,
LineController,
LineElement,
PointElement,
Title,
Tooltip,
} from "chart.js";
import React, { useEffect, useState } from "react";
import { Chart } from "react-chartjs-2";
ChartJS.register(CategoryScale, LineController, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
const BenchmarkResultChart = ({ name, dataset }: {
name: string;
dataset: {
bench: {
name: string;
range: string | number;
unit: string;
value: string | number;
extra: string | number | undefined;
};
commit: {
author: {
email: string;
name: string;
username: string;
};
committer: {
email: string;
name: string;
username: string;
};
distinct: true;
id: string;
message: string;
timestamp: string;
tree_id: string;
url: string;
};
date: number;
tool: string;
}[];
}) => {
const data = {
labels: dataset.map((d) => d.commit.id.slice(0, 7)),
datasets: [
{
label: name,
data: dataset.map((d) => d.bench.value),
borderColor: "#ff3838",
backgroundColor: "#ff383860", // Add alpha for #rrggbbaa
},
],
};
return (
<Chart
type="line"
data={data}
options={{
responsive: true,
scales: {
x: {
title: {
display: true,
text: "commit",
},
},
y: {
title: {
display: true,
text: dataset.length > 0 ? dataset[0].bench.unit : "",
},
beginAtZero: true,
},
},
plugins: {
tooltip: {
callbacks: {
afterTitle: (items) => {
const index = items[0].dataIndex;
const data = dataset[index];
return "\n" + data.commit.message + "\n\n" + data.commit.timestamp + " committed by @" +
data.commit.author.username + "\n";
},
label: (item) => {
let label = item.formattedValue;
const { range, unit } = dataset[item.datasetIndex].bench;
label += " " + unit;
if (range) {
label += " (" + range + ")";
}
return label;
},
afterLabel: (item) => {
const { extra } = dataset[item.datasetIndex].bench;
return extra ? "\n" + extra : "";
},
},
},
},
onClick: (_mouseEvent, activeElems) => {
if (activeElems.length === 0) {
return;
}
// XXX: Undocumented. How can we know the index?
const index = activeElems[0].index;
const url = dataset[index].commit.url;
window.open(url, "_blank");
},
}}
/>
);
};
export default function BenchmarkResultCharts(): JSX.Element {
const [data, setData] = useState<{ entries: { Benchmark: [] } }>();
useEffect(() => {
if (!data) {
(async () => {
setData(
JSON.parse(
(await (await fetch(
"https://raw.githubusercontent.com/discordeno/discordeno/benchies/benchmarksResult/data.js",
)).text()).slice(24),
),
);
})();
}
}, []);
function collectBenchesPerTestCase(entries) {
const dataMap = new Map();
for (const entry of entries) {
const { commit, date, tool, benches } = entry;
for (const bench of benches) {
const result = { commit, date, tool, bench };
const arr = dataMap.get(bench.name);
if (arr === undefined) {
dataMap.set(bench.name, [result]);
} else {
arr.push(result);
}
}
}
return dataMap;
}
return (
<div style={{ minHeight: "100vh" }}>
{data
? Array.from(
collectBenchesPerTestCase(data.entries.Benchmark),
([key, value]) => ({ benchName: key, benches: value }),
).map((
bench,
index,
) => (
<BenchmarkResultChart
key={index}
name={bench.benchName}
dataset={bench.benches}
/>
))
: <></>}
</div>
);
}
@@ -1,11 +0,0 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}
-93
View File
@@ -1,93 +0,0 @@
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: "REST does not rest!",
image: "/img/undraw_docusaurus_mountain.svg",
description: (
<>
<ul>
<li>Restart without losing any requests.</li>
<li>Prevent Invalid 1 Hour Discord Bans.</li>
<li>Freedom from global rate limit errors.</li>
<li>Single source of contact to API.</li>
<li>Beautiful analytics plugins.</li>
<li>Scalability! Scalability! Scalability!</li>
</ul>
</>
),
},
{
title: "Zero Downtime Updates",
image: "/img/undraw_docusaurus_tree.svg",
description: (
<>
<ul>
<li>Instant bot restarts/updates.</li>
<li>
Automated sharding &{" "}
<strong>
<i>re</i>
</strong>
sharding.
</li>
<li>Scale horizontally and save money!</li>
<li>Stop losing events during reboots!</li>
<li>Max efficiency with Shards/Worker!</li>
<li>Scalability! Scalability! Scalability!</li>
</ul>
</>
),
},
{
title: "No More Forks!",
image: "/img/undraw_docusaurus_react.svg",
description: (
<>
<ul>
<li>100% customizable without forking!</li>
<li>By default, the lib caches NOTHING!</li>
<li>100% Custom cache support.</li>
<li>Cache only the props your bot needs.</li>
<li>Customize any internal function!</li>
<li>Scalability! Scalability! Scalability!</li>
</ul>
</>
),
},
];
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>
);
}
@@ -1,93 +0,0 @@
import React, { useEffect, useState } from "react";
import ReactFlow, { Background, Controls, Edge, Handle, Node, Position, useEdgesState, useNodesState } from "reactflow";
import "reactflow/dist/style.css";
export const multiplier = 225;
export const height = 40;
export const widthMultiplier = 0.75;
export const defaultNodeOptions = {
targetPosition: Position.Left,
sourcePosition: Position.Right,
draggable: false,
style: { width: `${multiplier * 0.75}px`, height: `${height}px` },
};
export const defaultGroupOptions = {
draggable: false,
};
export default function BaseFlowChart(
{ initialNodes = [], initialEdges = [] }: { initialNodes: Node[]; initialEdges: Edge[] },
) {
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
return {
width,
height,
};
}
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
return (
<>
<div
style={{
width: windowDimensions.width >= 997
? `${
(100 *
((windowDimensions.width - 300 -
(windowDimensions.width >= 1620 ? (windowDimensions.width - 1620) * 0.5 : 0)) /
windowDimensions.width) - 2) * widthMultiplier
}vw`
: "95vw",
height: "25vh",
}}
>
<ReactFlow
nodes={nodes}
edges={edges}
defaultEdgeOptions={{ focusable: false, animated: true }}
nodeTypes={{
baseLineNode: () => (
<div style={{ height: "1px", width: "20px" }}>
<Handle
type="target"
position={Position.Bottom}
style={{ background: "rgba(0,0,0,0)", borderColor: "rgba(0,0,0,0)" }}
/>
<Handle
type="source"
position={Position.Top}
style={{ background: "rgba(0,0,0,0)", borderColor: "rgba(0,0,0,0)" }}
/>
</div>
),
baseLineNodeText: (n) => (
<div style={{ textAlign: "center", width: defaultNodeOptions.style.width }}>
<h3>{n.data.label}</h3>
</div>
),
}}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
</>
);
}
@@ -1,103 +0,0 @@
import React from "react";
import { Edge, Node } from "reactflow";
import "reactflow/dist/style.css";
import BaseFlowChart, { defaultNodeOptions, multiplier } from "./BaseFlowChart";
const initialNodes: Node[] = [
{
id: "discordGateway",
type: "input",
position: { x: 0 * multiplier, y: 0 },
data: { label: "Discord Gateway" },
...defaultNodeOptions,
},
{ id: "gateway", position: { x: 1 * multiplier, y: 0 }, data: { label: "Gateway" }, ...defaultNodeOptions },
{ id: "baseLineNode-1", type: "baseLineNode", position: { x: 0.85 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-2", type: "baseLineNode", position: { x: 0.85 * multiplier, y: -130 }, data: {} },
{ id: "baseLineNode-3", type: "baseLineNode", position: { x: 2.85 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-4", type: "baseLineNode", position: { x: 2.85 * multiplier, y: -130 }, data: {} },
{ id: "baseLineNode-5", type: "baseLineNode", position: { x: 3.85 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-6", type: "baseLineNode", position: { x: 3.85 * multiplier, y: -130 }, data: {} },
{ id: "baseLineNode-7", type: "baseLineNode", position: { x: 4.85 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-8", type: "baseLineNode", position: { x: 4.85 * multiplier, y: -130 }, data: {} },
{
id: "baseLineNodeText-1",
type: "baseLineNodeText",
position: { x: 0 * multiplier, y: -130 },
data: { label: "Discord" },
},
{
id: "baseLineNodeText-2",
type: "baseLineNodeText",
position: { x: 1.5 * multiplier, y: -130 },
data: { label: "Event In" },
},
{
id: "baseLineNodeText-3",
type: "baseLineNodeText",
position: { x: 3 * multiplier, y: -130 },
data: { label: "Event Processing" },
},
{
id: "baseLineNodeText-4",
type: "baseLineNodeText",
position: { x: 4 * multiplier, y: -130 },
data: { label: "Event out" },
},
{
id: "baseLineNodeText-6",
type: "baseLineNodeText",
position: { x: 5 * multiplier, y: -130 },
data: { label: "Discord" },
},
{ id: "bot", position: { x: 2 * multiplier, y: 0 }, data: { label: "Bot" }, ...defaultNodeOptions },
{ id: "yourCode", position: { x: 3 * multiplier, y: 0 }, data: { label: "Your Code" }, ...defaultNodeOptions },
{ id: "rest", position: { x: 4 * multiplier, y: 0 }, data: { label: "Rest" }, ...defaultNodeOptions },
{
id: "discordApiGateway",
type: "output",
position: { x: 5 * multiplier, y: 0 },
data: { label: "Discord Api" },
...defaultNodeOptions,
},
];
const initialEdges: Edge[] = [
{ id: "d-g", source: "discordGateway", target: "gateway" },
{ id: "g-b", source: "gateway", target: "bot" },
{ id: "b-y", source: "bot", target: "yourCode" },
{ id: "y-r", source: "yourCode", target: "rest" },
{ id: "r-d", source: "rest", target: "discordApiGateway" },
{
id: "baseLine-1",
source: "baseLineNode-1",
target: "baseLineNode-2",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
{
id: "baseLine-2",
source: "baseLineNode-3",
target: "baseLineNode-4",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
{
id: "baseLine-3",
source: "baseLineNode-5",
target: "baseLineNode-6",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
{
id: "baseLine-4",
source: "baseLineNode-7",
target: "baseLineNode-8",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
];
export default function FlowChart() {
return <BaseFlowChart initialNodes={initialNodes} initialEdges={initialEdges} />;
}
@@ -1,198 +0,0 @@
import React from "react";
import { Edge, Node, Position } from "reactflow";
import "reactflow/dist/style.css";
import BaseFlowChart, { defaultGroupOptions, defaultNodeOptions, multiplier } from "./BaseFlowChart";
const initialNodes: Node[] = [
{
id: "discordGateway",
type: "input",
position: { x: 0 * multiplier, y: 0 },
data: { label: "Discord Gateway" },
...defaultNodeOptions,
},
{ id: "baseLineNode-1", type: "baseLineNode", position: { x: 0.85 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-2", type: "baseLineNode", position: { x: 0.85 * multiplier, y: -130 }, data: {} },
{ id: "baseLineNode-3", type: "baseLineNode", position: { x: 3.75 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-4", type: "baseLineNode", position: { x: 3.75 * multiplier, y: -130 }, data: {} },
{
id: "baseLineNodeText-1",
type: "baseLineNodeText",
position: { x: 0 * multiplier, y: -130 },
data: { label: "Discord" },
},
{
id: "baseLineNodeText-2",
type: "baseLineNodeText",
position: { x: 2 * multiplier, y: -130 },
data: { label: "Gateway" },
},
{
id: "baseLineNodeText-3",
type: "baseLineNodeText",
position: { x: 4 * multiplier, y: -130 },
data: { label: "Bot" },
},
{
id: "gatewayManager",
type: "input",
position: { x: 2.0625 * multiplier, y: -80 },
data: { label: "Gateway Manager" },
...defaultNodeOptions,
targetPosition: Position.Top,
sourcePosition: Position.Bottom,
},
{
id: "shard-1",
type: "output",
position: { x: 1.25 * multiplier, y: -20 },
data: { label: "Shard-1" },
style: { width: `${multiplier * 2}px`, height: "80px" },
...defaultGroupOptions,
},
{
id: "shard-1-socket",
position: { x: 0.125 * multiplier, y: 20 },
data: { label: "webSocket" },
...defaultNodeOptions,
parentNode: "shard-1",
extent: "parent",
},
{
id: "shard-1-handleMessage",
position: { x: 1.125 * multiplier, y: 20 },
data: { label: "HandleMessage" },
...defaultNodeOptions,
parentNode: "shard-1",
extent: "parent",
},
{
id: "shard-2",
type: "output",
position: { x: 1.375 * multiplier, y: 0 },
data: { label: "Shard-2" },
style: { width: `${multiplier * 2}px`, height: "80px" },
...defaultGroupOptions,
},
{
id: "shard-2-socket",
position: { x: 0.125 * multiplier, y: 20 },
data: { label: "webSocket" },
...defaultNodeOptions,
parentNode: "shard-2",
extent: "parent",
},
{
id: "shard-2-handleMessage",
position: { x: 1.125 * multiplier, y: 20 },
data: { label: "HandleMessage" },
...defaultNodeOptions,
parentNode: "shard-2",
extent: "parent",
},
{
id: "shard-3",
type: "output",
position: { x: 1.5 * multiplier, y: 20 },
data: { label: "Shard-3" },
style: { width: `${multiplier * 2}px`, height: "80px" },
...defaultGroupOptions,
},
{
id: "shard-3-socket",
position: { x: 0.125 * multiplier, y: 20 },
data: { label: "webSocket" },
...defaultNodeOptions,
parentNode: "shard-3",
extent: "parent",
},
{
id: "shard-3-handleMessage",
position: { x: 1.125 * multiplier, y: 20 },
data: { label: "HandleMessage" },
...defaultNodeOptions,
parentNode: "shard-3",
extent: "parent",
},
{
id: "shard-n",
type: "output",
position: { x: 1.625 * multiplier, y: 40 },
data: { label: "Shard-N" },
style: { width: `${multiplier * 2}px`, height: "80px" },
...defaultGroupOptions,
},
{
id: "shard-n-socket",
position: { x: 0.125 * multiplier, y: 20 },
data: { label: "webSocket" },
...defaultNodeOptions,
parentNode: "shard-n",
extent: "parent",
},
{
id: "shard-n-handleMessage",
position: { x: 1.125 * multiplier, y: 20 },
data: { label: "HandleMessage" },
...defaultNodeOptions,
parentNode: "shard-n",
extent: "parent",
},
{ id: "bot", type: "output", position: { x: 4 * multiplier, y: 0 }, data: { label: "Bot" }, ...defaultNodeOptions },
];
const initialEdges: Edge[] = [
{ id: "d-g", source: "discordGateway", target: "gateway" },
{ id: "g-b", source: "gateway", target: "bot" },
{ id: "b-y", source: "bot", target: "yourCode" },
{ id: "y-r", source: "yourCode", target: "rest" },
{ id: "r-d", source: "rest", target: "discordApiGateway" },
{
id: "baseLine-1",
source: "baseLineNode-1",
target: "baseLineNode-2",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
{
id: "baseLine-2",
source: "baseLineNode-3",
target: "baseLineNode-4",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
{
id: "baseLine-3",
source: "baseLineNode-5",
target: "baseLineNode-6",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
{
id: "baseLine-4",
source: "baseLineNode-7",
target: "baseLineNode-8",
style: { stroke: "blue", strokeDasharray: 20 },
animated: false,
},
{ id: "d-shard-1", source: "discordGateway", target: "shard-1-socket", zIndex: 100 },
{ id: "d-shard-2", source: "discordGateway", target: "shard-2-socket", zIndex: 100 },
{ id: "d-shard-3", source: "discordGateway", target: "shard-3-socket", zIndex: 100 },
{ id: "d-shard-n", source: "discordGateway", target: "shard-n-socket", zIndex: 100 },
{ id: "shard-1-socket-handleMessage", source: "shard-1-socket", target: "shard-1-handleMessage", zIndex: 100 },
{ id: "shard-2-socket-handleMessage", source: "shard-2-socket", target: "shard-2-handleMessage", zIndex: 100 },
{ id: "shard-3-socket-handleMessage", source: "shard-3-socket", target: "shard-3-handleMessage", zIndex: 100 },
{ id: "shard-n-socket-handleMessage", source: "shard-n-socket", target: "shard-n-handleMessage", zIndex: 100 },
{ id: "shard-1-handleMessage-bot", source: "shard-1-handleMessage", target: "bot", zIndex: 100 },
{ id: "shard-2-handleMessage-bot", source: "shard-2-handleMessage", target: "bot", zIndex: 100 },
{ id: "shard-3-handleMessage-bot", source: "shard-3-handleMessage", target: "bot", zIndex: 100 },
{ id: "shard-n-handleMessage-bot", source: "shard-n-handleMessage", target: "bot", zIndex: 100 },
{ id: "gatewayManager-shard-1", source: "gatewayManager", target: "shard-1", zIndex: 10 },
{ id: "gatewayManager-shard-2", source: "gatewayManager", target: "shard-2", zIndex: 100 },
{ id: "gatewayManager-shard-3", source: "gatewayManager", target: "shard-3", zIndex: 100 },
{ id: "gatewayManager-shard-n", source: "gatewayManager", target: "shard-n", zIndex: 100 },
];
export default function FlowChart2() {
return <BaseFlowChart initialNodes={initialNodes} initialEdges={initialEdges} />;
}
@@ -1,669 +0,0 @@
import React, { useEffect, useState } from "react";
import ReactFlow, {
Background,
Controls,
Edge,
Handle,
Node,
NodeMouseHandler,
Position,
useEdgesState,
useNodesState,
} from "reactflow";
import "reactflow/dist/style.css";
import { defaultNodeOptions, height, multiplier, widthMultiplier } from "./BaseFlowChart";
const handlers: {
[index: string]: {
transformers: string[];
event: string;
};
} = {
handleChannelCreate: { transformers: ["transformers.channel"], event: "events.channelCreate" },
handleChannelDelete: {
transformers: ["transformers.channel", "transformers.snowflake"],
event: "events.channelDelete",
},
handleChannelPinsUpdate: {
transformers: ["transformers.snowflake", "transformers.snowflake"],
event: "events.channelPinsUpdate",
},
handleChannelUpdate: { transformers: ["transformers.channel"], event: "events.channelUpdate" },
handleStageInstanceCreate: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.stageInstanceCreate",
},
handleStageInstanceDelete: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.stageInstanceDelete",
},
handleStageInstanceUpdate: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.stageInstanceUpdate",
},
handleThreadCreate: { transformers: ["transformers.channel"], event: "events.threadCreate" },
handleThreadDelete: { transformers: ["transformers.channel"], event: "events.threadDelete" },
handleThreadListSync: {
transformers: [
"transformers.snowflake",
"transformers.snowflake",
"transformers.channel",
"transformers.snowflake",
"transformers.snowflake",
],
event: undefined,
},
handleThreadMembersUpdate: {
transformers: [
"transformers.snowflake",
"transformers.snowflake",
"transformers.threadMember",
"transformers.snowflake",
],
event: "events.threadMembersUpdate",
},
handleThreadMemberUpdate: {
transformers: ["transformers.snowflake", "transformers.snowflake"],
event: "events.threadMemberUpdate",
},
handleThreadUpdate: { transformers: ["transformers.channel"], event: "events.threadUpdate" },
handleGuildEmojisUpdate: {
transformers: ["transformers.snowflake", "transformers.snowflake"],
event: "events.guildEmojisUpdate",
},
handleAutoModerationActionExecution: {
transformers: ["transformers.automodActionExecution"],
event: "events.automodActionExecution",
},
handleAutoModerationRuleCreate: { transformers: ["transformers.automodRule"], event: "events.automodRuleCreate" },
handleAutoModerationRuleDelete: { transformers: ["transformers.automodRule"], event: "events.automodRuleDelete" },
handleAutoModerationRuleUpdate: { transformers: ["transformers.automodRule"], event: "events.automodRuleUpdate" },
handleGuildBanAdd: {
transformers: ["transformers.user", "transformers.snowflake"],
event: "events.guildBanAdd",
},
handleGuildBanRemove: {
transformers: ["transformers.user", "transformers.snowflake"],
event: "events.guildBanRemove",
},
handleGuildCreate: { transformers: ["transformers.guild"], event: "events.guildCreate" },
handleGuildDelete: { transformers: ["transformers.snowflake"], event: "events.guildDelete" },
handleGuildIntegrationsUpdate: { transformers: ["transformers.snowflake"], event: "events.integrationUpdate" },
handleGuildUpdate: { transformers: ["transformers.guild"], event: "events.guildUpdate" },
handleGuildScheduledEventCreate: {
transformers: ["transformers.scheduledEvent"],
event: "events.scheduledEventCreate",
},
handleGuildScheduledEventDelete: {
transformers: ["transformers.scheduledEvent"],
event: "events.scheduledEventDelete",
},
handleGuildScheduledEventUpdate: {
transformers: ["transformers.scheduledEvent"],
event: "events.scheduledEventUpdate",
},
handleGuildScheduledEventUserAdd: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.scheduledEventUserAdd",
},
handleGuildScheduledEventUserRemove: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.scheduledEventUserRemove",
},
handleIntegrationCreate: { transformers: ["transformers.integration"], event: "events.integrationCreate" },
handleIntegrationDelete: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.integrationDelete",
},
handleIntegrationUpdate: { transformers: ["transformers.integration"], event: "events.integrationUpdate" },
handleInteractionCreate: {
transformers: ["transformers.snowflake", "transformers.interaction"],
event: "events.interactionCreate",
},
handleInviteCreate: { transformers: ["transformers.invite"], event: "events.inviteCreate" },
handleInviteDelete: {
transformers: ["transformers.snowflake", "transformers.snowflake"],
event: "events.inviteDelete",
},
handleGuildMembersChunk: {
transformers: [
"transformers.snowflake",
"transformers.member",
"transformers.snowflake",
"transformers.snowflake",
"transformers.user",
"transformers.activity",
],
event: undefined,
},
handleGuildMemberAdd: {
transformers: ["transformers.snowflake", "transformers.user", "transformers.member"],
event: "events.guildMemberAdd",
},
handleGuildMemberRemove: {
transformers: ["transformers.snowflake", "transformers.user"],
event: "events.guildMemberRemove",
},
handleGuildMemberUpdate: {
transformers: ["transformers.user", "transformers.member", "transformers.snowflake"],
event: "events.guildMemberUpdate",
},
handleMessageCreate: { transformers: ["transformers.message"], event: "events.messageCreate" },
handleMessageDelete: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.messageDelete",
},
handleMessageDeleteBulk: {
transformers: [
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
],
event: "events.messageDeleteBulk",
},
handleMessageReactionAdd: {
transformers: [
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.member",
"transformers.user",
"transformers.emoji",
],
event: "events.reactionAdd",
},
handleMessageReactionRemove: {
transformers: [
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.emoji",
],
event: "events.reactionRemove",
},
handleMessageReactionRemoveAll: {
transformers: ["transformers.snowflake", "transformers.snowflake", "transformers.snowflake"],
event: "events.reactionRemoveAll",
},
handleMessageReactionRemoveEmoji: {
transformers: [
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.emoji",
],
event: "events.reactionRemoveEmoji",
},
handleMessageUpdate: { transformers: ["transformers.message"], event: "events.messageUpdate" },
handlePresenceUpdate: { transformers: ["transformers.presence"], event: "events.presenceUpdate" },
handleReady: {
transformers: [
"transformers.user",
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
],
event: "events.ready",
},
handleTypingStart: {
transformers: [
"transformers.snowflake",
"transformers.snowflake",
"transformers.snowflake",
"transformers.member",
],
event: "events.typingStart",
},
handleUserUpdate: { transformers: ["transformers.user"], event: "events.botUpdate" },
handleGuildRoleCreate: {
transformers: ["transformers.role", "transformers.snowflake"],
event: "events.roleCreate",
},
handleGuildRoleDelete: {
transformers: ["transformers.snowflake", "transformers.snowflake"],
event: "events.roleDelete",
},
handleGuildRoleUpdate: {
transformers: ["transformers.role", "transformers.snowflake"],
event: "events.roleUpdate",
},
handleVoiceServerUpdate: { transformers: ["transformers.snowflake"], event: "events.voiceServerUpdate" },
handleVoiceStateUpdate: {
transformers: ["transformers.snowflake", "transformers.voiceState"],
event: "events.voiceStateUpdate",
},
handleWebhooksUpdate: {
transformers: ["transformers.snowflake", "transformers.snowflake"],
event: "events.webhooksUpdate",
},
};
export default function FlowChart({ handlerFilter = (handler: string) => true }) {
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
return {
width,
height,
};
}
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
const transformers = [];
const events = [];
const initialNodes: Node[] = [
{
id: "baseNode-gateway",
type: "input",
position: { x: 0 * multiplier, y: 0 },
data: { label: "gateway" },
...defaultNodeOptions,
},
{
id: "baseNode-yourCode",
type: "output",
position: { x: 5 * multiplier, y: 0 },
data: { label: "Your Code" },
...defaultNodeOptions,
},
{ id: "baseLineNode-1", type: "baseLineNode", position: { x: 0.85 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-2", type: "baseLineNode", position: { x: 0.85 * multiplier, y: -130 }, data: {} },
{ id: "baseLineNode-3", type: "baseLineNode", position: { x: 4.85 * multiplier, y: 170 }, data: {} },
{ id: "baseLineNode-4", type: "baseLineNode", position: { x: 4.85 * multiplier, y: -130 }, data: {} },
{
id: "baseLineNodeText-1",
type: "baseLineNodeText",
position: { x: 0 * multiplier, y: -130 },
data: { label: "Gateway" },
},
{
id: "baseLineNodeText-2",
type: "baseLineNodeText",
position: { x: 1 * multiplier, y: -130 },
data: { label: "Bot" },
},
{
id: "baseLineNodeText-3",
type: "baseLineNodeText",
position: { x: 5 * multiplier, y: -130 },
data: { label: "Your Code" },
},
{
id: "baseNode-handleDiscordPayload",
position: { x: 1 * multiplier, y: 0 },
data: { label: "Handle discord payload" },
...defaultNodeOptions,
},
];
const initialEdges: Edge[] = [
{ id: "baseEdge-1", source: "baseNode-gateway", target: "baseNode-handleDiscordPayload" },
{ id: "baseEdge-3", source: "baseNode-g-1", target: "baseNode-g-2" },
{ id: "baseEdge-4", source: "baseNode-g-2", target: "baseNode-handleDiscordPayload" },
{
id: "baseLine-1",
source: "baseLineNode-1",
target: "baseLineNode-2",
style: { stroke: "blue", strokeDasharray: 20 },
},
{
id: "baseLine-2",
source: "baseLineNode-3",
target: "baseLineNode-4",
style: { stroke: "blue", strokeDasharray: 20 },
},
];
//@ts-ignore
for (const [index, handler] of Object.keys(handlers).filter(handlerFilter).entries()) {
initialNodes.push({
id: handler,
position: {
x: 2 * multiplier,
y: index * (height + 10) - Object.keys(handlers).filter(handlerFilter).length * ((height + 10) / 2) +
height / 2,
},
data: { label: handler.slice(6) },
...defaultNodeOptions,
});
initialEdges.push({
id: `handleDiscordPayload-${handler}`,
source: "baseNode-handleDiscordPayload",
target: handler,
});
if (!events.find((e) => e === handlers[handler].event) && handlers[handler].event) {
events.push(handlers[handler].event);
initialEdges.push({
id: `${handlers[handler].event}-yourCode`,
source: handlers[handler].event,
target: "baseNode-yourCode",
});
}
for (const transformer of handlers[handler].transformers) {
if (!transformers.find((t) => t === transformer) && transformer) transformers.push(transformer);
if (!initialEdges.find((edge) => edge.id === `${handler}-${transformer}`) && transformer) {
initialEdges.push({ id: `${handler}-${transformer}`, source: handler, target: transformer });
}
if (
!initialEdges.find((edge) => edge.id === `${transformer}-${handlers[handler].event}`) && handlers[handler].event
) {
initialEdges.push({
id: `${transformer}-${handlers[handler].event}`,
source: transformer,
target: handlers[handler].event,
});
}
}
}
//@ts-ignore
for (const [index, transformer] of transformers.entries()) {
initialNodes.push({
id: transformer,
position: {
x: 3 * multiplier,
y: index * (height + 10) - transformers.length * ((height + 10) / 2) + height / 2,
},
data: { label: transformer.slice(13) },
...defaultNodeOptions,
});
}
//@ts-ignore
for (const [index, event] of events.entries()) {
initialNodes.push({
id: event,
position: { x: 4 * multiplier, y: index * (height + 10) - events.length * ((height + 10) / 2) + height / 2 },
data: { label: event.slice(7) },
...defaultNodeOptions,
});
}
initialNodes.unshift(
{
id: "handlers",
type: "group",
position: {
x: 1.925 * multiplier,
y: -Object.keys(handlers).filter(handlerFilter).length * ((height + 10) / 2) - 45,
},
data: { label: "" },
style: {
height: `${Object.keys(handlers).filter(handlerFilter).length * (height + 10) + 75}px`,
width: `${multiplier * 0.9}px`,
borderColor: "rgba(0,0,0,0.25)",
},
draggable: false,
},
{
id: "baseLineNodeText-4",
type: "baseLineNodeText",
position: {
x: 2 * multiplier,
y: -Object.keys(handlers).filter(handlerFilter).length * ((height + 10) / 2) - ((height + 10) / 2),
},
data: { label: "Handlers" },
draggable: false,
},
{
id: "transformers",
type: "group",
position: { x: 2.925 * multiplier, y: -transformers.length * ((height + 10) / 2) - 45 },
data: { label: "" },
style: {
height: `${transformers.length * (height + 10) + 75}px`,
width: `${multiplier * 0.9}px`,
borderColor: "rgba(0,0,0,0.25)",
},
draggable: false,
},
{
id: "baseLineNodeText-5",
type: "baseLineNodeText",
position: { x: 3 * multiplier, y: -transformers.length * ((height + 10) / 2) - ((height + 10) / 2) },
data: { label: "Transformers" },
draggable: false,
},
{
id: "events",
type: "group",
position: { x: 3.925 * multiplier, y: -events.length * ((height + 10) / 2) - 45 },
data: { label: "" },
style: {
height: `${events.length * (height + 10) + 75}px`,
width: `${multiplier * 0.9}px`,
borderColor: "rgba(0,0,0,0.25)",
},
draggable: false,
},
{
id: "baseLineNodeText-6",
type: "baseLineNodeText",
position: { x: 4 * multiplier, y: -events.length * ((height + 10) / 2) - ((height + 10) / 2) },
data: { label: "Event" },
draggable: false,
},
);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const [handlerIndex, setHandlerIndex] = useState(0);
const [userClick, setUserClick] = useState(false);
const nodeMouseHandler: NodeMouseHandler = (_, node, userTrigger = true) => {
if (userTrigger) setUserClick(true);
if (node.id.split("-")[0] === "baseNode") {
edges.forEach((e) => {
if (e.id.startsWith("baseLine")) return;
e.animated = true;
e.style = { stroke: "blue" };
});
setEdges([...edges]);
return;
}
if (Object.keys(handlers).find((h) => handlers[h].event === node.id)) {
const handlerName = Object.keys(handlers).find((h) => handlers[h].event === node.id);
const handler = handlers[handlerName];
edges.forEach((e) => {
if (e.id.startsWith("baseLine")) return;
if (e.id.split("-")[0] === "baseEdge") {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (e.id.split("-")[0] === "handleDiscordPayload" && e.id.split("-")[1] === handlerName) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (e.id.split("-")[0] === handlerName && handler.transformers.includes(e.id.split("-")[1])) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (handler.transformers.includes(e.id.split("-")[0]) && e.id.split("-")[1] === handler.event) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (e.id.split("-")[0] === handler.event && e.id.split("-")[1] === "yourCode") {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
e.animated = false;
e.style = { opacity: 0.3 };
});
setEdges([...edges]);
return;
}
if (Object.keys(handlers).find((h) => handlers[h].transformers.includes(node.id))) {
edges.forEach((e) => {
if (e.id.startsWith("baseLine")) return;
if (e.id.split("-")[0] === "baseEdge") {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (
e.id.split("-")[0] === "handleDiscordPayload" &&
Object.keys(handlers).filter((h) => handlers[h].transformers.includes(node.id)).includes(e.id.split("-")[1])
) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (
Object.keys(handlers).filter((h) => handlers[h].transformers.includes(node.id)).includes(
e.id.split("-")[0],
) && e.id.split("-")[1] === node.id
) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (e.id.split("-")[0] === node.id) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
e.animated = false;
e.style = { opacity: 0.3 };
});
setEdges([...edges]);
return;
}
if (handlers[node.id]) {
const handler = handlers[node.id];
edges.forEach((e) => {
if (e.id.startsWith("baseLine")) return;
if (e.id.split("-")[0] === "baseEdge") {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (e.id.split("-")[0] === "handleDiscordPayload" && e.id.split("-")[1] === node.id) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (e.id.split("-")[0] === node.id && handler.transformers.includes(e.id.split("-")[1])) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (handler.transformers.includes(e.id.split("-")[0]) && e.id.split("-")[1] === handler.event) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
if (e.id.split("-")[0] === handler.event) {
e.animated = true;
e.style = { stroke: "blue" };
return;
}
e.animated = false;
e.style = { opacity: 0.3 };
});
setEdges([...edges]);
return;
}
edges.forEach((e) => {
if (e.id.startsWith("baseLine")) return;
e.animated = false;
e.style = {};
});
setEdges([...edges]);
};
useEffect(() => {
const interval = setInterval(() => {
const randomIndex = Math.round((Object.keys(handlers).filter(handlerFilter).length - 1) * Math.random());
if (!userClick) {
nodeMouseHandler(undefined, { id: Object.keys(handlers).filter(handlerFilter)[randomIndex] }, false);
}
setHandlerIndex(randomIndex);
}, 1000);
return () => clearInterval(interval);
}, [userClick]);
useEffect(() => {
if (userClick) {
const timeout = setTimeout(() => {
setUserClick(false);
}, 10000);
return () => clearTimeout(timeout);
}
}, [userClick]);
return (
<>
<div
style={{
width: windowDimensions.width >= 997
? `${
(100 *
((windowDimensions.width - 300 -
(windowDimensions.width >= 1620 ? (windowDimensions.width - 1620) * 0.5 : 0)) /
windowDimensions.width) - 2) * widthMultiplier
}vw`
: "95vw",
height: "50vh",
}}
>
<ReactFlow
nodes={nodes}
edges={edges}
defaultEdgeOptions={{ focusable: false }}
onNodeDoubleClick={nodeMouseHandler}
onNodeClick={nodeMouseHandler}
onClick={(e) => {
//@ts-ignore
if (e.target.className === "react-flow__pane") nodeMouseHandler(e, { id: " - ", data: { label: " - " } });
}}
nodeTypes={{
baseLineNode: () => (
<div style={{ height: "1px", width: "20px" }}>
<Handle
type="target"
position={Position.Bottom}
style={{ background: "rgba(0,0,0,0)", borderColor: "rgba(0,0,0,0)" }}
/>
<Handle
type="source"
position={Position.Top}
style={{ background: "rgba(0,0,0,0)", borderColor: "rgba(0,0,0,0)" }}
/>
</div>
),
baseLineNodeText: (n) => (
<div style={{ textAlign: "center", width: defaultNodeOptions.style.width }}>
<h3>{n.data.label}</h3>
</div>
),
}}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
</>
);
}
-39
View File
@@ -1,39 +0,0 @@
/**
* 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
@@ -1,23 +0,0 @@
/**
* 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;
}
-42
View File
@@ -1,42 +0,0 @@
import Link from "@docusaurus/Link";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import Layout from "@theme/Layout";
import clsx from "clsx";
import React from "react";
import HomepageFeatures from "../components/HomepageFeatures";
import styles from "./index.module.css";
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
@@ -1,7 +0,0 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.
View File
-1
View File
@@ -1 +0,0 @@
discordeno.mod.land
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

@@ -1,170 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 31 KiB

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

Before

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

-7
View File
@@ -1,7 +0,0 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
}
}
@@ -1,7 +0,0 @@
---
sidebar_position: 5
---
# Same as discord.js [collection][def]
[def]: https://discord.js.org/#/docs/collection/main/class/Collection
-7
View File
@@ -1,7 +0,0 @@
---
sidebar_position: 4
---
# Documentation [at][def]
[def]: https://deno.land/x/amethyst@v4.3.4/mod.ts?s=AmethystEmbed
-1
View File
@@ -1 +0,0 @@
{ "label": "Amethyst Framework", "position": 4 }
-99
View File
@@ -1,99 +0,0 @@
---
sidebar_position: 2
---
# Creating an client
Let's review each choice and what it does.
- `owners`, You may specify the proprietors of the bot using this. The inhibitors make use of this.
- `prefix`, The string a user should use at the beginning of their message to identify it as a command to the bot. Only
message commands can use this, and the parameter can be either a string or a function.
- `botMentionAsPrefix`, Determines whether a user's mention of a bot qualifies as a prefix.
- `ignoreBots`, Allow bots to execute commands.
- `defaultCooldown`, Defualt cooldown for all commands.
- `ignoreCooldown`, List of people who bypass cooldowns.
- `commandDir`, Path to the command directory used by the fileloader.
- `eventDir`, Path to the event directory used by the fileloader.
- `inhibitorDir`, Path to the inhibitor directory used by the fileloader.
- `prefixCaseSensitive`, Indicates whether or not the prefix is case-sensitive.
- `extras`, Extras that are used by your client, such as a database instance or a music player.
## Client Extras
When using discord.js we often do stuff like `client.musicplayer=player;` and in order to maintain this ease Amethyst
allows you to do `client.extras.musicplayer=player;`.
NOTE: Typing will not work on `client.extras`.
## Client Properties
```ts
user: User;
events: AmethystEvents;
messageCollectors: AmethystCollection<string, MessageCollector>;
componentCollectors: AmethystCollection<bigint, ComponentCollector>;
reactionCollectors: AmethystCollection<bigint, ReactionCollector>;
runningTasks: runningTasks;
tasks: AmethystCollection<string, AmethystTask>;
category: AmethystCollection<string, Category>;
inhibitors: AmethystCollection<
string,
<T extends Command = Command>(
bot: AmethystBot,
command: T,
options: { memberId?: bigint; channelId: bigint; guildId?: bigint }
) => true | AmethystError
>;
owners?: bigint[];
botMentionAsPrefix?: boolean;
prefixCaseSensitive?: boolean;
defaultCooldown?: CommandCooldown;
ignoreCooldown?: bigint[];
guildOnly?: boolean;
messageQuotedArguments?: boolean;
ignoreBots?: boolean;
dmOnly?: boolean;
eventHandler: AmethystEventHandler;
extras: any;
prefix?:
| string
| string[]
| ((bot: AmethystBot, message: Message) => Async<string | string[]>);
on(name: string, callback: (...args: any) => unknown): void;
once(name: string, callback: (...args: any) => unknown): void;
amethystUtils: {
awaitComponent(
messageId: bigint,
options?: ComponentCollectorOptions & { maxUsage?: number }
): Promise<Interaction[]>,
awaitReaction(
messageId: bigint,
options?: ReactionCollectorOptions & { maxUsage?: number }
): Promise<AmethystReaction[]>,
awaitMessage(
memberId: bigint,
channelId: bigint,
options?: MessageCollectorOptions & { maxUsage?: number }
): Promise<Message[]>,
createCommand(command: CommandOptions): void,
createCategory(category: CategoryOptions): void,
updateCategory(category: CategoryOptions): void,
createTask(task: AmethystTask): void,
clearTasks(): void,
createInhibitor<T extends Command = Command>(
name: string,
inhibitor: (
bot: AmethystBot,
command: T,
options?: { memberId?: bigint; guildId?: bigint; channelId: bigint }
) => true | AmethystError
): void,
deleteInhibitor(name: string): void,
updateSlashCommands(): void,
}
```
## [Documentation](https://deno.land/x/amethyst@v4.2.0/mod.ts?s=AmethystBotOptions)
-79
View File
@@ -1,79 +0,0 @@
---
sidebar_position: 3
---
# Lets Create a simple bot in Node.js
- **Step 1**: Create a typescript project with index.ts as main file.
- **Step 2**: Installing packages. Install following packages.
```bash
npm i @thereallonewolf/amethystframework
```
- **Step 3**: Create a index.ts file.
- **Step 4**: Add following code in index.ts file, replacing TOKEN with your bot token.
```ts
import { createBot, GatewayIntents, startBot } from "discordeno";
import { enableCachePlugin, enableCacheSweepers } from "discordeno/cache-plugin";
import {
AmethystBot,
Category,
Command,
Context,
enableAmethystPlugin,
Event,
} from "@thereallonewolf/amethystframework";
let baseClient = createBot({
token: "TOKEN",
intents: GatewayIntents.Guilds | GatewayIntents.GuildMessages | GatewayIntents.MessageContent,
});
//@ts-ignore
let client = enableAmethystPlugin(enableCachePlugin(baseClient), {
botMentionAsPrefix: true,
prefix: "!", //Can be a function or a string.
ignoreBots: false,
});
enableCacheSweepers(client);
startBot(client);
@Category({
name: "general",
description: "My general commands",
uniqueCommands: true,
default: "", //As all the commands are unique so no need to set the default command.
})
export class General {
@Command({
name: "ping",
description: "Pong!",
commandType: ["application", "message"],
category: "general",
args: [],
})
async ping(bot: AmethystBot, ctx: Context) {
ctx.reply({ content: "Pong!" });
}
@Event("ready")
async ready() {
console.log("I am ready!");
client.amethystUtils.updateSlashCommands();
}
}
```
- **Step 5**: Invite your bot and compile index.ts and run it. Then you can use `/general ping` or `!ping`
- **Step 6**: Useful links:
1. Command Options can be found
[here](https://github.com/AmethystFramework/framework/blob/master/src/types/commandOptions.ts).
2. Category Options [here](https://github.com/AmethystFramework/framework/blob/master/src/types/categoryOptions.ts)
3. Full [Documentation](https://deno.land/x/amethyst)
-54
View File
@@ -1,54 +0,0 @@
---
sidebar_position: 1
---
# Amethyst
Amethyst is a [Discordeno](https://github.com/discordeno/discordeno) plugin that is incredibly robust and flexible. It
promotes standard practices and is geared at bigger bots.
This framework is not for you if you cannot utilise Maps and Sets without reading them up. We presume you have a solid
foundation in typescript/javascript and dicord bots.
[Documentation](https://deno.land/x/amethyst)
## Features
- Thanks to Amethyst's adaptability, you can change a lot of things and add features as you see appropriate.
- A developer may create slash or message interactions with Amethyst.
- Assistance with interactions, such as selection, built-in buttons, and more.
- Custom discord.js like event system without the event emitter.
- Explore more incredible features of our framework.
## Why Amethyst?
Amethyst makes use of the [Discordeno](https://github.com/discordeno/discordeno) plugin system to streamline your coding
process and help you get going more quickly. Support for message and slash commands that doesn't need altering any code.
- Fully programmable
- Easy to learn and utilise.
- Conversion from Discord.js bots is simple.
## Ideas
The objective is to provide a framework that can be used with both Node.js and Deno while minimising transitions and
rewrites.
- Easy to understand and use.
- Cover up complexity in the engine keeping the end user interface as simple as possible.
## Future Updates
Creation of discord setup wizards for commands like welcome, context menus and paginated messages.
## Installation
Deno: [link](https://deno.land/x/amethyst)
Npm:
```bash
npm i @thereallonewolf/amethystframework
```
@@ -1,4 +0,0 @@
{
"label": "Big Bot Guide",
"position": 2
}
-119
View File
@@ -1,119 +0,0 @@
---
sidebar_position: 4
sidebar_label: Step 3 - Cache
---
# Step 3: Standalone Cache Process
The next part of this is going to be about making a standalone cache process. By now, you should have both a REST and a
Gateway process ready. Before, we start handling events we should build a Cache handler.
## Why Use Standalone Cache Process?
A standalone cache process allows you to retain cached data even after bot restarts. For example, if you are caching
member roles to track when a role was added or removed, you may want to cache the members. The question then comes to
play, when deciding where to keep your cache. Another reason to use this is, whether or not you are using a standalone
gateway process.
- Start rest process
- Start event handler process (bot)
- Start gateway process.
- Guild create events arrive providing all the data needed to cache in the bot process.
- Restart event handler process(maybe for an update/reboot)
- You lost all guilds/channels/permissions etc and can not get them again without restarting gateway. This defeats the
entire point of the standalone gateway.
If your cache is tied to the bot processes which is not tied to the gateway you lose all this info. The next thought is
to just keep the Cache entirely in the gateway process however, I do not like this personally however, should you desire
this you can do this as well. The reason I prefer not to do this is when your bot needs to make requests to your cache,
you do not want it occupying the thread for processing other gateway events arriving from discord. A separate cache
process makes it so it uses an entirely separate thread and will not slow down anything else.
## Understand Cache Types
When I use the term cache process, this is interchangeable with any similar term such as "custom cache", "redis cache",
"pgsql cache", etc... The fact is you can keep this "cache" anywhere. For this guide, we will implement a very simple
cache using pgsql. Feel free to modify this any way you like as advanced as you like. The point is Discordeno cache is
flexible enough to let you use anything for your Cache storage.
## Setting Up The Cache
This step is for you to create the base schema for your cache. For example, if you want to implement a pgsql or redis
cache perhaps you want to prepare the tables/schema. For this guide, we are just going to do a quick little hack to get
a custom cache working.
Create a file in a path like `src/bot/cache/schema.sql`
```sql
CREATE TABLE IF NOT EXISTS "users" (
id bigint NOT NULL,
username text COLLATE pg_catalog."default" NOT NULL,
discriminator text COLLATE pg_catalog."default" NOT NULL,
bot boolean,
CONSTRAINT "users_pkey" PRIMARY KEY (id)
)
```
Note that you can cache only properties you want and leave all other properties that you won't use.
Now that we have this schema ready for our users cache. Go ahead and repeat this for all other cache tables.
Cache Tables:
- users
- members
- guilds
- channels
- threads
- messages
- presences
- unavailableGuilds
Once you are finished continue forward, for the purpose of keeping this guide short we wont cover each table.
> You should also run this file to prepare your pgsql and have your pgsql database running by now. Or whatever, cache
> service you use.
### Cache Handler
Now we will initiate our cache service. This may be different for you based on your choice of cache type. Since we are
using PGSQL for our cache layer, we will now instantiate it.
```ts
import { postgres } from "../../../deps.ts";
// YOU CUSTOM PGSQL INFO GOES HERE
const DATABASE_USERNAME = "";
const DATABASE_PASSWORD = "";
const DATABASE_NAME = "";
const DATABASE_HOST = "";
const DATABASE_PORT = 8956;
const DATABASE_MAX = 20;
export const psql = postgres({
username: DATABASE_USERNAME,
password: DATABASE_PASSWORD,
database: DATABASE_NAME,
host: DATABASE_HOST,
port: DATABASE_PORT,
max: DATABASE_MAX,
/*onnotice: (data) => {
logger.psql(`${data.severity} ${bgBrightBlack(`[${data.code}| ${data.file}:${data.line}]`)}`, data.message);
},*/
types: {
bigint: postgres.BigInt,
},
});
```
To use the PGSQL driver we are using in this guide you can insert this into your `deps.ts`.
```ts
// @deno-types="https://denopkg.com/porsager/postgres@e2a8595d7aa8c3c838b83b9bca7b890c1707ad2c/types/index.d.ts"
export { default as postgres } from "https://denopkg.com/porsager/postgres@e2a8595d7aa8c3c838b83b9bca7b890c1707ad2c/deno/lib/index.js";
```
> Note: Remember you can use any driver you like. For deno users we prefer to use this library for PGSQL because it is
> more stable and more performant.
Now that the cache layer is ready, we can proceed to begin creating our bot.
-260
View File
@@ -1,260 +0,0 @@
---
sidebar_position: 5
sidebar_label: Step 4 - Event Handler
---
# Step 4: Creating Standalone Event Handler
Now we are about to start working on the bot code itself. The last 3 steps should be completed by the time you reach
this. The event handler process will be listening for events from any number of gateway instances and be ready to handle
them.
In this guide, we may use the term `Bot` or the term `event handler`, remember that these refer to the same thing. This
is your main bot code.
## Why Use Standalone Event Handler Process?
The standalone event handler is the portion of your bot code that you will be changing the most. The three previous
steps created processes that are intended to never be turned off. This process is designed to let you restart whenever
you wish and be incredibly quick to restart. Since we don't have the delay to start up shards anymore, your code becomes
reloaded instantly.
## Creating Event Handlers
Create a file path like `src/bot/mod.ts`.
```ts
import { DISCORD_TOKEN } from "../../configs.ts";
import { Collection, createBot, Intents } from "../../deps.ts";
import { psql } from "./cache/mod.ts";
export const bot = createBot({
token: DISCORD_TOKEN,
botId: 270010330782892032n,
intents: Intents.Guilds | Intents.GuildMessages,
events: {
messageCreate: function (bot, message) {
console.log("message arrived");
},
},
});
```
Alright that was a lot of code. Now let's break it down little by little.
### Understanding createBot()
**Basic Keys**
- `token` if you can't figure this out stop reading and find another guide please. Thanks.
- `botId` This is going to be your bot id. The reason we require this here is because we are going to set up a
standalone gateway process. With most other libs, they can fill this information using the READY event. However, since
our gateway is designed not to reboot, we are not going to get the READY event whenever we restart our bot. This means
we won't be able to fill this information later. Another method to get the id is to use the `token` but discord
developers have mentioned that this behavior is not documented and not supposed to be relied on to remain stable. Due
to these reasons, we chose to just require the bot id be passed here.
- `applicationId` is an optional choice if your bot is old and has a unique id different from it's bot id.
- `intents`: Provide the intents you like using a bitwise OR operation (eg. `Intents.Guilds | Intents.GuildsMessages`).
String form supports autocomplete and type safety.
- `events`: These are your event handler functions. When a MESSAGE_CREATE event arrives from Discord it will be
processed here. We will set up the routing to run these functions later in the guide but for now you can see how to
set it up. Note, you can create these functions in separate files and just import them here as you wish.
## Using Your Cache
Since we are using a standalone gateway, a custom cache is essentially required as explained in step 3 of this guide.
Here we'll have some basic functions to make use of the cache we created in step 3.
```ts
const cache = {
/** Get a single item from the table */
async get(key) {
return await psql`SELECT * FROM ${
psql(
tables[table],
)
} WHERE "id" = ${psql.types.bigint(key)}`;
},
/** Completely empty this table. */
async clear() {
await psql`TRUNCATE TABLE ${psql(tables[table])}`;
},
/** Delete the data related to this key from table. */
async delete(key) {
await psql`DELETE FROM ${
psql(
tables[table],
)
} WHERE "id" = ${psql.types.bigint(key)}`;
return true;
},
/** Check if there is data assigned to this key. */
async has(key) {
return Boolean(
await psql`SELECT 1 FROM ${
psql(
tables[table],
)
} WHERE "id" = ${psql.types.bigint(key)}`,
);
},
/** Check how many items are stored in this table. */
async size() {
return (await psql`SELECT COUNT("id") FROM ${psql(tables[table])}`)
.count;
},
/** Store new data to this table. */
async set(key, data) {
await psql`INSERT INTO ${psql(tables[table])} ${
psql(
data,
...Object.keys(data),
)
}`;
return true;
},
// THESE TWO ARE USELESS FOR CUSTOM CACHE BUT NEED TO SHUT UP TS ERRORS
async forEach(callback) {},
async filter(callback) {
return new Collection();
},
};
```
You can insert any code you desire for your cache system here. Since we were using PGSQL, we used sql queries to make
these requests. However, should you need to communicate to Redis or anything else of your choice, you can do so here.
> Note: The .filter() and .forEach() methods are unnecessary and should not be used for your bot as they are not
> optimized for performance. These are made for smaller bot users who would not leave itoh alone and in order to please
> them itoh gave them their hearts desire! LMAO!
## Customizing Internal Code
One of the best parts about discordeno is the flexibility. In order to show this off, we will use the `user` example but
you can apply this to any part of the library.
### Why Is Customizing Important?
At large scale, every single property can become expensive to store in your cache. For example, if your bot does not
make use of a `channel.topic` why storing potentially millions of strings in your memory for something you never
need/user. This could save you potentially GBs of memory to just remove this one property.
### Customizing Process
First, let's create a file in some path like `src/bot/internals/mod.ts`. Note that we will create quite a few files
below simply to keep code cleaner and simpler, in expectation that it will grow more complex later. You can merge them
as you wish.
```ts
import { Bot } from "../../../deps.ts";
import { customizeBotTransformers } from "./transformers/mod.ts";
export function customizeBotInternals(bot: Bot) {
bot = customizeBotTransformers(bot);
// ADD AS MANY MORE CUSTOMIZATIONS HERE AS YOU LIKE TO HANDLERS, HELPERS, UTILS ETC...
return bot;
}
```
We also need to add another file now at `src/bot/internals/transformers/mod.ts`
```ts
import { Bot } from "../../../../deps.ts";
import { customizeUserTransformer } from "./user.ts";
export function customizeBotTransformers(bot: Bot) {
bot = customizeUserTransformer(bot);
// ADD ANY MORE CUSTOM TRANSFORMERS HERE
return bot;
}
```
One more file at `src/bot/internals/transformers/user.ts`
```ts
import { Bot, DiscordenoUser, transformUser } from "../../../../deps.ts";
export function customizeUserTransformer(bot: Bot) {
bot.transformers.user = function (bot, payload) {
// REMOVE USELESS PROPS OUR BOT DOESNT USE
const { system, locale, verified, email, flags, mfaEnabled, premiumType, publicFlags, ...user } = transformUser(
bot,
payload,
);
// RETURN ONLY USEFUL PROPS WE NEED TO USE AND CACHE IF NECESSARY
return user as DiscordenoUser;
};
return bot;
}
```
First we override the internal transformer for the `user` object. What's cool is the typings will be automatically
provided :) Next, we use the `transformUser` function from the lib itself to make it create the internal user version.
The reason I do this is so when I update the library and a new property is added or removed i can simply update and get
it. Should you desire maximum control you can remove this entirely and only have what you want no matter what discord
sends. Discordeno gives you the ability to stay in control.
This method can be applied to any transformer, helper function, gateway event handler, util function or any part of the
library. Anything and everything is possible to override. You do NOT need to fork and modify the library ever and give
yourself a headache trying to maintain your fork with updates.
## Handling Incoming Gateway Events
Remember, this is a separate process we need to make sure we are listening to incoming events from our gateway
instances. Since we used http in our Gateway step, we can create an http listener here as well.
Create a file in a path like `src/bot/gatewayEventsListener.ts`
Now we should create a http listener, check for authorization in headers, run `bot.events.raw` and `bot.handlers[event]`
```ts
import { DiscordGatewayPayload } from "discordeno";
import { EVENT_HANDLER_PORT, REST_AUTHORIZATION } from "../../configs.ts";
const server = Deno.listen({ port: EVENT_HANDLER_PORT });
// Connections to the server will be yielded up as an async iterable.
for await (const conn of server) {
// In order to not be blocking, we need to handle each connection individually
// in its own async function.
handleRequest(conn);
}
async function handleRequest(conn: Deno.Conn) {
// This "upgrades" a network connection into an HTTP connection.
const httpConn = Deno.serveHttp(conn);
// Each request sent over the HTTP connection will be yielded as an async
// iterator from the HTTP connection.
for await (const requestEvent of httpConn) {
if (!REST_AUTHORIZATION || REST_AUTHORIZATION !== requestEvent.request.headers.get("AUTHORIZATION")) {
return requestEvent.respondWith(
new Response(JSON.stringify({ error: "Invalid authorization key." }), {
status: 401,
}),
);
}
const json = (await requestEvent.request.json()) as {
message: DiscordGatewayPayload;
shardId: number;
};
// Run raw event.
bot.events.raw(bot, json.message, json.shardId);
if (json.message.t && json.message.t !== "RESUMED") {
// When a guild or something isn't in cache this will fetch it before doing anything else.
if (!["READY", "GUILD_LOADED_DD"].includes(json.message.t)) {
await bot.events.dispatchRequirements(bot, json.message, json.shardId);
}
// Run event function provided in bot.events
bot.handlers[json.message.t]?.(bot, json.message, json.shardId);
}
new Response(undefined, { status: 200 });
}
}
```
-989
View File
@@ -1,989 +0,0 @@
---
sidebar_position: 3
sidebar_label: Step 2 - Gateway
---
# 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.
Before, we dive into how, here is a quick summary of why you will want a standalone gateway process.
## Why Use Standalone REST Process?
- **Zero Downtime Updates**:
- Your bot can be updated in a matter of seconds. With normal sharding, you have to restart which also has to process
identifying all your shards with a 1/~5s rate limit. With WS handling moved to a proxy process, this allows you to
instantly get the bot code restarted without any concerns of delays. If you have a bot on 200,000 servers normally
this would mean a 20 minute delay to restart your bot if you made a small change and restarted.
- **Zero Downtime Resharding**:
- Discord stops letting your bot get added to new servers at certain points in time. For example, suppose you had
150,000 servers running 150 shards. The maximum amount of servers your shards could hold is 150 \* 2500 = 375,000.
If your bot reaches this, it can no longer join new servers until it re-shards.
- DD proxy provides 2 types of re-sharding. Automated and manual. You can also have both.
- `Automated`: This system will automatically begin a Zero-downtime resharding process behind the scenes when you
reach 80% of your maximum servers allowed by your shards. For example, since 375,000 was the max, at 300,000 we
would begin re-sharding behind the scenes with `ZERO DOWNTIME`.
- 80% of maximum servers reached (The % of 80% is customizable.)
- Identify limits have room to allow re-sharding. (Also customizable)
- `Manual`: You can also trigger this manually should you choose.
- **Horizontal Scaling**:
- The proxy system allows you to scale the bot horizontally. When you reach a huge size, you can either keep spending
more money to keep beefing up your server or you can buy several cheaper servers and scale horizontally. The proxy
means you can have WS handling on a completely separate system.
- **No Loss Restarts**:
- When you restart a bot without the proxy system, normally you would lose many events. Users may be using commands or
messages are sent that will not be filtered. As your bot's grow this number rises dramatically. Users may join who
wont get the auto-roles or any other actions your bot should take. With the proxy system, you can keep restarting
your bot and never lose any events. Events will be put into a queue while your bot is down(max size of queue is
customizable), once the bot is available the queue will begin processing all events.
- **Controllers**:
- The controller aspect gives you full control over everything inside the proxy. You can provide a function to simply
override the handler. For example, if you would like a certain function to do something different, instead of having
to fork and maintain your fork, you can just provide a function to override.
- **Clustering With Workers**:
- Take full advantage of all your CPU cores by using workers to spread the load. Control how many shards per worker
and how many workers to maximize efficiency!
## Creating Gateway Manager
Create a file under some path like `src/gateway/mod.ts`.
```ts
import { DISCORD_TOKEN, REST_AUTHORIZATION, REST_PORT } from "../../configs.ts";
import { BASE_URL, createRestManager } from "../../deps.ts";
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: `http://localhost:${REST_PORT}`,
});
```
Throw another rest manager here which will be responsible for calling the main REST process we created in Step 1. This
will allow your gateway to communicate to the other process. Remember this is just to communicate outwards, this file
should not have the http listener.
> Feel free to refactor and optimize this should you wish to move `const rest...` to a separate file and reuse in both
> steps.
### Getting Gateway Bot Data
Now we need to use this rest manager to call the api to get information about how to connect to discord's gateway for
your bot.
```ts
import { routes } from "../../deps.ts";
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: `http://localhost:${REST_PORT}`,
});
// CALL THE REST PROCESS TO GET GATEWAY DATA
const gatewayBot = await rest.runMethod(rest, "GET", routes.GATEWAY_BOT()).then((res) => ({
url: res.url,
shards: res.shards,
sessionStartLimit: {
total: res.session_start_limit.total,
remaining: res.session_start_limit.remaining,
resetAfter: res.session_start_limit.reset_after,
maxConcurrency: res.session_start_limit.max_concurrency,
},
}));
```
With this info, we can now create our gateway manager.
### Understanding Gateway Manager
```ts
import { INTENTS, SHARDS_PER_WORKER, TOTAL_WORKERS } from "../../configs.ts";
const gateway = createGatewayManager({
gatewayBot,
gatewayConfig: {
token: DISCORD_TOKEN,
intents: INTENTS,
},
totalShards: gatewayBot.shards,
shardsPerWorker: SHARDS_PER_WORKER,
totalWorkers: TOTAL_WORKERS,
// debug: console.log,
// THIS WILL BE USED LATER IN WORKER SO LEAVE IT HERE
handleDiscordPayload: () => {},
});
```
#### Basic Keys
- `EVENT_HANDLER_SECRET_KEY` is from your configs that will be used to make sure requests sent to your event handler
process are indeed from you.
- `DISCORD_TOKEN` if you can't figure this out, this guide isn't for you. Please find another.
- `INTENTS` pass in a number or a string of intents. Autocomplete/type-safety is provided for strings :)
#### Powerful Keys
If your bot is going to be run on one process, you can re-use the data that discord gave you to connect.
- `totalShards`: is the maximum number of shards you want to use for connecting your bot. Should you think Discord is
not smart enough to recommend a good amount, use this to override their choice. Highly recommend just using theirs.
- `lastShardId`: is the last shard you want to connect in this process.
- Using a combination of `lastShardId` & `firstShardId`, you can create several processes or even several servers to
handle different amounts of shards should your bot get that big to require horizontal scaling. You can control how
many shards each gateway manager will be responsible for.
- `spawnShardDelay`: The delay in milliseconds to wait before spawning next shard.
- `shardsPerWorker`: The amount of shards to load per worker. Discussed in detail below.
- `totalWorkers`: The maximum amount of workers to use for your bot.
#### Gateway Cache
There is a few things that we cache in the gateway process directly, because sending them across the network is not
ideal. This is done to support custom cache functionality.
- `guildIds`: Used for determining what type of GUILD_CREATE event is received.
- `loadingGuildIds`: Used for determining if all guilds have arrived when initially connecting.
- `editedMessages`: Used to prevent spam of events across the network. MESSAGE_UPDATE are an extremely heavy event. Any
embed or link that is in a message will unfurl triggerring a message update event. This is undesired behavior for 99%
of bots out there. If someone sends a message with 5 urls, in there you will get a MESSAGE_CREATE and 5 MESSAGE_UPDATE
events. If that user edits a single letter on it you now get 6 MESSAGE_UPDATE events, 1 for the content change and 5
more for each url being unfurled. The editedMessages cache checks if the content of the message changed or not before
sending the event downstream. Override this behavior if you need different behavior.
#### Gateway Method Overriding
One of the benefits of Discordeno is that you can override/customize anything from the library. Should you desire to
change the logic in any method it is as simple as:
```ts
// TYPINGS WILL BE AUTOMATICALLY PROVIDED
gateway.heartbeat = function (gateway, shardId, interval) {
// YOUR CUSTOM HANDLING CODE HERE
};
```
## Workers
Now, we should take a minute here to talk about workers. Workers are just Clusters in Node.js
When you have a big bot and you are processing millions of events, you need to speed up that processing. Keeping it in 1
thread is not very nice since JavaScript is single threaded. This means it can only process 1 event at a time. With
workers, you can make it process several events at the same time. We mentioned the `shardsPerWorker` earlier. This
option was added to allow you to choose how many shards should be managed by each worker.
When shards are spawned, they are triggered by a method on gateway: `tellWorkerToIdentify`, so we'll have to modify it
to create workers and send message:
```ts
gateway.tellWorkerToIdentify = async (_gateway, workerId, shardId, _bucketId) => {
let worker = workers.get(workerId);
if (!worker) {
worker = createWorker(workerId);
workers.set(workerId, worker);
}
// TYPE TYPE WorkerMessage IS FROM WORKER FILE, DISCUSSED IN DETAIL BELOW
const identify: WorkerMessage = {
type: "IDENTIFY_SHARD",
shardId,
};
worker.postMessage(identify);
};
```
You can choose to replace the handler with any desired functionality you like. For example, should should you want to
create a new worker for each new workerId that appears and have that worker trigger the identify functionaly. How you
choose to handler workers is left in your care.
Now that we've setup our initial gateway manager and added `tellWorkerToIdentify` to `gateway`, we need to do the rest
of the work: creating workers, spawning shards etc.
```ts
import { EVENT_HANDLER_SECRET_KEY, EVENT_HANDLER_URL } from "../../configs.ts";
import { Worker } from "worker_threads";
import { WorkerCreateData, WorkerGetShardInfo, WorkerMessage, WorkerShardInfo, WorkerShardPayload } from "./worker.js";
// A COLLECTION OF WORKERS
const workers = new Collection<number, Worker>();
const nonces = new Collection<string, (data: any) => void>();
function createWorker(workerId: number) {
const workerData: WorkerCreateData = {
intents: gateway.manager.gatewayConfig.intents ?? 0,
token: DISCORD_TOKEN,
// TODO: PUT THIS SEPARATELY. CAN USE MULTIPLE URLS IF YOU HAVE MULTIPLE BOT PROCESSES HANDLING DIFFERENT SHARDS' EVENTS
handlerUrls: [EVENT_HANDLER_URL],
handlerAuthorization: EVENT_HANDLER_SECRET_KEY,
path: "./worker.ts",
totalShards: gateway.manager.totalShards,
workerId,
};
const worker = new Worker("./worker.js", {
workerData,
});
worker.on("message", async (data: ManagerMessage) => {
switch (data.type) {
case "REQUEST_IDENTIFY": {
await gateway.manager.requestIdentify(data.shardId);
const allowIdentify: WorkerMessage = {
type: "ALLOW_IDENTIFY",
shardId: data.shardId,
};
worker.postMessage(allowIdentify);
break;
}
case "NONCE_REPLY": {
nonces.get(data.nonce)?.(data.data);
}
}
});
return worker;
}
// TYPES WE USE
export type ManagerMessage = ManagerRequestIdentify | ManagerNonceReply<WorkerShardInfo[]>;
export type ManagerRequestIdentify = {
type: "REQUEST_IDENTIFY";
shardId: number;
};
export type ManagerNonceReply<T> = {
type: "NONCE_REPLY";
nonce: string;
data: T;
};
```
## Spawning Shards
Once you are ready and the gateway has been created as you desired, we can begin spawning the shards.
```ts
gateway.spawnShards(gateway);
```
This code now handles creating gateway manager, creating workers, spawning shards and sending the info to each workers.
Next is creating a worker file to receive these info, connecting to gateway and sending the events to bot process.
Here's the full code of `src/gateway/mod.ts`:
```ts
import {
DISCORD_TOKEN,
EVENT_HANDLER_SECRET_KEY,
EVENT_HANDLER_URL,
INTENTS,
REST_AUTHORIZATION,
REST_PORT,
SHARDS_PER_WORKER,
TOTAL_WORKERS,
} from "../../configs.ts";
import { BASE_URL, createRestManager, routes } from "../../deps.ts";
import { Worker } from "worker_threads";
import { WorkerCreateData, WorkerGetShardInfo, WorkerMessage, WorkerShardInfo, WorkerShardPayload } from "./worker.ts";
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: `http://localhost:${REST_PORT}`,
});
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: `http://localhost:${REST_PORT}`,
});
// CALL THE REST PROCESS TO GET GATEWAY DATA
const gatewayBot = await rest.runMethod(rest, "GET", routes.GATEWAY_BOT()).then((res) => ({
url: res.url,
shards: res.shards,
sessionStartLimit: {
total: res.session_start_limit.total,
remaining: res.session_start_limit.remaining,
resetAfter: res.session_start_limit.reset_after,
maxConcurrency: res.session_start_limit.max_concurrency,
},
}));
const gateway = createGatewayManager({
gatewayBot,
gatewayConfig: {
token: DISCORD_TOKEN,
intents: INTENTS,
},
totalShards: gatewayBot.shards,
shardsPerWorker: SHARDS_PER_WORKER,
totalWorkers: TOTAL_WORKERS,
// debug: console.log,
handleDiscordPayload: () => {},
tellWorkerToIdentify: async (_gateway, workerId, shardId, _bucketId) => {
let worker = workers.get(workerId);
if (!worker) {
worker = createWorker(workerId);
workers.set(workerId, worker);
}
// TYPE TYPE WorkerMessage IS FROM WORKER FILE, DISCUSSED IN DETAIL BELOW
const identify: WorkerMessage = {
type: "IDENTIFY_SHARD",
shardId,
};
worker.postMessage(identify);
},
});
// A COLLECTION OF WORKERS
const workers = new Collection<number, Worker>();
const nonces = new Collection<string, (data: any) => void>();
function createWorker(workerId: number) {
const workerData: WorkerCreateData = {
intents: gateway.manager.gatewayConfig.intents ?? 0,
token: DISCORD_TOKEN,
handlerUrls: [EVENT_HANDLER_URL],
handlerAuthorization: EVENT_HANDLER_SECRET_KEY,
path: "./worker.ts",
totalShards: gateway.manager.totalShards,
workerId,
};
const worker = new Worker("./worker.ts", {
workerData,
});
worker.on("message", async (data: ManagerMessage) => {
switch (data.type) {
case "REQUEST_IDENTIFY": {
await gateway.manager.requestIdentify(data.shardId);
const allowIdentify: WorkerMessage = {
type: "ALLOW_IDENTIFY",
shardId: data.shardId,
};
worker.postMessage(allowIdentify);
break;
}
case "NONCE_REPLY": {
nonces.get(data.nonce)?.(data.data);
}
}
});
return worker;
}
// TYPES WE USE
export type ManagerMessage = ManagerRequestIdentify | ManagerNonceReply<WorkerShardInfo[]>;
export type ManagerRequestIdentify = {
type: "REQUEST_IDENTIFY";
shardId: number;
};
export type ManagerNonceReply<T> = {
type: "NONCE_REPLY";
nonce: string;
data: T;
};
// SPAWN SHARDS INTO WORKERS
gateway.spawnShards();
```
## Worker File
Now that we've handled creating gateway, workers, we need to create a worker file to identify, receive gateway events
and send them to bot process.
Create a file in a path like `src/gateway/worker.ts`.
Now we'll have to create a Shard Manager, this is what will handle identifying, receiving events.
```ts
import { createShardManager } from "discordeno";
import { parentPort, workerData } from "worker_threads";
if (!parentPort) {
throw new Error("Parent port is null");
}
// THE DATA WE GET FROM GATEWAY FILE
const script: WorkerCreateData = workerData;
const identifyPromises = new Map<number, () => void>();
const manager = createShardManager({
gatewayConfig: {
intents: script.intents,
token: script.token,
},
shardIds: [],
totalShards: script.totalShards,
// WE WILL COVER THESE TWO FUNCTIONS IN LATER PART OF THE GUIDE, FOR NOW, LEAVE IT THIS WAY
handleMessage: () => {},
requestIdentify: async () => {},
});
```
The above code only creates a shard manager, we now have 3 more things to do:
- Listening to gateway process, sending events received to respective shards in the manager.
- Handling Discord Payloads.
- Requesting Identify.
## Sending events from Gateway to Shards in Manager
In order for the shards to receive events and send to bot process, we need to receive the event payloads from gateway
first, we can do this by using `message` event in `parentPort` like shown below:
```ts
import { Shard } from "discordeno";
function buildShardInfo(shard: Shard): WorkerShardInfo {
return {
workerId: script.workerId,
shardId: shard.id,
rtt: shard.heart.rtt || -1,
state: shard.state,
};
}
parentPort.on("message", async (data: WorkerMessage) => {
switch (data.type) {
// Gateway sends IDENTIFY_SHARD in gateway.tellWorkerToIdentify
case "IDENTIFY_SHARD": {
await manager.identify(data.shardId);
break;
}
// Gateway sends ALLOW_IDENTIFY when worker requests to identify
case "ALLOW_IDENTIFY": {
identifyPromises.get(data.shardId)?.();
identifyPromises.delete(data.shardId);
break;
}
// Gateway sends SHARD_PAYLOAD for every events it receives from Discord
case "SHARD_PAYLOAD": {
manager.shards.get(data.shardId)?.send(data.data);
break;
}
// Send shard info if gateway sends GET_SHARD_INFO
case "GET_SHARD_INFO": {
const infos = manager.shards.map(buildShardInfo);
parentPort?.postMessage({ type: "NONCE_REPLY", nonce: data.nonce, data: infos });
}
}
});
```
Now TypeScript will error because of missing types, add these to your code:
```ts
import { ShardSocketRequest, ShardState } from "discordeno";
export type WorkerMessage = WorkerIdentifyShard | WorkerAllowIdentify | WorkerShardPayload | WorkerGetShardInfo;
export type WorkerIdentifyShard = {
type: "IDENTIFY_SHARD";
shardId: number;
};
export type WorkerAllowIdentify = {
type: "ALLOW_IDENTIFY";
shardId: number;
};
export type WorkerShardPayload = {
type: "SHARD_PAYLOAD";
shardId: number;
data: ShardSocketRequest;
};
export type WorkerGetShardInfo = {
type: "GET_SHARD_INFO";
nonce: string;
};
export type WorkerCreateData = {
intents: number;
token: string;
handlerUrls: string[];
handlerAuthorization: string;
path: string;
totalShards: number;
workerId: number;
};
export type WorkerShardInfo = {
workerId: number;
shardId: number;
rtt: number;
state: ShardState;
};
```
## Handling Discord Payloads
One of the big things we didn't cover yet is the handler for discord payloads. This is the main sauce of your worker
process here. This is going to take the events that the gateway manager sent and send it to your event handler. How you
wish to communicate with your event handler is up to you. For this guide, we will use http, but you can replace that
with anything you like.
```ts
manager.createShardOptions.handleMessage = async (shard, message) => {
const url = script.handlerUrls[shard.id % script.handlerUrls.length];
if (!url) return console.error("ERROR: NO URL FOUND TO SEND MESSAGE");
await fetch(url, {
method: "POST",
body: JSON.stringify({ message, shardId: shard.id }),
headers: { "Content-Type": "application/json", Authorization: script.handlerAuthorization },
}).catch((error) => console.error(error));
};
```
You can change this function to use a WS or any form of communication you prefer to use to send this to your event
handler.
This is also the place where you make use of the [Gateway Cache](#gateway-cache) we mentioned earlier (`guildIds`,
`loadingGuildIds`, `editedMessages`).
## Gateway Queue
One thing we can add on here, which you will find already done in the template if you are using it. However, it is still
good to read this to learn and understand the logic behind it. When you need a downtime for whatever reason, you can
create a queue like system to avoid any missed events. Let's create a simple queue. If it errors, assuming something
like the bot event listener process is down for whatever reason, the `.catch` in `fetch` will run adding this event to
the queue to try again in one second by calling the `handleQueue` function.
```ts
.catch(() => {
// IF FAILED TRY TO QUEUE MAYBE LISTENER IS DOWN
if (message.t === "INTERACTION_CREATE") handleInteractionQueueing(message, shard.id);
else queue.events.push({ shardId: shard.id, message });
setTimeout(handleQueue, 1000);
});
```
Now TypeScript will probably throw some errors at your face, so let's fix those real quick. Create an object that will
hold the queue of events for our gateway.
```ts
import { DiscordGatewayPayload } from "discordeno";
const queue: GatewayQueue = {
processing: false,
events: [],
};
export interface QueuedEvent {
message: DiscordGatewayPayload;
shardId: number;
}
export interface GatewayQueue {
processing: boolean;
events: QueuedEvent[];
}
async function handleQueue() {
// PLACEHOLDER FUNCTION THAT WILL HANDLE PROCESSING THE QUEUE
}
async function handleInteractionQueueing(message: DiscordGatewayPayload, shardId: number) {
// PLACEHOLDER FUNCTION
}
```
Alrighty, since TypeScript stopped being annoying, let's continue. Next, we should make sure to avoid fetching when the
queue is already processing or has events queued up. This will help us preserve the order of events in the queue.
```ts
handleMessage: async function (shard, message) {
// IF QUEUE IS RUNNING JUST ADD TO QUEUE
if (queue.processing) {
if (message.t === "INTERACTION_CREATE") return handleInteractionQueueing(message, shard.id);
return queue.events.push({ shardId: shard.id, message });
}
await fetch(EVENT_HANDLER_URL, {
```
Typescript must be at it again so let's shut it up again. Keep in mind that we are handling interaction events
separately because they require a response within 3 seconds or they will become invalid. In this function first we
automatically respond to the ones that can not be deferred. For the interactions that can be deferred, we will simply
defer them and add this event to the queue.
```ts
import { DiscordInteraction, InteractionResponseTypes, InteractionTypes, routes } from "discordeno";
import { BOT_SERVER_INVITE_CODE } from "../../configs.ts";
async function handleInteractionQueueing(message: DiscordGatewayPayload, shardId: number) {
if (message.t !== "INTERACTION_CREATE") return;
const interaction = message.d as DiscordInteraction;
// IF THIS INTERACTION IS NOT DEFERABLE
if ([InteractionTypes.ModalSubmit, InteractionTypes.ApplicationCommandAutocomplete].includes(interaction.type)) {
return await rest.runMethod(
rest,
"POST",
routes.INTERACTION_ID_TOKEN(BigInt(interaction.id), interaction.token),
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
content:
`The bot is having a temporary issue, please try again or contact us at https://discord.gg/${BOT_SERVER_INVITE_CODE}`,
},
},
);
}
await rest.runMethod(rest, "POST", endpoints.INTERACTION_ID_TOKEN(BigInt(interaction.id), interaction.token), {
// MESSAGE COMPONENTS NEED SPECIAL DEFER
type: InteractionTypes.MessageComponent === interaction.type
? InteractionResponseTypes.DeferredUpdateMessage
: InteractionResponseTypes.DeferredChannelMessageWithSource,
});
// ADD EVENT TO QUEUE
queue.events.push({ shardId, message });
}
```
Oh no, TypeScript is at it again. We need to make a REST manager so that our gateway proxy can communicate with our REST
proxy. We then can make use of it to send a POST request.
```ts
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: REST_URL,
});
```
So now there is only one thing left the `handleQueue` function. First we get the first item from the queue using
`.shift()`. Then we check to see if that item exists. If it does not exist, we mark the queue as no longer processing
and cancel out. However, if it does exist, we send a fetch request to the bot event handler process. In the `.catch()`
we will add this event back in to the start of the queue in case the bot is still down. Finally we call this function
again to run the next item in the queue.
```ts
async function handleQueue() {
const event = queue.events.shift();
// QUEUE IS EMPTY
if (!event) {
console.log("GATEWAY QUEUE ENDING");
queue.processing = false;
return;
}
await fetch(EVENT_HANDLER_URL, {
headers: {
Authorization: EVENT_HANDLER_SECRET_KEY,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
shardId: event.shardId,
message: event.message,
}),
})
.then((res) => {
res.text();
handleQueue();
})
.catch(() => {
// EVENT HANDLER STILL NOT ACCEPTING REQUEST. SO ADD BACK TO QUEUE
queue.events.unshift(event);
setTimeout(handleQueue, 1000);
});
}
```
## Requesting Identify
We need to request identify in order to trigger initial handshake with the gateway, we'll use `manager.requestIdentify`
to do this.
```ts
import { ManagerMessage } from "./mod.ts";
manager.requestIdentify = async function (shardId: number): Promise<void> {
return await new Promise((resolve) => {
identifyPromises.set(shardId, resolve);
const identifyRequest: ManagerMessage = {
type: "REQUEST_IDENTIFY",
shardId,
};
parentPort?.postMessage(identifyRequest);
});
};
```
That's all, you've now setup your gateway and worker. Here's the full code of `src/gateway/worker.ts`:
```ts
import {
createRestManager,
createShardManager,
DiscordGatewayPayload,
DiscordInteraction,
InteractionResponseTypes,
InteractionTypes,
routes,
Shard,
ShardSocketRequest,
ShardState,
} from "discordeno";
import { parentPort, workerData } from "worker_threads";
import { ManagerMessage } from "./mod";
import {
BOT_SERVER_INVITE_CODE,
DISCORD_TOKEN,
EVENT_HANDLER_SECRET_KEY,
EVENT_HANDLER_URL,
REST_AUTHORIZATION,
REST_URL,
} from "../../configs.ts";
if (!parentPort) {
throw new Error("Parent port is null");
}
// THE DATA WE GET FROM GATEWAY FILE
const script: WorkerCreateData = workerData;
const identifyPromises = new Map<number, () => void>();
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: REST_URL,
});
const manager = createShardManager({
gatewayConfig: {
intents: script.intents,
token: script.token,
},
shardIds: [],
totalShards: script.totalShards,
handleMessage: async (shard, message) => {
const url = script.handlerUrls[shard.id % script.handlerUrls.length];
if (!url) return console.error("ERROR: NO URL FOUND TO SEND MESSAGE");
await fetch(url, {
method: "POST",
body: JSON.stringify({ message, shardId: shard.id }),
headers: { "Content-Type": "application/json", Authorization: script.handlerAuthorization },
}).catch(() => {
// IF FAILED TRY TO QUEUE MAYBE LISTENER IS DOWN
if (message.t === "INTERACTION_CREATE") handleInteractionQueueing(message, shard.id);
else queue.events.push({ shardId: shard.id, message });
setTimeout(handleQueue, 1000);
});
},
requestIdentify: async function (shardId: number): Promise<void> {
return await new Promise((resolve) => {
identifyPromises.set(shardId, resolve);
const identifyRequest: ManagerMessage = {
type: "REQUEST_IDENTIFY",
shardId,
};
parentPort?.postMessage(identifyRequest);
});
},
});
function buildShardInfo(shard: Shard): WorkerShardInfo {
return {
workerId: script.workerId,
shardId: shard.id,
rtt: shard.heart.rtt || -1,
state: shard.state,
};
}
parentPort.on("message", async (data: WorkerMessage) => {
switch (data.type) {
// Gateway sends IDENTIFY_SHARD in gateway.tellWorkerToIdentify
case "IDENTIFY_SHARD": {
await manager.identify(data.shardId);
break;
}
// Gateway sends ALLOW_IDENTIFY when worker requests to identify
case "ALLOW_IDENTIFY": {
identifyPromises.get(data.shardId)?.();
identifyPromises.delete(data.shardId);
break;
}
// Gateway sends SHARD_PAYLOAD for every events it receives from Discord
case "SHARD_PAYLOAD": {
manager.shards.get(data.shardId)?.send(data.data);
break;
}
// Send shard info if gateway sends GET_SHARD_INFO
case "GET_SHARD_INFO": {
const infos = manager.shards.map(buildShardInfo);
parentPort?.postMessage({ type: "NONCE_REPLY", nonce: data.nonce, data: infos });
}
}
});
const queue: GatewayQueue = {
processing: false,
events: [],
};
async function handleQueue() {
const event = queue.events.shift();
// QUEUE IS EMPTY
if (!event) {
console.log("GATEWAY QUEUE ENDING");
queue.processing = false;
return;
}
await fetch(EVENT_HANDLER_URL, {
headers: {
Authorization: EVENT_HANDLER_SECRET_KEY,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
shardId: event.shardId,
message: event.message,
}),
})
.then((res) => {
res.text();
handleQueue();
})
.catch(() => {
// EVENT HANDLER STILL NOT ACCEPTING REQUEST. SO ADD BACK TO QUEUE
queue.events.unshift(event);
setTimeout(handleQueue, 1000);
});
}
async function handleInteractionQueueing(message: DiscordGatewayPayload, shardId: number) {
if (message.t !== "INTERACTION_CREATE") return;
const interaction = message.d as DiscordInteraction;
// IF THIS INTERACTION IS NOT DEFERABLE
if ([InteractionTypes.ModalSubmit, InteractionTypes.ApplicationCommandAutocomplete].includes(interaction.type)) {
return await rest.runMethod(rest, "POST", routes.INTERACTION_ID_TOKEN(BigInt(interaction.id), interaction.token), {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
content:
`The bot is having a temporary issue, please try again or contact us at https://discord.gg/${BOT_SERVER_INVITE_CODE}`,
},
});
}
await rest.runMethod(rest, "POST", routes.INTERACTION_ID_TOKEN(BigInt(interaction.id), interaction.token), {
// MESSAGE COMPONENTS NEED SPECIAL DEFER
type: InteractionTypes.MessageComponent === interaction.type
? InteractionResponseTypes.DeferredUpdateMessage
: InteractionResponseTypes.DeferredChannelMessageWithSource,
});
// ADD EVENT TO QUEUE
queue.events.push({ shardId, message });
}
export type WorkerMessage = WorkerIdentifyShard | WorkerAllowIdentify | WorkerShardPayload | WorkerGetShardInfo;
export type WorkerIdentifyShard = {
type: "IDENTIFY_SHARD";
shardId: number;
};
export type WorkerAllowIdentify = {
type: "ALLOW_IDENTIFY";
shardId: number;
};
export type WorkerShardPayload = {
type: "SHARD_PAYLOAD";
shardId: number;
data: ShardSocketRequest;
};
export type WorkerGetShardInfo = {
type: "GET_SHARD_INFO";
nonce: string;
};
export type WorkerCreateData = {
intents: number;
token: string;
handlerUrls: string[];
handlerAuthorization: string;
path: string;
totalShards: number;
workerId: number;
};
export type WorkerShardInfo = {
workerId: number;
shardId: number;
rtt: number;
state: ShardState;
};
export interface QueuedEvent {
message: DiscordGatewayPayload;
shardId: number;
}
export interface GatewayQueue {
processing: boolean;
events: QueuedEvent[];
}
```
Note, you can take this concept and expand on it as much as you like. You can swap out the fetch() with websockets or
any other system you like to communicate between your processes. I highly recommend you take some time to add checks in
place to prevent adding to queue when the queue reaches a certain size. You don't want this to become a memory leak of
infinite size and crash your gateway. So take the time to do this right in your setup.
If you have any questions, please contact us on our [discord server](https://discord.gg/ddeno).
-136
View File
@@ -1,136 +0,0 @@
---
sidebar_position: 2
sidebar_label: Step 1 - REST
---
# Creating A Standalone REST Process
The first thing we want to make is our standalone REST process. This process will be used by almost every other process,
so it is going to be the foundation of the bot.
Before, we dive into how, here is a quick summary of why you will want a standalone REST process.
## Why Use Standalone REST Process?
- Easily host on any serverless infrastructure.
- Freedom from global rate limit errors
- As your bot grows, you want to handle global rate limits better. Shards don't communicate fast enough to truly
handle it properly so this allows 1 rest handler across the entire bot.
- In fact, you can host multiple instances of your bot and all connect to the same rest server.
- REST does not rest!
- Separate rest means if your bot for whatever reason crashes, your requests that are queued will still keep going and
will not be lost.
- Seamless updates! When you want to update and reboot the bot, you could potentially lose tons of messages or
responses that are in queue. Using this you could restart your bot without ever worrying about losing any responses.
- Single source of contact to Discord API
- This will allow you to make requests to discord from anywhere including a bot dashboard. You no longer need to have
to communicate to your bot processes just to make a request or anything. Free up your bot process for processing bot
events.
- Scalability! Scalability! Scalability!
## Preparations
Before going further, you should have already made the following pieces:
- rest/mod.ts
- deps.ts (Make sure to import discordeno)
- configs.ts
- Deno extension(if you are using deno, this is required)
- TabNine extension to make your life so much better. (Optional)
## Creating Rest Manager
Now let's open up that rest file and start coding.
```ts
import { DISCORD_TOKEN, REST_AUTHORIZATION, REST_PORT } from "../../configs.ts";
import { BASE_URL, createRestManager } from "../../deps.ts";
const rest = createRestManager({
token: DISCORD_TOKEN,
secretKey: REST_AUTHORIZATION,
customUrl: `http://localhost:${REST_PORT}`,
});
```
- `createRestManager` is imported from your deps file which should have exported everything from discordeno.
- `DISCORD_TOKEN` is the bot's token itself.
- `REST_AUTHORIZATION` is a special password you want to use to authenticate that requests being sent to your port are
indeed from you.
- `customUrl` the url where this rest process will be running. This can be localhost which we are using in this guide if
you want all processes on same VPS or separate them to different servers for horizontal scaling. `REST_PORT` is just
the port where you want the process hosted.
Now you have an entire Rest manager ready and waiting. Only thing you need now, is to listen for requests.
## Creating HTTP Listener
Since this is not a beginner guide, I am assuming you know already how to create a HTTP listener. There are enough
guides on this out there. I will only cover the rough functionality.
```ts
// START LISTENING TO THE URL(localhost)
const server = Deno.listen({ port: REST_PORT });
console.info(
`HTTP webserver running. Access it at: http://localhost:${REST_PORT}/`,
);
// Connections to the server will be yielded up as an async iterable.
for await (const conn of server) {
// In order to not be blocking, we need to handle each connection individually
// in its own async function.
handleRequest(conn);
}
async function handleRequest(conn: Deno.Conn) {
// This "upgrades" a network connection into an HTTP connection.
const httpConn = Deno.serveHttp(conn);
// Each request sent over the HTTP connection will be yielded as an async
// iterator from the HTTP connection.
for await (const requestEvent of httpConn) {
if (
!REST_AUTHORIZATION ||
REST_AUTHORIZATION !== requestEvent.request.headers.get("AUTHORIZATION")
) {
return requestEvent.respondWith(
new Response(JSON.stringify({ error: "Invalid authorization key." }), {
status: 401,
}),
);
}
const json = (await requestEvent.request.json()) as any;
// IMPLEMENT ANY ERROR HANDLING HERE IF YOU WOULD LIKE BY WRAPPING THIS IN A CATCH
// MAKE THE REQUEST TO DISCORD
const result = await rest.runMethod(
rest,
// USE THE SAME METHOD THAT CAME IN. IF DELETE CAME IN WE SEND DELETE OUT
requestEvent.request.method as any,
// OVERWRITE THE CUSTOM URL WITH DISCORDS BASE URL
`${BASE_URL}/v${rest.version}${
requestEvent.request.url.substring(
rest.customUrl.length,
)
}`,
json,
);
// RETURN DISCORDS RESPONSE BACK TO THE PROCESS MAKING THE REQUEST
if (result) {
requestEvent.respondWith(
new Response(JSON.stringify(result), {
status: 200,
}),
);
} else {
requestEvent.respondWith(
new Response(undefined, {
status: 204,
}),
);
}
}
}
```
@@ -1,40 +0,0 @@
---
sidebar_position: 1
---
# Step By Step Guide
THIS IS A WORK IN PROGRESS GUIDE USING THE NEW v16 OF DISCORDENO.
## Understanding The Goals of This Guide
This guide is a quick-paced walkthrough meant for big bot developers. It is expected that you have a decent amount of
understanding of how to code your bots.
## Is This Guide Meant For You?
If your goal is not to have a bot in millions of discord servers, please find another guide/library. Discordeno is
heavily opinionated towards optimizing for bots at scale. If you do not know what a Map or a Set is without having to
google it, you are at the wrong place.
## Why You Should Use Discordeno?
The best way I can describe why you should use Discordeno, is from the words of the biggest bot developers themselves.
After speaking to some of the developers of the biggest JS/TS bots, you begin to see a pattern of users unhappy with the
current state of JS/TS libraries. They are no longer able to help them scale easily and are starting to move away to
other libraries or having to make their own libraries because they need to be able to make their bot distributed.
The following quotes are from developers who have bot's in atleast 1 million+ discord servers.
- Flexibility like no other library.
- One of the big bot developers found that when their bot got too big, Eris was just very painful to optimize.
- "A pretty large hassle, I had to fork eris and modify it. There was a lot of interdependency on the values from
caches that made it difficult to remove properties "safely" without searching the entire codebase"
- When discovering how easy it was to do the same thing in Discordeno:
- "the convenience of being able to do so puts confidence in me that the lib is versatile so it'd certainly draw me
towards it"
- Scalability: Standalone Gateway, Rest, Event Handler, Commands, Cache and much more.
- "All this sound like a dream (especially when you currently use eris)"
Discordeno provides you all the tools that you need to make bot development really easy. As the old saying goes, the
best way to learn to ride a bicycle is to actually try riding a bicycle. So let's try out Discordeno.
@@ -1,4 +0,0 @@
{
"label": "Command Handler",
"position": 9
}
@@ -1,92 +0,0 @@
---
sidebar_position: 2
---
# Command Manager
Currently, you probably have something like this in your code:
```js
const Discord = require("discordeno.js");
// Ideally you should move to an `.env` file
const config = require("./config.json");
const bot = Discord.createBot({
events: {
messageCreate(client, message) {
if (message.content === "!ping") {
client.helpers.sendMessage(message.channelId, { content: "pong" });
}
},
},
intents: Discord.Intents.Guilds | Discord.Intents.GuildMessages,
token: config.token,
});
const client = Discord.enableCachePlugin(bot, {});
Discord.startBot(client);
```
Of course, if you add more and more commands and as your code base grows, you can lose track very quickly.
To avoid this, it is recommended to store the commands in separate folders divided into different categories.
[Previously, we introduced you to our plugin structure, which has a lot of advantages.](../design.md)
```root
├Plugins/
├── General/
│ ├── commands/
│ │ ├── ping.js
│ │ └── ...
├── Developer/
│ ├── commands/
│ │ ├── eval.js
│ │ └── ...
└── ...
```
**Get [this file](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/CommandManager.js) from
the [nodejs template](https://github.com/discordeno/discordeno/tree/main/template)**
```js
const CommandManager = require("./template/Managers/CommandManager.js");
const manager = new CommandManager({});
manager.load({ plugin: true }); // Load the commands
client.commands = manager;
client.commands.cache.get("ping"); // Get the `ping` command
```
The Manager will automatically iterate through all files in the folder and then load them into the cache property, which
is mapped on the command name.
**Take a look at [Create Command](./create-command.md) to learn how to create a command.**
## Handle Command
The manager also contains a handler for executing the command when a message is received.
:::important
Currently checks for permissions, cooldowns, and rate limits are not covered, but these will be added soon.
:::
### Message Create Event:
```js
module.exports = async (client, message) => {
client.commands.isCommand(message);
};
```
### Interaction Create Event:
```js
module.exports = async (client, interaction) => {
client.commands.isInteraction(interaction);
};
```
You can also customize the `isCommand` function to your use case.
@@ -1,61 +0,0 @@
---
sidebar_position: 3
---
# Create Command
One of the most important features we wanted in our template, was that you can use the same code for handling
`slash commands` and `message based commands`.
This can be done by saving the static class in the command cache, creating a constructor and passing the desired data.
Moreover the `BaseCommand` is extended with the `Response Command` class, so you can take advantage of functions such as
`.reply()`
**Copy the [`BaseCommand`](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Structures/BaseCommand.js)
&
[`CommandResponses`](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Structures/CommandResponses.js)
code from the template**
### Creating a Ping Command:
```js
const BaseCommand = require("../../../Structures/BaseCommand.js");
const Embed = require("../../../Structures/Embed.js");
class pingCommand extends BaseCommand {
static name = "ping";
static description = "See if the bot latency is okay";
static usage = "";
static category = "General";
static slash = { name: "ping", category: "info" };
constructor(data) {
super(data);
}
async execute() {
const msg = await this.reply({content: `Pinging...`});
// Assign properties to the response
const ping = msg.timestamp - this.message.timestamp;
const embed = new Embed()
.setTitle(`The Bots ping is ${ping} ms`)
.toJSON();
// Edit Message with the Embed
return await msg.edit({embeds: [embed] });
});
}
}
module.exports = pingCommand;
```
- The `BaseCommand` is extended with the `CommandResponses` class.
- The ping command class is extended with the `BaseCommand` class.
- Some static properties are assigned to the ping command class, in order to access it in the cache, such as `name`,
`description`, `usage`, `category` and `slash`...
- The `execute()` function will be called, when the command has been run by the user.
- The constructor allows to access data, such as `this.message`, `this.args`, `this.client`...
You can customize the `CommandManager` file, in order to pass arguments in the `execute()` function.
@@ -1,22 +0,0 @@
---
sidebar_position: 1
---
# Getting Started with the Command Manager
One of the most important characteristics of bots is that they have commands that can be used to interact with the bot.
Hard coding your commands in an event function is not the best code practice and should be strictly prevented.
In the following we will show you how to create a command manager, which is compatible with Discordeno's Client.
- Load Commands
- Handle Commands
- Reload Commands
:::info template
You can also copy the
[`CommandManager` from the template repo](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/CommandManager.js).
:::
@@ -1,4 +0,0 @@
{
"label": "Event Handler",
"position": 8
}
@@ -1,119 +0,0 @@
---
sidebar_position: 2
---
# Create Event Manager
In order to process certain events, you must provide the Discordeno client with functions for these events.
```js
const Discord = require("discordeno");
const config = require("./config.json");
const client = Discord.createBot({
events: {
ready(client, payload) {
console.log(`Successfully connected Shard ${payload.shardId} to the gateway`);
},
async messageCreate(client, message) {
if (message.content === "!ping") {
await client.helpers.sendMessage(message.channelId, { content: "pong" });
}
console.log(`Received message: ${message.content || message.embeds}`);
},
},
intents: ["Guilds", "GuildMessages"],
token: config.token,
});
Discord.startBot(client);
```
As you listen to more and more events, the functions code grows along with them, so you can quickly lose track.
To avoid this, we recommend storing the event functions divided into files in a separate folder.
## Create Event Folder
Create a folder called `events` in your project folder.
:::info note
The event files have to be named using camelCase so that they can be understood by the client. e.g `message` ->
`messageCreate.js`. You can check the typings see how the events are called.
:::
Ready Event:
```js
module.exports = (client, payload) => {
if (payload.shardId + 1 === client.gateway.maxShards) {
// All Shards are ready
console.log(`Successfully connected to the gateway as ${payload.user.username}#${payload.user.discriminator}`);
}
};
```
## Load your Events
```js
const fs = require("fs");
const path = require("path");
const resolveFolder = (folderName) => path.resolve(__dirname, ".", folderName);
class EventManager {
constructor(client) {
this.cache = new Map();
this._events = {};
}
load(options = {}) {
const eventsFolder = resolveFolder("../events");
fs.readdirSync(eventsFolder).map(async (file) => {
if (!file.endsWith(".js")) return;
const fileName = path.join(eventsFolder, file);
const event = require(fileName);
const eventName = file.split(".")[0];
this._events[`${eventName}`] = event;
});
return this._events;
}
}
module.exports = EventManager;
```
The code above, which can also be found in the
[template repo](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/EventManager.js) will loop
through all the files in the `events` folder and load the functions into the `_events` object.
In order to let the client know which events should be processed, you need to pass the functions in the
`createBot<options>.events` object.
```js
const Discord = require("discordeno");
const config = require("./config.json");
const EventManager = require("./Managers/EventManager.js");
const events = new EventManager({});
const client = Discord.createBot({
events: events.load({}),
intents: ["Guilds", "GuildMessages"],
token: config.token,
});
Discord.startBot(client);
```
Moreover, you can customize the `EventManager` and add more functionality to it and make it exactly fit your needs or
even emit events, by extending it.
Of course you wonder what you can do with all of this now. We will explain this further on the next page.
@@ -1,29 +0,0 @@
---
sidebar_position: 1
---
# Getting Started with the Event Handler
An event handler is essential to process the data, which Discord sends to you.
With a good implementation, you will have a nice code structure and thus have a good overview in long term.
Since the `EventEmitter` class is commonly used you probably already know it from other libraries.
Discordeno decided against it as it comes with several downsides which are mentioned below.
- It's easy to create memory leaks, when you add too many listeners or go carelessly with it.
- Many fragmented parts of event code complicate maintenance.
- ErrorHandling is difficult and debugging is harder when many listeners are open for the same events.
Performance plays a more important role than handling, however this event management system can be easily implemented
since it only needs a few changes in your code.
In the following we will show you, how to create an event manager, which is compatible with Discordeno's Client.
:::info template
You can also copy the
[`EventManager` from the template repo](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Managers/EventManager.js).
:::
@@ -1,75 +0,0 @@
---
sidebar_position: 3
---
# Handle Events
When an event is fired, Discordeno sends two important things: the `client` instance and the `payload`.
As mentioned in the `Structure` section, the `payload` object does not contain any functions as it's a plain json
object.
In order to take use of our nice built structures, we need to transform the payload into a structure.
:::info
The Structures can be found [here](https://github.com/discordeno/discordeno/tree/main/template/nodejs/Structures)
:::
Sometimes it's important to listen to events, in order to get informed of changes and updating the cache based on it.
### Message Event
This file should be called `messageCreate.js`.
```js
const Message = require("./structures/Message");
module.exports = async (client, payload) => {
const message = client.messages.forge(payload);
if (message.author.bot) return;
if (message.content === "!ping") return await message.reply("pong");
};
```
### Interaction Event
This file should be called `interactionCreate.js`.
```js
const Interaction = require("./structures/Interaction");
module.exports = async (client, payload) => {
const interaction = client.interactions.forge(payload);
if (interaction.data.name === "ping") return await interaction.reply({ content: "pong" });
};
```
### Ready Event
This file should be called `ready.js`.
:::tip
There is a small difference with the `ready` Event. The Event is fired `shard` wise, in other words it fires every time
a `shard` becomes ready.
:::
In order to fire the "real event" a small code snippet has to be added to the `ready` Event.
```js
const User = require("../Structures/User");
module.exports = async (client, payload) => {
client.user = client.users.forge(payload.user);
if (payload.shardId + 1 === client.gateway.maxShards) {
// All Shards are ready
console.log(`Successfully connected to the gateway as ${client.user.tag}`);
}
};
```
@@ -1,4 +0,0 @@
{
"label": "Structures",
"position": 7
}
@@ -1,55 +0,0 @@
---
sidebar_position: 5
---
# Create Collectors
Some of your commands or features are sometimes based on user interactions. E.g. if a user presses a button and you want
to know whether it was pressed. This is actually done by listening to the `interactionCreate` event.
But sometimes you need to access locale variables or don't want to "hardcode" the part.
That's why it's sometimes recommended to create collectors.
Collectors are listeners that listen to a specific event. In addition, you can provide a filter, so you only receive
certain interactions.
## Use a Collector
:::note Template The template code is used below. You must have the EventManager part to use the collector feature. :::
We have a pre-made class for collectors which you can find
[here](https://github.com/meister03/discordeno.js/blob/master/Util/Collectors.js).
```js
const Discord = require("discordeno.js");
const filter = (m) => m.data?.customId === "warn_modal" && m.user.id === interaction.user.id;
const listener = client.eventListener; // When the eventListener property is named different
const collector = new Discord.Collector("interactionCreate", {
client: client,
timeout: 60000,
filter,
max: 20,
listener,
});
collector.on("collect", (m) => {
const interaction = client.interactions.forge(m);
// Stop Collector
// collector.stop();
});
// Fires on a timeout, when the collector has reached the max amount of interactions or when it has been closed
collector.on("end", (collected) => {
// Map of Collected Interactions
console.log(collected);
});
```
As you can see, this opens up many possibilities. You can listen to any event and get the interaction you need.
### Collector Options
`filter`: Function, just fire the event if the filter returns true. `timeout`: Number, the time in milliseconds until
the collector times out. `max`: Number, the max amount of interactions the collector can collect. `listener`: Function,
the listener that will be fired when the collector collects an interaction. Just required when client property is named
differently.
@@ -1,223 +0,0 @@
---
sidebar_position: 4
---
# Create Components
Since Discord has decided to make message content accessible only to privileged bots, components will play an
increasingly important role in the future. Discord has released some components already and many more will follow. Of
course, this opens up completely new possibilities. On the one hand, it improves the user experience and on the other
hand, the interactions can be easily handled by the developer.
To take advantage of this, we'll go into more detail on how to use them.
:::note Runtime Overhead
Constructor classes are nice to use and make your code look better, but they incur a slight runtime overhead compared to
just using raw data because they still execute methods, which takes more time to process.
:::
We already have a Template for `Components`, which can be found
[here](https://github.com/meister03/discordeno.js/tree/master/Structures/Component.js).
## Different Components:
There are many different components, which you can quickly read about here:
### Action Row (`type: 1`):
This is a top level component, which contains a limited amount of other components. It can be described as container.
An Action Row ...
- can not include an action row
- can maximal have 5 Buttons
- can have 1 SelectMenu
- can have 1 Text Input (only available in modal responses)
### Button (`type: 2`):
Buttons are interactive components, are bound to a message and they sent an interaction payload, when a user clicks on
it.
![Different Button Styles](https://i.imgur.com/jUE2Kp0.png)
- Needs a customId, except the Link Button
- An Action Row can have maximal 5 Buttons
There are different styles of buttons, which can be used:
- `1` - PRIMARY - blurple - customId required
- `2` - DEFAULT - grey - customId required
- `3` - SUCCESS - green - customId required
- `4` - DANGER - red - customId required
- `5` - LINK - grey - url required
### Select Menu (`type: 3`):
Select Menus are a simple drop-down with selectable options. They accept a set of allowed selects, which sends an
interaction payload, when a user selects sth. from the menu.
![Select Menu](https://i.imgur.com/42Hwiuw.png)
- You can specify a range of allowed selects (`minValue` and `maxValue`)
- Every Select Item can have an `emoji` and has a `value`, in order to identify the selected item
- A default Select Item can be set
- An Action Row can have maximal 1 Select Menu
### Text Input (`type: 4`):
Text Inputs are interactive components, which can just be sent with a modal response.
- You can specify a range of text length (`minLength` and `maxLength`)
- You can add a placeholder, a pre-filled value and specify whether the text input is required
- An Action Row can have maximal 1 Text Input
## Send Components
As mentioned above there are different types of components. This requires to define a type, so that Discord knows, which
component you want to use.
```js
class ActionRow {
constructor(options = {}) {
this.type = 1;
}
setComponents(...components) {
this.components = components;
return this;
}
}
```
```js
const button = new Button();
const button2 = new Button();
const actionRow = new ActionRow().setComponents(button, button2);
```
This code will obviously not work because it's a missing a lot required of data. The other reason is that we can't send
a class to Discord, we need sth. to transform it to a json object.
We have a pre-made class for components which you can find
[here](https://github.com/meister03/discordeno.js/tree/master/Structures/Component.js).
### Button
```js
const Discord = require("discordeno.js");
const message = client.messages.forge(rawMessage);
const button = new Discord.Component()
.setType("BUTTON")
.setStyle("LINK")
.setLabel("Click me!")
.setUrl("https://google.com")
.toJSON();
// Button with raw types
const button2 = new Discord.Component()
.setType(2)
.setStyle(4)
.setLabel("DO NOT CLICK")
.setCustomId("12345")
.toJSON();
const actionRow = new Discord.Component()
.setType("ACTION_ROW")
.setComponents(button, button2)
.toJSON();
// Message to send
const messageOptions = { content: "hello", components: [actionRow] };
// await client.helpers.sendMessage(channelId, messageOptions); // Do it the raw way
message.channel.send(messageOptions); // Do it with the structure
```
As you can see, for simplicity you can use strings instead of numbers (types), which are hard to remember.
### Select Menu
```js
const Discord = require("discordeno.js");
const message = client.messages.forge(rawMessage);
const selectMenu = new Discord.Component()
.setType("SELECT_MENU")
.setCustomId("12345")
.setOptions([
{
label: "Option 1",
value: "1",
description: `This is option 1`,
},
{
label: "Option 2",
value: "2",
description: `This is option 2`,
},
{
label: "Default Option",
value: "3",
description: `Default option...`,
default: true,
},
])
.setPlaceholder("Select an option")
.toJSON();
const actionRow = new Discord.Component()
.setType("ACTION_ROW")
.setComponents(selectMenu)
.toJSON();
const messageOptions = { content: "hello", components: [actionRow] };
// await client.helpers.sendMessage(channelId, messageOptions); // Do it the raw way
message.channel.send(messageOptions); // Do it with the structure
```
### Text Input
```js
const Discord = require("discordeno.js");
const interaction = client.messages.forge(rawInteraction);
const textInput = new Component()
.setType("TEXT_INPUT")
.setStyle("SHORT")
.setCustomId("t1")
.setLabel("User ID")
.setPlaceholder("User ID")
.setRequired(true)
.setMaxLength(20)
.setMinLength(1)
.toJSON();
const textInput2 = new Component()
.setType("TEXT_INPUT")
.setStyle("PARAGRAPH")
.setCustomId("t2")
.setLabel("Reason")
.setPlaceholder("Reason for Ban")
.setRequired(false)
.setMaxLength(300)
.toJSON();
const actionRow = new Component().setType("ACTION_ROW").setComponents(textInput).toJSON();
const actionRow2 = new Component().setType("ACTION_ROW").setComponents(textInput2).toJSON();
interaction.popupModal({
customId: "ban_modal",
title: "Ban User",
components: [actionRow, actionRow2],
});
```
### Receive Interactions
When a user clicks a button or selects an option from a Select Menu, Discord sends an `interactionCreate` event, which
contains the information necessary to process it.
@@ -1,100 +0,0 @@
---
sidebar_position: 2
---
# Create Structure
Structures are often used to transform data and add methods to existing objects. To make it easier to work with them.
Imagine you have a channel object to which you want to send a message.
```js
const data = {
id: 806947972004839444n,
name: "spam-and-bots",
};
```
The recommended way would be:
```js
await client.helpers.sendMessage(data.id, { content: "hello" });
```
However, you probably want to use something shorter, such as the following:
```js
class Channel {
constructor(client, data) {
this.client = client;
this.id = data.id;
this.name = data.name;
}
async send(options) {
return await this.client.helpers.sendMessage(this.id, options);
}
}
```
Now you can use the `.send()` method on the channel object without using such a long code:
```js
const channel = new Channel(client, data);
await channel.send({ content: "hello" });
```
Moreover, you can modify the `.send()` method to better suit your use case e.g not send the message if the channel is
blacklisted.
This naturally opens a lot of opportunities and makes coding a lot easier. Because you decide what you want to do with
the data, how the methods are named and how you want to process the request.
## Using Template Structures:
When you are migrating from another library and you want to utilize the djs-like wrapper, you'll likely choose to
continue using special structures. Therefore we have ready-made structures for the wrapper `Discordeno.js`.
- [Guild](https://github.com/meister03/discordeno.js/tree/master/Structures/Guild.js)
- [Channel](https://github.com/meister03/discordeno.js/tree/master/Structures/Channel.js)
- [Role](https://github.com/meister03/discordeno.js/tree/master/Structures/Role.js)
- [Member](https://github.com/meister03/discordeno.js/tree/master/Structures/Member.js)
- [User](https://github.com/meister03/discordeno.js/tree/master/Structures/User.js)
- [Message](https://github.com/meister03/discordeno.js/tree/master/Structures/Message.js)
- [Interaction](https://github.com/meister03/discordeno.js/tree/master/Structures/Interaction.js)
- [Emoji](https://github.com/meister03/discordeno.js/tree/master/Structures/Emoji.js)
- [Webhook](https://github.com/meister03/discordeno.js/tree/master/Structures/Webhook.js)
- [Embed](https://github.com/meister03/discordeno.js/tree/master/Structures/Embed.js)
- [Component](https://github.com/meister03/discordeno.js/tree/master/Structures/Component.js)
- [Collection](https://github.com/meister03/discordeno.js/tree/master/Structures/Collection.js)
We recommend that you check the wrappers [Readme](https://github.com/meister03/discordeno.js#discordclient) in order to
construct the client for following the Guide
**Using the Structures:**
```js
const Discord = require("discordeno.js");
const client = new Discord.Client(clientOptions, cacheOptions); //See the Readme above
Discord.startBot(client);
const guild = client.guilds.forge(guildData);
const channel = guild.channels.forge(channelData);
const role = guild.roles.forge(roleData);
const member = guild.members.forge(memberData);
const user = guild.users.forge(userData);
const message = guild.messages.forge(messageData);
const interaction = guild.interactions.forge(interactionData);
const emoji = guild.emojis.forge(emojiData);
const webhook = new Discord.Webhook(client, webhookData);
const embed = new Discord.Embed(embedData); // embedData is optional
const component = new Discord.Component(componentData); // componentData is optional
const collection = new Discord.Collection();
```
Some popular methods have been added to the structures so that you can use them without having to come up with your own.
In order to use the Structures from the Wrapper, you need to invoke the `.forge` method with the raw discord data,
whereas it will construct the structure for you.
Next we're going to give a better insight into how create [`Embeds`](embeds) and [`Components`](components) with the
wrappers structures.
-106
View File
@@ -1,106 +0,0 @@
---
sidebar_position: 3
---
# Create Embeds
Embeds are widely used by bots in order to display messages in a fancy way.
Unfortunately, the Discord API does not accept funky classes such as `new MessageEmbed().setTitle("hello")`, instead it
takes a json object, e.g. `{ title: "hello" }`. Therefore, we need to create an embed Structure that converts the
user-supplied data into the format which Discord uses.
:::note Runtime Overhead
Constructor classes are nice to use and make your code look better, but they incur a slight runtime overhead compared to
just using raw data because they still execute methods, which takes more time to process.
:::
```js
class Embed() {
constructor() {}
setTitle(title) {
this.title = title;
}
}
```
Now we have created a class which we can use to create embeds. But we can't just send this to Discord.
So we need an additional method which will convert the data from the class to the correct format.
```js
class Embed(){
constructor() {}
setTitle(title) {
this.title = title;
}
toJSON() {
return {
title: this.title
}
}
}
```
Wow, now you can create a embed and send it to Discord.
```js
const Channel = require("./structures/Channel"); // Path to structure
const channel = new Channel(client, data);
await channel.send({ embeds: [embed] });
```
You probably want more methods which you can use to create embeds.
[Here is how the Embed Structure looks like](https://github.com/meister03/discordeno.js/blob/master/Structures/Embed.js)
### Using the Embed Structure:
```js
const Discord = require("discordeno.js");
const channel = client.channels.forge(channelData);
const showCaseEmbed = new Discord.Embed()
.setColor(0x00AE86)
.setTitle("A Random Title")
.setURL("https://github.com/discordeno")
.setAuthor({
name: "Author name",
iconUrl: "https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png",
url: "https://github.com/discordeno",
})
.setDescription("A Random Description")
.setThumbnail("https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png")
.addFields(
{ name: "Field 1 Name", value: "Normal Field Value" },
{ name: "\u200B", value: "\u200B" },
{ name: "Field 2 Name", value: "Inline Field Value", inline: true },
{ name: "Field 3 Name", value: "Inline Field Value", inline: true },
)
.addField({ name: "Field 4", value: "Field Value" })
.setImage("https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png")
.setTimestamp()
.setFooter({
text: "A Footer Text",
iconUrl: "https://raw.githubusercontent.com/discordeno/discordeno/main/site/static/img/logo.png",
})
.toJSON();
await channel.send({ embeds: [showCaseEmbed] });
```
### Embed Limits:
- Title: 256 characters
- Description: 4096 characters
- Field Name: 256 characters
- Field Value: 1024 characters
- Footer Text: 2048 characters
- Author Name: 256 characters
- 10 Embeds per message
- In total over all 10 Embeds not more than 6000 characters
@@ -1,30 +0,0 @@
---
sidebar_position: 1
---
# Getting Started with Structures
As previously mentioned, Discordeno was built with as few classes as possible, this is in favor of performance.
For example, you cannot execute functions on objects.
```diff
- message.channel.send({content: "hello"})
+ client.helpers.sendMessage(message.channel.id, {content: "hello"})
```
This seems to be more complicated at first, but has many advantages:
- You get full control over the actions
- Errors are easier to debug
- A validation by classes does not have to take place
One of the disadvantages is that you have to change a lot in your code.
Of course, we recommend that you try out the upper way, but we will introduce structures in this guide because they are
used by many users who eventually want to migrate.
For example, if you want to get correctly formatted objects, structures are obviously beneficial, because they support
the readability of the code by their ease of use
In the following, we will introduce how to create your own structures and how to use the ones available in the template.
-4
View File
@@ -1,4 +0,0 @@
{
"label": "Nodejs",
"position": 3
}
@@ -1,29 +0,0 @@
---
sidebar_position: 3
---
# Create Application
1. Go to the [Developer Portal](https://discord.com/developers/applications) and create a new application.
2. Navigate to the Section `Bot` and confirm with "Yes, do it!"
3. Now copy your token and save it under a safe environment.
:::caution Token Security
Keep your token safe, because it is like a password that grants access to your bot, which then can be used for mass
DMing, mass banning or any other kind of malicious activity.
:::
## Add your Bot to your Server
In order to use your Bot, it should be in a server where you can interact with it.
1. Go to the [Developer Portal](https://discord.com/developers/applications) and click on your previously created bot.
2. Click on `OAuth2` and there go to the `URL Generator`.
3. Select the `bot` and the `applications.commands` scope.
4. Scroll down and select the `Administrator` permission.
5. Copy the generated URL and open it in your browser.
6. Select your Server and click the invite button.
The bot should now have been added to your server and show as an offline user.
-203
View File
@@ -1,203 +0,0 @@
---
sidebar_position: 6
---
# Design
In order to ensure long-term scalability and maintainability, the code structure is of enormous importance. In the
following, we show how such a code structure could look like.
The essential parts are a `CommandHandler/CommandManager`, `EventHandler/EventManager`, lots of `Structures` in order to
code faster and `Plugins`, where your different features will be, such as `Commands`, `DB Stuff`...
## Code Structure
We recommend following structure for your code:
```root
├index.js
├─Structures/
├─Managers/
├─events/
├─Plugins/
├── General/
│ ├── commands/
│ │ ├── ping.js
│ │ └── ...
├── Developer/
│ ├── commands/
│ │ ├── eval.js
│ │ └── ...
├─Util/
└── ...
```
The following explains why this structure is suitable. If you want to follow this guide further, you should create these
folders.
In the `Managers` folder the Managers will be added e.g. `CommandManager.js`, `EventManager.js`. Generally codes, which
manage the system.
While in the `Structures` folder mainly classes are added like `BaseCommand.js`, `CommandResponse.js`, `Embed.js`,
`Components.js`, which make it easier to add methods to objects.
The `events` folder will contain the event handlers such as `messageCreate.js`, `debug.js`
Your many useful features and categories end up in the `Plugins` folder, where they should be categorically divided into
many folders.
The `Util` folder contains functions or classes that help you convert certain things, such as timestamps, into a
human-readable format.
## CommandHandler & BaseCommand
The `CommandHandler` is the main class of the bot, which will handle all the commands and the events received from
Discord.
The `BaseCommand` is the base class of all commands, which will be extended with the`CommandResponse` class.
### Steps showed in the following Guide
- Loading commands from different plugins
- Deploying slash commands
- Handling `messageCreate` & `interactionCreate` events
- Command rate limit handling
- Handle `Interaction` & `Message` commands with the same code
- Validating user provided arguments
- Correct permission and error handling
- Hot reloading commands
- Creating message and interaction collectors
## EventHandler
You probably realized that Discordeno does not use an `EventEmitter` to fire the events, but your own event function is
fired.
There are ways to adapt to an `EventEmitter`, but we decided against it for the following reasons:
- It's easy to create memory leaks, when you add too many listeners or go carelessly with it.
- Many fragmented parts of event code complicate maintenance.
- ErrorHandling is difficult and debugging is harder when many listeners are open for the same events.
## Structures
Structures are essential to abstract larger parts of code in smaller ready-made methods and to modify them if necessary.
Example:
```js
class Command {
static name = "ping";
static aliases = ["pong"];
static botPermission = ["SEND_EMBED_LINKS"];
run(message, args) {
// do something
}
}
```
It would be annoying adding everytime the `botPermission` property to the class Command, when the Permission is used
from every Command, then it is unnecessary to add it, when you can extend the class.
It would be annoying to add the `botPermission` property to the command class every time the same permissions are used
by each command. Extending the class makes this extra step obsolete.
```js
class BaseCommand {
constructor(client) {
this.client = client;
this.basePermission = ["SEND_EMBED_LINKS"];
}
}
class Command extends BaseCommand {
static name = "ping";
static aliases = ["pong"];
constructor(data) {
super(data);
}
run(message, args) {
// do something
}
}
```
## Plugins
The plugins folder helps you categorize your code into many parts to give some structure.
Of course, this has many advantages, you have a much clearer code, you can debug problems much easier.
This also opens possibilities for open source contributions, since not all parts of the code have to be published in
order to add new plugins, since they are "independent".
There will be the main `Plugins` folder, which by default contains a `General` folder for all your base commands. The
`Plugins` folder will also contain all your other plugins.
## Error Handling
One of the most important things is how to handle errors. This is done to provide a user-friendly experience and to find
errors faster.
You should catch errors and log them in your logger so you can fix them later. There are several open source `Sentry`'s
that give you a good overview of the latest errors through a website.
Sometimes errors have a positive effect on maintainability and scalability.
In addition, handling errors caused by users is very important to increase transparency. If they don't know why the
error happened, then they'll be very surprised with what they did wrong and might even remove your bot from their
server.
## Caching
Normally libraries cache all the info they get, which can of course be helpful at the beginning to discover all
functionalities but later it turns out to be a resource-consuming method. Therefore, this way should be avoided.
Discordeno allows `Custom Caching` and even `Custom Property Caching` which gives you fine-grained control over the
caching of data. Normally you only need 20% of the data received by Discord, which makes caching unnecessary in most
cases.
There are also some `Filter` and `Sweeper` methods which help you to empty unused cache values.
## Cross Communication & Scaling
If you are running many different processes, such as a Welcomer API, communication is of central importance in order to
send or receive data, with which you can then perform certain actions.
Cross communication can be easily done with sockets or a TCP client.
This brings up this Structure:
```js
Bridge (Heart)
- Machine 1
- Cluster [0-9]
- Machine 2
- Cluster [10-18]
- Machine 3 -> Welcomer Api
- Machine 4 -> Dashboard
```
It's important to use something fast to have a proper "real time" communication.
Discordeno already offers many internal options for scaling bots, no matter what size.
As you scale, you will likely separate many parts of your bot and put them in separate processes, such as a
`RestManager`, a `Gateway Manager` etc.
This of course opens up a lot of possibilities:
- Zero downtime updates
- Global cache
- Synced rate limits
[Check the Github Readme for more information](https://github.com/discordeno/discordeno#features)
:::tip congratulations
You just learned how to design a scalable bot, let's get into implementing it with the next pages.
:::
-48
View File
@@ -1,48 +0,0 @@
---
sidebar_position: 1
---
# Getting Started
If you are reading this, you probably want to create a Discord bot with Discordeno or migrate from popular libraries like Discord.js.
If this is going to be your first time making a bot, you should use Deno instead of Node.js. Although in some cases, Deno might not be suitable for you because of missing packages or a code base that is too large to migrate to a slightly different language.
This guide will help you make your first Discord bot using Node.js or even migrate your bot from another library.
Moreover this guide will utilize two different options. One option to use the Discordeno package without any frameworks
and one, which uses the wrapper called `Discordeno.js`, which aims to achieve a djs-like interface.
:::important Disclaimer
Some features are not documented yet. If you want to know more about them, kindly ask for help in the
[Discord Server](https://discord.gg/ddeno).
:::
## Why should I switch?
Discordeno was built with the purpose of being scalable, flexible and easy to use.
Libraries like `Discord.js` and `Eris` often have excessive caching behavior that can only be changed slightly without
breaking the entire library. There is a lack of customization and many nested classes, which makes it almost impossible
to edit the code without having unwanted side effects. Moreover scalability is only possible on a limited extend.
Discordeno has been kept plain and simple, which opens up a lot of opportunities for customization such as
`custom-caching (custom-property-caching)`, [`Standalone Rest`](../big-bot-guide/rest.md),
[`Gateway`](../big-bot-guide/gateway.md), [`Cache`](../big-bot-guide/cache.md) and more. Check the detailed advantages
[here](https://github.com/discordeno/discordeno#features).
This guide will also help you making your code more scalable and easier to maintain with bringing you closer to the
Discord API.
# Before you start
Before you start digging in this guide, you should have a solid understanding of `javascript`. If you are not familiar
with it, then you should take a look at some popular resources.
- [W3Schools Course](https://www.w3schools.com/js/DEFAULT.asp)
- [Mozilla Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- [JavaScript.Info](https://javascript.info)
A basic understanding is of great importance in order to solve problems skillfully.
-46
View File
@@ -1,46 +0,0 @@
---
sidebar_position: 4
---
# Initial Setup
## Config File
Ideally, you should save your configs in an `.env` file. Out of simplicity for this guide, we are saving it in a
`config.json` file.
Create a file named `config.json` in your project folder and insert the following content:
```json
{
"token": "YOUR_TOKEN_HERE",
"prefix": "!"
}
```
## Edit the main file
Open the `index.js` file which you have created earlier and then insert the following content:
```js
const Discord = require("discordeno");
const config = require("./config.json");
const client = Discord.createBot({
events: {
ready(client, payload) {
console.log(`Successfully connected Shard ${payload.shardId} to the gateway`);
},
},
intents: ["Guilds", "GuildMessages"],
token: config.token,
});
Discord.startBot(client);
```
Now you can start your bot by running the following command in your terminal:
```cli
$ node index.js
```
-30
View File
@@ -1,30 +0,0 @@
---
sidebar_position: 2
---
# Installing Node.js and Discordeno
To use the Discordeno library you first need to install Node.js and then Discordeno from NPM.
Go to [nodejs.org](https://nodejs.org/en/) and download the latest version of Node.js. Open the downloaded file and
follow the instructions of the installer to install Node.js.
## Create a Folder
Open your file manager and create a new folder (e.g.: `discordbot`) in your desired directory. Then open the code editor
of your choice and create a new file (e.g.: `index.js`) in the folder you just have created.
### Initalize NPM & Install Discordeno
In order to keep track of the dependencies, you need to initialize NPM, which generates a `package.json` file.
```cli
$ npm init --yes
```
Then you need to install Discordeno. When you want to go along with the wrapper named `Discordeno.js`, then install it
too. Go to your terminal and run the following command:
```cli
$ npm install discordeno
```
-64
View File
@@ -1,64 +0,0 @@
---
sidebar_position: 5
---
# Slash Commands
Since Discord has decided to make message content accessible only to privileged bots, message commands will play a
subordinate role in the future. Discord users will be more used to slash commands. That's why it's essential that every
bot offers them.
In the following we will show you how to create slash commands:
## Deploying Slash Commands
There is a difference between global and guild commands. Global commands take a while to appear in all guilds. Guild
commands show up directly.
For this reason, we will now show how to create guild commands, in order to test them immediately.
```js
const guildId = BigInt("YOUR_GUILD_ID");
const command = {
name: "ping",
description: "Retrieves the Bot latency",
options: [],
};
client.helpers.createApplicationCommand(command, guildId);
```
This is just very simple example, you can also add sub commands, select options and much more.
## Handling Slash Commands
Discord sends a WebSocket Event when a user runs a slash command. You can listen to this event by adding the
`interactionCreate` function in the client.
```js
const Discord = require("discordeno");
const config = require("./config.json");
const client = Discord.createBot({
events: {
ready(client, payload) {
console.log(`Successfully connected Shard ${payload.shardId} to the gateway`);
},
async interactionCreate(client, interaction) {
if (interaction.data?.name === "ping") {
return await client.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: Discord.InteractionResponseTypes.ChannelMessageWithSource,
data: { content: "🏓 Pong!" },
});
}
},
},
intents: Discord.Intents.Guilds | Discord.Intents.GuildMessages,
token: config.token,
});
Discord.startBot(client);
```
The handling may see complicated in the beginning, but as mentioned before, we will introduce structures to make it
easier.
-30
View File
@@ -1,30 +0,0 @@
/**
* 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
tutorial: [{ type: "autogenerated", dirName: "." }],
// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
};
module.exports = sidebars;