스킵 구현

This commit is contained in:
2023-12-20 19:45:25 +09:00
parent 6503fd167b
commit 8a987320e0
775 changed files with 162601 additions and 135 deletions

View File

@@ -0,0 +1,60 @@
\page automating-with-jenkins Automating Your Bot with Jenkins
\note This page does NOT go into explaining how to install Jenkins, nor how to initially setup Jenkins. This is a tutorial for the CMake version with Linux (more specifically Ubuntu 22.04 LTS). If you don't know how to use CMake or you don't use CMake for your bot (and would like to) then please visit \ref buildcmake. If you wish to automate this tutorial from GitHub pushes then you can simply download the GitHub plugin for Jenkins, set that up and this tutorial will still work as this tutorial will only build what it can see!
## Getting started
First of all, you'll want to create your project. For this, we'll use a "Freestyle project" as we're just going to be calling some bash commands to tell CMake to build. We'll be calling this "DiscordBot" but you can name it whatever you want. I would advise against non-ASCII characters.
\image html jenkinsproject.png
From here, just hit `Ok` and now you've created your Jenkins project, Well done! From here you can add a description, change the security policy (if your Jenkins is public), really whatever your heart desires.
## Automating the Building Process
Scrolling down, you'll find `Build Steps`. You can also click `Build Steps` on the left. Here, you'll want to hit `Add build step` and hit `Execute shell`.
\image html buildstepjenkins.png
Inside of this, you'll want to enter this script below.
~~~~~~~~~~bash
# Check if the "build" directory doesn't exist (if you've not setup CMake or deleted its content).
if [ ! -d "build/" ]
then # As it doesn't, create the build directory.
cmake -B build
fi
# Commence build.
cmake --build build/
~~~~~~~~~~
\note This script does not make use of multiple threads. If you know how to do this and you would like to use threads, then feel free to. However, I would be careful not to use all your threads as Jenkins may dislike this.
This script will build your project for you and also set up CMake if you've deleted the build directory, allowing you to easily refresh CMake. You can change this to a build parameter if you want, meaning you can hit `Build with Parameters` and state what you'd like to do. This would require the script to be changed so only do that if you know what you're doing!
\image html shelljenkins.png
Now you can hit save!
## Seeing the Builds Work
Making sure you have your project files in the workspace directory (you can see this by pressing `Workspace` on the left, the files will automatically be pulled from GitHub if you're using the GitHub plugin), you should be able to hit `Build Now` and see a new build in the History appear. If everything went well, you should have a green tick!
\note Building can take a while if you haven't set up your build directory before (doing `cmake ..`), especially on less-powerful machines, so don't be alarmed!
\image html buildhistoryjenkins.png
## Running the Build
Running the builds is the same as any other time, but we'll still cover it! However, we won't cover running it in background and whatnot, that part is completely down to you.
First, you need to get into the `jenkins` user. If you're like me and don't have the Jenkins user password, you can login with your normal login (that has sudo perms) and do `sudo su - jenkins`. Once logged in, you'll be in `/var/lib/jenkins/`. From here, you'll want to do `cd workspace/DiscordBot/` (make sure to replace "DiscordBot" with your bot's name. Remember, you can tab-complete this) and then `cd build`. Now, you can simply do `./DiscordBot`!
\warning If you are going to be running the bot at the same time as builds, I would heavily advise that you copy the bot (if it's not statically linked, then copy the entire build directory) to a different location. This is so you can pick and choose when the bot gets updated (and even means you can run experimental builds as opposed to stable builds) but also means you avoid any risk of the bot crashing during build (as Jenkins will be overwriting your executable and libraries).
Once you're happy with everything, then you're good to go! Enjoy your automated builds!
## Possible Permission Issues
Sometimes, doing `./DiscordBot` can end up with an error, saying you don't have permission to execute. If that's the case, simply do `chmod +x DiscordBot` and now you can re-run `./DiscordBot`.

View File

@@ -0,0 +1,178 @@
\page clusters-shards-guilds Clusters, Shards, and Guilds
D++ takes a three-tiered highly scalable approach to bots, with three levels known as Clusters, Shards, and Guilds as documented below.
\dot
digraph "Clusters, Shards and Guilds" {
node [colorscheme="blues9",fontname="helvetica"];
subgraph Bot {
node [style=filled, color=1];
label = "Bot"
"Bot" [shape=folder, label="Bot", bordercolor=black];
};
subgraph Processes {
node [style=filled, color=2];
label = "Processes"
"Bot" -> "Process 1"
"Bot" -> "Process 2"
"Process 1" [shape=record, label="Process"];
"Process 2" [shape=record, label="Process"];
};
subgraph Clusters {
node [style=filled, color=3];
label = "Clusters"
"Process 1" -> "Cluster 1"
"Process 2" -> "Cluster 2"
"Cluster 1" [shape=record, label="Cluster"];
"Cluster 2" [shape=record, label="Cluster"];
};
subgraph Shards {
node [style=filled, color=4];
label = "Shards"
"Shard 1" [shape=record, label="Shard"];
"Shard 2" [shape=record, label="Shard"];
"Shard 3" [shape=record, label="Shard"];
"Shard 4" [shape=record, label="Shard"];
"Shard 5" [shape=record, label="Shard"];
"Shard 6" [shape=record, label="Shard"];
"Shard 7" [shape=record, label="Shard"];
"Shard 8" [shape=record, label="Shard"];
"Cluster 1" -> "Shard 1"
"Cluster 1" -> "Shard 3"
"Cluster 2" -> "Shard 2"
"Cluster 2" -> "Shard 4"
"Cluster 1" -> "Shard 5"
"Cluster 1" -> "Shard 7"
"Cluster 2" -> "Shard 6"
"Cluster 2" -> "Shard 8"
};
subgraph Guilds {
node [style=filled, color=5];
label = "Guilds";
"Guild 1" [shape=record, label="Guild"];
"Guild 2" [shape=record, label="Guild"];
"Guild 3" [shape=record, label="Guild"];
"Guild 4" [shape=record, label="Guild"];
"Guild 5" [shape=record, label="Guild"];
"Guild 6" [shape=record, label="Guild"];
"Guild 7" [shape=record, label="Guild"];
"Guild 8" [shape=record, label="Guild"];
"Guild 9" [shape=record, label="Guild"];
"Guild 10" [shape=record, label="Guild"];
"Guild 11" [shape=record, label="Guild"];
"Guild 12" [shape=record, label="Guild"];
"Guild 13" [shape=record, label="Guild"];
"Guild 14" [shape=record, label="Guild"];
"Guild 15" [shape=record, label="Guild"];
"Guild 16" [shape=record, label="Guild"];
"Shard 1" -> "Guild 1"
"Shard 1" -> "Guild 5"
"Shard 2" -> "Guild 2"
"Shard 2" -> "Guild 6"
"Shard 3" -> "Guild 3"
"Shard 3" -> "Guild 7"
"Shard 4" -> "Guild 4"
"Shard 4" -> "Guild 8"
"Shard 5" -> "Guild 9"
"Shard 5" -> "Guild 11"
"Shard 6" -> "Guild 10"
"Shard 6" -> "Guild 12"
"Shard 7" -> "Guild 13"
"Shard 7" -> "Guild 15"
"Shard 8" -> "Guild 14"
"Shard 8" -> "Guild 16"
};
}
\enddot
## Clusters
A bot may be made of one or more clusters. Each cluster maintains a queue of commands waiting to be sent to Discord, a queue of replies from Discord for all commands executed, and zero or more **shards**. Usually, each process has one cluster, but the D++ library does not enforce this as a restriction. Small bots will require just one cluster. Clusters will split the required number of shards equally across themselves. There is no communication between clusters unless you add some yourself, they all remain independent without any central "controller" process. This ensures that there is no single point of failure in the design. Whenever you instantiate the library, you generally instantiate a cluster:
```cpp
#include <dpp/dpp.h>
int main()
{
/* This is a cluster */
dpp::cluster bot("Token goes here");
}
```
## Shards
A cluster contains zero or more shards. Each shard maintains a persistent connection to Discord via a websocket, which receives all events the bot is made aware of, e.g. messages, channel edits, etc. Requests to the API on the other hand go out to Discord as separate HTTP requests.
Small bots will require only one shard and this is the default when you instantiate a cluster. The library will automatically determine and create the correct number of shards needed, if you do not configure it by hand. If you do want to specify a number of shards, you can specify this when creating a cluster:
```cpp
#include <dpp/dpp.h>
int main()
{
/* This is a cluster */
int total_shards = 10;
dpp::cluster bot("Token goes here", dpp::i_default_intents, total_shards);
}
```
Remember that if there are multiple clusters, the number of shards you request will be split equally across these clusters!
@note To spawn multiple clusters, you can specify this as the 4th and 5th parameter of the dpp::cluster constructor. You must do this, if you want this functionality. The library will not create additional clusters for you, as what you require is dependent upon your system specifications. It is your responsibility to somehow get the cluster id and total clusters into the process, e.g. via a command line argument. An example of this is shown below based on the cluster setup code of **TriviaBot**:
```cpp
#include <dpp/dpp.h>
#include <iostream>
#include <stdlib.h>
#include <getopt.h>
#include <string>
int main(int argc, char** argv)
{
int total_shards = 64;
int index;
char arg;
bool clusters_defined = false;
uint32_t clusterid = 0;
uint32_t maxclusters = 1;
/* Parse command line parameters using getopt() */
struct option longopts[] =
{
{ "clusterid", required_argument, NULL, 'c' },
{ "maxclusters", required_argument, NULL, 'm' },
{ 0, 0, 0, 0 }
};
opterr = 0;
while ((arg = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
switch (arg) {
case 'c':
clusterid = std::stoul(optarg);
clusters_defined = true;
break;
case 'm':
maxclusters = std::stoul(optarg);
break;
default:
std::cerr << "Unknown parameter '" << argv[optind - 1] << "'\n";
exit(1);
break;
}
}
if (clusters_defined && maxclusters == 0) {
std::cerr << "ERROR: You have defined a cluster id with -clusterid but no cluster count with -maxclusters.\n";
exit(2);
}
dpp::cluster bot("Token goes here", dpp::default_intents, total_shards, clusterid, maxclusters);
}
```
### Large Bot Sharding
Discord restricts how many shards you can connect to at any one time to one per five seconds, unless your bot is in at least 150,000 guilds. Once you reach 150,000 guilds, Discord allow your bot to connect to more guilds concurrently, and your number of shards must divide cleanly into this value. By default, at 150,000 guilds this concurrency value is 16 meaning D++ will attempt to connect 16 shards in parallel, then wait for all these to connect and then connect another 16, until all shards are connected. In practice, this means a large bot with many shards (read: hundreds!) will connect significantly faster after a full restart. **You do not need to manually configure large bot sharding and connection concurrency, the D++ library will handle this for you if you are able to use it**.
## Guilds
Guilds are what servers are known as to the Discord API. There can be up to **2500** of these per shard. Once you reach 2500 guilds on your bot, Discord force your bot to shard, the D++ library will automatically create additional shards to accommodate if not explicitly configured with a larger number. Discord *does not restrict sharding* to bots on 2500 guilds or above. You can shard at any size of bot, although it would be a waste of resources to do so unless it is required.

View File

@@ -0,0 +1,161 @@
\page coding-standards Coding Style Standards
This page lists the coding style we stick to when maintaining the D++ library. If you are submitting a pull request or other code contribution to the library, you should stick to the styles listed below. If something is not covered here, ask on the [official Discord server](https://discord.gg/dpp)!
## Class Names, Function Names, and Method Names
All class, variable/member, function and method names should use `snake_case`, similar to the style of the C++ standard library.
## Enums
Enums and their values should be `snake_case` as with class, function and method names. You do not need to use `enum class`, so make sure that enum values are prefixed with a prefix to make them unique and grouped within the IDE, e.g. `ll_debug`, `ll_trace`, etc.
## Curly Braces, Brackets, etc.
This covers your standard Curly Braces (commonly known as squiggly brackets), and lists.
### Curly Braces
Curly Braces should be on the same line as the keyword, for example:
\include{cpp} coding_style_standards/curly_braces.cpp
This applies to functions, `while` statements, `if` statements, lambdas, nearly anything that uses curly braces with statements!
### Lists
Lists should have a space after the comma in parameter lists, and after opening brackets and before closing brackets except when calling a function, for example:
\include{cpp} coding_style_standards/lists.cpp
## Dot (.) Notation
When using the dot notation repeatedly (For example, creating an embed.), you should start each `.function()` on a new line, as such:
\include{cpp} coding_style_standards/dot_notation.cpp
## Indentation
Indentation should always be tab characters. It is up to you how wide you set tab characters in your editor for your personal tastes. All code blocks delimited within curly braces should be indented neatly and uniformly.
## Constants and \#define Macros
Constants and macros should be all `UPPERCASE` with `SNAKE_CASE` to separate words. Macros should not have any unexpected side effects.
## Comments
All comments should be in `doxygen` format (similar to javadoc). Please see existing class definitions for an example. You should use doxygen style comments in a class definition inside a header file. Be liberal with comments, especially if your code makes any assumptions! Comments should follow the format below:
\note Comments that contain doxygen stuff need to use two stars at the beginning (/**). This example doesn't because doxygen gets confused and doesn't show the comments.
\include{cpp} coding_style_standards/comments.cpp
## Spell Checks
To prevent typos, a GitHub-Action checks the documentation. If it fails for a word that was falsely flagged, you can add them to `.cspell.json`.
## Symbol Exporting
If you export a class which is to be accessible to users, be sure to prefix it with the `DPP_EXPORT` macro, for example:
\include{cpp} coding_style_standards/symbol_exporting.cpp
The `DPP_EXPORT` macro ensures that on certain platforms (notably Windows) the symbol is exported to be available to the library user.
## Public vs Private vs Protected
It is a design philosophy of D++ that everything possible in a class should be public, unless the user really does not need it (you should consider justifying in comments why) or user adjustment of the variable could badly break the functioning of the library. Avoid the use of accessors for setting/getting values in a class, except for bit fields, where you should provide accessors for setting and getting individual bits (for example, see `user.h`), or in the event you want to provide a "fluent" interface. The exception to this is where you want to provide a logic validation of a field, for example if you have a string field with a minimum and maximum length, you can provide a setter the user can *optionally use* which will validate their input.
## Exceptions
All exceptions thrown should derive from dpp::exception (see `dpp/exception.h`) - when validating string lengths, a string which is too long should be truncated using dpp::utility::utf8substr and any strings that are too short should throw a dpp::length_exception.
## Inheritance
Keep levels of inheritance low. If you need to inherit more than 3 levels deep, it is probable that the design could be simplified. Remember that at scale, there can be tens of millions of certain classes and each level of virtual nesting adds to the `vtable` of that object's instance in RAM.
## Bit Field Packing
Where Discord provides boolean flags, if the user is expected to store many of the objects in RAM, or in cache, you should pack all these booleans into bit fields (see `user.h` and `channel.h` for examples). In the event that the object is transient, such as an interaction or a message, packing the data into bit fields is counter intuitive. Remember that you should provide specific accessors for bit field values!
## Keep Dependencies Internal!
Where you are making use of an external dependency such as `opus` or `libssl`, do not place references to the types/structs, or the header files of these external libraries within the header files of D++. Doing so adds that library as a public dependency to the project (which is bad!). Instead make an opaque class, and/or forward-declare the structs (for examples see `sslclient.h` and `discordvoiceclient.h`).
## API Type Names
Where Discord provides a name in PascalCase we should stick as closely to that name as possible but convert it to `snake_case`. For example, GuildMember would become `guild_member`.
## Don't Introduce Any Platform-Specific Code
Do not introduce platform specific (e.g. Windows only) code or libc functions. If you really must use these functions safely wrap them e.g. in `#ifdef _WIN32` and provide a cross-platform alternative so that it works for everyone.
## C++ Version
The code must work with the C++17 standard, unless, for an optional feature that can be enabled and that uses a more recent standard (e.g. Coroutines).
## Select the Right Size Type for Numeric Types
If a value will only hold values up to 255, use `uint8_t`. If a value cannot hold over 65536, use `uint16_t`. These types can help use a lot less RAM at scale.
## Fluent Design
Where possible, if you are adding methods to a class you should consider fluent design. Fluent design is the use of class methods that return a reference to self (via `return *this`), so that you can chain object method calls together (in the way dpp::message and dpp::embed do). For example:
\include{cpp} coding_style_standards/fluent_design.cpp
This would allow the user to do this:
\include{cpp} coding_style_standards/fluent_design2.cpp
## Keep All D++ Related Types in the dpp Namespace
All types for the library should be within the `dpp` namespace. There are a couple of additional namespaces, e.g. dpp::utility for static standalone helper functions and helper classes, and dpp::events for internal websocket event handlers.
## Commit Messages and Git
All pull requests ("PRs") should be submitted against the `dev` branch in GitHub.
### Naming Conventions
Its good to have descriptive commit messages, or PR titles so that other contributors can understand about your commit or the PR Created. Commits must be prefixed with a type, which consists of a noun, `feat`, `fix`, etc., followed by a colon and a space. Other commit types can be `breaking`, `docs`, `refactor`, `deprecate`, `perf`, `test`, `chore`, and `misc`. Read [conventional commits](https://www.conventionalcommits.org/en/v1.0.0-beta.3/) for more information on how we like to format commit messages.
### GitHub Actions
All PRs must pass the [GitHub Actions](https://github.com/brainboxdotcc/DPP/actions) tests before being allowed to be merged. This is to ensure that no code committed into the project fails to compile on any of our officially supported platforms or architectures.
### Developer Certificate of Origin
All code contributed to D++ must be submitted under agreement of the Linux Foundation Developer Certificate of Origin. This is a simple agreement which protects you and us from any potential legal issues:
```
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```

View File

@@ -0,0 +1,86 @@
\page docs-standards Documentation Style Standards
This page lists the documentation standards that you must follow when making PRs for files in `docpages`, this does not mean documentation for functions/variables inside actual code. If something is not covered here, ask on the [official Discord server](https://discord.gg/dpp)!
Please also follow the \ref coding-standards for your PRs related to docs. This covers any code stuff that you may end up writing along with other stuff (for example, conventional commits).
## Adding Pages
All pages should start off with `\ page <page_name> <page title>`, with the space between `\` and `page` removed.
Adding a new page requires you to also add the page as a subpage to a section. For example, if you're creating a new page under "The Basics", you should edit "the_basics.md" inside "example_programs" and add your page as a subpage.
All subpages should be added like so: `* \ subpage <page_name>`, with the space between `\` and `subpage` removed.
## Page and File Names
The naming convention for page and file names are `kebab-case`, so you should name pages and files something like `name-of-page`, with `.md` added to the end of the file name.
However, code files should be `snake_case` to follow the \ref coding-standards. Images follow the same format.
\note There are pages that do not follow this format, **please do not change them**. This is explained in the **Renaming Files** section.
## Page Titles
Page titles shouldn't be mistaken with page names! Page titles are the, sometimes, long-ish titles you see in the navbar on the left of the docs page or at the top of each page when you view it in a web browser. Try not to make these too long (80 chars is the max limit but reach for less) as it's a bit daunting seeing a long title!
## Renaming Files
Renaming `.md` files is something that shouldn't really be done unless you have a very good reason. Changing it harms our SEO, meaning google will get a bit confused.
Changing images in the `images` folder or files inside the `example_code` folder is more acceptable, however, still shouldn't be done without reason.
## Referencing Other Pages
Pages located within our file structure should be referenced like `\ ref <page_name>` (this will insert a reference with the actual page title) or `\ ref <page_name> "text here"` (instead of the page title, it'll be whatever is in quotation marks), with the space between `\` and `ref` removed. Pages outside of our file structure (like a discord invite link, or a github page, etc) should be referenced like `[text](url)`.
## Adding Code to Examples
All code needs to be a `.cpp` file in the `example_code` folder. You then reference it via `\ include{cpp} file.cpp`, with the space between `\` and `include{cpp}` removed.
Any code that will **not** build, for example:
```cpp
bot.start(dpp::st_wait);
/* This code will not build as it has no entry (int main), which will cause CI fails. */
```
should be placed in the file itself. This is so we do not have to worry about the CI testing your example when we know it will not work.
Your examples **need** to be tested. For more information about this, visit the **Testing Your Changes** section.
## Language (Not Programming)
Your text and images should be in English.
## Images
Images are a great way to show people how their bot should act when they're finished with the tutorial! A lot of tutorials don't have them, however, we recommend that you add some as, not only does it tell us that you've tested your example, but it helps users know if they've went wrong! We ask that you don't add images that are inappropriate and only add them if they make sense. Cut them down to a size that is readable and make sure it only contains information that is needed, for example, we don't need to see your server's channels list if the tutorial isn't covering it!
Take a look at some tutorial pages (for example, \ref embed-message and \ref callback-functions) to understand what we mean.
All your images should be placed in the `images` folder and should be named similar to your page's name. If you have multiple images, you can either do something like `image_name2`, `image_name3`, and so on. You can also give them unique names like `image_name_overview`, `image_name_end`, and so on.
## Grammar and Spelling
Your spelling and grammar matters a lot, it's essential that you get it right. We do have a GitHub Action runner that may flag for incorrect spellings if you accidentally spell something incorrectly (We're all human!), so keep an eye out for that. If this falsely flags you, read the \ref coding-standards page for more information about how to tell it to ignore a word.
## Consistency
Try to match other pages if you are making a similar guide (for example, the building from source pages). If the pages are inconsistent, they will start to confuse people. This also goes for pages that may not even be similar! For example, you should try keep your code examples similar to others, so that people don't get confused and can easily follow along.
## Being Simplistic
As you'll be writing tutorials, try be simplistic! Don't overcomplicate pages and don't make assumptions that, just because you know it, everyone else will, because some people may not know what you're talking about! An easy, yet informational, tutorial is key to ensuring that D++ has great documentation for newcomers.
## AI and Desktop Utilities
We are strongly against the use of AI within any part of code and docs. AI can be unreliable and may give the wrong information, so please look to research and test what you're documenting, without the use of AI. If we feel your entire PR was wrote by AI, we will close the PR.
We also advise you do not use grammarly or other tools that fix your grammar/spelling for you. They can mess with text, get it wrong, not understand the context, etc. Whilst you can certainly use spell checkers and/or tools to see if there's issues with your text, you should look over each issue yourself to make sure if it matches the context and the issue is actually correct.
## Testing Your Changes
To test your commits, you should install `doxygen` and `graphviz`. Then, inside your DPP folder, run `doxygen`. This will start generating a bunch of files inside a `docs` folder and may take a while. After that, view the .md file in your web browser.
If this seems to fail, you can make your PR draft and use the netlify bot that replies with a link (the link may take a bit to generate) to preview your changes.
If you are adding code with your commits, you **need** to test that it actually works with a bot. Our CI will test compile it but will not run it, we expect that you test it on your system and ensure you are giving the correct information to users.

View File

@@ -0,0 +1,19 @@
\page governance Governance and Project Development Structure
The D++ Project was originally created by Craig Edwards, A.K.A. @brain on Discord.
## Governance
@brain steers the project, but in effect the project is entirely built upon the contributions of others via pull requests and feedback. For most decisions and most changes, control is ceded to whoever is creating a feature or change, with oversight given to make sure only that the code is stable and scalable and does not operate in a way counter-intuitive to the design of the library.
## Project Maintainers
Other maintainers with access to merge pull requests (those with the `@PR Review` role in the Discord server) have access to and responsibility for checking pull requests sent in by contributors and may request additional changes to keep the pulls aligned with the project goals. These members of the D++ team may and do also merge pull requests at their discretion.
## Decision Making
For most decisions, these are discussed in our `#library-development` channel on Discord. This channel is public for all to view but only contributors may comment. This helps keep the chat clean of discussion that may derail development topics.
## Contingency
*In the case of any unforeseen disaster such as death of the project leader, control over domain (the only part of the project which has a direct cost attached) would pass to his next of kin who would arrange for transfer to a pre-arranged trusted third party who would administer the domain going forward. Everything else relating to D++ is hosted on GitHub and would continue as normal.*

View File

@@ -0,0 +1,56 @@
\page lambdas-and-locals Ownership of Local Variables and Safely Transferring into a Lambda
If you are reading this page, you have likely been sent here by someone helping you diagnose why your bot is crashing or why seemingly invalid values are being passed into lambdas within your program that uses D++.
It is important to remember that when you put a lambda callback onto a function in D++, that this lambda will execute at some point in the **future**. As with all things in the future and as 80s Sci-Fi movies will tell you, when you reach the future, things may well have changed!
\image html delorean-time-travel.gif
To explain this situation and how it causes issues, I'd like you to imagine the age-old magic trick, where a magician sets a fine table full of cutlery, pots, pans and wine. He indicates to the audience that this is authentic, then with a whip of his wrist, he whips the tablecloth away, leaving the cutlery and other tableware in place (if he is any good as a magician!)
Now imagine the following code scenario. We will describe this code scenario as the magic trick above, in the steps below:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~cpp
bot.on_message_create([&bot](const dpp::message_create_t & event) {
int myvar = 0;
bot.message_create(dpp::message(event.msg.channel_id, "foobar"), [&](const auto & cc) {
myvar = 42;
});
});
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this scenario, the outer event, `on_message_create` is your tablecloth. The lambda inside the `bot.message_create` is the tableware and cutlery. The following chain of events happens in this code:
* The magician executes his magic trick (D++ the `bot.on_message_create entering` the outer lambda)
* Your code executes `bot.message_create()` inside this outer lambda
* D++ inserts your request to send a message into its queue, in another thread. The inner lambda, where you might later set `myvar = 42` is safely copied into the queue for later calling.
* The tablecloth is whipped away... in other words, `bot.on_message_create` ends, and all local variables including `myvar` become invalid
* At a later time (usually 80ms through to anything up to 4 seconds depending on rate limits!) the message is sent, and your inner lambda which was saved at the earlier step is called.
* Your inner lambda attempts to set `myvar` to 42... but `myvar` no longer exists, as the outer lambda has been destroyed...
* The table wobbles... the cutlery shakes... and...
* Best case scenario: you access invalid RAM no longer owned by your program by trying to write to `myvar`, and [your bot outright crashes horribly](https://www.youtube.com/watch?v=sm8qb2kP-fQ)!
* Worse case scenario: you silently corrupt ram and end up spending days trying to track down a bug that subtly breaks your bot...
The situation I am trying to describe here is one of object and variable ownership. When you call a lambda, **always assume that every non-global reference outside of that lambda will be invalid when the lambda is called**! For any non-global variable, always take a **copy** of the variable (not reference, or pointer). Global variables or those declared directly in `main()` are safe to pass as references.
For example, if we were to fix the broken code above, we could rewrite it like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~cpp
bot.on_message_create([&bot](const dpp::message_create_t & event) {
int myvar = 0;
bot.message_create(dpp::message(event.msg.channel_id, "foobar"), [myvar](const auto & cc) {
myvar = 42;
});
std::cout << "here\n";
});
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note, however that when you set `myvar` within the inner lambda, this does **not affect** the value of `myvar` outside it. Lambdas should be considered self-contained silos, and as they execute in other threads should not be relied upon to set anything that exists **outside of that lambda**.
\warning Always avoid just using `[&]` in a lambda to access all in the scope above. It is unlikely that half of this scope will still even be valid by the time you get a look at it!
Similarly, and important to note, your program **will not wait for bot.message_create to send its message and call its lambda** before continuing on to print `here`. It will instantly insert the request into its queue and bail straight back out (see the steps above) and immediately print the text.
If you do want to get variables out of your lambda, create a class, or call a separate function, and pass what you need into that function from the lambda **by value** or alternatively, you can use `std::bind` to bind a lambda directly to an object's method instead (this is great for modular bots).
If you are stuck, as this is a complex subject, please do feel free to ask on the [official support server](https://discord.gg/dpp)!

View File

@@ -0,0 +1,7 @@
\page roadmap Development Roadmap
At present our roadmap is:
*Short term (6 months):*: Stabilise coroutine support and release it as stable a feature
*Long term*: Continue development of the library to implement Discord new features as they add them. Discord does not share their internal roadmap with library developers, so we are informed of these new features shortly before they become public given enough time to implement them. This is our permanent ongoing goal.

View File

@@ -0,0 +1,10 @@
\page security Project Security Design
D++ is designed with the following security goals in mind:
* D++ design will be user friendly to help avoid shooting yourself in the foot and introducing security vulnerabilities in the code.
* D++ will keep external dependencies to an absolute minimum at all times so there is less chance of third party code making your bot vulnerable to attack.
* D++ design will take the path of 'least surprise', and will be simple and straightforward to use, leading to less developer errors that could lead to vulnerabilities.
* Any reported CVEs which are logged via the proper channels will be fixed within 14 days.
* All settings, configuration, and parameters will be secure by default.
* D++ settings and design will conform to Discord ToS and will not implement or support features that break the Discord ToS.

View File

@@ -0,0 +1,77 @@
\page separate-events Separating Events into New Classes
If you're someone that loves file organisation (or you hate how cluttered your `main.cpp` has become) then you may be interested in moving events into separate classes outside of the `main.cpp` file. This is a great way to improve readability and can be helpful in many cases! For example, you can have two classes on the same event, except one could be reading messages for spam and one could be reading messages for bad words!
In this tutorial, we'll be taking the \ref detecting-messages "Listening to messages" example and moving the `on_message_create` event into a different class.
To get started, you can create a folder called `listeners` inside `src` (where your `main.cpp` is) if you'd like to put it there! We'll be doing exactly that so, if you'd like to stick along with the tutorial, get creating that folder!
Now, you can create a new header and cpp file in this folder! For this tutorial, we'll be naming both these files `message_listener`!
If you're using CMake, you'll need to add this to your `CMakeLists.txt`. Some IDEs automatically do this but it's always worth double-checking!
Once that's done, it should look similar to this (this screenshot has more files in, so it won't be identical!):
\image html file_example_listeners.png
First, we need to define the function that will be called when the event fires. We do this in the `message_listener.h`, like so:
~~~~~~~~~~cpp
#pragma once
#include <dpp/dpp.h>
class message_listener {
public:
/* Create a static function that can be called anywhere. */
static void on_message_create(const dpp::message_create_t& event);
};
~~~~~~~~~~
Then we need to add our code for what should happen when this event fires. We do this in the `message_listener.cpp`, like so:
~~~~~~~~~~cpp
#include "message_listener.h"
void message_listener::on_message_create(const dpp::message_create_t &event) {
/* See if the message contains the phrase we want to check for.
* If there's at least a single match, we reply and say it's not allowed.
*/
if (event.msg.content.find("bad word") != std::string::npos) {
event.reply("That is not allowed here. Please, mind your language!", true);
}
}
~~~~~~~~~~
Now, you'll have a nice area where you can easily see the code, without scrolling through all of your `main.cpp` file just to get to this event!
However, we've not finished yet! If you thought "How does the `main.cpp` file actually know to call this?" then, 10 points to you! It doesn't know! We need to go do that now. So, let's do exactly that.
~~~~~~~~~~cpp
#include <dpp/dpp.h>
#include "listeners/message_listener.h"
int main() {
/* Create the bot, but with our intents so we can use messages. */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Fires our event that is located in MessageListener
* when the bot detects a message in any server and any channel it has access to.
*/
bot.on_message_create(&message_listener::on_message_create);
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~
And there we go! How tidy is that?
Now, the possibilities to this are not limited. If you wish to do this twice (as I explained at first), you can simply have another class and just copy the `bot.on_message_create` line below in the `main.cpp` file and then you can change it to reference the second class, meaning you have two message events firing in two separate classes!

View File

@@ -0,0 +1,81 @@
\page thread-model Thread Model
\dot
digraph "Thread Model" {
graph [ranksep=1];
node [colorscheme="blues9",fontname="helvetica"];
"Discord Events" -> "Your Program"
"Your Program" [style=filled, color=1, shape=rect]
"Cluster" [style=filled, color=1, shape=rect]
subgraph cluster_4 {
style=filled;
color=lightgrey;
node [style=filled,color=2]
"Your Program"
"Cluster"
label = "User Code";
}
subgraph cluster_0 {
style=filled;
color=lightgrey;
node [style=filled,color=4]
"Shard 1" [style=filled, color=4]
"Shard 2"
"Shard 3..."
label = "Shards (Each is a thread, one per 2500 Discord guilds)";
}
subgraph cluster_1 {
style=filled
color=lightgrey;
node [style=filled,color=4]
"REST Requests"
"Request In Queue 1"
"Request In Queue 2"
"Request In Queue 3..."
"Request Out Queue"
label = "REST Requests (Each in queue, and the out queue, are threads)"
}
subgraph cluster_3 {
style=filled
color=lightgrey;
node [style=filled,color=4]
"Discord Events" [style=filled,color=4]
"User Callback Functions"
label = "Events and Callbacks"
}
"Cluster" [shape=rect]
"REST Requests" [shape=rect]
"Request In Queue 1" [shape=rect]
"Request In Queue 2" [shape=rect]
"Request In Queue 3..." [shape=rect]
"Shard 1" [shape=rect]
"Shard 2" [shape=rect]
"Shard 3..." [shape=rect]
"Request Out Queue" [shape=rect]
"Discord Events" [shape=rect]
"User Callback Functions" [shape=rect]
"Cluster" -> "REST Requests"
"Shard 1" -> "Discord Events"
"Shard 2" -> "Discord Events"
"Shard 3..." -> "Discord Events"
"Your Program" -> "Cluster"
"Cluster" -> "Shard 1"
"Cluster" -> "Shard 2"
"Cluster" -> "Shard 3..."
"REST Requests" -> "Request In Queue 1"
"REST Requests" -> "Request In Queue 2"
"REST Requests" -> "Request In Queue 3..."
"Request In Queue 1" -> "Request Out Queue"
"Request In Queue 2" -> "Request Out Queue"
"Request In Queue 3..." -> "Request Out Queue"
"Request Out Queue" -> "User Callback Functions"
"User Callback Functions" -> "Your Program"
}
\enddot

View File

@@ -0,0 +1,29 @@
\page unit-tests Unit Tests
## Running Unit Tests
If you are adding functionality to D++, make sure to run unit tests. This makes sure that the changes do not break anything. All pull requests must pass all unit tests before merging.
Before running test cases, create a test server for your test bot. You should:
* Make sure that the server only has you and your test bot, and no one else
* Give your bot the administrator permission
* Enable community for the server
* Make an event
* Create at least one voice channel
* Create at least one text channel
Then, set the following variables to the appropriate values. (Below is a fake token, don't bother trying to use it)
```bash
export DPP_UNIT_TEST_TOKEN="ODI2ZSQ4CFYyMzgxUzkzzACy.HPL5PA.9qKR4uh8po63-pjYVrPAvQQO4ln"
export TEST_GUILD_ID="907951970017480704"
export TEST_TEXT_CHANNEL_ID="907951970017480707"
export TEST_VC_ID="907951970017480708"
export TEST_USER_ID="826535422381391913"
export TEST_EVENT_ID="909928577951203360"
```
Then, after cloning and building DPP, run `cd build && ctest -VV` for unit test cases.
If you do not specify the `DPP_UNIT_TEST_TOKEN` environment variable, a subset of the tests will run which do not require discord connectivity.

View File

@@ -0,0 +1,124 @@
\page voice-model Voice Model
\dot
digraph "Example Directory" {
graph [ranksep=1];
node [colorscheme="blues9", fontname="helvetica"];
"Your bot" [style=filled, color=1, shape=rect]
"Discord" [style=filled, color=1, shape=rect]
subgraph cluster_0 {
style=filled;
color=lightgrey;
node [style=filled, color=3, shape=rect]
"guild::connect_member_voice";
"discord_client::connect_voice";
"guild::connect_member_voice" -> "discord_client::connect_voice";
label = "This is the front-end of D++.\n'connect_voice' will now queue a JSON message.";
}
subgraph cluster_1 {
style=filled;
color=lightgrey;
node [style=filled, color=2, shape=rect]
"message_queue";
label = "This holds all our messages.\n'one_second_timer' reads this data";
}
subgraph cluster_2 {
style=filled;
color=lightgrey;
node [style=filled, color=3, shape=rect]
"discord_client::one_second_timer";
"websocket_client::write";
"ssl_client::write";
"discord_client::one_second_timer" -> "websocket_client::write";
"websocket_client::write" -> "ssl_client::write";
"ssl_client::write" -> "Discord";
label = "This is where we start sending\nwebsocket connections to Discord.";
}
subgraph cluster_3 {
style=filled;
color=lightgrey;
node [style=filled, color=3, shape=rect]
"ssl_client::read_loop";
"Response from Discord?";
"No";
"HTTP/1.1 204 No Content...";
"HTTP/1.1 101 Switching Protocols";
"ssl_client::read_loop" -> "Response from Discord?";
"Response from Discord?" -> "No";
"Response from Discord?" -> "HTTP/1.1 204 No Content...";
"Response from Discord?" -> "HTTP/1.1 101 Switching Protocols";
"No" -> "ssl_client::read_loop";
"Discord" -> "HTTP/1.1 204 No Content...";
"Discord" -> "HTTP/1.1 101 Switching Protocols";
label = "Now, we're waiting for a response from Discord.\nIf we receive 204, we'll start initiating voiceconn. However, if we receive 101, then we can do all the voice stuff.";
}
subgraph cluster_4 {
style=filled;
color=lightgrey;
node [style=filled, color=3, shape=rect]
"voice_state_update::handle";
"voice_server_update::handle";
"HTTP/1.1 204 No Content..." -> "voice_state_update::handle";
"HTTP/1.1 204 No Content..." -> "voice_server_update::handle";
label = "These events can fire in any order. Discord picks whatever it likes.";
}
subgraph cluster_5 {
style=filled;
color=lightgrey;
node [style=filled, color=3, shape=rect]
"voiceconn::connect";
"new discord_voice_client"
"websocket_client::connect"
"discord_voice_client::run"
"discord_voice_client::thread_run"
"voiceconn::connect" -> "new discord_voice_client";
"new discord_voice_client" -> "websocket_client::connect";
"websocket_client::connect" -> "websocket_client::write";
"voiceconn::connect" -> "discord_voice_client::run" [label="Once websocket_client has finished"];
"discord_voice_client::run" -> "discord_voice_client::thread_run";
"discord_voice_client::thread_run" -> "ssl_client::read_loop";
label = "Voice initalisation.\nThis will only fire when 'voice_server_update' AND 'voice_state_update' has fired.\nIf everything goes well, Discord should send back '101 Switching Protocals'.";
}
subgraph cluster_6 {
style=filled;
color=lightgrey;
node [style=filled, color=3, shape=rect]
"discord_voice_client::handle_frame";
"HTTP/1.1 101 Switching Protocols" -> "discord_voice_client::handle_frame";
label = "Do the voice stuff.";
}
"Your bot" -> "guild::connect_member_voice";
"discord_client::connect_voice" -> "message_queue";
"message_queue" -> "discord_client::one_second_timer";
"discord_client::one_second_timer" -> "message_queue";
"voice_state_update::handle" -> "voiceconn::connect";
"voice_server_update::handle" -> "voiceconn::connect";
}
\enddot