1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2025-06-23 02:27:12 +02:00

Add D++ library.

This commit is contained in:
Sandu Liviu Catalin
2023-03-23 20:20:44 +02:00
parent b08a024298
commit cbd8f8b028
568 changed files with 131250 additions and 0 deletions

View File

@ -0,0 +1,109 @@
# Frequently Asked Questions (FAQ)
[TOC]
## Is this library in production use?
This library powers the bot [TriviaBot](https://triviabot.co.uk) which has over **151,000 servers**, and [Sporks](https://sporks.gg) which has over **3,500 severs**. The library's use in these bots shows that the library is production ready for bots of all sizes.
## How much RAM does this library use?
In production on TriviaBot, the bot takes approximately 2gb of ram to run 18 separate processes (this is approximately **140mb** per process) on a production bot with 36 million users and 151,000 guilds. Each process takes under 1% CPU. This is less than a quarter of the memory of a similar C++ Discord library, **Aegis.cpp** (version 2).
For a very small bot, you can get the memory usage as low as **6 megabytes** on a Raspberry Pi.
## How do I use this library in Windows?
The easiest way is to use our [template project](https://github.com/brainboxdotcc/windows-bot-template). If you are unable to do this, download the precompiled latest release from our GitHub releases, and take the dlls, .lib file, and header files (`bin`, `lib` and `include` directories), placing them in a easily accessible place on your computer. Go into Visual Studio project settings in a new project, and point the project directories (notably the library directories and and include directories) at the correct locations. Add the `include` folder you extracted to your include directories, and add `dpp.lib` to your library directories. Ensure the project is set to C++17 standard in the settings. You should then be able to compile example programs within that project. When you run the program you have compiled you must ensure that all the dll files from the `bin` directory exist in the same directory as your executable.
## Does this library support Visual Studio 2022?
Yes! The master branch comes with pre-built binaries for Visual Studio 2022 and our windows bot template has a [vs2022 branch](https://github.com/brainboxdotcc/windows-bot-template/tree/vs2022) which you can clone to get Visual Studio 2022 specific code. For the time being we support both Visual Studio 2019 and 2022. At some point in the future only 2022 may be supported as 2019 becomes outdated.
## How much of the library is completed?
All REST calls (outbound commands) are completed including all currently available interactions, and all Discord events are available. The library also has voice support.
## How do I chat with the developers or get help?
The best place to do this is on the [Discord server](https://discord.gg/dpp). You most likely won't get an answer immediately (we have lives, and need to sleep sometimes), but we will be willing to help!
## How can I contribute to development?
Just star and fork a copy of the repository, and submit a Pull Request! We won't bite! Authors of accepted pull requests get a special role on our [Discord server](https://discord.gg/dpp).
## Whats the best way to learn C++?
A simple search can find some learning tools, however not all are good. Here is a list of some (good) learning resources:
* [CodeAcademy](https://www.codecademy.com/learn/c-plus-plus)
* [Learn CPP](https://www.learncpp.com/)
* [Learn CPP (Very Basic)](https://www.learn-cpp.org/)
If you don't understand something then feel free to ask in the [Discord server](https://discord.gg/dpp) ...*we don't bite!*
## Do I need to be an expert in C++ to use this library?
NO! Definitely not! We have tried to keep things as simple as possible. We only use language features where they make sense, not just because they exist. Take a look at the example program (`test.cpp` and you'll see just how simple it is to get up and running quickly). We use a small subset of C++17 and C++14 features.
## Why is D++ also called DPP
DPP is short for *D Plus Plus* (D++), a play on the Discord and C++ names. You'll see the library referred to as `dpp` within source code as `d++` is not a valid symbol so we couldn't exactly use that as our namespace name.
## Is D++ a single header library?
No, D++ is a classically designed library which installs itself to your library directory/system directory as a shared object or dll. You must link to its .lib file and include its header files to make use of it. We have no plans for a single-header build.
## Does this library support slash commands/interactions?
Yes! This library supports slash commands and interactions. For more information please see \ref slashcommands "Using Slash Commands and Interactions".
## Does this library support buttons/drop down menus (message components)?
Yes! This library supports button message components, e.g. interactive buttons on the bottom of messages. For more information please see our \ref components "Using component interactions" and \ref components2 "Using component interactions (advanced)" examples.
## Is the library asynchronous?
All functions within D++ are multi-threaded. You should still avoid doing long operations within event handlers or within callbacks, to prevent starvation of the threads managing each shard. Various blocking operations such as running files and making HTTP requests are offered as library functions (for example dpp::utility::exec)
## Does this library support voice?
Yes! This library supports voice and will automatically enable voice if your system has the libopus and libsodium libraries. When running `cmake` the script will identify if these libraries are found. See the example programs for information on how to send audio.
## Does this library support sharding?
Yes! D++ supports sharding and also clustering (grouping of shards into one process) to ensure it is scalable for small and large bots alike.
## How do I contribute to the documentation and website?
The documentation and website are built using Doxygen. To contribute to site pages, submit a Pull Request to the main repository. The site pages can be found within the `docpages` directory. Details of classes, variables, namespaces etc are auto generated from Doxygen comments within the library source code in the `include` and `src` folders.
## What version of the Discord API does this library support?
D++ only supports Discord API v10, the latest version. D++ major version numbers match the supported Discord API version.
## Does this Discord library support the threads feature?
Yes! D++ supports Discord threads. You can create, edit and delete threads and also attach events watching for messages within threads.
## Does D++ require C++20 support?
No, at the current time we do not use any C++20 features. Some C++17 features are used, which are available in all recent compilers.
## When I start my bot i get an error: "error while loading shared libraries: libdpp.so: cannot open shared object file: No such file or directory"
To fix this issue, run `ldconfig`: `sudo ldconfig` as root. Log out if your SSH session and log back in, and the bot should be able to find the library.
## When compiling with voice support, i get an error: "No rule to make target 'sodium_LIBRARY_DEBUG-NOTFOUND', needed by 'libdpp.so'. Stop."
The libsodium package requires pkg-config, but does not check for it when installed. Install it as root, e.g. `sudo apt install pkg-config`. Rerun cmake, and rebuild the library.
## When I try to instantiate a dpp::cluster in windows, a std::bad_alloc exception is thrown
If this happens, ensure you are using the correct precompiled build of the library. Our precompiled binaries are built in two forms, **release mode** and **debug mode** for Visual Studio 2019/2022. These two versions of the library are not cross-compatible due to differences in the debug and release libstdc++. You should not need to build your own copy, but please see the section about \ref buildwindows for more information on how to build your own copy, if needed.
## Does this library build/run on Raspberry Pi?
Yes! This project will build and run on Raspberry Pi and is very much suited to this kind of system. It may take some time (read: hours) to compile the project on your Raspberry Pi unless you build it using a cross compiler. We offer pre-built `.deb` files for arm6, arm7 and arm64, you should use these where possible to avoid having to compile it by hand, or you can use a cross-compiler to build it on your PC then transfer the compiled binaries across.
## There are so many versions! Which deb file do i need for my Raspberry Pi?
Depending on which Raspberry Pi version you have, you will need to download a different release binary:
<table>
<tr>
<th>Raspberry Pi Model</th>
<th>Deb file to install</th>
<th>Arch</th>
</tr>
<tr><td>Raspberry Pi Zero/Zero W</td><td>`libdpp-x.x.x-linux-rpi-arm6.deb`</td><td>ARMv6</td></tr>
<tr><td>Raspberry Pi 3</td><td>`libdpp-x.x.x-linux-rpi-arm7hf.deb`</td><td>ARMv7HF</td></tr>
<tr><td>Raspberry Pi 4</td><td>`libdpp-x.x.x-linux-rpi-arm7hf.deb`</td><td>ARMv7HF</td></tr>
<tr><td>Raspberry Pi 4 with 64 Bit Linux</td><td>`libdpp-x.x.x-linux-rpi-arm64.deb`</td><td>ARM64</td></tr>
</table>
## Are other ARM devices supported?
Yes! We have confirmed that the D++ deb file will successfully install and operate on various forms of cellphone or non-pi ARM devices. If you get it working on any other devices please let us know and we can build a compatibility chart.
## Can I run a D++ bot in repl.it?
Yes! You can indeed run your bot in a repl.it container. [You can find a ready to go demo repl here](https://replit.com/@braindigitalis/dpp-demo-bot). We also have a [guide showing how to do this](https://dpp.dev/building-a-cpp-discord-bot-in-repl.html).
## Why do the "get" functions like "messages_get" return void rather than what I'm after?
All the functions that obtain data directly from Discord (as opposed to the cache) perform HTTPS requests and may have to wait, either for the request itself or for their turn in a queue to respect rate limits. As such, it does not make sense that they should return a value, as this would mean they block execution of your event. Instead, each has a lambda, a function handler which receives the result of the request, which you can then read from within that function to get the data you are interested in. Note that this result will arrive on a different thread to the one which made the request. If you instead want the function to return a value, use the methods ending with `_sync` that will block until they have a response. Note that these forms of call will throw an exception on failure.
## Can i use a user token with this library (as opposed to a bot token)?
No. This feature is not supported as it is against the Discord Terms Of Service, and therefore we have no plans to ever support it. You should not automate any user token. Some libraries used to support this but it is a legacy feature of those libraries (where still available) dating back to before Discord offered an official public API. Please be aware that if Discord ever catch you automating a user token (or making a user client that uses a bot token) they can and do ban people for this.

11
vendor/DPP/docpages/01_installing.md vendored Normal file
View File

@ -0,0 +1,11 @@
# Installing D++
There are many ways to install D++, either from a package manager, or from source. Please choose your desired option from the list below:
* \subpage install-linux-deb
* \subpage install-linux-rpm
* \subpage install-vcpkg
* \subpage install-arch-aur
* \subpage install-windows-zip
* \subpage install-xmake
* \subpage install-from-source

View File

@ -0,0 +1,14 @@
# Creating a Discord Bot
If you are wanting to build a bot using C++, you're in the right place! The fast and easy tutorials below will guide you through how to build a bot using the D++ library on either a UNIX-like (e.g. Linux) system with CMake or with Windows using Visual Studio 2019.
Click on a link below for a guide specifically for your system:
* \subpage creating-a-bot-application "Creating a Bot Token"
* \subpage build-a-discord-bot-windows-visual-studio "Building a discord bot in Windows using Visual Studio"
* \subpage build-a-discord-bot-windows-wsl "Building a discord bot in Windows using WSL (Windows Subsystem for Linux)"
* \subpage build-a-discord-bot-linux-clion "Building a discord bot in Linux using CLion"
* \subpage buildcmake "Building a Discord Bot using CMake/UNIX"
* \subpage buildmeson "Building a Discord Bot using Meson"
* \subpage building-a-cpp-discord-bot-in-repl "Creating a Discord bot in Repl.it"

View File

@ -0,0 +1,10 @@
# Example Programs
There are example programs here for all skill levels demonstrating a great many features of the bot. New examples are added frequently, please check regularly for new content.
* \subpage the-basics
* \subpage interactions-and-components
* \subpage music-and-audio
* \subpage misc
Is the example you are looking for missing from these sections? Pop over to our [discord server](https://discord.com/dpp) and let us know what you need... Or, even better, if you know how to do something and want to contribute an example of your own, just submit a PR!

View File

@ -0,0 +1,7 @@
# Advanced Reference
* \subpage clusters-shards-guilds "Clusters, Shards and Guilds"
* \subpage thread-model "Thread Model"
* \subpage coding-standards "Coding Style Standards"
* \subpage unit-tests "Unit Tests"
* \subpage lambdas-and-locals "Ownership of local variables and safely transferring into a lambda"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

77
vendor/DPP/docpages/INDEX.md vendored Normal file
View File

@ -0,0 +1,77 @@
# D++: A C++ Discord API Library for Bots
## What is D++ (DPP)?
D++ is a lightweight and simple library for Discord written in modern C++. It is designed to cover as much of the API specification as possible and to have a incredibly small memory footprint, even when caching large amounts of data.
It is created by the developer of [TriviaBot](https://triviabot.co.uk) and contributed to by a dedicated team of developers.
*This project is in stable development and accepting PRs and feature requests — Don't be a stranger! If you want to contribute, just get in touch via [GitHub](https://github.com/brainboxdotcc/DPP) or our official [Discord server](https://discord.gg/dpp)!*
<img src="code_editor.png" style="margin-top: 2rem; margin-bottom: 2rem"/><br />
## Downloads
The following downloads are for the most recent version:
* [Source Code](https://github.com/brainboxdotcc/DPP)
* [x64 Linux .deb (64 bit Debian, Ubuntu etc)](https://dl.dpp.dev/latest)
* [x86 Linux .deb (32 bit Debian, Ubuntu etc)](https://dl.dpp.dev/latest/linux-i386)
* [x64 Linux .rpm (64 bit Redhat, CentOS etc)](https://dl.dpp.dev/latest/linux-x64/rpm)
* [x86 Linux .rpm (32 bit Redhat, CentOS etc)](https://dl.dpp.dev/latest/linux-i386/rpm)
* [x64 Windows (64 bit vs2019 release build)](https://dl.dpp.dev/latest/win64-release-vs2019)
* [x64 Windows (64 bit vs2022 release build)](https://dl.dpp.dev/latest/win64-release-vs2022)
* [x64 Windows (64 bit vs2019 debug build)](https://dl.dpp.dev/latest/win64-debug-vs2019)
* [x64 Windows (64 bit vs2022 debug build)](https://dl.dpp.dev/latest/win64-debug-vs2022)
* [ARM6 Linux .deb (32 bit Raspberry Pi 1, 2)](https://dl.dpp.dev/latest/linux-rpi-arm6)
* [ARM7 Linux .deb (32 bit Raspberry Pi 3, 4)](https://dl.dpp.dev/latest/linux-rpi-arm7hf)
* [ARM64 Linux .deb (64 bit Raspberry Pi 4, Smartphones)](https://dl.dpp.dev/latest/linux-rpi-arm64)
You can find further releases in other architectures and formats or the source code on the [GitHub Repository](https://github.com/brainboxdotcc/DPP/releases). For a realtime JSON format list of all download links, click [here](https://dl.dpp.dev/json)
## Library features
* Support for Discord API v10
* Really small memory footprint
* Efficient caching system for guilds, channels, guild members, roles, users
* Sharding and clustering (Many shards, one process: specify the number of shards, or let the library decide)
* Highly optimised ETF (Erlang Term Format) support for very fast websocket throughput (*no other C++ Discord library has this!*)
* [Slash Commands/Interactions support](https://dpp.dev/slashcommands.html)
* [Voice support](https://dpp.dev/soundboard.html) (sending **and** receiving audio)
* The entire Discord API is available for use in the library
* Stable [Windows support](https://dpp.dev/buildwindows.html)
* Ready-made compiled packages for Windows, Raspberry Pi (ARM64/ARM7/ARMv6), Debian x86/x64 and RPM based distributions
* Highly scalable for large amounts of guilds and users
## Supported Operating Systems
### Linux
The library runs ideally on **Linux**.
### Mac OS X and FreeBSD
The library is well-functional and stable on **Mac OS X** and **FreeBSD** too.
### Raspberry Pi
For running your bot on a **Raspberry Pi**, we offer a prebuilt .deb package for ARM64, ARM6, and ARM7 so that you do not have to wait for it to compile.
### Windows
**Windows** is well-supported with ready-made compiled DLL and LIB files, please check out our [Windows Bot Template repository](https://github.com/brainboxdotcc/windows-bot-template). The Windows Bot repository can be cloned and integrated immediately into any Visual Studio 2019 and 2022 project in a matter of minutes.
### Other OS
The library should work fine on other operating systems as well, and if you run a D++ bot on something not listed here, please let us know!
## Getting started
* [GitHub Repository](https://github.com/brainboxdotcc/DPP)
* [Discord Server](https://discord.gg/dpp)
* [Frequently Asked Questions](/md_docpages_01_frequently_asked_questions.html)
* [Installing D++](/md_docpages_01_installing.html)
* [Example Programs](/md_docpages_03_example_programs.html)
## Architecture
* \ref clusters-shards-guilds
* \ref thread-model
## Learning Resources
* [C++ for JavaScript Developers](https://pawelgrzybek.com/cpp-for-javascript-developers/)
* [C++ In Four Hours](https://www.youtube.com/watch?v=vLnPwxZdW4Y&vl=en)

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 websocket 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 accomodate 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,116 @@
\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
Open curly braces on the same line as the keyword, for example:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
if (a == b) {
c();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use a space after the comma in parameter lists, and after opening brackets and before closing brackets except when calling a function, e.g.:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
std::vector<std::string> clowns = { "pennywise", "bobo" };
evaluate_clown(clowns[0], evilness(2.5, factor));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## 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, and can use any other comment types within the .cpp file. Be liberal with comments, especially if your code makes any assumptions!
## 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:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
class DPP_EXPORT my_new_class {
public:
int hats;
int clowns;
};
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 object 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 provide 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.
## 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 tha 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:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
class DPP_EXPORT my_new_class {
public:
int hats;
int clowns;
my_new_class& set_hats(int new_hats);
my_new_class& set_clowns(int new_clowns);
};
my_new_class& my_new_class::set_hats(int new_hats) {
hats = new_hats;
return *this;
}
my_new_class& my_new_class::set_clowns(int new_clowns) {
clowns = new_clowns;
return *this;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This would allow the user to do this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
dpp::my_new_class nc;
nc.set_hats(3).set_clowns(9001);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## 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. Its good to have descriptive commit messages, or PR titles so that other contributors can understand about your commit or the PR Created. Read [conventional commits](https://www.conventionalcommits.org/en/v1.0.0-beta.3/) for information on how we like to format commit messages.
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.

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 effect** the value of the var 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,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,28 @@
\page unit-tests Unit Tests
## Running Unit Tests
If you are adding functionality to DPP, 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)
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,10 @@
\page install-from-source Building D++ From Source
The way you build D++ varies from system to system. Please follow the guide below for your OS:
* \subpage buildlinux "Building on Linux"
* \subpage buildwindows "Building on Windows"
* \subpage buildosx "Building on OSX"
* \subpage buildfreebsd "Building on FreeBSD"
@warning Note that you most likely don't need to build D++ from source if you're on Linux or Windows. We offer prebuilt binaries for these platforms and are listed in package managers! Check the downloads in the releases section on github.

49
vendor/DPP/docpages/building/freebsd.md vendored Normal file
View File

@ -0,0 +1,49 @@
\page buildfreebsd Building on FreeBSD
## 1. Toolchain
This project uses CMake. Install it with `pkg install cmake`
## 2. Install External Dependencies
Your FreeBSD base system should have all the required dependencies installed by default.
For voice support, additional dependencies are required
pkg install libsodium opus pkgconf
## 3. Build Source Code
cmake -B ./build
cmake --build ./build -j8
Replace the number after -j with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library.
## 4. Install globally
cd build; make install
## 5. Installation to a different directory
If you want to install the library, its dependencies and header files to a different directory, specify this directory when running `cmake`:
cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install
Then once the build is complete, run `make install` to install to the location you specified.
## 7. Using the library
Once installed, you can make use of the library in standalone programs simply by including it and linking to it:
clang++ -std=c++17 -ldpp mydppbot.cpp -o dppbot
The important flags in this command-line are:
* `-std=c++17` - Required to compile the headers
* `-ldpp` - Link to libdpp.dylib
* `mydppbot.cpp` - Your source code
* `dppbot` - The name of the executable to make
Of course, this is just a proof of concept - you should really use a more robust build system like [`cmake`](@ref buildcmake).
If you are having trouble setting up CMake, you can try [our template bot](https://github.com/brainboxdotcc/templatebot).
**Have fun!**

42
vendor/DPP/docpages/building/linux.md vendored Normal file
View File

@ -0,0 +1,42 @@
\page buildlinux Building on Linux
\note You might not need to build a copy of the library for Linux - precompiled deb files for 64 bit and 32 bit Debian and Ubuntu are provided in the GitHub version releases. Unless you are on a different Linux distribution which does not support the installation of deb files, or wish to submit fixes and enhancements to the library itself you should have an easier time installing the precompiled version instead.
## 1. Build Source Code
cmake -B ./build
cmake --build ./build -j8
Replace the number after -j with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library.
## 2. Install to /usr/local/include and /usr/local/lib
cd build; sudo make install
## 3. Installation to a different directory
If you want to install the library, its dependencies and header files to a different directory, specify this directory when running `cmake`:
cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install
Then once the build is complete, run `make install` to install to the location you specified.
## 4. Using the library
Once installed to the /usr/local directory, you can make use of the library in standalone programs simply by including it and linking to it:
g++ -std=c++17 mydppbot.cpp -o dppbot -ldpp
The important flags in this command-line are:
* `-std=c++17` - Required to compile the headers
* `mydppbot.cpp` - Your source code
* `dppbot` - The name of the executable to make
Of course, this is just a proof of concept — you should really use a more robust build system like GNU `make` or [`cmake`](@ref buildcmake).
If you are having trouble setting up CMake, you can try [our template bot](https://github.com/brainboxdotcc/templatebot).
**Have fun!**

55
vendor/DPP/docpages/building/osx.md vendored Normal file
View File

@ -0,0 +1,55 @@
\page buildosx Building on OSX
## 1. Toolchain
Before compiling make sure you have all the tools installed.
1. To install the dependencies, this guide will use homebrew which has [installation instructions on their project page](https://brew.sh/).
2. This project uses CMake to generate the makefiles. Install it with `brew install cmake`.
## 2. Install External Dependencies
brew install openssl
For voice support, additional dependencies are required:
brew install libsodium opus
## 3. Build Source Code
cmake -B ./build
cmake --build ./build -j8
Replace the number after -j with a number suitable for your setup, usually the same as the number of cores on your machine. `cmake` will fetch any dependencies that are required for you and ensure they are compiled alongside the library.
## 4. Install globally
cd build; sudo make install
## 5. Installation to a different directory
If you want to install the library, its dependencies and header files to a different directory, specify this directory when running `cmake`:
cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install
Then once the build is complete, run `make install` to install to the location you specified.
## 6. Using the library
Once installed, you can make use of the library in standalone programs simply by including it and linking to it:
clang++ -std=c++17 -ldpp mydppbot.cpp -o dppbot
The important flags in this command-line are:
* `-std=c++17` - Required to compile the headers
* `-ldpp` - Link to libdpp.dylib
* `mydppbot.cpp` - Your source code
* `dppbot` - The name of the executable to make
Of course, this is just a proof of concept - you should really use a more robust build system like GNU `make` or [`cmake`](@ref buildcmake).
If you are having trouble setting up CMake, you can try [our template bot](https://github.com/brainboxdotcc/templatebot).
**Have fun!**

29
vendor/DPP/docpages/building/windows.md vendored Normal file
View File

@ -0,0 +1,29 @@
\page buildwindows Building on Windows
To build on windows follow these steps *exactly*. The build process depends on specific libraries being installed on your system in specific locations.
## Wait a minute! Read this first!
\warning **You do not need to follow this tutorial unless you plan to contribute to or modify the library itself**. Unless you consider yourself an **advanced user** with a specific **requirement to build from source** you should [obtain a pre-made visual studio template containing the latest D++ build (for 32 and 64 bit, release and debug profiles) by clicking here](https://github.com/brainboxdotcc/windows-bot-template/) and completely skip this guide! Instead, read \ref build-a-discord-bot-windows-visual-studio.
## If you are absolutely sure you need this guide, read on:
1. Make sure you have Visual Studio 2019 or Visual Studio 2022. The Community, Professional or Enterprise versions all work, however you will probably want to install Community. You do **NOT** want to use *Visual Studio Code* for this. You can [download the correct version here](https://visualstudio.microsoft.com/downloads/).
2. Check out the DPP project source using git
3. From within Visual Studio 2019, click the "File" menu, choose "Open" then "CMake", and select the CMakeLists.txt within the project folder
\image html winbuild_1.png
\image html winbuild_2.png
4. Go to the "Build" menu and choose "Build all" or just press F7
\image html winbuild_3.png
5. Check that compilation succeeded. You may now use the library in your projects!
\image html winbuild_4.png
## Troubleshooting
* If you do not have an option to open the CMakeLists.txt, ensure that you have installed the C++ development portions of visual studio (not just web development portions) with at least the default options.
* If the project does not build, please ask for help on the [official discord server](https://discord.gg/dpp).
## After compiling
After compilation you can directly reference the compiled project in your own CMakeLists.txt as a library or use the lib/dll/headers as you wish. Note that `openssl` and `zlib` will also be an indirect dependency of your program (as `DLL` files) and should be copied alongside `dpp.dll`.

View File

@ -0,0 +1,47 @@
<?php
$githubApi = "https://api.github.com/repos/brainboxdotcc/dpp/releases";
$json = json_decode(
file_get_contents(
$githubApi,
false,
stream_context_create(
[
"http" => [
"method" => "GET",
"header" => "User-Agent: DPP/Website"
]
]
)
)
);
$downloads = 0;
foreach ($json as $index => $release) {
$releaseDownloads = 0;
foreach ($release->assets as $asset) {
$releaseDownloads += $asset->download_count;
}
$downloads += $releaseDownloads;
}
header("Content-Type: image/svg+xml");
echo <<<IMG
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="106" height="20" role="img" aria-label="downloads: {$downloads}">
<title>downloads: {$downloads}</title>
<linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="106" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="69" height="20" fill="#555"/><rect x="69" width="37" height="20" fill="#97ca00"/>
<rect width="106" height="20" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
<text aria-hidden="true" x="355" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="590">downloads</text>
<text x="355" y="140" transform="scale(.1)" fill="#fff" textLength="590">downloads</text>
<text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">{$downloads}</text>
<text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="270">{$downloads}</text>
</g>
</svg>
IMG;

View File

@ -0,0 +1,68 @@
<?php
// Force no caching of the download
header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Status: 200 OK");
// Split up url and set defaults
list($version, $arch, $type) = explode('/', preg_replace('/https:\/\/dl\.dpp\.dev\//', '', $_SERVER['REDIRECT_SCRIPT_URI']), 3);
$version = !empty($version) ? $version : 'latest';
$arch = !empty($arch) ? $arch : 'linux-x64';
$type = !empty($type) ? $type : 'deb';
$urls = [];
// All windows downloads are of type 'zip', if not specified, it is defaulted
if (preg_match('/^win/i', $arch) && $type === 'deb') {
$type = 'zip';
}
// the short word 'win' is a shorthand for the vs2019 release build
if ($arch === 'win') {
$arch = 'win32-release-vs2019';
}
// A crontab keeps this updated so we only perform one github api request per 5 minutes
$json = json_decode(file_get_contents('release.json'));
// If the user asked for 'latest', we find out what the latest release tag name is
if ($version === 'latest') {
$version = $json[0]->tag_name;
}
// Build search filename
$searchName = 'libdpp-' . preg_replace('/^v/', '', $version) . '-' . $arch . '.' . $type;
// Iterate list of release artifacts across all releases
foreach ($json as $index => $release) {
foreach ($release->assets as $index2 => $asset) {
$url = $asset->browser_download_url;
$name = $asset->name;
$thisVersion = $release->tag_name;
// We found a matching file, stream it to the user
if (strtoupper($searchName) == strtoupper($name)) {
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $name . '"');
readfile($url);
exit;
}
$urls[] = [
'name' => $name,
'url' => $url,
'version' => $thisVersion,
];
}
}
if ($version === 'json') {
header('Content-Type: application/json');
echo json_encode($urls);
} else {
// Nothing found, offer up some useful info
foreach ($urls as $thisUrl) {
printf("%s - <a href='%s' target='_blank'>%s</a><br />", $thisUrl['version'], $thisUrl['url'], $thisUrl['name']);
}
}

View File

@ -0,0 +1,14 @@
\page interactions-and-components Interactions And Components
The example programs listed here demonstrate lots of things to do with interactions, application commands (slash commands) and message components. If you're looking to make your bot **modern and user friendly** these examples are what you need.
* \subpage slashcommands "Using Slash Commands and Interactions"
* \subpage context-menu "Context Menus"
* \subpage subcommands "Slash command sub-commands"
* \subpage components "Using button components"
* \subpage components3 "Using select menu components"
* \subpage components2 "Advanced components"
* \subpage modal-dialog-interactions "Modal Dialogs"
* \subpage commandhandler "Unified message/slash command handler"
* \subpage application-command-autocomplete "Slash command auto completion"
* \subpage discord-application-command-file-upload "Using file parameters in slash commands"

View File

@ -0,0 +1,72 @@
\page application-command-autocomplete Slash command auto completion
Discord now supports sending auto completion lists for slash command choices. To use this feature you can use code such as the example below:
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main()
{
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new global command once on ready event */
bot.global_command_create(dpp::slashcommand("blep", "Send a random adorable animal photo", bot.me.id)
.add_option(
/* If you set the auto complete setting on a command option, it will trigger the on_autocomplete
* event whenever discord needs to fill information for the choices. You cannot set any choices
* here if you set the auto complete value to true.
*/
dpp::command_option(dpp::co_string, "animal", "The type of animal").set_auto_complete(true)
)
);
}
});
/* The interaction create event is fired when someone issues your commands */
bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) {
/* Check which command they ran */
if (event.command.get_command_name() == "blep") {
/* Fetch a parameter value from the command parameters */
std::string animal = std::get<std::string>(event.get_parameter("animal"));
/* Reply to the command. There is an overloaded version of this
* call that accepts a dpp::message so you can send embeds.
*/
event.reply("Blep! You chose " + animal);
}
});
/* The on_autocomplete event is fired whenever discord needs information to fill in a command options's choices.
* You must reply with a REST event within 500ms, so make it snappy!
*/
bot.on_autocomplete([&bot](const dpp::autocomplete_t & event) {
for (auto & opt : event.options) {
/* The option which has focused set to true is the one the user is typing in */
if (opt.focused) {
/* In a real world usage of this function you should return values that loosely match
* opt.value, which contains what the user has typed so far. The opt.value is a variant
* and will contain the type identical to that of the slash command parameter.
* Here we can safely know it is string.
*/
std::string uservalue = std::get<std::string>(opt.value);
bot.interaction_response_create(event.command.id, event.command.token, dpp::interaction_response(dpp::ir_autocomplete_reply)
.add_autocomplete_choice(dpp::command_option_choice("squids", "lots of squids"))
.add_autocomplete_choice(dpp::command_option_choice("cats", "a few cats"))
.add_autocomplete_choice(dpp::command_option_choice("dogs", "bucket of dogs"))
.add_autocomplete_choice(dpp::command_option_choice("elephants", "bottle of elephants"))
);
bot.log(dpp::ll_debug, "Autocomplete " + opt.name + " with value '" + uservalue + "' in field " + event.name);
break;
}
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~

View File

@ -0,0 +1,68 @@
\page commandhandler Using a command handler object
If you have many commands in your bot, and want to handle commands from multiple sources (for example modern slash commands, and more regular
prefixed channel messages) you should consider instantiating a dpp::commandhandler object. This object can be used to automatically route
commands and their parameters to functions in your program. A simple example of using this object to route commands is shown below, and will
route both the /ping (global slash command) and .ping (prefixed channel message command) to a lambda where a reply can be generated.
\note This example automatically hooks the dpp::cluster::on_message_create and dpp::cluster::on_slashcommand events. This can be overridden if needed to allow you to still make use of these functions for your own code, if you need to do this please see the constructor documentation for dpp::commandhandler.
Note that because the dpp::commandhandler::add_command method accepts a std::function as the command handler, you may point a command handler
at a simple lambda (as shown in this example), a function pointer, or an instantiated class method of an object. This is extremely flexible
and allows you to decide how and where commands should be routed, either to an object oriented system or to a lambda based system.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main()
{
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* Create command handler, and specify prefixes */
dpp::commandhandler command_handler(&bot);
/* Specifying a prefix of "/" tells the command handler it should also expect slash commands */
command_handler.add_prefix(".").add_prefix("/");
bot.on_ready([&command_handler](const dpp::ready_t &event) {
command_handler.add_command(
/* Command name */
"ping",
/* Parameters */
{
{"testparameter", dpp::param_info(dpp::pt_string, true, "Optional test parameter") }
},
/* Command handler */
[&command_handler](const std::string& command, const dpp::parameter_list_t& parameters, dpp::command_source src) {
std::string got_param;
if (!parameters.empty()) {
got_param = std::get<std::string>(parameters[0].second);
}
command_handler.reply(dpp::message("Pong! -> " + got_param), src);
},
/* Command description */
"A test ping command",
/* Guild id (omit for a global command) */
819556414099554344
);
/* NOTE: We must call this to ensure slash commands are registered.
* This does a bulk register, which will replace other commands
* that are registered already!
*/
command_handler.register_commands();
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,54 @@
\page components Using button components
Discord's newest features support sending buttons alongside messages, which when clicked by the user trigger an interaction which is routed by
D++ as an on_button_click event. To make use of this, use code as in this example.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iostream>
#include <dpp/message.h>
int main() {
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Message handler to look for a command called !button */
bot.on_message_create([&bot](const dpp::message_create_t & event) {
if (event.msg.content == "!button") {
/* Create a message containing an action row, and a button within the action row. */
bot.message_create(
dpp::message(event.msg.channel_id, "this text has buttons").add_component(
dpp::component().add_component(
dpp::component().set_label("Click me!").
set_type(dpp::cot_button).
set_emoji(u8"😄").
set_style(dpp::cos_danger).
set_id("myid")
)
)
);
}
});
/* When a user clicks your button, the on_button_click event will fire,
* containing the custom_id you defined in your button.
*/
bot.on_button_click([&bot](const dpp::button_click_t & event) {
/* Button clicks are still interactions, and must be replied to in some form to
* prevent the "this interaction has failed" message from Discord to the user.
*/
event.reply("You clicked: " + event.custom_id);
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When the feature is functioning, the code below will produce buttons on the reply message like in the image below:
\image html button.png

View File

@ -0,0 +1,55 @@
\page components2 Advanced components
This example demonstrates receiving button clicks and sending response messages.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
using json = nlohmann::json;
int main() {
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); // Privileged intent required to receive message content
bot.on_log(dpp::utility::cout_logger());
bot.on_button_click([&bot](const dpp::button_click_t & event) {
if (event.custom_id == "10") {
event.reply(dpp::message("Correct").set_flags(dpp::m_ephemeral));
} else {
event.reply(dpp::message("Incorrect").set_flags(dpp::m_ephemeral));
}
});
bot.on_message_create([&bot](const dpp::message_create_t & event) {
if (event.msg.content == "!ping2") {
bot.message_create(
dpp::message(event.msg.channel_id, "What is 5+5?").add_component(
dpp::component().add_component(
dpp::component().set_label("9").
set_style(dpp::cos_primary).
set_id("9")
).add_component(
dpp::component().set_label("10").
set_style(dpp::cos_primary).
set_id("10")
).add_component(
dpp::component().set_label("11").
set_style(dpp::cos_primary).
set_id("11")
)
)
);
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This code will send a different message for correct and incorrect answers.
\image html button_2.png

View File

@ -0,0 +1,47 @@
\page components3 Using select menu components
This example demonstrates receiving select menu clicks and sending response messages.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
using json = nlohmann::json;
int main() {
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Message handler to look for a command called !select */
bot.on_message_create([&bot](const dpp::message_create_t & event) {
if (event.msg.content == "!select") {
/* Create a message containing an action row, and a select menu within the action row. */
dpp::message m(event.msg.channel_id, "this text has a select menu");
m.add_component(
dpp::component().add_component(
dpp::component().set_type(dpp::cot_selectmenu).
set_placeholder("Pick something").
add_select_option(dpp::select_option("label1","value1","description1").set_emoji(u8"😄")).
add_select_option(dpp::select_option("label2","value2","description2").set_emoji(u8"🙂")).
set_id("myselid")
)
);
bot.message_create(m);
}
});
/* When a user clicks your select menu , the on_select_click event will fire,
* containing the custom_id you defined in your select menu.
*/
bot.on_select_click([&bot](const dpp::select_click_t & event) {
/* Select clicks are still interactions, and must be replied to in some form to
* prevent the "this interaction has failed" message from Discord to the user.
*/
event.reply("You clicked " + event.custom_id + " and chose: " + event.values[0]);
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,51 @@
\page context-menu Context Menus
Context menus are application commands that appear on the context menu (right click or tap) of users or messages to perform context-specific actions. They can be created using `dpp::slashcommand`. Once you create a context menu, try right-clicking either a user or message to see it in your server!
\image html context_menu_user_command.png
The following example shows how to create and handle **user context menus**.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iostream>
int main()
{
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
bot.on_ready([&bot](const dpp::ready_t &event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Register the command */
bot.guild_command_create(
dpp::slashcommand()
.set_type(dpp::ctxm_user)
.set_name("High Five")
.set_application_id(bot.me.id),
857692897221033129 // you need to put your guild-id in here
);
}
});
/* Use the on_user_context_menu event to look for user context menu actions */
bot.on_user_context_menu([&](const dpp::user_context_menu_t &event) {
/* check if the context menu name is High Five */
if (event.command.get_command_name() == "High Five") {
dpp::user user = event.get_user(); // the user who the command has been issued on
dpp::user author = event.command.get_issuing_user(); // the user who clicked on the context menu
event.reply(author.get_mention() + " slapped " + user.get_mention());
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~
It registers a guild command that can be called by right-click a user and click on the created menu.
\image html context_menu_user_command_showcase.png

View File

@ -0,0 +1,78 @@
\page modal-dialog-interactions Modal Dialog Interactions
Modal dialog interactions are a new Discord API feature that allow you to have pop-up windows which prompt the user to input information. Once the user has filled in this information, your program will receive an `on_form_submit` event which will contain the data which was input. You must use a slash command interaction response to submit your modal form data to Discord, via the `on_slashcommand` event. From here calling the `dialog` method of the `interaction_create_t` event object will trigger the dialog to appear.
Each dialog box may have up to five rows of input fields. The example below demonstrates a simple setup with just one text input:
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iostream>
int main(int argc, char const *argv[])
{
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
bot.on_ready([&](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a slash command and register it as a global command */
bot.global_command_create(dpp::slashcommand("dialog", "Make a modal dialog box", bot.me.id));
}
});
bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) {
/* Check for our /dialog command */
if (event.command.get_command_name() == "dialog") {
/* Instantiate an interaction_modal_response object */
dpp::interaction_modal_response modal("my_modal", "Please enter stuff");
/* Add a text component */
modal.add_component(
dpp::component().
set_label("Short type rammel").
set_id("field_id").
set_type(dpp::cot_text).
set_placeholder("gumd").
set_min_length(5).
set_max_length(50).
set_text_style(dpp::text_short)
);
/* Add another text component in the next row, as required by Discord */
modal.add_row();
modal.add_component(
dpp::component().
set_label("Type rammel").
set_id("field_id2").
set_type(dpp::cot_text).
set_placeholder("gumf").
set_min_length(1).
set_max_length(2000).
set_text_style(dpp::text_paragraph)
);
/* Trigger the dialog box. All dialog boxes are ephemeral */
event.dialog(modal);
}
});
/* This event handles form submission for the modal dialog we create above */
bot.on_form_submit([&](const dpp::form_submit_t & event) {
/* For this simple example we know the first element of the first row ([0][0]) is value type string.
* In the real world it may not be safe to make such assumptions!
*/
std::string v = std::get<std::string>(event.components[0].components[0].value);
dpp::message m;
m.set_content("You entered: " + v).set_flags(dpp::m_ephemeral);
/* Emit a reply. Form submission is still an interaction and must generate some form of reply! */
event.reply(m);
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~
If you compile and run this program and wait for the global command to register, typing `/dialog` will present you with a dialog box like the one below:
\image html modal_dialog.png

View File

@ -0,0 +1,61 @@
\page slashcommands Using Slash Commands and Interactions
Slash commands and interactions are a newer feature of Discord which allow bot's commands to be registered centrally within the system and for users to easily explore and get help with available commands through the client itself.
To add a slash command you should use the dpp::cluster::global_command_create method for global commands (available to all guilds) or dpp::cluster::guild_command_create to create a local command (available only to one guild).
When a user issues these commands the reply will arrive via the `on_slashcommand` event which you can hook, and take action when you see your commands. It is possible to reply to an interaction by using either the dpp::interaction_create_t::reply method, or by manually instantiating an object of type dpp::interaction_response and attaching a dpp::message object to it.
dpp::interaction_create_t::reply has two overloaded versions of the method, one of which accepts simple std::string replies, for basic text-only messages (if your message is 'ephemeral' you must use this) and one which accepts a dpp::message for more advanced replies. Please note that at present, Discord only supports a small subset of message and embed features within an interaction response object.
\note You can also use the unified command handler, which lets you combine channel based message commands and slash commands under the same lambda with the same code like they were one and the same. Note that after August of 2022 Discord will be discouraging bots from using commands that are prefixed messages via means of a privileged message intent. It is advised that you exclusively use slash commands, or the unified handler with only a prefix of "/" going forward for any new bots you create and look to migrating existing bots to this setup.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main()
{
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when someone issues your commands */
bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) {
/* Check which command they ran */
if (event.command.get_command_name() == "blep") {
/* Fetch a parameter value from the command parameters */
std::string animal = std::get<std::string>(event.get_parameter("animal"));
/* Reply to the command. There is an overloaded version of this
* call that accepts a dpp::message so you can send embeds.
*/
event.reply(std::string("Blep! You chose") + animal);
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new global command on ready event */
dpp::slashcommand newcommand("blep", "Send a random adorable animal photo", bot.me.id);
newcommand.add_option(
dpp::command_option(dpp::co_string, "animal", "The type of animal", true).
add_choice(dpp::command_option_choice("Dog", std::string("animal_dog"))).
add_choice(dpp::command_option_choice("Cat", std::string("animal_cat"))).
add_choice(dpp::command_option_choice("Penguin", std::string("animal_penguin")
)
)
);
/* Register the command */
bot.global_command_create(newcommand);
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\note For demonstration purposes, and small bots, this code is OK, but in the real world once your bot gets big, it's not recommended to create slash commands in the `on_ready` event because it gets called often (discord forces reconnections and sometimes these do not resume). You could for example add a commandline parameter to your bot (`argc`, `argv`) so that if you want the bot to register commands it must be launched with a specific command line argument.

View File

@ -0,0 +1,78 @@
\page subcommands Using sub-commands in slash commands
This demonstrates how to use sub-commands within slash commands. Also shown below is an example of how to get a "resolved" parameter without having to use the cache or an extra API call.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iostream>
int main() {
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* Executes on ready. */
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Define a slash command. */
dpp::slashcommand image("image", "Send a specific image.", bot.me.id);
image.add_option(
/* Create a subcommand type option for "dog". */
dpp::command_option(dpp::co_sub_command, "dog", "Send a picture of a dog.").
add_option(dpp::command_option(dpp::co_user, "user", "User to turn into a dog.", false))
);
image.add_option(
/* Create another subcommand type option for "cat". */
dpp::command_option(dpp::co_sub_command, "cat", "Send a picture of a cat.").
add_option(dpp::command_option(dpp::co_user, "user", "User to turn into a cat.", false))
);
/* Create command */
bot.global_command_create(image);
}
});
/* Use the on_slashcommand event to look for commands */
bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) {
dpp::interaction interaction = event.command;
dpp::command_interaction cmd_data = interaction.get_command_interaction();
/* Check if the command is the image command. */
if (interaction.get_command_name() == "image") {
/* Get the sub command */
auto subcommand = cmd_data.options[0];
/* Check if the subcommand is "dog" */
if (subcommand.name == "dog") {
/* Checks if the subcommand has any options. */
if (!subcommand.options.empty()) {
/* Get the user from the parameter */
dpp::user user = interaction.get_resolved_user(
subcommand.get_value<dpp::snowflake>(0)
);
event.reply(user.get_mention() + " has now been turned into a dog.");
} else {
/* Reply if there were no options.. */
event.reply("No user specified");
}
}
/* Check if the subcommand is "cat" */
if (subcommand.name == "cat") {
/* Checks if the subcommand has any options. */
if (!subcommand.options.empty()) {
/* Get the user from the parameter */
dpp::user user = interaction.get_resolved_user(
subcommand.get_value<dpp::snowflake>(0)
);
event.reply(user.get_mention() + " has now been turned into a cat.");
} else {
/* Reply if there were no options.. */
event.reply("No user specified");
}
}
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,48 @@
\page discord-application-command-file-upload Using file parameters for application commands (slash commands)
The program below demonstrates how to use the 'file' type parameter to an application command (slash command).
You must first get the file_id via std::get, and then you can find the attachment details in the 'resolved'
section, `event.command.resolved`.
The file is uploaded to Discord's CDN so if you need it locally you should fetch the `.url` value, e.g. by using
something like `dpp::cluster::request()`.
~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main()
{
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) {
if (event.command.type == dpp::it_application_command) {
dpp::command_interaction cmd_data = std::get<dpp::command_interaction>(event.command.data);
if (cmd_data.name == "show") {
dpp::snowflake file_id = std::get<dpp::snowflake>(event.get_parameter("file"));
auto iter = event.command.resolved.attachments.find(file_id);
if (iter != event.command.resolved.attachments.end()) {
const dpp::attachment& att = iter->second;
event.reply(att.url);
}
}
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand newcommand("show", "Show an uploaded file", bot.me.id);
newcommand.add_option(dpp::command_option(dpp::co_attachment, "file", "Select an image"));
bot.global_command_create(newcommand);
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,9 @@
\page misc Miscellaneous Examples
This section lists examples that do not fit neatly into any of the categories above.
* \subpage making_a_http_request "Making arbitrary HTTP requests using D++"
* \subpage spdlog "Integrating with spdlog"
* \subpage caching-messages "Caching messages"
* \subpage collecting-reactions "Collecting Reactions"
* \subpage cpp-eval-command-discord "Making an eval command in C++"

View File

@ -0,0 +1,61 @@
\page caching-messages Caching Messages
By default D++ does not cache messages. The example program below demonstrates how to instantiate a custom cache using dpp::cache which will allow you to cache messages and query the cache for messages by ID.
This can be adjusted to cache any type derived from dpp::managed including types you define yourself.
@note This example will cache and hold onto messages forever! In a real world situation this would be bad. If you do use this,
you should use the dpp::cache::remove() method periodically to remove stale items. This is left out of this example as a learning
exercise to the reader. For further reading please see the documentation of dpp::cache
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <sstream>
int main() {
/* Create bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
/* Create a cache to contain types of dpp::message */
dpp::cache<dpp::message> message_cache;
bot.on_log(dpp::utility::cout_logger());
/* Message handler */
bot.on_message_create([&](const dpp::message_create_t &event) {
/* Make a permanent pointer using new, for each message to be cached */
dpp::message* m = new dpp::message();
/* Store the message into the pointer by copying it */
*m = event.msg;
/* Store the new pointer to the cache using the store() method */
message_cache.store(m);
/* Simple ghetto command handler. In the real world, use slashcommand or commandhandler here. */
std::stringstream ss(event.msg.content);
std::string cmd;
dpp::snowflake msg_id;
ss >> cmd;
/* Look for our command */
if (cmd == "!get") {
ss >> msg_id;
/* Search our cache for a cached message */
dpp::message* find_msg = message_cache.find(msg_id);
if (find_msg != nullptr) {
/* Found a cached message, echo it out */
bot.message_create(dpp::message(event.msg.channel_id, "This message had the following content: " + find_msg->content));
} else {
/* Nothing like that here. Have you checked under the carpet? */
bot.message_create(dpp::message(event.msg.channel_id, "There is no message cached with this ID"));
}
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~

View File

@ -0,0 +1,55 @@
\page collecting-reactions Collecting Reactions
D++ comes with many useful helper classes, but amongst these is something called dpp::collector. Collector is a template which can be specialised to automatically collect objects of a pre-determined type from events for a specific interval of time. Once this time period is up, or the class is otherwise signalled, a method is called with the complete set of collected objects.
In the example below we will use it to collect all reactions on a message.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
/* To create a collector we must derive from dpp::collector. As dpp::collector is a complicated template,
* various pre-made forms exist such as this one, reaction_collector.
*/
class react_collector : public dpp::reaction_collector {
public:
/* Collector will run for 20 seconds */
react_collector(dpp::cluster* cl, snowflake id) : dpp::message_collector(cl, 20, id) { }
/* On completion just output number of collected reactions to as a message. */
virtual void completed(const std::vector<dpp::collected_reaction>& list) {
if (list.size()) {
owner->message_create(dpp::message(list[0].channel_id, "I collected " + std::to_string(list.size()) + " reactions!"));
} else {
owner->message_create(dpp::message("... I got nothin'."));
}
}
};
int main() {
/* Create bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
/* Pointer to reaction collector */
react_collector* r = nullptr;
bot.on_log(dpp::utility::cout_logger());
/* Message handler */
bot.on_message_create([&](const dpp::message_create_t &event) {
/* If someone sends a message that has the text 'collect reactions!' start a reaction collector */
if (event.msg.content == "collect reactions!" && r == nullptr) {
/* Create a new reaction collector to collect reactions */
r = new react_collector(&bot, event.msg.id);
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~

View File

@ -0,0 +1,244 @@
\page cpp-eval-command-discord Making an eval command in C++
### What is an eval command anyway?
Many times people will ask: "how do i make a command like 'eval' in C++". For the uninitiated, an `eval` command is a command often found in interpreted languages such as Javascript and Python, which allows the developer to pass in raw interpreter statements which are then executed within the context of the running program, without any sandboxing. Eval commands are plain **evil**, if not properly coded in.
Needless to say, this is very dangerous. If you are asking how to do this, and want to put this into your bot, we trust that you have a very good reason to do so and have considered alternatives before resorting to this. The code below is for educational purposes only and makes several assumptions:
1. This code will only operate on UNIX-like systems such as Linux (not **Darwin**)
2. It assumes you use GCC, and have `g++` installed on your server and in your $PATH
3. The program will attempt to write to the current directory
4. No security checks will be done against the code, except for to check that it is being run by the bot's developer by snowflake id. It is entirely possible to send an `!eval exit(0);` and make the bot quit, for example, or delete files from the host operating system, if misused or misconfigured.
5. You're willing to wait a few seconds for compilation before your evaluated code runs. There isn't a way around this, as C++ is a compiled language.
To create this program you must create two files, `eval.h` and `eval.cpp`. The header file lists forward declarations of functions that you will be able to use directly within your `eval` code. As well as this the entire of D++ will be available to the eval command via the local variable `bot`, and the entire `on_message_create` event variable via a local variable called `event`.
The evaluated code will run within its own thread, so can execute for as long as it needs (but use common sense, don't go spawning a tight `while` loop that runs forever, you'll lock a thread at 100% CPU that won't ever end!).
### Implementation details
This code operates by outputting your provided code to be evaluated into a simple boilerplate program which can be compiled to a
shared object library (.so file). This .so file is then compiled with g++, using the `-shared` and `-fPIC` flags. If the program can be successfully compiled, it is then loaded using `dlopen()`, and the symbol `so_exec()` searched for within it, and called. This `so_exec()` function will contain the body of the code given to the eval command. Once this has been called and it has returned,
the `dlclose()` function is called to unload the library, and finally any temporary files (such as the .so file and its corresponding .cpp file) are cleaned up.
Docker is definitely recommended if you code on Windows/Mac OS, because docker desktop still uses a linux VM, so your code can easily use `.so` file and your code runs the same on your vps (if it also uses Linux distro)
### Source code
\warning If you manage to get your system, network, or anything else harmed by use or misuse of this code, we are not responsible. Don't say we didn't warn you! Find another way to solve your problem!
#### eval.h
Remember that eval.h contains forward-declarations of any functions you want to expose to the eval command. It is included both by the bot itself, and by any shared object files compiled for evaluation.
~~~~~~~~~~~~~~~~{.cpp}
#pragma once
/* This is the snowflake ID of the bot's developer.
* The eval command will be restricted to this user.
*/
#define MY_DEVELOPER 189759562910400512
/* Any functions you want to be usable from within an eval,
* that are not part of D++ itself or the message event, you
* can put here as forward declarations. The test_function()
* serves as an example.
*/
int test_function();
~~~~~~~~~~~~~~~~
#### eval.cpp
This is the main body of the example program.
~~~~~~~~~~~~~~~~{.cpp}
/**
* D++ eval command example.
* This is dangerous and for educational use only, here be dragons!
*/
#include <dpp/dpp.h>
#include <fmt/format.h>
#include <fstream>
#include <iostream>
/* We have to define this to make certain functions visible */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <link.h>
#include <dlfcn.h>
#include "eval.h"
/* This is an example function you can expose to your eval command */
int test_function() {
return 42;
}
/* Important: This code is for UNIX-like systems only, e.g.
* Linux, BSD, OSX. It will NOT work on Windows!
* Note for OSX you'll probably have to change all references
* from .so to .dylib.
*/
int main()
{
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* This won't work in a slash command very well yet, as there is not yet
* a multi-line slash command input type.
*/
bot.on_message_create([&bot](const auto & event) {
if (dpp::utility::utf8substr(event.msg.content, 0, 5) == "!eval") {
/**
* THIS IS CRITICALLY IMPORTANT!
* Never EVER make an eval command that isn't restricted to a specific developer by user id.
* With access to this command the person who invokes it has at best full control over
* your bot's user account and at worst, full control over your entire network!!!
* Eval commands are Evil (pun intended) and could even be considered a security
* vulnerability. YOU HAVE BEEN WARNED!
*/
if (event.msg.author.id != MY_DEVELOPER) {
bot.message_create(dpp::message(event.msg.channel_id, "On the day i do this for you, Satan will be ice skating to work."));
return;
}
/* We start by creating a string that contains a cpp program for a simple library.
* The library will contain one exported function called so_exec() that is called
* containing the raw C++ code to eval.
*/
std::string code = "#include <iostream>\n\
#include <string>\n\
#include <map>\n\
#include <unordered_map>\n\
#include <stdint.h>\n\
#include <dpp/dpp.h>\n\
#include <nlohmann/json.hpp>\n\
#include <fmt/format.h>\n\
#include \"eval.h\"\n\
extern \"C\" void so_exec(dpp::cluster& bot, dpp::message_create_t event) {\n\
" + dpp::utility::utf8substr(
event.msg.content,
6,
dpp::utility::utf8len(event.msg.content)
) + ";\n\
return;\n\
}";
/* Next we output this string full of C++ to a cpp file on disk.
* This code assumes the current directory is writeable. The file will have a
* unique name made from the user's id and the message id.
*/
std::string source_filename = std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp";
std::fstream code_file(source_filename, std::fstream::binary | std::fstream::out);
if (!code_file.is_open()) {
bot.message_create(dpp::message(event.msg.channel_id, "Unable to create source file for `eval`"));
return;
}
code_file << code;
code_file.close();
/* Now to actually compile the file. We use dpp::utility::exec to
* invoke a compiler. This assumes you are using g++, and it is in your path.
*/
double compile_start = dpp::utility::time_f();
dpp::utility::exec("g++", {
"-std=c++17",
"-shared", /* Build the output as a .so file */
"-fPIC",
std::string("-o") + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so",
std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp",
"-ldpp",
"-ldl"
}, [event, &bot, source_filename, compile_start](const std::string &output) {
/* After g++ is ran we end up inside this lambda with the output as a string */
double compile_time = dpp::utility::time_f() - compile_start;
/* Delete our cpp file, we don't need it any more */
std::string del_file = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp";
unlink(del_file.c_str());
/* On successful compilation g++ outputs nothing, so any output here is error output */
if (output.length()) {
bot.message_create(dpp::message(event.msg.channel_id, "Compile error: ```\n" + output + "\n```"));
} else {
/* Now for the meat of the function. To actually load
* our shared object we use dlopen() to load it into the
* memory space of our bot. If dlopen() returns a nullptr,
* the shared object could not be loaded. The user probably
* did something odd with the symbols inside their eval.
*/
std::string dl = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so";
auto shared_object_handle = dlopen(dl.c_str(), RTLD_NOW);
if (!shared_object_handle) {
const char *dlsym_error = dlerror();
bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" +
std::string(dlsym_error ? dlsym_error : "Unknown error") +"\n```"));
return;
}
/* This type represents the "void so_exec()" function inside
* the shared object library file.
*/
using function_pointer = void(*)(dpp::cluster&, dpp::message_create_t);
/* Attempt to find the function called so_exec() inside the
* library we just loaded. If we can't find it, then the user
* did something really strange in their eval. Also note it's
* important we call dlerror() here to reset it before trying
* to use it a second time. It's weird-ass C code and is just
* like that.
*/
dlerror();
function_pointer exec_run = (function_pointer)dlsym(shared_object_handle, "so_exec");
const char *dlsym_error = dlerror();
if (dlsym_error) {
bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" + std::string(dlsym_error) +"\n```"));
dlclose(shared_object_handle);
return;
}
/* Now we have a function pointer to our actual exec code in
* 'exec_run', so lets call it, and pass it a reference to
* the cluster, and also a copy of the message_create_t.
*/
double run_start = dpp::utility::time_f();
exec_run(bot, event);
double run_time = dpp::utility::time_f() - run_start;
/* When we're done with a .so file we must always dlclose() it */
dlclose(shared_object_handle);
/* We are now done with the compiled code too */
unlink(dl.c_str());
/* Output some statistics */
bot.message_create(dpp::message(event.msg.channel_id,
"Execution completed. Compile time: " + std::to_string(compile_time) +
"s, execution time " + std::to_string(run_time) + "s"));
}
});
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~
### Compilation
To compile this program you must link against `libdl`. It is also critically important to include the `-rdynamic` flag. For example:
```
g++ -std=c++17 -rdynamic -oeval eval.cpp -ldpp -ldl
```
### Example usage
\image html eval_example.png

View File

@ -0,0 +1,33 @@
\page making_a_http_request Making arbitrary HTTP requests using D++
If you wish to make arbitrary HTTP(S) requests to websites and APIs, e.g. to update statistics on bot lists, you can use code similar to the code below. You may pass any arbitrary POST data:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main() {
dpp::cluster bot("TOKEN GOES HERE");
bot.on_log(dpp::utility::cout_logger());
bot.on_ready([&bot](const dpp::ready_t& event) {
// Arbitrary post data as a string
std::string mypostdata = "{\"value\": 42}";
// Make a HTTP POST request. HTTP and HTTPS are supported here.
bot.request(
"http://www.somebotlist.com/api/servers", dpp::m_post, [](const dpp::http_request_completion_t & cc) {
// This callback is called when the HTTP request completes. See documentation of
// dpp::http_request_completion_t for information on the fields in the parameter.
std::cout << "I got reply: " << cc.body << " with HTTP status code: " << cc.status << "\n";
},
mypostdata,
"application/json",
{
{"Authorization", "Bearer tokengoeshere"}
}
);
});
bot.start(dpp::st_wait);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,63 @@
\page spdlog Integrating with spdlog
If you want to make your bot use spdlog, like aegis does, you can attach it to the on_log event. You can do this as follows:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <iomanip>
#include <dpp/dpp.h>
#include <fmt/format.h>
int main(int argc, char const *argv[])
{
dpp::cluster bot("token");
const std::string log_name = "mybot.log";
/* Set up spdlog logger */
std::shared_ptr<spdlog::logger> log;
spdlog::init_thread_pool(8192, 2);
std::vector<spdlog::sink_ptr> sinks;
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >();
auto rotating = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_name, 1024 * 1024 * 5, 10);
sinks.push_back(stdout_sink);
sinks.push_back(rotating);
log = std::make_shared<spdlog::async_logger>("logs", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
spdlog::register_logger(log);
log->set_pattern("%^%Y-%m-%d %H:%M:%S.%e [%L] [th#%t]%$ : %v");
log->set_level(spdlog::level::level_enum::debug);
/* Integrate spdlog logger to D++ log events */
bot.on_log([&bot, &log](const dpp::log_t & event) {
switch (event.severity) {
case dpp::ll_trace:
log->trace("{}", event.message);
break;
case dpp::ll_debug:
log->debug("{}", event.message);
break;
case dpp::ll_info:
log->info("{}", event.message);
break;
case dpp::ll_warning:
log->warn("{}", event.message);
break;
case dpp::ll_error:
log->error("{}", event.message);
break;
case dpp::ll_critical:
default:
log->critical("{}", event.message);
break;
}
});
/* Add the rest of your events */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,9 @@
\page music-and-audio Music and Audio
If you want to make noise, or capture noise, you're in the right place. You'll find examples here for creating basic music bots, or recording voice, amongst other things.
* \subpage soundboard "Creating a Sound Board"
* \subpage oggopus "Streaming Ogg Opus file"
* \subpage stream-mp3-discord-bot "Streaming MP3 files"
* \subpage record-user "Record yourself in a VC"
* \subpage joinvc "Join or switch to the voice channel of the user issuing a command"

View File

@ -0,0 +1,75 @@
\page joinvc Join or switch to the voice channel of the user issuing a command
When a user issues a command you may want to join their voice channel, e.g. in a music bot. If you are already on the same voice channel, the bot should do nothing (but be ready to instantly play audio) and if the user is on a different voice channel, the bot should switch to it. If the user is on no voice channel at all, this should be considered an error. This example shows how to do this.
\note Please be aware this example sends no audio, but indicates clearly in the comments where and how you should do so.
~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
int main(int argc, char const *argv[])
{
/* Setup the bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); // Privileged intent required to receive message content
bot.on_log(dpp::utility::cout_logger());
/* Use the on_message_create event to look for commands */
bot.on_message_create([&bot, robot, robot_size](const dpp::message_create_t & event) {
std::stringstream ss(event.msg.content);
std::string command;
ss >> command;
/* Switch to or join the vc the user is on. Syntax: .join */
if (command == ".join") {
dpp::guild * g = dpp::find_guild(event.msg.guild_id);
auto current_vc = event.from->get_voice(event.msg.guild_id);
bool join_vc = true;
/* Check if we are currently on any vc */
if (current_vc) {
/* Find the channel id that the user is currently on */
auto users_vc = g->voice_members.find(event.msg.author.id);
/* See if we currently share a channel with the user */
if (users_vc != g->voice_members.end() && current_vc->channel_id == users_vc->second.channel_id) {
join_vc = false;
/* We are on this voice channel, at this point we can send any audio instantly to vc:
* current_vc->send_audio_raw(...)
*/
} else {
/* We are on a different voice channel. Leave it, then join the new one
* by falling through to the join_vc branch below.
*/
event.from->disconnect_voice(event.msg.guild_id);
join_vc = true;
}
}
/* If we need to join a vc at all, join it here if join_vc == true */
if (join_vc) {
if (!g->connect_member_voice(event.msg.author.id)) {
/* The user issuing the command is not on any voice channel, we can't do anything */
bot.message_create(dpp::message(event.msg.channel_id, "You don't seem to be on a voice channel! :("));
} else {
/* We are now connecting to a vc. Wait for on_voice_ready
* event, and then send the audio within that event:
*
* event.voice_client->send_audio_raw(...);
*
* NOTE: We can't instantly send audio, as we have to wait for
* the connection to the voice server to be established!
*/
}
}
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,107 @@
\page stream-mp3-discord-bot Streaming MP3 files
To stream MP3 files via D++ you need to link an additional dependency to your bot, namely `libmpg123`. It is relatively simple when linking this library to your bot to then decode audio to PCM and send it to the dpp::discord_voice_client::send_audio_raw function as shown below:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <nlohmann/json.hpp>
#include <fmt/format.h>
#include <iomanip>
#include <sstream>
#include <vector>
#include <fstream>
#include <iostream>
#include <mpg123.h>
#include <out123.h>
/* For an example we will hardcode a path to some awesome music here */
#define MUSIC_FILE "/media/music/Rick Astley/Whenever You Need Somebody/Never Gonna Give You Up.mp3"
int main(int argc, char const *argv[])
{
/* This will hold the decoded MP3.
* The D++ library expects PCM format, which are raw sound
* data, 2 channel stereo, 16 bit signed 48000Hz.
*/
std::vector<uint8_t> pcmdata;
mpg123_init();
int err = 0;
unsigned char* buffer;
size_t buffer_size, done;
int channels, encoding;
long rate;
/* Note it is important to force the frequency to 48000 for Discord compatibility */
mpg123_handle *mh = mpg123_new(NULL, &err);
mpg123_param(mh, MPG123_FORCE_RATE, 48000, 48000.0);
/* Decode entire file into a vector. You could do this on the fly, but if you do that
* you may get timing issues if your CPU is busy at the time and you are streaming to
* a lot of channels/guilds.
*/
buffer_size = mpg123_outblock(mh);
buffer = new unsigned char[buffer_size];
/* Note: In a real world bot, this should have some error logging */
mpg123_open(mh, MUSIC_FILE);
mpg123_getformat(mh, &rate, &channels, &encoding);
unsigned int counter = 0;
for (int totalBytes = 0; mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK; ) {
for (auto i = 0; i < buffer_size; i++) {
pcmdata.push_back(buffer[i]);
}
counter += buffer_size;
totalBytes += done;
}
delete buffer;
mpg123_close(mh);
mpg123_delete(mh);
/* Setup the bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Use the on_message_create event to look for commands */
bot.on_message_create([&bot, &pcmdata](const dpp::message_create_t & event) {
std::stringstream ss(event.msg.content);
std::string command;
ss >> command;
/* Tell the bot to join the discord voice channel the user is on. Syntax: .join */
if (command == ".join") {
dpp::guild * g = dpp::find_guild(event.msg.guild_id);
if (!g->connect_member_voice(event.msg.author.id)) {
bot.message_create(dpp::message(event.msg.channel_id, "You don't seem to be on a voice channel! :("));
}
}
/* Tell the bot to play the mp3 file. Syntax: .mp3 */
if (command == ".mp3") {
dpp::voiceconn* v = event.from->get_voice(event.msg.guild_id);
if (v && v->voiceclient && v->voiceclient->is_ready()) {
/* Stream the already decoded MP3 file. This passes the PCM data to the library to be encoded to OPUS */
v->voiceclient->send_audio_raw((uint16_t*)pcmdata.data(), pcmdata.size());
}
}
});
/* Start bot */
bot.start(dpp::st_wait);
/* Clean up */
mpg123_exit();
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To compile this program you must remember to specify `libmpg123` alongside `libdpp` in the build command, for example:
` g++ -std=c++17 -o musictest musictest.cpp -lmpg123 -ldpp`

View File

@ -0,0 +1,268 @@
\page oggopus Streaming Ogg Opus file
This example shows how to stream an Ogg Opus file to a voice channel. This example requires some additional dependencies, namely `libogg` and `opusfile`.
~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ogg/ogg.h>
#include <opus/opusfile.h>
int main(int argc, char const *argv[])
{
/* Load an ogg opus file into memory.
* The bot expects opus packets to be 2 channel stereo, 48000Hz.
*
* You may use ffmpeg to encode songs to ogg opus:
* ffmpeg -i /path/to/song -c:a libopus -ar 48000 -ac 2 -vn -b:a 96K /path/to/opus.ogg
*/
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Use the on_message_create event to look for commands */
bot.on_message_create([&bot](const dpp::message_create_t & event) {
std::stringstream ss(event.msg.content);
std::string command;
ss >> command;
/* Tell the bot to join the discord voice channel the user is on. Syntax: .join */
if (command == ".join") {
dpp::guild * g = dpp::find_guild(event.msg.guild_id);
if (!g->connect_member_voice(event.msg.author.id)) {
bot.message_create(dpp::message(event.msg.channel_id, "You don't seem to be on a voice channel! :("));
}
}
/* Tell the bot to play the sound file */
if (command == ".play") {
dpp::voiceconn* v = event.from->get_voice(event.msg.guild_id);
if (v && v->voiceclient && v->voiceclient->is_ready()) {
ogg_sync_state oy;
ogg_stream_state os;
ogg_page og;
ogg_packet op;
OpusHead header;
char *buffer;
FILE *fd;
fd = fopen("/path/to/opus.ogg", "rb");
fseek(fd, 0L, SEEK_END);
size_t sz = ftell(fd);
rewind(fd);
ogg_sync_init(&oy);
int eos = 0;
int i;
buffer = ogg_sync_buffer(&oy, sz);
fread(buffer, 1, sz, fd);
ogg_sync_wrote(&oy, sz);
/**
* We must first verify that the stream is indeed ogg opus
* by reading the header and parsing it
*/
if (ogg_sync_pageout(&oy, &og) != 1)
{
fprintf(stderr,"Does not appear to be ogg stream.\n");
exit(1);
}
ogg_stream_init(&os, ogg_page_serialno(&og));
if (ogg_stream_pagein(&os,&og) < 0) {
fprintf(stderr,"Error reading initial page of ogg stream.\n");
exit(1);
}
if (ogg_stream_packetout(&os,&op) != 1)
{
fprintf(stderr,"Error reading header packet of ogg stream.\n");
exit(1);
}
/* We must ensure that the ogg stream actually contains opus data */
if (!(op.bytes > 8 && !memcmp("OpusHead", op.packet, 8)))
{
fprintf(stderr,"Not an ogg opus stream.\n");
exit(1);
}
/* Parse the header to get stream info */
int err = opus_head_parse(&header, op.packet, op.bytes);
if (err)
{
fprintf(stderr,"Not a ogg opus stream\n");
exit(1);
}
/* Now we ensure the encoding is correct for Discord */
if (header.channel_count != 2 && header.input_sample_rate != 48000)
{
fprintf(stderr,"Wrong encoding for Discord, must be 48000Hz sample rate with 2 channels.\n");
exit(1);
}
/* Now loop though all the pages and send the packets to the vc */
while (ogg_sync_pageout(&oy, &og) == 1){
ogg_stream_init(&os, ogg_page_serialno(&og));
if(ogg_stream_pagein(&os,&og)<0){
fprintf(stderr,"Error reading page of Ogg bitstream data.\n");
exit(1);
}
while (ogg_stream_packetout(&os,&op) != 0)
{
/* Read remaining headers */
if (op.bytes > 8 && !memcmp("OpusHead", op.packet, 8))
{
int err = opus_head_parse(&header, op.packet, op.bytes);
if (err)
{
fprintf(stderr,"Not a ogg opus stream\n");
exit(1);
}
if (header.channel_count != 2 && header.input_sample_rate != 48000)
{
fprintf(stderr,"Wrong encoding for Discord, must be 48000Hz sample rate with 2 channels.\n");
exit(1);
}
continue;
}
/* Skip the opus tags */
if (op.bytes > 8 && !memcmp("OpusTags", op.packet, 8))
continue;
/* Send the audio */
int samples = opus_packet_get_samples_per_frame(op.packet, 48000);
v->voiceclient->send_audio_opus(op.packet, op.bytes, samples / 48);
}
}
/* Cleanup */
ogg_stream_clear(&os);
ogg_sync_clear(&oy);
}
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~
You can compile this example using the following command
c++ /path/to/source.cc -ldpp -lopus -lopusfile -logg -I/usr/include/opus
## Using `liboggz`
You can use `liboggz` to stream an Ogg Opus file to discord voice channel.
`liboggz` provides higher level abstraction and useful APIs. Some API `liboggz` provide includes seeking and timestamp interpretation.
Read more on the [documentation](https://www.xiph.org/oggz/doc/).
~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <oggz/oggz.h>
int main(int argc, char const *argv[])
{
/* Load an ogg opus file into memory.
* The bot expects opus packets to be 2 channel stereo, 48000Hz.
*
* You may use ffmpeg to encode songs to ogg opus:
* ffmpeg -i /path/to/song -c:a libopus -ar 48000 -ac 2 -vn -b:a 96K /path/to/opus.ogg
*/
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Use the on_message_create event to look for commands */
bot.on_message_create([&bot](const dpp::message_create_t & event) {
std::stringstream ss(event.msg.content);
std::string command;
ss >> command;
/* Tell the bot to join the discord voice channel the user is on. Syntax: .join */
if (command == ".join") {
dpp::guild * g = dpp::find_guild(event.msg.guild_id);
if (!g->connect_member_voice(event.msg.author.id)) {
bot.message_create(dpp::message(event.msg.channel_id, "You don't seem to be on a voice channel! :("));
}
}
/* Tell the bot to play the sound file */
if (command == ".play") {
dpp::voiceconn* v = event.from->get_voice(event.msg.guild_id);
if (v && v->voiceclient && v->voiceclient->is_ready()) {
// load the audio file with oggz
OGGZ *track_og = oggz_open("/path/to/opus.ogg", OGGZ_READ);
if (track_og) {
// set read callback, this callback will be called on packets with the serialno,
// -1 means every packet will be handled with this callback
oggz_set_read_callback(
track_og, -1,
[](OGGZ *oggz, oggz_packet *packet, long serialno,
void *user_data) {
dpp::voiceconn *voiceconn = (dpp::voiceconn *)user_data;
// send the audio
voiceconn->voiceclient->send_audio_opus(packet->op.packet,
packet->op.bytes);
// make sure to always return 0 here, read more on oggz documentation
return 0;
},
// this will be the value of void *user_data
(void *)v);
// read loop
while (v && v->voiceclient && !v->voiceclient->terminating) {
// you can tweak this to whatever. Here I use BUFSIZ, defined in
// stdio.h as 8192
static const constexpr long CHUNK_READ = BUFSIZ * 2;
const long read_bytes = oggz_read(track_og, CHUNK_READ);
// break on eof
if (!read_bytes)
break;
}
} else {
fprintf(stderr, "Error opening file\n");
}
// don't forget to free the memory
oggz_close(track_og);
}
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~
You can compile this example using the following command
c++ /path/to/source.cc -ldpp -loggz

View File

@ -0,0 +1,67 @@
\page record-user Record yourself in a VC
DPP supports receiving audio. This examples show how to use it to record some user in a VC.
\note Voice receiving by bots is not officially supported by the Discord API. We cannot guarantee that this feature will work in the future.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
int main(int argc, char const *argv[])
{
/* Example to record a user in a VC
*
* Recording is output as './me.pcm' and you can play it via the soundboard example
* or use ffmpeg 'ffplay -f s16le -ar 48000 -ac 2 -i ./me.pcm'
*/
/* Replace with the user's id you wish to record */
dpp::snowflake user_id = 407877550216314882;
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
FILE *fd;
fd = fopen("./me.pcm", "wb");
bot.on_log(dpp::utility::cout_logger());
/* Use the on_message_create event to look for commands */
bot.on_message_create([&bot, &fd](const dpp::message_create_t & event) {
std::stringstream ss(event.msg.content);
std::string command;
ss >> command;
/* Tell the bot to record */
if (command == ".record") {
dpp::guild * g = dpp::find_guild(event.msg.guild_id);
if (!g->connect_member_voice(event.msg.author.id)) {
bot.message_create(dpp::message(
event.msg.channel_id,
"You don't seem to be on a voice channel! :("
));
}
}
/* Tell the bot to stop recording */
if (command == ".stop") {
event.from->disconnect_voice(event.msg.guild_id);
fclose(fd);
}
});
bot.on_voice_receive([&bot, &fd, &user_id](const dpp::voice_receive_t &event) {
if (event.user_id == user_id) {
fwrite((char *)event.audio, 1, event.audio_size, fd);
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~

View File

@ -0,0 +1,68 @@
\page soundboard Creating a Sound Board
This example script shows how to send a sound file to a voice channel. A few shortcuts are taken here, for more advanced techniques for connecting to a voice channel see the tutorial \ref joinvc
~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
int main(int argc, char const *argv[])
{
/* Load a sound file called Robot.pcm into memory.
* The bot expects PCM format, which are raw sound data,
* 2 channel stereo, 16 bit signed 48000Hz.
*
* You can use audacity to export these from WAV or MP3 etc.
*
* If you wanted to send a more complicated format, you could
* use a separate library to decode that audio to PCM. For
* example purposes, a raw PCM will suffice. This PCM file can
* be found within the bot's github repo.
*/
uint8_t* robot = nullptr;
size_t robot_size = 0;
std::ifstream input ("../testdata/Robot.pcm", std::ios::in|std::ios::binary|std::ios::ate);
if (input.is_open()) {
robot_size = input.tellg();
robot = new uint8_t[robot_size];
input.seekg (0, std::ios::beg);
input.read ((char*)robot, robot_size);
input.close();
}
/* Setup the bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); // Privileged intent required to receive message content
bot.on_log(dpp::utility::cout_logger());
/* Use the on_message_create event to look for commands */
bot.on_message_create([&bot, robot, robot_size](const dpp::message_create_t & event) {
std::stringstream ss(event.msg.content);
std::string command;
ss >> command;
/* Tell the bot to join the discord voice channel the user is on. Syntax: .join */
if (command == ".join") {
dpp::guild * g = dpp::find_guild(event.msg.guild_id);
if (!g->connect_member_voice(event.msg.author.id)) {
bot.message_create(dpp::message(event.msg.channel_id, "You don't seem to be on a voice channel! :("));
}
}
/* Tell the bot to play the sound file 'Robot.pcm'. Syntax: .robot */
if (command == ".robot") {
dpp::voiceconn* v = event.from->get_voice(event.msg.guild_id);
if (v && v->voiceclient && v->voiceclient->is_ready()) {
v->voiceclient->send_audio_raw((uint16_t*)robot, robot_size);
}
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,8 @@
\page the-basics The Basics
These example programs are great to get started with simple things in the D++ library, ideal for beginners to the language or to the Discord API.
* \subpage firstbot "Creating Your First Bot"
* \subpage embed-message "Sending Embeds"
* \subpage attach-file "Attaching a file"
* \subpage webhooks "Webhooks"

View File

@ -0,0 +1,112 @@
\page attach-file Attaching a file to a message
Attached files must be locally stored.
To attach a file to a message, you can upload a local image.
D++ has this helper function to read a file: `dpp::utility::read_file`.
An example program:
@note Because these examples utilizes message content, they require the message content privileged intent.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main() {
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Message handler to look for a command called !file */
bot.on_message_create([&bot](const dpp::message_create_t &event) {
if (event.msg.content == "!file") {
// create a message
dpp::message msg(event.msg.channel_id, "Hey there, i've got a new file!");
// attach the file to the message
msg.add_file("foobar.txt", dpp::utility::read_file("path_to_your_file.txt"));
// send the message
bot.message_create(msg);
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~
Attachments via an url aren't possible. But there's a workaround for. You can download the file and then attach it to the message.
To make requests, D++ also has a helper function: `dpp::cluster::request`.
The following example program shows how to request a file and attach it to a message.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main() {
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Message handler to look for a command called !file */
bot.on_message_create([&bot](const dpp::message_create_t &event) {
if (event.msg.content == "!file") {
// request an image
bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [&bot, channel_id = event.msg.channel_id](const dpp::http_request_completion_t & httpRequestCompletion) {
// create a message
dpp::message msg(channel_id, "This is my new attachment:");
// attach the image on success
if (httpRequestCompletion.status == 200) {
msg.add_file("logo.png", httpRequestCompletion.body);
}
// send the message
bot.message_create(msg);
});
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~
Here's another example of how to add a local image to an embed.
Upload the image in the same message as the embed and then reference it in the embed.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main() {
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* Message handler to look for a command called !file */
bot.on_message_create([&bot](const dpp::message_create_t &event) {
if (event.msg.content == "!file") {
// create a message
dpp::message msg(event.msg.channel_id, "");
// attach the image to the message
msg.add_file("image.jpg", dpp::utility::read_file("path_to_your_image.jpg"));
dpp::embed embed;
embed.set_image("attachment://image.jpg"); // reference to the attached file
msg.add_embed(embed);
// send the message
bot.message_create(msg);
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~

View File

@ -0,0 +1,56 @@
\page embed-message Sending Embeds
You might have seen these special messages, often sent by bots. In this section, we will show how to create an embed.
@note Because this example utilizes message content, it requires the message content privileged intent.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main() {
/* Setup the bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
/* Message handler to look for a command called !embed */
bot.on_message_create([&bot](const dpp::message_create_t & event) {
if (event.msg.content == "!embed") {
/* create the embed */
dpp::embed embed = dpp::embed().
set_color(dpp::colors::sti_blue).
set_title("Some name").
set_url("https://dpp.dev/").
set_author("Some name", "https://dpp.dev/", "https://dpp.dev/DPP-Logo.png").
set_description("Some description here").
set_thumbnail("https://dpp.dev/DPP-Logo.png").
add_field(
"Regular field title",
"Some value here"
).
add_field(
"Inline field title",
"Some value here",
true
).
add_field(
"Inline field title",
"Some value here",
true
).
set_image("https://dpp.dev/DPP-Logo.png").
set_footer(dpp::embed_footer().set_text("Some footer text here").set_icon("https://dpp.dev/DPP-Logo.png")).
set_timestamp(time(0));
/* reply with the created embed */
bot.message_create(dpp::message(event.msg.channel_id, embed).set_reference(event.msg.id));
}
});
bot.start(dpp::st_wait);
return 0;
}
~~~~~~~~~~
The code will send the following message.
\image html embed.png

View File

@ -0,0 +1,244 @@
\page firstbot Creating Your First Bot
In this example we will create a C++ version of the [discord.js](https://discord.js.org/#/) example program.
The two programs can be seen side by side below:
<table>
<tr>
<th>D++</th>
<th>Discord.js</td>
</tr>
<tr>
<td>
~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
const std::string BOT_TOKEN = "add your token here";
int main() {
dpp::cluster bot(BOT_TOKEN);
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([](const dpp::slashcommand_t& event) {
if (event.command.get_command_name() == "ping") {
event.reply("Pong!");
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
bot.global_command_create(
dpp::slashcommand("ping", "Ping pong!", bot.me.id)
);
}
});
bot.start(dpp::st_wait);
}
~~~~~~~~~~~~~~~
</td>
<td>
~~~~~~~~~~~~~~~{.cpp}
let Discord = require('discord.js');
let BOT_TOKEN = 'add your token here';
let bot = new Discord.Client({ intents: [] });
bot.on('interactionCreate', (interaction) => {
if (interaction.isCommand() && interaction.commandName === 'ping') {
interaction.reply({content: 'Pong!'});
}
});
bot.once('ready', async () => {
await client.commands.create({
name: 'ping',
description: "Ping pong!"
});
});
bot.login(BOT_TOKEN);
~~~~~~~~~~~~~~~
</td>
</tr>
</table>
Let's break this program down step by step:
### 1. Start with an empty C++ program
Make sure to include the header file for the D++ library with the instruction \#include `<dpp/dpp.h>`!
~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main() {
}
~~~~~~~~~~~~~~
### 2. Create an instance of dpp::cluster
To make use of the library you must create a dpp::cluster object. This object is the main object in your program like the `Discord.Client` object in Discord.js.
You can instantiate this class as shown below. Remember to put your bot token in the constant!
~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
const std::string BOT_TOKEN = "add your token here";
int main() {
dpp::cluster bot(BOT_TOKEN);
}
~~~~~~~~~~~~~~~
### 3. Attach to an event
To have a bot that does something, you should attach to some events. Let's start by attaching to the `on_ready` event (dpp::cluster::on_ready) which will notify your program when the bot is connected. In this event, we will register a slash
command called 'ping'. Note that we must wrap our registration of the command in a template called `dpp::run_once` which prevents it from being re-run
every time your bot does a full reconnection (e.g. if the connection fails).
~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
const std::string BOT_TOKEN = "add your token here";
int main() {
dpp::cluster bot(BOT_TOKEN);
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id));
}
});
}
~~~~~~~~~~~~~~~~
### 4. Attach to another event to receive slash commands
If you want to handle a slash command, you should also attach your program to the `on_slashcommand` event (dpp::cluster::on_slashcommand) which is basically the same as the Discord.js `interactionCreate` event. Lets add this to the program before the `on_ready` event:
~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
const std::string BOT_TOKEN = "add your token here";
int main() {
dpp::cluster bot(BOT_TOKEN);
bot.on_slashcommand([](const dpp::slashcommand_t& event) {
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id));
}
});
}
~~~~~~~~~~~~~~
### 5 . Add some content to the events
Attaching to an event is a good start, but to make a bot you should actually put some program code into the interaction event. We will add some code to the `on_slashcommand` to look for our slash command '/ping' and reply with `Pong!`:
~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
const std::string BOT_TOKEN = "add your token here";
int main() {
dpp::cluster bot(BOT_TOKEN);
bot.on_slashcommand([](const dpp::slashcommand_t& event) {
if (event.command.get_command_name() == "ping") {
event.reply("Pong!");
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id));
}
});
}
~~~~~~~~~~~~~~~~~~~~~~~
Let's break down the code in the `on_slashcommand` event so that we can discuss what it is doing:
~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
bot.on_slashcommand([](const dpp::slashcommand_t& event) {
if (event.command.get_command_name() == "ping") {
event.reply("Pong!");
}
});
~~~~~~~~~~~~~~~~~~~~~~~
This code is simply comparing the command name `event.command.get_command_name()` (dpp::interaction::get_command_name) against the value in a constant string value `"ping"`. If they match, then the `event.reply` method is called.
The `event.reply` function (dpp::slashcommand_t::reply) replies to a slash command with a message. There are many ways to call this function to send embed messages, upload files, and more, but for this simple demonstration we will just send some message text.
### 6. Add code to start the bot!
To make the bot start, we must call the cluster::start method, e.g. in our program by using `bot.start(dpp::st_wait)`.
We also add a line to tell the library to output all its log information to the console, `bot.on_log(dpp::utility::cout_logger());` - if you wanted to do something more advanced, you can replace this parameter with a lambda just like all other events.
The parameter which we set to false indicates if the function should return once all shards are created. Passing `false` here tells the program you do not need to do anything once `bot.start` is called.
~~~~~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
const std::string BOT_TOKEN = "add your token here";
int main() {
dpp::cluster bot(BOT_TOKEN);
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([](const dpp::slashcommand_t& event) {
if (event.command.get_command_name() == "ping") {
event.reply("Pong!");
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
bot.global_command_create(dpp::slashcommand("ping", "Ping pong!", bot.me.id));
}
});
bot.start(dpp::st_wait);
}
~~~~~~~~~~~~~~
### 7. Compile and run your bot
Compile your bot using `g++ -std=c++17 -o bot bot.cpp -ldpp` (if your .cpp file is called `bot.cpp`) and run it with `./bot`.
### 8. Inviting your bot to your server
When you invite your bot, you must use the `applications.commands` and `bots` scopes to ensure your bot can create guild slash commands. For example:
`https://discord.com/oauth2/authorize?client_id=YOUR-BOTS-ID-HERE&scope=bot+applications.commands&permissions=BOT-PERMISSIONS-HERE`
Replace `YOUR-BOTS-ID-HERE` with your bot's user ID, and `BOT-PERMISSIONS-HERE` with the permissions your bot requires.
**Congratulations** - you now have a working bot written using the D++ library!

View File

@ -0,0 +1,26 @@
\page webhooks Webhooks
Webhooks are a simple way to post messages from other apps and websites into Discord. They allow getting automated messages and data updates sent to a text channel in your server. [Read more](https://support.discord.com/hc/en-us/articles/228383668) in this article about Webhooks.
The following code shows how to send messages in a channel using a webhook.
~~~~~~~~~~{.cpp}
#include <dpp/dpp.h>
int main()
{
dpp::cluster bot(""); // normally, you put your bot token in here. But to just run a webhook its not required
bot.on_log(dpp::utility::cout_logger());
/* construct a webhook object using the URL you got from Discord */
dpp::webhook wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE");
/* send a message with this webhook */
bot.execute_webhook_sync(wh, dpp::message("Have a great time here :smile:"));
return 0;
}
~~~~~~~~~~
The above is just a very simple example. You can also send embed messages. All you have to do is to add an embed to the message you want to send. If you want to, you can also send it into a thread.

58
vendor/DPP/docpages/footer.html vendored Normal file
View File

@ -0,0 +1,58 @@
<div id="nav-path" class="navpath">
<ul>
$navpath
</ul>
</div>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-QTH6YHBNG5"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/base16/dracula.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100&display=swap" rel="stylesheet">
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-QTH6YHBNG5');
$(function() {
$(".fragment").each(function(i,node) {
var $node = $(node);
$node.children(":not(.line)").remove();
$node.html("<pre><code class='stan'>" + $node.text().trim().replaceAll("<", "&lt;").replaceAll(">", "&gt;") + "</code></pre>");
hljs.configure({
languages: ['cpp','diff','cmake','bash','sh','text'],
ignoreUnescapedHTML: true
});
hljs.highlightAll(node);
hljs.initLineNumbersOnLoad(node);
});
$(".fragment").parent().parent().parent().parent().removeClass('doxtable');
});
</script>
<style>
.hljs-ln-code, code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span {
font-family: 'JetBrains Mono', monospace !important;
font-size: 0.8rem !important;
}
.fragment {
padding: 0 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
background: none !important;
border: 0 !important;
}
.hljs-ln-n::before {
content: attr(data-line-number);
padding-right: 1rem !important;
}
</style>
<div style="z-index: -9999; position: absolute; right: 0; top: 0; font-size: 0.0001rem;color:transparent;background:none">
<!-- For crawlability of past versions -->
<a href='/9.0.13/'>D++ Library version 9.0.13</a><a href='/9.0.12/'>D++ Library version 9.0.12</a><a href='/9.0.11/'>D++ Library version 9.0.11</a><a href='/9.0.10/'>D++ Library version 9.0.10</a><a href='/9.0.9/'>D++ Library version 9.0.9</a><a href='/9.0.8/'>D++ Library version 9.0.8</a><a href='/9.0.7/'>D++ Library version 9.0.7</a><a href='/9.0.6/'>D++ Library version 9.0.6</a><a href='/9.0.5/'>D++ Library version 9.0.5</a><a href='/9.0.4/'>D++ Library version 9.0.4</a><a href='/9.0.3/'>D++ Library version 9.0.3</a><a href='/9.0.2/'>D++ Library version 9.0.2</a><a href='/9.0.1/'>D++ Library version 9.0.1</a><a href='/9.0.0/'>D++ Library version 9.0.0</a><a href='/1.0.2/'>D++ Library version 1.0.2</a><a href='/1.0.1/'>D++ Library version 1.0.1</a><a href='/1.0.0/'>D++ Library version 1.0.0</a>
</div>
</body>
</html>

View File

@ -0,0 +1,63 @@
<div id="nav-path" class="navpath">
<ul>
$navpath
</ul>
</div>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-QTH6YHBNG5"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/base16/dracula.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@100&display=swap" rel="stylesheet">
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-QTH6YHBNG5');
$(function() {
$(".fragment").each(function(i,node) {
var $node = $(node);
$node.children(":not(.line)").remove();
$node.html("<pre><code class='stan'>" + $node.text().trim().replaceAll("<", "&lt;").replaceAll(">", "&gt;") + "</code></pre>");
hljs.configure({
languages: ['cpp','diff','cmake','bash','sh','text'],
ignoreUnescapedHTML: true
});
hljs.highlightAll(node);
hljs.initLineNumbersOnLoad(node);
});
$(".fragment").parent().parent().parent().parent().removeClass('doxtable');
});
</script>
<style>
.hljs-ln-code, code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span {
font-family: 'JetBrains Mono', monospace !important;
font-size: 0.8rem !important;
}
.fragment {
padding: 0 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
background: none !important;
border: 0 !important;
}
.hljs-ln-n::before {
content: attr(data-line-number);
padding-right: 1rem !important;
}
table.markdownTable code td, table.markdownTable code th, table.fieldtable code td, table.fieldtable code th, table.doxtable code td, table.doxtable code th {
border: 0 !important;
padding: 0 !important;
}
</style>
<div style="z-index: -9999; position: absolute; right: 0; top: 0; font-size: 0.0001rem;color:transparent;background:none">
<!-- For crawlability of past versions -->
###PREV###
</div>
</body>
</html>

66
vendor/DPP/docpages/header.html vendored Normal file
View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<meta property="og:title" content="$title - D++ - The lightweight C++ Discord API Library">
<meta property="og:description" content="A lightweight C++ Discord API library supporting the entire Discord API, including Slash Commands, Voice/Audio, Sharding, Clustering and more!">
<meta name="description" content="$title - D++ - A lightweight C++ Discord API library supporting the entire Discord API, including Slash Commands, Voice/Audio, Sharding, Clustering and more!">
<meta property="og:image" content="https://dpp.dev/DPP-Logo.png">
<meta property="og:url" content="https://dpp.dev/">
<meta property="og:type" content="website">
<meta property="twitter:title" content="$title - D++ - The lightweight C++ Discord API Library">
<!--BEGIN PROJECT_NAME--><title>$title - D++ - The lightweight C++ Discord API Library</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title - D++ - The lightweight C++ Discord API Library</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<script>
setTimeout(function() {
$("#main-menu").html($("#main-menu").html() + "<li><select name='vsv' onchange='window.location.href=this.options[this.selectedIndex].value'><option value='/'>master</option><option value='/9.0.13/'>9.0.13</option><option value='/9.0.12/'>9.0.12</option><option value='/9.0.11/'>9.0.11</option><option value='/9.0.10/'>9.0.10</option><option value='/9.0.9/'>9.0.9</option><option value='/9.0.8/'>9.0.8</option><option value='/9.0.7/'>9.0.7</option><option value='/9.0.6/'>9.0.6</option><option value='/9.0.5/'>9.0.5</option><option value='/9.0.4/'>9.0.4</option><option value='/9.0.3/'>9.0.3</option><option value='/9.0.2/'>9.0.2</option><option value='/9.0.1/'>9.0.1</option><option value='/9.0.0/'>9.0.0</option><option value='/1.0.2/'>1.0.2</option><option value='/1.0.1/'>1.0.1</option><option value='/1.0.0/'>1.0.0</option></select></li>");
}, 500);
</script>
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr>
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
<label for="MSearchField" style="display: none">Search</label>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<meta property="og:title" content="$title - D++ - The lightweight C++ Discord API Library">
<meta property="og:description" content="A lightweight C++ Discord API library supporting the entire Discord API, including Slash Commands, Voice/Audio, Sharding, Clustering and more!">
<meta name="description" content="$title - D++ - A lightweight C++ Discord API library supporting the entire Discord API, including Slash Commands, Voice/Audio, Sharding, Clustering and more!">
<meta property="og:image" content="https://dpp.dev/DPP-Logo.png">
<meta property="og:url" content="https://dpp.dev/">
<meta property="og:type" content="website">
<meta property="twitter:title" content="$title - D++ - The lightweight C++ Discord API Library">
<!--BEGIN PROJECT_NAME--><title>$title - D++ - The lightweight C++ Discord API Library</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title - D++ - The lightweight C++ Discord API Library</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<script>
setTimeout(function() {
$("#main-menu").html($("#main-menu").html() + "<li><select name='vsv' onchange='window.location.href=this.options[this.selectedIndex].value'>##VERSION_OPTIONS##</select></li>");
}, 500);
</script>
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr>
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->
<label for="MSearchField" style="display: none">Search</label>

BIN
vendor/DPP/docpages/images/DPP-Logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
vendor/DPP/docpages/images/DPP-Small.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

View File

@ -0,0 +1,499 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1503pt" height="965pt" viewBox="0 0 1503 965" version="1.1">
<defs>
<g>
<symbol overflow="visible" id="glyph0-0">
<path style="stroke:none;" d="M 8.238281 -8.609375 L 8.238281 -7.277344 C 7.8125 -7.671875 7.359375 -7.964844 6.878906 -8.164063 C 6.398438 -8.355469 5.886719 -8.453125 5.347656 -8.457031 C 4.277344 -8.453125 3.460938 -8.128906 2.898438 -7.480469 C 2.332031 -6.828125 2.050781 -5.886719 2.050781 -4.652344 C 2.050781 -3.425781 2.332031 -2.484375 2.898438 -1.832031 C 3.460938 -1.179688 4.277344 -0.855469 5.347656 -0.855469 C 5.886719 -0.855469 6.398438 -0.953125 6.878906 -1.148438 C 7.359375 -1.34375 7.8125 -1.636719 8.238281 -2.035156 L 8.238281 -0.71875 C 7.796875 -0.417969 7.328125 -0.191406 6.835938 -0.0429688 C 6.339844 0.105469 5.820313 0.175781 5.273438 0.179688 C 3.859375 0.175781 2.746094 -0.253906 1.9375 -1.113281 C 1.121094 -1.976563 0.714844 -3.15625 0.71875 -4.652344 C 0.714844 -6.152344 1.121094 -7.332031 1.9375 -8.199219 C 2.746094 -9.058594 3.859375 -9.492188 5.273438 -9.496094 C 5.828125 -9.492188 6.351563 -9.417969 6.847656 -9.273438 C 7.339844 -9.121094 7.804688 -8.898438 8.238281 -8.609375 Z M 8.238281 -8.609375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M 1.207031 -9.71875 L 2.355469 -9.71875 L 2.355469 0 L 1.207031 0 Z M 1.207031 -9.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-2">
<path style="stroke:none;" d="M 1.085938 -2.761719 L 1.085938 -6.996094 L 2.234375 -6.996094 L 2.234375 -2.804688 C 2.230469 -2.140625 2.359375 -1.644531 2.621094 -1.3125 C 2.878906 -0.980469 3.269531 -0.8125 3.785156 -0.816406 C 4.402344 -0.8125 4.890625 -1.011719 5.253906 -1.410156 C 5.613281 -1.804688 5.792969 -2.34375 5.796875 -3.03125 L 5.796875 -6.996094 L 6.945313 -6.996094 L 6.945313 0 L 5.796875 0 L 5.796875 -1.074219 C 5.515625 -0.644531 5.191406 -0.332031 4.824219 -0.128906 C 4.453125 0.0742188 4.027344 0.175781 3.542969 0.179688 C 2.734375 0.175781 2.125 -0.0703125 1.710938 -0.566406 C 1.292969 -1.0625 1.082031 -1.792969 1.085938 -2.761719 Z M 1.085938 -2.761719 "/>
</symbol>
<symbol overflow="visible" id="glyph0-3">
<path style="stroke:none;" d="M 5.664063 -6.789063 L 5.664063 -5.703125 C 5.339844 -5.867188 5.003906 -5.992188 4.652344 -6.078125 C 4.300781 -6.15625 3.9375 -6.199219 3.566406 -6.203125 C 2.992188 -6.199219 2.5625 -6.113281 2.28125 -5.941406 C 1.992188 -5.765625 1.851563 -5.5 1.855469 -5.152344 C 1.851563 -4.882813 1.953125 -4.675781 2.160156 -4.523438 C 2.363281 -4.371094 2.773438 -4.226563 3.390625 -4.089844 L 3.785156 -4.003906 C 4.597656 -3.824219 5.175781 -3.578125 5.523438 -3.261719 C 5.863281 -2.941406 6.035156 -2.496094 6.039063 -1.929688 C 6.035156 -1.277344 5.777344 -0.765625 5.265625 -0.386719 C 4.75 -0.0117188 4.042969 0.175781 3.148438 0.179688 C 2.769531 0.175781 2.378906 0.140625 1.976563 0.0703125 C 1.566406 0 1.140625 -0.109375 0.695313 -0.257813 L 0.695313 -1.441406 C 1.117188 -1.222656 1.535156 -1.054688 1.949219 -0.945313 C 2.359375 -0.832031 2.765625 -0.777344 3.171875 -0.78125 C 3.710938 -0.777344 4.128906 -0.871094 4.421875 -1.058594 C 4.710938 -1.242188 4.855469 -1.503906 4.859375 -1.84375 C 4.855469 -2.152344 4.75 -2.390625 4.542969 -2.5625 C 4.332031 -2.726563 3.871094 -2.886719 3.160156 -3.042969 L 2.761719 -3.136719 C 2.042969 -3.28125 1.527344 -3.511719 1.214844 -3.824219 C 0.894531 -4.132813 0.738281 -4.558594 0.742188 -5.101563 C 0.738281 -5.757813 0.972656 -6.265625 1.441406 -6.625 C 1.90625 -6.980469 2.566406 -7.160156 3.429688 -7.164063 C 3.851563 -7.160156 4.253906 -7.128906 4.628906 -7.070313 C 5.003906 -7.003906 5.347656 -6.910156 5.664063 -6.789063 Z M 5.664063 -6.789063 "/>
</symbol>
<symbol overflow="visible" id="glyph0-4">
<path style="stroke:none;" d="M 2.34375 -8.984375 L 2.34375 -6.996094 L 4.710938 -6.996094 L 4.710938 -6.101563 L 2.34375 -6.101563 L 2.34375 -2.304688 C 2.34375 -1.730469 2.421875 -1.363281 2.578125 -1.203125 C 2.734375 -1.039063 3.050781 -0.960938 3.53125 -0.960938 L 4.710938 -0.960938 L 4.710938 0 L 3.53125 0 C 2.640625 0 2.027344 -0.164063 1.691406 -0.496094 C 1.351563 -0.824219 1.183594 -1.425781 1.1875 -2.304688 L 1.1875 -6.101563 L 0.34375 -6.101563 L 0.34375 -6.996094 L 1.1875 -6.996094 L 1.1875 -8.984375 Z M 2.34375 -8.984375 "/>
</symbol>
<symbol overflow="visible" id="glyph0-5">
<path style="stroke:none;" d="M 7.191406 -3.785156 L 7.191406 -3.222656 L 1.90625 -3.222656 C 1.953125 -2.429688 2.191406 -1.824219 2.621094 -1.414063 C 3.042969 -0.996094 3.636719 -0.789063 4.402344 -0.792969 C 4.839844 -0.789063 5.269531 -0.84375 5.6875 -0.953125 C 6.097656 -1.058594 6.507813 -1.222656 6.921875 -1.441406 L 6.921875 -0.355469 C 6.507813 -0.179688 6.085938 -0.0507813 5.652344 0.0429688 C 5.21875 0.128906 4.78125 0.175781 4.335938 0.179688 C 3.21875 0.175781 2.332031 -0.144531 1.683594 -0.792969 C 1.027344 -1.441406 0.703125 -2.320313 0.707031 -3.429688 C 0.703125 -4.570313 1.011719 -5.480469 1.632813 -6.15625 C 2.25 -6.824219 3.085938 -7.160156 4.136719 -7.164063 C 5.074219 -7.160156 5.816406 -6.855469 6.367188 -6.253906 C 6.914063 -5.644531 7.1875 -4.824219 7.191406 -3.785156 Z M 6.039063 -4.121094 C 6.027344 -4.746094 5.851563 -5.25 5.511719 -5.628906 C 5.164063 -6 4.710938 -6.1875 4.148438 -6.191406 C 3.507813 -6.1875 2.992188 -6.003906 2.609375 -5.644531 C 2.21875 -5.277344 1.996094 -4.769531 1.941406 -4.117188 Z M 6.039063 -4.121094 "/>
</symbol>
<symbol overflow="visible" id="glyph0-6">
<path style="stroke:none;" d="M 5.257813 -5.921875 C 5.125 -5.996094 4.984375 -6.050781 4.835938 -6.085938 C 4.679688 -6.121094 4.515625 -6.140625 4.335938 -6.140625 C 3.683594 -6.140625 3.183594 -5.925781 2.835938 -5.503906 C 2.488281 -5.078125 2.3125 -4.472656 2.316406 -3.683594 L 2.316406 0 L 1.160156 0 L 1.160156 -6.996094 L 2.316406 -6.996094 L 2.316406 -5.910156 C 2.554688 -6.332031 2.871094 -6.648438 3.257813 -6.855469 C 3.644531 -7.058594 4.113281 -7.160156 4.671875 -7.164063 C 4.75 -7.160156 4.839844 -7.15625 4.933594 -7.148438 C 5.027344 -7.136719 5.132813 -7.121094 5.253906 -7.101563 Z M 5.257813 -5.921875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-7">
<path style="stroke:none;" d="M 6.847656 -9.019531 L 6.847656 -7.789063 C 6.367188 -8.015625 5.914063 -8.1875 5.492188 -8.300781 C 5.0625 -8.410156 4.652344 -8.464844 4.261719 -8.46875 C 3.570313 -8.464844 3.042969 -8.332031 2.671875 -8.070313 C 2.296875 -7.800781 2.109375 -7.421875 2.113281 -6.933594 C 2.109375 -6.519531 2.234375 -6.210938 2.484375 -6 C 2.730469 -5.789063 3.199219 -5.617188 3.890625 -5.492188 L 4.652344 -5.335938 C 5.59375 -5.152344 6.289063 -4.835938 6.738281 -4.386719 C 7.183594 -3.933594 7.40625 -3.328125 7.410156 -2.574219 C 7.40625 -1.667969 7.101563 -0.984375 6.5 -0.519531 C 5.890625 -0.0546875 5.003906 0.175781 3.835938 0.179688 C 3.390625 0.175781 2.921875 0.125 2.425781 0.03125 C 1.925781 -0.0703125 1.410156 -0.214844 0.878906 -0.414063 L 0.878906 -1.710938 C 1.390625 -1.421875 1.890625 -1.203125 2.386719 -1.0625 C 2.875 -0.914063 3.359375 -0.84375 3.835938 -0.84375 C 4.554688 -0.84375 5.109375 -0.984375 5.503906 -1.265625 C 5.890625 -1.546875 6.085938 -1.949219 6.089844 -2.480469 C 6.085938 -2.933594 5.945313 -3.292969 5.667969 -3.554688 C 5.382813 -3.808594 4.921875 -4.003906 4.285156 -4.136719 L 3.515625 -4.285156 C 2.574219 -4.46875 1.890625 -4.761719 1.472656 -5.164063 C 1.050781 -5.5625 0.84375 -6.121094 0.84375 -6.832031 C 0.84375 -7.65625 1.132813 -8.304688 1.714844 -8.78125 C 2.292969 -9.253906 3.09375 -9.492188 4.117188 -9.496094 C 4.550781 -9.492188 4.996094 -9.453125 5.453125 -9.375 C 5.902344 -9.296875 6.367188 -9.175781 6.847656 -9.019531 Z M 6.847656 -9.019531 "/>
</symbol>
<symbol overflow="visible" id="glyph0-8">
<path style="stroke:none;" d="M 7.019531 -4.222656 L 7.019531 0 L 5.871094 0 L 5.871094 -4.183594 C 5.871094 -4.847656 5.742188 -5.34375 5.484375 -5.671875 C 5.226563 -6 4.839844 -6.164063 4.324219 -6.164063 C 3.699219 -6.164063 3.207031 -5.964844 2.851563 -5.570313 C 2.492188 -5.171875 2.3125 -4.632813 2.316406 -3.953125 L 2.316406 0 L 1.160156 0 L 1.160156 -9.71875 L 2.316406 -9.71875 L 2.316406 -5.910156 C 2.589844 -6.328125 2.914063 -6.640625 3.289063 -6.851563 C 3.65625 -7.054688 4.085938 -7.160156 4.578125 -7.164063 C 5.378906 -7.160156 5.988281 -6.910156 6.402344 -6.417969 C 6.8125 -5.917969 7.019531 -5.1875 7.019531 -4.222656 Z M 7.019531 -4.222656 "/>
</symbol>
<symbol overflow="visible" id="glyph0-9">
<path style="stroke:none;" d="M 4.386719 -3.515625 C 3.453125 -3.515625 2.808594 -3.40625 2.453125 -3.195313 C 2.09375 -2.980469 1.914063 -2.621094 1.917969 -2.113281 C 1.914063 -1.703125 2.050781 -1.378906 2.320313 -1.140625 C 2.585938 -0.898438 2.949219 -0.777344 3.417969 -0.78125 C 4.050781 -0.777344 4.5625 -1.003906 4.949219 -1.457031 C 5.332031 -1.90625 5.523438 -2.507813 5.527344 -3.261719 L 5.527344 -3.515625 Z M 6.675781 -3.992188 L 6.675781 0 L 5.527344 0 L 5.527344 -1.0625 C 5.261719 -0.632813 4.9375 -0.320313 4.546875 -0.121094 C 4.15625 0.078125 3.675781 0.175781 3.109375 0.179688 C 2.390625 0.175781 1.820313 -0.0234375 1.402344 -0.421875 C 0.976563 -0.820313 0.765625 -1.355469 0.769531 -2.035156 C 0.765625 -2.820313 1.03125 -3.414063 1.558594 -3.816406 C 2.082031 -4.214844 2.867188 -4.417969 3.917969 -4.417969 L 5.527344 -4.417969 L 5.527344 -4.527344 C 5.523438 -5.054688 5.351563 -5.464844 5.003906 -5.753906 C 4.65625 -6.042969 4.167969 -6.1875 3.542969 -6.191406 C 3.140625 -6.1875 2.75 -6.140625 2.371094 -6.046875 C 1.992188 -5.949219 1.628906 -5.804688 1.28125 -5.617188 L 1.28125 -6.675781 C 1.699219 -6.835938 2.105469 -6.960938 2.503906 -7.042969 C 2.894531 -7.121094 3.28125 -7.160156 3.660156 -7.164063 C 4.671875 -7.160156 5.425781 -6.898438 5.925781 -6.375 C 6.421875 -5.851563 6.671875 -5.054688 6.675781 -3.992188 Z M 6.675781 -3.992188 "/>
</symbol>
<symbol overflow="visible" id="glyph0-10">
<path style="stroke:none;" d="M 5.808594 -5.933594 L 5.808594 -9.71875 L 6.957031 -9.71875 L 6.957031 0 L 5.808594 0 L 5.808594 -1.050781 C 5.5625 -0.628906 5.257813 -0.320313 4.890625 -0.121094 C 4.519531 0.078125 4.078125 0.175781 3.566406 0.179688 C 2.71875 0.175781 2.03125 -0.160156 1.5 -0.832031 C 0.96875 -1.503906 0.703125 -2.390625 0.707031 -3.492188 C 0.703125 -4.589844 0.96875 -5.476563 1.5 -6.152344 C 2.03125 -6.824219 2.71875 -7.160156 3.566406 -7.164063 C 4.078125 -7.160156 4.519531 -7.058594 4.890625 -6.859375 C 5.257813 -6.65625 5.5625 -6.347656 5.808594 -5.933594 Z M 1.894531 -3.492188 C 1.890625 -2.644531 2.066406 -1.980469 2.414063 -1.5 C 2.761719 -1.019531 3.238281 -0.777344 3.847656 -0.78125 C 4.453125 -0.777344 4.933594 -1.019531 5.285156 -1.5 C 5.632813 -1.980469 5.804688 -2.644531 5.808594 -3.492188 C 5.804688 -4.335938 5.632813 -4.996094 5.285156 -5.480469 C 4.933594 -5.957031 4.453125 -6.199219 3.847656 -6.203125 C 3.238281 -6.199219 2.761719 -5.957031 2.414063 -5.480469 C 2.066406 -4.996094 1.890625 -4.335938 1.894531 -3.492188 Z M 1.894531 -3.492188 "/>
</symbol>
<symbol overflow="visible" id="glyph0-11">
<path style="stroke:none;" d="M 5.679688 -4.371094 C 5.945313 -4.277344 6.210938 -4.082031 6.46875 -3.785156 C 6.722656 -3.484375 6.976563 -3.070313 7.238281 -2.546875 L 8.519531 0 L 7.164063 0 L 5.972656 -2.390625 C 5.660156 -3.015625 5.363281 -3.429688 5.074219 -3.632813 C 4.785156 -3.835938 4.390625 -3.9375 3.890625 -3.941406 L 2.515625 -3.941406 L 2.515625 0 L 1.253906 0 L 1.253906 -9.324219 L 4.105469 -9.324219 C 5.167969 -9.320313 5.960938 -9.097656 6.488281 -8.65625 C 7.011719 -8.207031 7.277344 -7.535156 7.277344 -6.640625 C 7.277344 -6.046875 7.140625 -5.558594 6.867188 -5.175781 C 6.59375 -4.785156 6.195313 -4.519531 5.679688 -4.371094 Z M 2.515625 -8.289063 L 2.515625 -4.976563 L 4.105469 -4.976563 C 4.710938 -4.972656 5.167969 -5.113281 5.480469 -5.398438 C 5.785156 -5.675781 5.941406 -6.089844 5.945313 -6.640625 C 5.941406 -7.179688 5.785156 -7.589844 5.480469 -7.871094 C 5.167969 -8.144531 4.710938 -8.285156 4.105469 -8.289063 Z M 2.515625 -8.289063 "/>
</symbol>
<symbol overflow="visible" id="glyph0-12">
<path style="stroke:none;" d="M 1.253906 -9.324219 L 7.152344 -9.324219 L 7.152344 -8.265625 L 2.515625 -8.265625 L 2.515625 -5.503906 L 6.957031 -5.503906 L 6.957031 -4.441406 L 2.515625 -4.441406 L 2.515625 -1.0625 L 7.265625 -1.0625 L 7.265625 0 L 1.253906 0 Z M 1.253906 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-13">
<path style="stroke:none;" d="M -0.0390625 -9.324219 L 7.851563 -9.324219 L 7.851563 -8.265625 L 4.542969 -8.265625 L 4.542969 0 L 3.273438 0 L 3.273438 -8.265625 L -0.0390625 -8.265625 Z M -0.0390625 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-14">
<path style="stroke:none;" d=""/>
</symbol>
<symbol overflow="visible" id="glyph0-15">
<path style="stroke:none;" d="M 1.894531 -3.492188 C 1.890625 -2.644531 2.066406 -1.980469 2.414063 -1.5 C 2.761719 -1.019531 3.238281 -0.777344 3.847656 -0.78125 C 4.453125 -0.777344 4.933594 -1.019531 5.285156 -1.5 C 5.632813 -1.980469 5.804688 -2.644531 5.808594 -3.492188 C 5.804688 -4.335938 5.632813 -4.996094 5.285156 -5.480469 C 4.933594 -5.957031 4.453125 -6.199219 3.847656 -6.203125 C 3.238281 -6.199219 2.761719 -5.957031 2.414063 -5.480469 C 2.066406 -4.996094 1.890625 -4.335938 1.894531 -3.492188 Z M 5.808594 -1.050781 C 5.5625 -0.628906 5.257813 -0.320313 4.890625 -0.121094 C 4.519531 0.078125 4.078125 0.175781 3.566406 0.179688 C 2.71875 0.175781 2.03125 -0.160156 1.5 -0.832031 C 0.96875 -1.503906 0.703125 -2.390625 0.707031 -3.492188 C 0.703125 -4.589844 0.96875 -5.476563 1.5 -6.152344 C 2.03125 -6.824219 2.71875 -7.160156 3.566406 -7.164063 C 4.078125 -7.160156 4.519531 -7.058594 4.890625 -6.859375 C 5.257813 -6.65625 5.5625 -6.347656 5.808594 -5.933594 L 5.808594 -6.996094 L 6.957031 -6.996094 L 6.957031 2.660156 L 5.808594 2.660156 Z M 5.808594 -1.050781 "/>
</symbol>
<symbol overflow="visible" id="glyph0-16">
<path style="stroke:none;" d="M 5.039063 -8.46875 C 4.125 -8.464844 3.398438 -8.125 2.859375 -7.445313 C 2.320313 -6.761719 2.050781 -5.832031 2.050781 -4.652344 C 2.050781 -3.476563 2.320313 -2.546875 2.859375 -1.867188 C 3.398438 -1.183594 4.125 -0.84375 5.039063 -0.84375 C 5.953125 -0.84375 6.675781 -1.183594 7.214844 -1.867188 C 7.746094 -2.546875 8.015625 -3.476563 8.019531 -4.652344 C 8.015625 -5.832031 7.746094 -6.761719 7.214844 -7.445313 C 6.675781 -8.125 5.953125 -8.464844 5.039063 -8.46875 Z M 6.808594 -0.167969 L 8.46875 1.648438 L 6.945313 1.648438 L 5.566406 0.15625 C 5.425781 0.160156 5.320313 0.164063 5.25 0.171875 C 5.175781 0.171875 5.105469 0.175781 5.039063 0.179688 C 3.726563 0.175781 2.675781 -0.257813 1.894531 -1.132813 C 1.105469 -2.007813 0.714844 -3.183594 0.71875 -4.652344 C 0.714844 -6.125 1.105469 -7.300781 1.894531 -8.179688 C 2.675781 -9.054688 3.726563 -9.492188 5.039063 -9.496094 C 6.347656 -9.492188 7.390625 -9.054688 8.175781 -8.179688 C 8.957031 -7.300781 9.351563 -6.125 9.351563 -4.652344 C 9.351563 -3.570313 9.132813 -2.644531 8.699219 -1.875 C 8.261719 -1.101563 7.632813 -0.53125 6.808594 -0.167969 Z M 6.808594 -0.167969 "/>
</symbol>
<symbol overflow="visible" id="glyph0-17">
<path style="stroke:none;" d="M 2.316406 -1.050781 L 2.316406 2.660156 L 1.160156 2.660156 L 1.160156 -6.996094 L 2.316406 -6.996094 L 2.316406 -5.933594 C 2.554688 -6.347656 2.859375 -6.65625 3.230469 -6.859375 C 3.597656 -7.058594 4.039063 -7.160156 4.554688 -7.164063 C 5.398438 -7.160156 6.089844 -6.824219 6.625 -6.152344 C 7.152344 -5.476563 7.417969 -4.589844 7.421875 -3.492188 C 7.417969 -2.390625 7.152344 -1.503906 6.625 -0.832031 C 6.089844 -0.160156 5.398438 0.175781 4.554688 0.179688 C 4.039063 0.175781 3.597656 0.078125 3.230469 -0.121094 C 2.859375 -0.320313 2.554688 -0.628906 2.316406 -1.050781 Z M 6.226563 -3.492188 C 6.222656 -4.335938 6.046875 -4.996094 5.703125 -5.480469 C 5.351563 -5.957031 4.875 -6.199219 4.273438 -6.203125 C 3.660156 -6.199219 3.183594 -5.957031 2.835938 -5.480469 C 2.488281 -4.996094 2.3125 -4.335938 2.316406 -3.492188 C 2.3125 -2.644531 2.488281 -1.980469 2.835938 -1.5 C 3.183594 -1.019531 3.660156 -0.777344 4.273438 -0.78125 C 4.875 -0.777344 5.351563 -1.019531 5.703125 -1.5 C 6.046875 -1.980469 6.222656 -2.644531 6.226563 -3.492188 Z M 6.226563 -3.492188 "/>
</symbol>
<symbol overflow="visible" id="glyph0-18">
<path style="stroke:none;" d="M 4.117188 0.648438 C 3.789063 1.480469 3.472656 2.023438 3.164063 2.277344 C 2.855469 2.53125 2.441406 2.660156 1.929688 2.660156 L 1.011719 2.660156 L 1.011719 1.699219 L 1.6875 1.699219 C 2 1.699219 2.242188 1.621094 2.421875 1.472656 C 2.59375 1.320313 2.789063 0.96875 3.003906 0.414063 L 3.210938 -0.113281 L 0.382813 -6.996094 L 1.597656 -6.996094 L 3.785156 -1.523438 L 5.972656 -6.996094 L 7.191406 -6.996094 Z M 4.117188 0.648438 "/>
</symbol>
<symbol overflow="visible" id="glyph0-19">
<path style="stroke:none;" d="M 1.113281 -9.324219 L 2.378906 -9.324219 L 2.378906 -3.660156 C 2.378906 -2.65625 2.558594 -1.9375 2.921875 -1.5 C 3.285156 -1.0625 3.871094 -0.84375 4.683594 -0.84375 C 5.488281 -0.84375 6.074219 -1.0625 6.441406 -1.5 C 6.800781 -1.9375 6.984375 -2.65625 6.984375 -3.660156 L 6.984375 -9.324219 L 8.25 -9.324219 L 8.25 -3.503906 C 8.25 -2.285156 7.949219 -1.367188 7.347656 -0.75 C 6.746094 -0.132813 5.855469 0.175781 4.683594 0.179688 C 3.503906 0.175781 2.617188 -0.132813 2.015625 -0.75 C 1.414063 -1.367188 1.113281 -2.285156 1.113281 -3.503906 Z M 1.113281 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-20">
<path style="stroke:none;" d="M 6.226563 -3.492188 C 6.222656 -4.335938 6.046875 -4.996094 5.703125 -5.480469 C 5.351563 -5.957031 4.875 -6.199219 4.273438 -6.203125 C 3.660156 -6.199219 3.183594 -5.957031 2.835938 -5.480469 C 2.488281 -4.996094 2.3125 -4.335938 2.316406 -3.492188 C 2.3125 -2.644531 2.488281 -1.980469 2.835938 -1.5 C 3.183594 -1.019531 3.660156 -0.777344 4.273438 -0.78125 C 4.875 -0.777344 5.351563 -1.019531 5.703125 -1.5 C 6.046875 -1.980469 6.222656 -2.644531 6.226563 -3.492188 Z M 2.316406 -5.933594 C 2.554688 -6.347656 2.859375 -6.65625 3.230469 -6.859375 C 3.597656 -7.058594 4.039063 -7.160156 4.554688 -7.164063 C 5.398438 -7.160156 6.089844 -6.824219 6.625 -6.152344 C 7.152344 -5.476563 7.417969 -4.589844 7.421875 -3.492188 C 7.417969 -2.390625 7.152344 -1.503906 6.625 -0.832031 C 6.089844 -0.160156 5.398438 0.175781 4.554688 0.179688 C 4.039063 0.175781 3.597656 0.078125 3.230469 -0.121094 C 2.859375 -0.320313 2.554688 -0.628906 2.316406 -1.050781 L 2.316406 0 L 1.160156 0 L 1.160156 -9.71875 L 2.316406 -9.71875 Z M 2.316406 -5.933594 "/>
</symbol>
<symbol overflow="visible" id="glyph0-21">
<path style="stroke:none;" d="M 6.242188 -6.726563 L 6.242188 -5.652344 C 5.914063 -5.828125 5.589844 -5.960938 5.261719 -6.054688 C 4.933594 -6.140625 4.601563 -6.1875 4.273438 -6.191406 C 3.523438 -6.1875 2.945313 -5.949219 2.535156 -5.480469 C 2.121094 -5.003906 1.914063 -4.34375 1.917969 -3.492188 C 1.914063 -2.636719 2.121094 -1.972656 2.535156 -1.5 C 2.945313 -1.027344 3.523438 -0.789063 4.273438 -0.792969 C 4.601563 -0.789063 4.933594 -0.835938 5.261719 -0.925781 C 5.589844 -1.015625 5.914063 -1.148438 6.242188 -1.332031 L 6.242188 -0.269531 C 5.914063 -0.117188 5.582031 -0.0078125 5.242188 0.0664063 C 4.898438 0.136719 4.535156 0.175781 4.148438 0.179688 C 3.09375 0.175781 2.253906 -0.152344 1.636719 -0.8125 C 1.011719 -1.472656 0.703125 -2.367188 0.707031 -3.492188 C 0.703125 -4.632813 1.015625 -5.527344 1.644531 -6.183594 C 2.269531 -6.832031 3.128906 -7.160156 4.222656 -7.164063 C 4.574219 -7.160156 4.917969 -7.125 5.257813 -7.054688 C 5.59375 -6.980469 5.921875 -6.871094 6.242188 -6.726563 Z M 6.242188 -6.726563 "/>
</symbol>
<symbol overflow="visible" id="glyph0-22">
<path style="stroke:none;" d="M 1.160156 -9.71875 L 2.316406 -9.71875 L 2.316406 -3.980469 L 5.746094 -6.996094 L 7.214844 -6.996094 L 3.503906 -3.722656 L 7.371094 0 L 5.871094 0 L 2.316406 -3.417969 L 2.316406 0 L 1.160156 0 Z M 1.160156 -9.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-23">
<path style="stroke:none;" d="M 1.253906 -9.324219 L 6.613281 -9.324219 L 6.613281 -8.265625 L 2.515625 -8.265625 L 2.515625 -5.515625 L 6.214844 -5.515625 L 6.214844 -4.453125 L 2.515625 -4.453125 L 2.515625 0 L 1.253906 0 Z M 1.253906 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-24">
<path style="stroke:none;" d="M 7.019531 -4.222656 L 7.019531 0 L 5.871094 0 L 5.871094 -4.183594 C 5.871094 -4.847656 5.742188 -5.34375 5.484375 -5.671875 C 5.226563 -6 4.839844 -6.164063 4.324219 -6.164063 C 3.699219 -6.164063 3.207031 -5.964844 2.851563 -5.570313 C 2.492188 -5.171875 2.3125 -4.632813 2.316406 -3.953125 L 2.316406 0 L 1.160156 0 L 1.160156 -6.996094 L 2.316406 -6.996094 L 2.316406 -5.910156 C 2.589844 -6.328125 2.914063 -6.640625 3.289063 -6.851563 C 3.65625 -7.054688 4.085938 -7.160156 4.578125 -7.164063 C 5.378906 -7.160156 5.988281 -6.910156 6.402344 -6.417969 C 6.8125 -5.917969 7.019531 -5.1875 7.019531 -4.222656 Z M 7.019531 -4.222656 "/>
</symbol>
<symbol overflow="visible" id="glyph0-25">
<path style="stroke:none;" d="M 1.207031 -6.996094 L 2.355469 -6.996094 L 2.355469 0 L 1.207031 0 Z M 1.207031 -9.71875 L 2.355469 -9.71875 L 2.355469 -8.265625 L 1.207031 -8.265625 Z M 1.207031 -9.71875 "/>
</symbol>
<symbol overflow="visible" id="glyph0-26">
<path style="stroke:none;" d="M 3.917969 -6.191406 C 3.296875 -6.1875 2.808594 -5.945313 2.453125 -5.46875 C 2.09375 -4.984375 1.914063 -4.328125 1.917969 -3.492188 C 1.914063 -2.652344 2.09375 -1.992188 2.449219 -1.511719 C 2.804688 -1.03125 3.292969 -0.789063 3.917969 -0.792969 C 4.527344 -0.789063 5.011719 -1.03125 5.371094 -1.515625 C 5.726563 -1.996094 5.90625 -2.65625 5.910156 -3.492188 C 5.90625 -4.320313 5.726563 -4.976563 5.371094 -5.460938 C 5.011719 -5.945313 4.527344 -6.1875 3.917969 -6.191406 Z M 3.917969 -7.164063 C 4.914063 -7.160156 5.699219 -6.835938 6.269531 -6.1875 C 6.839844 -5.539063 7.125 -4.640625 7.128906 -3.492188 C 7.125 -2.34375 6.839844 -1.445313 6.269531 -0.796875 C 5.699219 -0.148438 4.914063 0.175781 3.917969 0.179688 C 2.910156 0.175781 2.125 -0.148438 1.558594 -0.796875 C 0.988281 -1.445313 0.703125 -2.34375 0.707031 -3.492188 C 0.703125 -4.640625 0.988281 -5.539063 1.558594 -6.1875 C 2.125 -6.835938 2.910156 -7.160156 3.917969 -7.164063 Z M 3.917969 -7.164063 "/>
</symbol>
<symbol overflow="visible" id="glyph0-27">
<path style="stroke:none;" d="M 2.515625 -8.289063 L 2.515625 -1.035156 L 4.042969 -1.035156 C 5.328125 -1.03125 6.269531 -1.324219 6.867188 -1.910156 C 7.464844 -2.492188 7.761719 -3.410156 7.765625 -4.671875 C 7.761719 -5.917969 7.464844 -6.835938 6.867188 -7.417969 C 6.269531 -7.996094 5.328125 -8.285156 4.042969 -8.289063 Z M 1.253906 -9.324219 L 3.847656 -9.324219 C 5.652344 -9.320313 6.976563 -8.945313 7.824219 -8.195313 C 8.667969 -7.441406 9.09375 -6.265625 9.09375 -4.671875 C 9.09375 -3.0625 8.667969 -1.882813 7.820313 -1.128906 C 6.96875 -0.375 5.644531 0 3.847656 0 L 1.253906 0 Z M 1.253906 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-28">
<path style="stroke:none;" d="M 0.382813 -6.996094 L 1.597656 -6.996094 L 3.785156 -1.125 L 5.972656 -6.996094 L 7.191406 -6.996094 L 4.566406 0 L 3.003906 0 Z M 0.382813 -6.996094 "/>
</symbol>
<symbol overflow="visible" id="glyph0-29">
<path style="stroke:none;" d="M -0.0234375 -9.324219 L 1.332031 -9.324219 L 3.917969 -5.492188 L 6.484375 -9.324219 L 7.839844 -9.324219 L 4.542969 -4.441406 L 4.542969 0 L 3.273438 0 L 3.273438 -4.441406 Z M -0.0234375 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-30">
<path style="stroke:none;" d="M 2.515625 -8.289063 L 2.515625 -4.785156 L 4.105469 -4.785156 C 4.6875 -4.78125 5.140625 -4.933594 5.464844 -5.238281 C 5.78125 -5.542969 5.941406 -5.976563 5.945313 -6.539063 C 5.941406 -7.097656 5.78125 -7.527344 5.464844 -7.832031 C 5.140625 -8.132813 4.6875 -8.285156 4.105469 -8.289063 Z M 1.253906 -9.324219 L 4.105469 -9.324219 C 5.148438 -9.320313 5.9375 -9.085938 6.472656 -8.613281 C 7.007813 -8.140625 7.277344 -7.449219 7.277344 -6.539063 C 7.277344 -5.617188 7.007813 -4.921875 6.472656 -4.453125 C 5.9375 -3.980469 5.148438 -3.742188 4.105469 -3.746094 L 2.515625 -3.746094 L 2.515625 0 L 1.253906 0 Z M 1.253906 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-31">
<path style="stroke:none;" d="M 5.808594 -3.578125 C 5.804688 -4.410156 5.632813 -5.054688 5.292969 -5.515625 C 4.945313 -5.96875 4.464844 -6.199219 3.847656 -6.203125 C 3.226563 -6.199219 2.746094 -5.96875 2.40625 -5.515625 C 2.0625 -5.054688 1.890625 -4.410156 1.894531 -3.578125 C 1.890625 -2.746094 2.0625 -2.101563 2.40625 -1.648438 C 2.746094 -1.1875 3.226563 -0.960938 3.847656 -0.960938 C 4.464844 -0.960938 4.945313 -1.1875 5.292969 -1.648438 C 5.632813 -2.101563 5.804688 -2.746094 5.808594 -3.578125 Z M 6.957031 -0.867188 C 6.953125 0.320313 6.691406 1.203125 6.164063 1.789063 C 5.632813 2.367188 4.820313 2.660156 3.734375 2.660156 C 3.328125 2.660156 2.949219 2.628906 2.589844 2.570313 C 2.230469 2.507813 1.882813 2.414063 1.550781 2.292969 L 1.550781 1.175781 C 1.882813 1.355469 2.214844 1.488281 2.546875 1.578125 C 2.875 1.664063 3.210938 1.710938 3.554688 1.710938 C 4.304688 1.710938 4.867188 1.511719 5.246094 1.121094 C 5.617188 0.722656 5.804688 0.132813 5.808594 -0.664063 L 5.808594 -1.230469 C 5.566406 -0.816406 5.261719 -0.507813 4.894531 -0.304688 C 4.519531 -0.101563 4.078125 0 3.566406 0 C 2.707031 0 2.015625 -0.324219 1.492188 -0.980469 C 0.964844 -1.628906 0.703125 -2.496094 0.707031 -3.578125 C 0.703125 -4.660156 0.964844 -5.527344 1.492188 -6.183594 C 2.015625 -6.832031 2.707031 -7.160156 3.566406 -7.164063 C 4.078125 -7.160156 4.519531 -7.058594 4.894531 -6.855469 C 5.261719 -6.652344 5.566406 -6.34375 5.808594 -5.933594 L 5.808594 -6.996094 L 6.957031 -6.996094 Z M 6.957031 -0.867188 "/>
</symbol>
<symbol overflow="visible" id="glyph0-32">
<path style="stroke:none;" d="M 6.652344 -5.652344 C 6.933594 -6.164063 7.277344 -6.542969 7.679688 -6.792969 C 8.078125 -7.035156 8.550781 -7.160156 9.09375 -7.164063 C 9.820313 -7.160156 10.382813 -6.90625 10.78125 -6.398438 C 11.175781 -5.886719 11.371094 -5.160156 11.375 -4.222656 L 11.375 0 L 10.21875 0 L 10.21875 -4.183594 C 10.21875 -4.855469 10.097656 -5.351563 9.863281 -5.675781 C 9.621094 -6 9.261719 -6.164063 8.777344 -6.164063 C 8.179688 -6.164063 7.707031 -5.964844 7.363281 -5.570313 C 7.015625 -5.171875 6.84375 -4.632813 6.847656 -3.953125 L 6.847656 0 L 5.691406 0 L 5.691406 -4.183594 C 5.6875 -4.855469 5.570313 -5.351563 5.332031 -5.679688 C 5.09375 -6 4.726563 -6.164063 4.234375 -6.164063 C 3.644531 -6.164063 3.179688 -5.964844 2.835938 -5.566406 C 2.488281 -5.167969 2.3125 -4.628906 2.316406 -3.953125 L 2.316406 0 L 1.160156 0 L 1.160156 -6.996094 L 2.316406 -6.996094 L 2.316406 -5.910156 C 2.574219 -6.335938 2.890625 -6.652344 3.257813 -6.855469 C 3.625 -7.058594 4.058594 -7.160156 4.566406 -7.164063 C 5.070313 -7.160156 5.503906 -7.03125 5.863281 -6.777344 C 6.21875 -6.515625 6.480469 -6.140625 6.652344 -5.652344 Z M 6.652344 -5.652344 "/>
</symbol>
<symbol overflow="visible" id="glyph0-33">
<path style="stroke:none;" d="M 1.253906 -9.324219 L 2.515625 -9.324219 L 2.515625 -1.0625 L 7.058594 -1.0625 L 7.058594 0 L 1.253906 0 Z M 1.253906 -9.324219 "/>
</symbol>
<symbol overflow="visible" id="glyph0-34">
<path style="stroke:none;" d="M 1.5 -1.585938 L 2.816406 -1.585938 L 2.816406 0 L 1.5 0 Z M 1.5 -6.613281 L 2.816406 -6.613281 L 2.816406 -5.027344 L 1.5 -5.027344 Z M 1.5 -6.613281 "/>
</symbol>
<symbol overflow="visible" id="glyph0-35">
<path style="stroke:none;" d="M 1.585938 -1.0625 L 3.648438 -1.0625 L 3.648438 -8.175781 L 1.40625 -7.726563 L 1.40625 -8.875 L 3.636719 -9.324219 L 4.898438 -9.324219 L 4.898438 -1.0625 L 6.957031 -1.0625 L 6.957031 0 L 1.585938 0 Z M 1.585938 -1.0625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-36">
<path style="stroke:none;" d="M 4.066406 -8.496094 C 3.417969 -8.496094 2.929688 -8.175781 2.601563 -7.535156 C 2.273438 -6.894531 2.109375 -5.933594 2.113281 -4.652344 C 2.109375 -3.371094 2.273438 -2.410156 2.601563 -1.773438 C 2.929688 -1.132813 3.417969 -0.8125 4.066406 -0.816406 C 4.71875 -0.8125 5.207031 -1.132813 5.535156 -1.773438 C 5.859375 -2.410156 6.023438 -3.371094 6.027344 -4.652344 C 6.023438 -5.933594 5.859375 -6.894531 5.535156 -7.535156 C 5.207031 -8.175781 4.71875 -8.496094 4.066406 -8.496094 Z M 4.066406 -9.496094 C 5.109375 -9.492188 5.90625 -9.078125 6.460938 -8.253906 C 7.011719 -7.425781 7.289063 -6.226563 7.289063 -4.652344 C 7.289063 -3.082031 7.011719 -1.882813 6.460938 -1.058594 C 5.90625 -0.234375 5.109375 0.175781 4.066406 0.179688 C 3.019531 0.175781 2.21875 -0.234375 1.671875 -1.058594 C 1.117188 -1.882813 0.84375 -3.082031 0.84375 -4.652344 C 0.84375 -6.226563 1.117188 -7.425781 1.671875 -8.253906 C 2.21875 -9.078125 3.019531 -9.492188 4.066406 -9.496094 Z M 4.066406 -9.496094 "/>
</symbol>
<symbol overflow="visible" id="glyph0-37">
<path style="stroke:none;" d="M 1.367188 -1.585938 L 2.6875 -1.585938 L 2.6875 0 L 1.367188 0 Z M 1.367188 -1.585938 "/>
</symbol>
<symbol overflow="visible" id="glyph0-38">
<path style="stroke:none;" d="M 4.222656 -5.164063 C 3.65625 -5.160156 3.207031 -4.96875 2.875 -4.582031 C 2.542969 -4.195313 2.378906 -3.664063 2.378906 -2.992188 C 2.378906 -2.320313 2.542969 -1.789063 2.875 -1.402344 C 3.207031 -1.007813 3.65625 -0.8125 4.222656 -0.816406 C 4.785156 -0.8125 5.234375 -1.007813 5.566406 -1.402344 C 5.898438 -1.789063 6.0625 -2.320313 6.066406 -2.992188 C 6.0625 -3.664063 5.898438 -4.195313 5.566406 -4.582031 C 5.234375 -4.96875 4.785156 -5.160156 4.222656 -5.164063 Z M 6.726563 -9.121094 L 6.726563 -7.96875 C 6.410156 -8.117188 6.089844 -8.230469 5.769531 -8.3125 C 5.445313 -8.390625 5.125 -8.429688 4.808594 -8.433594 C 3.972656 -8.429688 3.335938 -8.148438 2.898438 -7.589844 C 2.457031 -7.023438 2.207031 -6.175781 2.148438 -5.039063 C 2.390625 -5.402344 2.699219 -5.679688 3.070313 -5.875 C 3.441406 -6.066406 3.847656 -6.164063 4.296875 -6.164063 C 5.230469 -6.164063 5.972656 -5.878906 6.515625 -5.3125 C 7.058594 -4.742188 7.328125 -3.96875 7.332031 -2.992188 C 7.328125 -2.03125 7.046875 -1.261719 6.480469 -0.6875 C 5.914063 -0.109375 5.160156 0.175781 4.222656 0.179688 C 3.140625 0.175781 2.316406 -0.234375 1.75 -1.058594 C 1.175781 -1.882813 0.890625 -3.082031 0.894531 -4.652344 C 0.890625 -6.125 1.238281 -7.300781 1.941406 -8.179688 C 2.636719 -9.054688 3.578125 -9.492188 4.761719 -9.496094 C 5.074219 -9.492188 5.394531 -9.460938 5.71875 -9.402344 C 6.039063 -9.335938 6.375 -9.242188 6.726563 -9.121094 Z M 6.726563 -9.121094 "/>
</symbol>
<symbol overflow="visible" id="glyph0-39">
<path style="stroke:none;" d="M 1.5 -1.585938 L 2.816406 -1.585938 L 2.816406 -0.511719 L 1.792969 1.488281 L 0.988281 1.488281 L 1.5 -0.511719 Z M 1.5 -1.585938 "/>
</symbol>
<symbol overflow="visible" id="glyph0-40">
<path style="stroke:none;" d="M 2.453125 -1.0625 L 6.859375 -1.0625 L 6.859375 0 L 0.9375 0 L 0.9375 -1.0625 C 1.414063 -1.554688 2.066406 -2.21875 2.894531 -3.054688 C 3.722656 -3.886719 4.242188 -4.425781 4.453125 -4.671875 C 4.855469 -5.121094 5.136719 -5.507813 5.296875 -5.824219 C 5.457031 -6.136719 5.535156 -6.445313 5.539063 -6.753906 C 5.535156 -7.246094 5.363281 -7.648438 5.015625 -7.964844 C 4.667969 -8.273438 4.214844 -8.429688 3.660156 -8.433594 C 3.261719 -8.429688 2.84375 -8.363281 2.40625 -8.226563 C 1.964844 -8.085938 1.496094 -7.875 1 -7.601563 L 1 -8.875 C 1.503906 -9.078125 1.976563 -9.234375 2.421875 -9.339844 C 2.859375 -9.441406 3.265625 -9.492188 3.636719 -9.496094 C 4.601563 -9.492188 5.371094 -9.25 5.945313 -8.769531 C 6.519531 -8.285156 6.808594 -7.640625 6.808594 -6.832031 C 6.808594 -6.449219 6.734375 -6.085938 6.59375 -5.742188 C 6.445313 -5.398438 6.183594 -4.992188 5.808594 -4.527344 C 5.699219 -4.40625 5.371094 -4.058594 4.816406 -3.480469 C 4.257813 -2.902344 3.46875 -2.09375 2.453125 -1.0625 Z M 2.453125 -1.0625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-41">
<path style="stroke:none;" d="M 0.625 -4.015625 L 3.992188 -4.015625 L 3.992188 -2.992188 L 0.625 -2.992188 Z M 0.625 -4.015625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-42">
<path style="stroke:none;" d="M 4.371094 -8.082031 L 2.660156 -3.441406 L 6.089844 -3.441406 Z M 3.660156 -9.324219 L 5.089844 -9.324219 L 8.644531 0 L 7.332031 0 L 6.484375 -2.390625 L 2.28125 -2.390625 L 1.429688 0 L 0.101563 0 Z M 3.660156 -9.324219 "/>
</symbol>
</g>
</defs>
<g id="surface144239">
<rect x="0" y="0" width="1503" height="965" style="fill:rgb(100%,100%,100%);fill-opacity:1;stroke:none;"/>
<path style="fill-rule:evenodd;fill:rgb(89.803922%,89.803922%,89.803922%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 23.187567 0.685658 L 34.111005 0.685658 L 34.111005 7.312806 L 23.187567 7.312806 Z M 23.187567 0.685658 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 23.187567 0.685658 L 34.111005 0.685658 L 34.111005 7.312806 L 23.187567 7.312806 Z M 23.187567 0.685658 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-0" x="695.660156" y="430.292969"/>
<use xlink:href="#glyph0-1" x="704.585938" y="430.292969"/>
<use xlink:href="#glyph0-2" x="708.140625" y="430.292969"/>
<use xlink:href="#glyph0-3" x="716.246094" y="430.292969"/>
<use xlink:href="#glyph0-4" x="722.90625" y="430.292969"/>
<use xlink:href="#glyph0-5" x="727.925781" y="430.292969"/>
<use xlink:href="#glyph0-6" x="735.796875" y="430.292969"/>
</g>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M -5.991925 15.99679 L 4.931513 15.99679 L 4.931513 22.623939 L -5.991925 22.623939 Z M -5.991925 15.99679 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M -5.991925 15.99679 L 4.931513 15.99679 L 4.931513 22.623939 L -5.991925 22.623939 Z M -5.991925 15.99679 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 7.806317 15.859486 L 18.729755 15.859486 L 18.729755 22.486634 L 7.806317 22.486634 Z M 7.806317 15.859486 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 7.806317 15.859486 L 18.729755 15.859486 L 18.729755 22.486634 L 7.806317 22.486634 Z M 7.806317 15.859486 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 19.685028 15.951087 L 30.608466 15.951087 L 30.608466 22.578236 L 19.685028 22.578236 Z M 19.685028 15.951087 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 19.685028 15.951087 L 30.608466 15.951087 L 30.608466 22.578236 L 19.685028 22.578236 Z M 19.685028 15.951087 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-7" x="116.054688" y="736.515625"/>
<use xlink:href="#glyph0-8" x="124.179688" y="736.515625"/>
<use xlink:href="#glyph0-9" x="132.285156" y="736.515625"/>
<use xlink:href="#glyph0-6" x="140.117188" y="736.515625"/>
<use xlink:href="#glyph0-10" x="145.371094" y="736.515625"/>
</g>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-7" x="392.019531" y="733.773438"/>
<use xlink:href="#glyph0-8" x="400.144531" y="733.773438"/>
<use xlink:href="#glyph0-9" x="408.25" y="733.773438"/>
<use xlink:href="#glyph0-6" x="416.082031" y="733.773438"/>
<use xlink:href="#glyph0-10" x="421.335938" y="733.773438"/>
</g>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-7" x="629.59375" y="735.601563"/>
<use xlink:href="#glyph0-8" x="637.71875" y="735.601563"/>
<use xlink:href="#glyph0-9" x="645.824219" y="735.601563"/>
<use xlink:href="#glyph0-6" x="653.65625" y="735.601563"/>
<use xlink:href="#glyph0-10" x="658.910156" y="735.601563"/>
</g>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="959.320313" y="742.324219"/>
<use xlink:href="#glyph0-12" x="968.207031" y="742.324219"/>
<use xlink:href="#glyph0-7" x="976.292969" y="742.324219"/>
<use xlink:href="#glyph0-13" x="984.417969" y="742.324219"/>
<use xlink:href="#glyph0-14" x="992.230469" y="742.324219"/>
<use xlink:href="#glyph0-11" x="996.292969" y="742.324219"/>
<use xlink:href="#glyph0-5" x="1005.179688" y="742.324219"/>
<use xlink:href="#glyph0-15" x="1013.050781" y="742.324219"/>
<use xlink:href="#glyph0-2" x="1021.175781" y="742.324219"/>
<use xlink:href="#glyph0-5" x="1029.28125" y="742.324219"/>
<use xlink:href="#glyph0-3" x="1037.152344" y="742.324219"/>
<use xlink:href="#glyph0-4" x="1043.8125" y="742.324219"/>
<use xlink:href="#glyph0-14" x="1048.832031" y="742.324219"/>
<use xlink:href="#glyph0-16" x="1052.894531" y="742.324219"/>
<use xlink:href="#glyph0-2" x="1062.972656" y="742.324219"/>
<use xlink:href="#glyph0-5" x="1071.078125" y="742.324219"/>
<use xlink:href="#glyph0-2" x="1078.949219" y="742.324219"/>
<use xlink:href="#glyph0-5" x="1087.054688" y="742.324219"/>
</g>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 56.952802 17.127454 L 67.876239 17.127454 L 67.876239 23.754603 L 56.952802 23.754603 Z M 56.952802 17.127454 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 56.952802 17.127454 L 67.876239 17.127454 L 67.876239 23.754603 L 56.952802 23.754603 Z M 56.952802 17.127454 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="1334.128906" y="759.132813"/>
<use xlink:href="#glyph0-12" x="1343.015625" y="759.132813"/>
<use xlink:href="#glyph0-7" x="1351.101563" y="759.132813"/>
<use xlink:href="#glyph0-13" x="1359.226563" y="759.132813"/>
<use xlink:href="#glyph0-14" x="1367.039063" y="759.132813"/>
<use xlink:href="#glyph0-11" x="1371.101563" y="759.132813"/>
<use xlink:href="#glyph0-5" x="1379.988281" y="759.132813"/>
<use xlink:href="#glyph0-17" x="1387.859375" y="759.132813"/>
<use xlink:href="#glyph0-1" x="1395.984375" y="759.132813"/>
<use xlink:href="#glyph0-18" x="1399.539063" y="759.132813"/>
<use xlink:href="#glyph0-14" x="1407.117188" y="759.132813"/>
<use xlink:href="#glyph0-16" x="1411.179688" y="759.132813"/>
<use xlink:href="#glyph0-2" x="1421.257813" y="759.132813"/>
<use xlink:href="#glyph0-5" x="1429.363281" y="759.132813"/>
<use xlink:href="#glyph0-2" x="1437.234375" y="759.132813"/>
<use xlink:href="#glyph0-5" x="1445.339844" y="759.132813"/>
</g>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 56.818427 0.875697 L 67.741864 0.875697 L 67.741864 7.502845 L 56.818427 7.502845 Z M 56.818427 0.875697 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 56.818427 0.875697 L 67.741864 0.875697 L 67.741864 7.502845 L 56.818427 7.502845 Z M 56.818427 0.875697 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-19" x="1314.332031" y="434.097656"/>
<use xlink:href="#glyph0-3" x="1323.6875" y="434.097656"/>
<use xlink:href="#glyph0-5" x="1330.347656" y="434.097656"/>
<use xlink:href="#glyph0-6" x="1338.21875" y="434.097656"/>
<use xlink:href="#glyph0-14" x="1343.472656" y="434.097656"/>
<use xlink:href="#glyph0-0" x="1347.535156" y="434.097656"/>
<use xlink:href="#glyph0-9" x="1356.460938" y="434.097656"/>
<use xlink:href="#glyph0-1" x="1364.292969" y="434.097656"/>
<use xlink:href="#glyph0-1" x="1367.847656" y="434.097656"/>
<use xlink:href="#glyph0-20" x="1371.402344" y="434.097656"/>
<use xlink:href="#glyph0-9" x="1379.527344" y="434.097656"/>
<use xlink:href="#glyph0-21" x="1387.359375" y="434.097656"/>
<use xlink:href="#glyph0-22" x="1394.390625" y="434.097656"/>
<use xlink:href="#glyph0-14" x="1401.792969" y="434.097656"/>
<use xlink:href="#glyph0-23" x="1405.855469" y="434.097656"/>
<use xlink:href="#glyph0-2" x="1413.21875" y="434.097656"/>
<use xlink:href="#glyph0-24" x="1421.324219" y="434.097656"/>
<use xlink:href="#glyph0-21" x="1429.429688" y="434.097656"/>
<use xlink:href="#glyph0-4" x="1436.460938" y="434.097656"/>
<use xlink:href="#glyph0-25" x="1441.480469" y="434.097656"/>
<use xlink:href="#glyph0-26" x="1445.035156" y="434.097656"/>
<use xlink:href="#glyph0-24" x="1452.867188" y="434.097656"/>
<use xlink:href="#glyph0-3" x="1460.972656" y="434.097656"/>
</g>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1.407685 0.667494 L 12.331122 0.667494 L 12.331122 7.294642 L 1.407685 7.294642 Z M 1.407685 0.667494 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1.407685 0.667494 L 12.331122 0.667494 L 12.331122 7.294642 L 1.407685 7.294642 Z M 1.407685 0.667494 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-27" x="234.925781" y="429.933594"/>
<use xlink:href="#glyph0-25" x="244.769531" y="429.933594"/>
<use xlink:href="#glyph0-3" x="248.324219" y="429.933594"/>
<use xlink:href="#glyph0-21" x="254.984375" y="429.933594"/>
<use xlink:href="#glyph0-26" x="262.015625" y="429.933594"/>
<use xlink:href="#glyph0-6" x="269.847656" y="429.933594"/>
<use xlink:href="#glyph0-10" x="275.101563" y="429.933594"/>
<use xlink:href="#glyph0-14" x="283.226563" y="429.933594"/>
<use xlink:href="#glyph0-12" x="287.289063" y="429.933594"/>
<use xlink:href="#glyph0-28" x="295.375" y="429.933594"/>
<use xlink:href="#glyph0-5" x="302.953125" y="429.933594"/>
<use xlink:href="#glyph0-24" x="310.824219" y="429.933594"/>
<use xlink:href="#glyph0-4" x="318.929688" y="429.933594"/>
<use xlink:href="#glyph0-3" x="323.949219" y="429.933594"/>
</g>
<path style="fill-rule:evenodd;fill:rgb(89.803922%,89.803922%,89.803922%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 38.207489 0.750111 L 49.130927 0.750111 L 49.130927 7.377259 L 38.207489 7.377259 Z M 38.207489 0.750111 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 38.207489 0.750111 L 49.130927 0.750111 L 49.130927 7.377259 L 38.207489 7.377259 Z M 38.207489 0.750111 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 27.878583 7.358119 L 26.01706 15.471986 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 25.940302 15.806165 L 25.819794 15.309486 L 26.01706 15.471986 L 26.265302 15.411634 Z M 25.940302 15.806165 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 25.940302 15.806165 L 25.819794 15.309486 L 26.01706 15.471986 L 26.265302 15.411634 Z M 25.940302 15.806165 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 25.245185 7.357337 L 16.988935 15.502454 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 16.744989 15.743275 L 16.909833 15.259486 L 16.988935 15.502454 L 17.230731 15.584876 Z M 16.744989 15.743275 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 16.744989 15.743275 L 16.909833 15.259486 L 16.988935 15.502454 L 17.230731 15.584876 Z M 16.744989 15.743275 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 23.14206 6.888978 L 5.371161 16.213783 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.06745 16.372962 L 5.366083 15.958314 L 5.371161 16.213783 L 5.578388 16.363001 Z M 5.06745 16.372962 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.06745 16.372962 L 5.366083 15.958314 L 5.371161 16.213783 L 5.578388 16.363001 Z M 5.06745 16.372962 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(89.803922%,89.803922%,89.803922%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 28.706708 -17.233288 L 41.797724 -17.233288 L 44.416083 -10.948913 L 41.797724 -4.664538 L 28.706708 -4.664538 L 26.088349 -10.948913 L 28.706708 -17.233288 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 28.706708 -17.233288 L 41.797724 -17.233288 L 44.416083 -10.948913 L 41.797724 -4.664538 L 28.706708 -4.664538 L 26.088349 -10.948913 L 28.706708 -17.233288 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-29" x="806.644531" y="131.453125"/>
<use xlink:href="#glyph0-26" x="814.457031" y="131.453125"/>
<use xlink:href="#glyph0-2" x="822.289063" y="131.453125"/>
<use xlink:href="#glyph0-6" x="830.394531" y="131.453125"/>
<use xlink:href="#glyph0-14" x="835.648438" y="131.453125"/>
<use xlink:href="#glyph0-30" x="839.710938" y="131.453125"/>
<use xlink:href="#glyph0-6" x="847.425781" y="131.453125"/>
<use xlink:href="#glyph0-26" x="852.679688" y="131.453125"/>
<use xlink:href="#glyph0-31" x="860.511719" y="131.453125"/>
<use xlink:href="#glyph0-6" x="868.636719" y="131.453125"/>
<use xlink:href="#glyph0-9" x="873.890625" y="131.453125"/>
<use xlink:href="#glyph0-32" x="881.722656" y="131.453125"/>
</g>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 33.965302 -4.618444 L 30.955536 0.260658 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 30.775458 0.552454 L 30.820966 0.0434701 L 30.955536 0.260658 L 31.210028 0.283509 Z M 30.775458 0.552454 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 30.775458 0.552454 L 30.820966 0.0434701 L 30.955536 0.260658 L 31.210028 0.283509 Z M 30.775458 0.552454 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M -7.268487 23.674134 L 5.679755 23.674134 L 5.679755 24.793861 L -7.268487 24.793861 Z M -7.268487 23.674134 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-12" x="0" y="837.898438"/>
<use xlink:href="#glyph0-9" x="8.085938" y="837.898438"/>
<use xlink:href="#glyph0-21" x="15.917969" y="837.898438"/>
<use xlink:href="#glyph0-8" x="22.949219" y="837.898438"/>
<use xlink:href="#glyph0-14" x="31.054688" y="837.898438"/>
<use xlink:href="#glyph0-3" x="35.117188" y="837.898438"/>
<use xlink:href="#glyph0-8" x="41.777344" y="837.898438"/>
<use xlink:href="#glyph0-9" x="49.882813" y="837.898438"/>
<use xlink:href="#glyph0-6" x="57.714844" y="837.898438"/>
<use xlink:href="#glyph0-10" x="62.96875" y="837.898438"/>
<use xlink:href="#glyph0-14" x="71.09375" y="837.898438"/>
<use xlink:href="#glyph0-25" x="75.15625" y="837.898438"/>
<use xlink:href="#glyph0-3" x="78.710938" y="837.898438"/>
<use xlink:href="#glyph0-14" x="85.371094" y="837.898438"/>
<use xlink:href="#glyph0-9" x="89.433594" y="837.898438"/>
<use xlink:href="#glyph0-14" x="97.265625" y="837.898438"/>
<use xlink:href="#glyph0-3" x="101.328125" y="837.898438"/>
<use xlink:href="#glyph0-5" x="107.988281" y="837.898438"/>
<use xlink:href="#glyph0-17" x="115.859375" y="837.898438"/>
<use xlink:href="#glyph0-9" x="123.984375" y="837.898438"/>
<use xlink:href="#glyph0-6" x="131.816406" y="837.898438"/>
<use xlink:href="#glyph0-9" x="137.070313" y="837.898438"/>
<use xlink:href="#glyph0-4" x="144.902344" y="837.898438"/>
<use xlink:href="#glyph0-5" x="149.921875" y="837.898438"/>
<use xlink:href="#glyph0-14" x="157.792969" y="837.898438"/>
<use xlink:href="#glyph0-4" x="161.855469" y="837.898438"/>
<use xlink:href="#glyph0-8" x="166.875" y="837.898438"/>
<use xlink:href="#glyph0-6" x="174.980469" y="837.898438"/>
<use xlink:href="#glyph0-5" x="180.234375" y="837.898438"/>
<use xlink:href="#glyph0-9" x="188.105469" y="837.898438"/>
<use xlink:href="#glyph0-10" x="195.9375" y="837.898438"/>
</g>
<path style="fill:none;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:10;" d="M 8.450849 23.64972 L 31.41745 23.64972 L 31.41745 24.769447 L 8.450849 24.769447 Z M 8.450849 23.64972 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-29" x="314.386719" y="837.410156"/>
<use xlink:href="#glyph0-26" x="322.199219" y="837.410156"/>
<use xlink:href="#glyph0-2" x="330.03125" y="837.410156"/>
<use xlink:href="#glyph0-14" x="338.136719" y="837.410156"/>
<use xlink:href="#glyph0-32" x="342.199219" y="837.410156"/>
<use xlink:href="#glyph0-9" x="354.660156" y="837.410156"/>
<use xlink:href="#glyph0-18" x="362.492188" y="837.410156"/>
<use xlink:href="#glyph0-14" x="370.070313" y="837.410156"/>
<use xlink:href="#glyph0-21" x="374.132813" y="837.410156"/>
<use xlink:href="#glyph0-6" x="381.164063" y="837.410156"/>
<use xlink:href="#glyph0-5" x="386.417969" y="837.410156"/>
<use xlink:href="#glyph0-9" x="394.289063" y="837.410156"/>
<use xlink:href="#glyph0-4" x="402.121094" y="837.410156"/>
<use xlink:href="#glyph0-5" x="407.140625" y="837.410156"/>
<use xlink:href="#glyph0-14" x="415.011719" y="837.410156"/>
<use xlink:href="#glyph0-9" x="419.074219" y="837.410156"/>
<use xlink:href="#glyph0-3" x="426.90625" y="837.410156"/>
<use xlink:href="#glyph0-14" x="433.566406" y="837.410156"/>
<use xlink:href="#glyph0-32" x="437.628906" y="837.410156"/>
<use xlink:href="#glyph0-9" x="450.089844" y="837.410156"/>
<use xlink:href="#glyph0-24" x="457.921875" y="837.410156"/>
<use xlink:href="#glyph0-18" x="466.027344" y="837.410156"/>
<use xlink:href="#glyph0-14" x="473.605469" y="837.410156"/>
<use xlink:href="#glyph0-3" x="477.667969" y="837.410156"/>
<use xlink:href="#glyph0-8" x="484.328125" y="837.410156"/>
<use xlink:href="#glyph0-9" x="492.433594" y="837.410156"/>
<use xlink:href="#glyph0-6" x="500.265625" y="837.410156"/>
<use xlink:href="#glyph0-10" x="505.519531" y="837.410156"/>
<use xlink:href="#glyph0-3" x="513.644531" y="837.410156"/>
<use xlink:href="#glyph0-14" x="520.304688" y="837.410156"/>
<use xlink:href="#glyph0-17" x="524.367188" y="837.410156"/>
<use xlink:href="#glyph0-5" x="532.492188" y="837.410156"/>
<use xlink:href="#glyph0-6" x="540.363281" y="837.410156"/>
<use xlink:href="#glyph0-14" x="545.617188" y="837.410156"/>
<use xlink:href="#glyph0-21" x="549.679688" y="837.410156"/>
<use xlink:href="#glyph0-1" x="556.710938" y="837.410156"/>
<use xlink:href="#glyph0-2" x="560.265625" y="837.410156"/>
<use xlink:href="#glyph0-3" x="568.371094" y="837.410156"/>
<use xlink:href="#glyph0-4" x="575.03125" y="837.410156"/>
<use xlink:href="#glyph0-5" x="580.050781" y="837.410156"/>
<use xlink:href="#glyph0-6" x="587.921875" y="837.410156"/>
<use xlink:href="#glyph0-14" x="593.175781" y="837.410156"/>
<use xlink:href="#glyph0-9" x="597.238281" y="837.410156"/>
<use xlink:href="#glyph0-3" x="605.070313" y="837.410156"/>
<use xlink:href="#glyph0-14" x="611.730469" y="837.410156"/>
<use xlink:href="#glyph0-18" x="615.792969" y="837.410156"/>
<use xlink:href="#glyph0-26" x="623.371094" y="837.410156"/>
<use xlink:href="#glyph0-2" x="631.203125" y="837.410156"/>
<use xlink:href="#glyph0-14" x="639.308594" y="837.410156"/>
<use xlink:href="#glyph0-24" x="643.371094" y="837.410156"/>
<use xlink:href="#glyph0-5" x="651.476563" y="837.410156"/>
<use xlink:href="#glyph0-5" x="659.347656" y="837.410156"/>
<use xlink:href="#glyph0-10" x="667.21875" y="837.410156"/>
</g>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 43.203388 21.039564 L 54.126825 21.039564 L 54.126825 27.666712 L 43.203388 27.666712 Z M 43.203388 21.039564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 43.203388 21.039564 L 54.126825 21.039564 L 54.126825 27.666712 L 43.203388 27.666712 Z M 43.203388 21.039564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 41.753388 20.164564 L 52.676825 20.164564 L 52.676825 26.791712 L 41.753388 26.791712 Z M 41.753388 20.164564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 41.753388 20.164564 L 52.676825 20.164564 L 52.676825 26.791712 L 41.753388 26.791712 Z M 41.753388 20.164564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 40.403388 18.889564 L 51.326825 18.889564 L 51.326825 25.516712 L 40.403388 25.516712 Z M 40.403388 18.889564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 40.403388 18.889564 L 51.326825 18.889564 L 51.326825 25.516712 L 40.403388 25.516712 Z M 40.403388 18.889564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 39.203388 17.539564 L 50.126825 17.539564 L 50.126825 24.166712 L 39.203388 24.166712 Z M 39.203388 17.539564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 39.203388 17.539564 L 50.126825 17.539564 L 50.126825 24.166712 L 39.203388 24.166712 Z M 39.203388 17.539564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(67.843139%,84.705883%,90.196079%);fill-opacity:1;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(67.843139%,84.705883%,90.196079%);stroke-opacity:1;stroke-miterlimit:10;" d="M 38.008075 16.294056 L 48.931513 16.294056 L 48.931513 22.921204 L 38.008075 22.921204 Z M 38.008075 16.294056 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.0914097;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 38.008075 16.294056 L 48.931513 16.294056 L 48.931513 22.921204 L 38.008075 22.921204 Z M 38.008075 16.294056 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 48.931513 19.607533 L 56.466864 20.073353 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 56.841083 20.096595 L 56.32663 20.31515 L 56.466864 20.073353 L 56.357489 19.816126 Z M 56.841083 20.096595 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 43.62663 7.377064 L 43.518622 15.807337 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 43.513739 16.182337 L 43.270185 15.679212 L 43.518622 15.807337 L 43.770185 15.685658 Z M 43.513739 16.182337 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 62.386981 17.127454 L 62.311591 7.989564 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 62.308466 7.614759 L 62.562567 8.112611 L 62.311591 7.989564 L 62.062567 8.116712 Z M 62.308466 7.614759 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="943.632813" y="742.460938"/>
<use xlink:href="#glyph0-12" x="952.519531" y="742.460938"/>
<use xlink:href="#glyph0-7" x="960.605469" y="742.460938"/>
<use xlink:href="#glyph0-13" x="968.730469" y="742.460938"/>
<use xlink:href="#glyph0-14" x="976.542969" y="742.460938"/>
<use xlink:href="#glyph0-11" x="980.605469" y="742.460938"/>
<use xlink:href="#glyph0-5" x="989.492188" y="742.460938"/>
<use xlink:href="#glyph0-15" x="997.363281" y="742.460938"/>
<use xlink:href="#glyph0-2" x="1005.488281" y="742.460938"/>
<use xlink:href="#glyph0-5" x="1013.59375" y="742.460938"/>
<use xlink:href="#glyph0-3" x="1021.464844" y="742.460938"/>
<use xlink:href="#glyph0-4" x="1028.125" y="742.460938"/>
<use xlink:href="#glyph0-14" x="1033.144531" y="742.460938"/>
<use xlink:href="#glyph0-16" x="1037.207031" y="742.460938"/>
<use xlink:href="#glyph0-2" x="1047.285156" y="742.460938"/>
<use xlink:href="#glyph0-5" x="1055.390625" y="742.460938"/>
<use xlink:href="#glyph0-2" x="1063.261719" y="742.460938"/>
<use xlink:href="#glyph0-5" x="1071.367188" y="742.460938"/>
<use xlink:href="#glyph0-3" x="1079.238281" y="742.460938"/>
</g>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 56.818817 1.130579 L 43.107294 -6.549303 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 42.780145 -6.732506 L 43.338544 -6.706335 L 43.107294 -6.549303 L 43.094208 -6.270006 Z M 42.780145 -6.732506 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-33" x="88.859375" y="961.296875"/>
<use xlink:href="#glyph0-9" x="95.988281" y="961.296875"/>
<use xlink:href="#glyph0-3" x="103.820313" y="961.296875"/>
<use xlink:href="#glyph0-4" x="110.480469" y="961.296875"/>
<use xlink:href="#glyph0-14" x="115.5" y="961.296875"/>
<use xlink:href="#glyph0-2" x="119.5625" y="961.296875"/>
<use xlink:href="#glyph0-17" x="127.667969" y="961.296875"/>
<use xlink:href="#glyph0-10" x="135.792969" y="961.296875"/>
<use xlink:href="#glyph0-9" x="143.917969" y="961.296875"/>
<use xlink:href="#glyph0-4" x="151.75" y="961.296875"/>
<use xlink:href="#glyph0-5" x="156.769531" y="961.296875"/>
<use xlink:href="#glyph0-10" x="164.640625" y="961.296875"/>
<use xlink:href="#glyph0-34" x="172.765625" y="961.296875"/>
<use xlink:href="#glyph0-14" x="177.082031" y="961.296875"/>
<use xlink:href="#glyph0-35" x="181.144531" y="961.296875"/>
<use xlink:href="#glyph0-36" x="189.289063" y="961.296875"/>
<use xlink:href="#glyph0-37" x="197.433594" y="961.296875"/>
<use xlink:href="#glyph0-36" x="201.496094" y="961.296875"/>
<use xlink:href="#glyph0-37" x="209.640625" y="961.296875"/>
<use xlink:href="#glyph0-38" x="213.703125" y="961.296875"/>
<use xlink:href="#glyph0-39" x="221.847656" y="961.296875"/>
<use xlink:href="#glyph0-14" x="225.910156" y="961.296875"/>
<use xlink:href="#glyph0-35" x="229.972656" y="961.296875"/>
<use xlink:href="#glyph0-40" x="238.117188" y="961.296875"/>
<use xlink:href="#glyph0-41" x="246.261719" y="961.296875"/>
<use xlink:href="#glyph0-42" x="250.871094" y="961.296875"/>
<use xlink:href="#glyph0-17" x="259.621094" y="961.296875"/>
<use xlink:href="#glyph0-6" x="267.746094" y="961.296875"/>
<use xlink:href="#glyph0-41" x="273" y="961.296875"/>
<use xlink:href="#glyph0-40" x="277.609375" y="961.296875"/>
<use xlink:href="#glyph0-36" x="285.753906" y="961.296875"/>
<use xlink:href="#glyph0-40" x="293.898438" y="961.296875"/>
<use xlink:href="#glyph0-40" x="302.042969" y="961.296875"/>
</g>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 12.330731 1.108509 L 27.305536 -6.768835 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 27.637567 -6.943249 L 27.311395 -6.489342 L 27.305536 -6.768835 L 27.078583 -6.931725 Z M 27.637567 -6.943249 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 1.069013 15.997376 L 5.058661 7.732533 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 5.221552 7.394837 L 5.229364 7.953822 L 5.058661 7.732533 L 4.779169 7.736439 Z M 5.221552 7.394837 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 11.872724 15.860072 L 8.453778 7.742884 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 8.30827 7.397181 L 8.732685 7.761048 L 8.453778 7.742884 L 8.271942 7.954994 Z M 8.30827 7.397181 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 21.183856 15.950697 L 11.205927 7.607337 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 10.918231 7.366712 L 11.462177 7.495619 L 11.205927 7.607337 L 11.141474 7.879212 Z M 10.918231 7.366712 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 34.110224 4.022572 L 37.721356 4.038197 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<path style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 38.096356 4.039759 L 37.595185 4.287611 L 37.721356 4.038197 L 37.597333 3.787611 Z M 38.096356 4.039759 " transform="matrix(20,0,0,20,145.369747,345.861067)"/>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-27" x="963.949219" y="423.585938"/>
<use xlink:href="#glyph0-25" x="973.792969" y="423.585938"/>
<use xlink:href="#glyph0-3" x="977.347656" y="423.585938"/>
<use xlink:href="#glyph0-21" x="984.007813" y="423.585938"/>
<use xlink:href="#glyph0-26" x="991.039063" y="423.585938"/>
<use xlink:href="#glyph0-6" x="998.871094" y="423.585938"/>
<use xlink:href="#glyph0-10" x="1004.125" y="423.585938"/>
<use xlink:href="#glyph0-14" x="1012.25" y="423.585938"/>
<use xlink:href="#glyph0-9" x="1016.3125" y="423.585938"/>
<use xlink:href="#glyph0-24" x="1024.144531" y="423.585938"/>
<use xlink:href="#glyph0-10" x="1032.25" y="423.585938"/>
<use xlink:href="#glyph0-14" x="1040.375" y="423.585938"/>
<use xlink:href="#glyph0-19" x="1044.4375" y="423.585938"/>
<use xlink:href="#glyph0-3" x="1053.792969" y="423.585938"/>
<use xlink:href="#glyph0-5" x="1060.453125" y="423.585938"/>
<use xlink:href="#glyph0-6" x="1068.324219" y="423.585938"/>
</g>
<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">
<use xlink:href="#glyph0-11" x="970.667969" y="439.585938"/>
<use xlink:href="#glyph0-12" x="979.554688" y="439.585938"/>
<use xlink:href="#glyph0-7" x="987.640625" y="439.585938"/>
<use xlink:href="#glyph0-13" x="995.765625" y="439.585938"/>
<use xlink:href="#glyph0-14" x="1003.578125" y="439.585938"/>
<use xlink:href="#glyph0-11" x="1007.640625" y="439.585938"/>
<use xlink:href="#glyph0-5" x="1016.527344" y="439.585938"/>
<use xlink:href="#glyph0-15" x="1024.398438" y="439.585938"/>
<use xlink:href="#glyph0-2" x="1032.523438" y="439.585938"/>
<use xlink:href="#glyph0-5" x="1040.628906" y="439.585938"/>
<use xlink:href="#glyph0-3" x="1048.5" y="439.585938"/>
<use xlink:href="#glyph0-4" x="1055.160156" y="439.585938"/>
<use xlink:href="#glyph0-3" x="1060.179688" y="439.585938"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
vendor/DPP/docpages/images/button.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
vendor/DPP/docpages/images/button_2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
vendor/DPP/docpages/images/cprog.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
vendor/DPP/docpages/images/embed.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
vendor/DPP/docpages/images/jsprog.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
vendor/DPP/docpages/images/progs.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

BIN
vendor/DPP/docpages/images/repl.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
vendor/DPP/docpages/images/runbot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
vendor/DPP/docpages/images/vcpkg.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
vendor/DPP/docpages/images/vsproj_1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
vendor/DPP/docpages/images/vsproj_10.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
vendor/DPP/docpages/images/vsproj_11.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
vendor/DPP/docpages/images/vsproj_12.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
vendor/DPP/docpages/images/vsproj_13.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
vendor/DPP/docpages/images/vsproj_14.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
vendor/DPP/docpages/images/vsproj_2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
vendor/DPP/docpages/images/vsproj_3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
vendor/DPP/docpages/images/vsproj_4.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
vendor/DPP/docpages/images/vsproj_5.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
vendor/DPP/docpages/images/vsproj_6.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
vendor/DPP/docpages/images/vsproj_7.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
vendor/DPP/docpages/images/vsproj_8.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
vendor/DPP/docpages/images/vsproj_9.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Some files were not shown because too many files have changed in this diff Show More