스킵 구현

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

View File

@@ -0,0 +1,142 @@
\page frequently-asked-questions Frequently Asked Questions (FAQ)
[TOC]
## Is this library in production use?
This library powers the bot [TriviaBot](https://triviabot.co.uk) which has over **201,000 servers**, and [Sporks](https://sporks.gg) which has over **3,500 servers**. 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 or later 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).
## What's 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:
* [Codecademy](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 and \ref components2 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, the library only requires C++17. We have some optional features such as \ref using-coroutines "coroutines" that do require C++20, but they are disabled by default.
## 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, I get "D++ Debug/Release mismatch"
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 STL (Microsoft standard library implementation). 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 Replit?
No. We used to support it (and still have our page about it \ref building-a-cpp-discord-bot-in-repl "which you can find here"), however, Replit does not have up-to-date packages, meaning D++ can no longer run. We've attempted to reach out to ask about this but we've had no luck.
## 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, you can enable \ref using-coroutines "coroutines" and use for example `co_await cluster->co_message_get(...)` to retrieve the result in your function.
## 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.
## What are nightly builds?
These are automatic builds that happen every night without any human supervision. They allow you to try the latest state of the library without having to build it yourself, but they come without any guarantees.

View File

@@ -0,0 +1,55 @@
\page glossary-of-common-discord-terms A Glossary of Common Discord Terms
[TOC]
This is a list of terms that you should know if you want to use D++ (or any other Discord library). These terms are not D++ specific, and are commonly used throughout most of the Discord developer community. This list, with a few exceptions, ***is Discord specific***, that is to say that this is not an explanation of commonly used C++ terms, it is for people who are not familiar with the terminology of the Discord API and libraries themselves.
## Glossary
Listed in alphabetical order, with terms in bold, here are the basics on...
* **Action row**: A collection of up to five **components** which is attached to a message.
* **Application Command**: See \ref slashcommand.
* **Audit log**: A log of **events** that have happened in a **guild**.
* **Auto mod**: Discord's low-code solution to moderation. However, it is very limited in scope.
* **Badge**: A decoration on someone's profile showing certain things about them, such as if they have Nitro, if they are a Discord developer, etc.
* **Bot token**: A secret string of characters that is used as a "login" to your bot. If you lose it or it gets leaked you will have to get a new one from the Discord developer portal, so be sure to keep it in a place that is both secure and where you won't forget it.
* **Button**: A **component** on a message that can be styled that sends an **event** when clicked on by a user.
* **Cache**: A type of storage efficient for things like messages.
* **Callback**: While not strictly related to Discord, it is used a LOT in D++. A callback is when a function is passed to another function, sort of like how you might give someone a telephone number (you give them the means to do some sort of interaction rather than asking them how to interact), which is used to handle responses to **events**.
* **Cluster**: A singular bot application, which is composed of one or more **shards**, a **cluster** is the centre of bot development.
* **Component**: A component is anything that can appear in a bot's message besides text, such as **buttons** and **select menus**.
* **Drop down/Select menu**: A **component** of a message that upon being clicked drops down and allows the user to select an option.
* **Embeds**: A widget attached to a message which can contain multiple fields of texts, an image, and much more information.
* **Ephemeral**: A message only visible to the user being replied to.
* **Event**: Something that a Discord bot can respond to, such as a message being sent, a **button** being clicked, or an option being selected, among others.
* **Guild**: What the Discord API (and most libraries for it) call a server.
* **Intents**: The right for a bot to receive certain data from the Discord API.
* **Interaction**: An object that contains information about whenever a user interacts with an application, such as sending a message or clicking a button. It is the main part of an **event** that will be accessed in an application.
* **Modal**: A pop up form that contains text that can be sent by a bot.
* **[Shards](\ref clusters-shards-guilds)**: A shard manages part of the workload of your Discord application.
* **Slashcommand**: The primary way a user interacts with a bot. It is a command sent to the bot with **parameters** (which may be optional) and is initiated by beginning a message with `/`. \anchor slashcommand
* **Snowflake**: An unsigned 64 bit integer (it can represent anything from 0 to 2^64-1) that is used by Discord to identify basically everything, including but not limited to, **guilds**, users, messages, and much more.
* **Subcommands**: A command which is derived from a different command, such as a bot that allows a person to get statistics for Discord might have a `stats guild` command and a `stats global` command, both of which are **subcommands** of `stats`.

View File

@@ -0,0 +1,13 @@
\page installing 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-vs-zip
* \subpage install-windows-clion-vcpkg
* \subpage install-xmake
* \subpage install-brew
* \subpage install-from-source

View File

@@ -0,0 +1,14 @@
\page creating-a-discord-bot 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
* \subpage build-a-discord-bot-windows-visual-studio
* \subpage build-a-discord-bot-windows-wsl
* \subpage build-a-discord-bot-linux-clion
* \subpage buildcmake
* \subpage buildmeson
* \subpage building-a-cpp-discord-bot-in-repl
* \subpage build-a-bot-xcode

View File

@@ -0,0 +1,11 @@
\page example-programs 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 using-coroutines
* \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,14 @@
\page advanced-reference Advanced Reference
* \subpage clusters-shards-guilds
* \subpage thread-model
* \subpage voice-model
* \subpage coding-standards
* \subpage docs-standards
* \subpage unit-tests
* \subpage lambdas-and-locals
* \subpage governance
* \subpage roadmap
* \subpage security
* \subpage automating-with-jenkins
* \subpage separate-events

View File

@@ -0,0 +1,7 @@
\page deprecated Deprecated List
## Deprecation policy
We keep things marked as deprecated until the next major API version. If Discord removes the function, we may remove the method from the library or replace it with a thrown exception depending on the type of function and at our discretion. Such functions which are made to throw will then be removed at the next major API version.
<hr />

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
\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
* \subpage buildwindows
* \subpage buildosx
* \subpage buildfreebsd
* \subpage buildopenbsd
\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.

View File

@@ -0,0 +1,75 @@
\page buildfreebsd Building on FreeBSD
\note This page assumes you are the root user. If you are not, start the package install commands with `sudo`, along with `make install`. You will need `sudo` installed if you are not the root user.
## 1. Toolchain
Since the project uses `CMake`, you'll need to install it! If you don't have it, you can do the following:
```bash
pkg install cmake
```
## 2. Install Voice Dependencies (Optional)
If you wish to use voice support, you'll need to install opus and libsodium:
First, you need to install opus.
```bash
cd /usr/ports/audio/opus
make && make install
```
Then, you need to install libsodium.
```bash
cd /usr/ports/security/libsodium
make && make install
```
## 3. Build Source Code
```bash
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
```bash
cd build
make install
```
## 5. Installation to a Different Directory (Optional)
If you want to install the library, its dependencies and header files to a different directory, specify this directory when running `cmake`:
```bash
cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install
```
Then once the build is complete, run `sudo 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:
```bash
clang++ -std=c++17 -L/usr/local/lib -I/usr/local/include -ldpp bot.cpp -o dppbot
```
The important flags in this command-line are:
* `-std=c++17` - Required to compile the headers
* `-L/usr/local/lib` - Required to tell the linker where libdpp is located.
* `-I/usr/local/include` - Required to tell the linker where dpp headers are located.
* `-ldpp` - Link to `libdpp.so`.
* `bot.cpp` - Your source code.
* `-o dppbot` - The name of the executable to make.
\include{doc} install_prebuilt_footer.dox
**Have fun!**

View File

@@ -0,0 +1,48 @@
\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
```bash
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
```bash
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`:
```bash
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:
```bash
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
* `-ldpp` - Link to libdpp.so
* `mydppbot.cpp` - Your source code
* `dppbot` - The name of the executable to make
\include{doc} install_prebuilt_footer.dox
**Have fun!**

View File

@@ -0,0 +1,50 @@
\page buildopenbsd Building on OpenBSD
\note This page assumes you are the root user. If you are not, start the package install commands with `doas`, along with `make install`.
## 1. Toolchain
Since the project uses `CMake`, you'll need to install it! If you don't have it, you can do the following:
```bash
pkg_add cmake
```
## 2. Install Voice Dependencies (Optional)
If you wish to use voice support, you'll need to do the following:
```bash
pkg_add libsodium opus pkgconf
```
## 3. Build Source Code
```bash
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
```bash
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`:
```bash
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 to the `/usr/local` directory, you can make use of the library in CMake, without linking to a folder! You can't use this with `clang++`, nor `g++`, as OpenBSD seems to be broken on this end, so your only option from here is to use CMake. This isn't a bad thing, as we recommend people to use CMake anyways!
**Have fun!**

View File

@@ -0,0 +1,67 @@
\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 an [installation guide 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
```bash
brew install openssl pkgconfig
```
\note Usually, you do not need pkgconfig. However, it seems that it throws errors about openssl without.
For voice support, additional dependencies are required:
```bash
brew install libsodium opus
```
## 3. Build Source Code
```bash
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
```bash
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`:
```bash
cmake .. -DCMAKE_INSTALL_PREFIX=/path/to/install
```
Then once the build is complete, run `sudo 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:
```bash
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
\include{doc} install_prebuilt_footer.dox
**Have fun!**

View File

@@ -0,0 +1,30 @@
\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 D++ 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`.
**Have fun!**

View File

@@ -0,0 +1 @@
ErrorDocument 404 /index.php

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,46 @@
#
# D++ (DPP), The Lightweight C++ Discord Library
#
# Copyright 2021 Craig Edwards <support@brainbox.cc>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Example programs test compilation
# This build script is executed by a GitHub action to ensure all example
# programs compile correctly. It does not attempt to run them, as there
# is no way to know if the program successfully did its thing, plus
# examples do not have a valid token. This build script assumes the
# following system dependencies are available:
#
# g++-12 or later
# liboggz-dev
# libmpg123-dev
# dpp latest master with -DDPP_CORO=ON installed sytemwide
cmake_minimum_required (VERSION 3.16)
project(documentation_tests)
string(ASCII 27 Esc)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDPP_CORO -std=c++20 -pthread -O0 -fPIC -rdynamic -DFMT_HEADER_ONLY -Wall -Wextra -Wpedantic -Werror -Wno-unused-parameter")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0")
file(GLOB example_list ./*.cpp)
foreach (example ${example_list})
get_filename_component(examplename ${example} NAME)
message(STATUS "Found example '${Esc}[1;34m${examplename}${Esc}[m'")
add_executable(${examplename}_out ${example})
target_link_libraries(${examplename}_out dl dpp mpg123 oggz ogg opusfile opus)
include_directories(/usr/include/opus)
endforeach(example)

View File

@@ -0,0 +1,32 @@
#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() == "file") {
dpp::message msg(event.command.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"));
/* Reply to the user with the message, with our file attached. */
event.reply(msg);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("file", "Send a message with a file attached!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,40 @@
#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() == "file") {
/* Request the image from the URL specified and capture the event in a lambda. */
bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [event](const dpp::http_request_completion_t & httpRequestCompletion) {
/* Create a message */
dpp::message msg(event.command.channel_id, "This is my new attachment:");
/* Attach the image to the message, only on success (Code 200). */
if (httpRequestCompletion.status == 200) {
msg.add_file("logo.png", httpRequestCompletion.body);
}
/* Send the message, with our attachment. */
event.reply(msg);
});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("file", "Send a message with an image attached from the internet!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,39 @@
#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() == "file") {
/* Create a message. */
dpp::message msg(event.command.channel_id, "");
/* Attach the image to the message we just created. */
msg.add_file("image.jpg", dpp::utility::read_file("path_to_your_image.jpg"));
/* Create an embed. */
dpp::embed embed;
embed.set_image("attachment://image.jpg"); /* Set the image of the embed to the attached image. */
/* Add the embed to the message. */
msg.add_embed(embed);
event.reply(msg);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("file", "Send a local image along with an embed with the image!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,67 @@
#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,60 @@
#include <dpp/dpp.h>
#include <sstream>
int main() {
/* Create bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content); /* Because we're handling messages, we need to use the "i_message_content" intent! */
/* 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([&message_cache](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);
});
/* The event is fired when someone issues your commands */
bot.on_slashcommand([&bot, &message_cache](const dpp::slashcommand_t& event) {
/* Check which command they ran */
if (event.command.get_command_name() == "get") {
dpp::message* find_msg = message_cache.find(std::get<std::string>(event.get_parameter("message_id")));
/* If find_msg is null, tell the user and return. */
if (!find_msg) {
event.reply("There is no message cached with this ID");
return;
}
event.reply("This message had the following content: " + find_msg->content);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
dpp::slashcommand newcommand("get", "Get the contents of a message that was cached via an id", bot.me.id);
/* Add a parameter option. */
newcommand.add_option(dpp::command_option(dpp::co_string, "message_id", "The ID of the message you want to find", true));
/* Register the command */
bot.global_command_create(newcommand);
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,89 @@
#include <dpp/dpp.h>
int main() {
dpp::cluster bot("Token Was Here", dpp::i_default_intents | dpp::i_message_content);
/* the second argument is a bitmask of intents - i_message_content is needed to get messages */
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) {
if (event.command.get_command_name() == "msgs-get") {
int64_t limit = std::get<int64_t>(event.get_parameter("quantity"));
/* get messages using ID of the channel the command was issued in */
bot.messages_get(event.command.channel_id, 0, 0, 0, limit, [event](const dpp::confirmation_callback_t& callback) {
if (callback.is_error()) { /* catching an error to log it */
std::cout << callback.get_error().message << std::endl;
return;
}
auto messages = callback.get<dpp::message_map>();
/* std::get<dpp::message_map>(callback.value) would give the same result */
std::string contents;
for (const auto& x : messages) { /* here we iterate through the dpp::message_map we got from callback... */
contents += x.second.content + '\n'; /* ...where x.first is ID of the current message and x.second is the message itself. */
}
event.reply(contents); /* we will see all those messages we got, united as one! */
});
} else if (event.command.get_command_name() == "channel-create") {
/* create a text channel */
dpp::channel channel = dpp::channel()
.set_name("test")
.set_guild_id(event.command.guild_id);
bot.channel_create(channel, [&bot, event](const dpp::confirmation_callback_t& callback) -> void {
if (callback.is_error()) { /* catching an error to log it */
bot.log(dpp::loglevel::ll_error, callback.get_error().message);
return;
}
auto channel = callback.get<dpp::channel>();
/* std::get<dpp::channel>(callback.value) would give the same result */
/* reply with the created channel information */
dpp::message message = dpp::message("The channel's name is `" + channel.name + "`, ID is `" + std::to_string(channel.id) + " and type is `" + std::to_string(channel.get_type()) + "`.");
/* note that channel types are represented as numbers */
event.reply(message);
});
} else if (event.command.get_command_name() == "msg-error") {
bot.message_get(0, 0, [event](const dpp::confirmation_callback_t& callback) -> void {
/* the error will occur since there is no message with ID '0' that is in a channel with ID '0' (I'm not explaining why) */
if (callback.is_error()) {
event.reply(callback.get_error().message);
return;
}
/* we won't be able to get here because of the return; statement */
auto message = callback.get<dpp::message>();
event.reply(message);
});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once <struct register_global_commands>()) {
dpp::slashcommand msgs_get("msgs-get", "Get messages", bot.me.id);
constexpr int64_t min_val{1};
constexpr int64_t max_val{100};
msgs_get.add_option(
dpp::command_option(dpp::co_integer, "quantity", "Quantity of messages to get. Max - 100.")
.set_min_value(min_val)
.set_max_value(max_val)
);
dpp::slashcommand channel_create("channel-create", "Create a channel", bot.me.id);
dpp::slashcommand msg_error("msg-error", "Get an error instead of message :)", bot.me.id);
bot.global_bulk_command_create({ msgs_get, channel_create, msg_error });
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,24 @@
#include <dpp/dpp.h>
int main() {
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* We won't be performing any commands, so we don't need to add the event here! */
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct clear_bot_commands>()) {
/* Now, we're going to wipe our commands */
bot.global_bulk_command_delete();
/* This one requires a guild id, otherwise it won't know what guild's commands it needs to wipe! */
bot.guild_bulk_command_delete(857692897221033129);
}
/* Because the run_once above uses a 'clear_bot_commands' struct, you can continue to register commands below! */
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,27 @@
/*
* @brief This is a function that does some cool stuff.
* More stuff here will still go in brief!
* @warning This does nothing!
*/
func_name();
/*
* @brief This turns a name into a meme name!
*
* @param name The name of the user that you want to meme-ify.
* @return a meme name!
*/
std::string name_to_meme(const std::string& name) const;
/* -------------------- .cpp file -------------------- */
int main() {
/* We are now going to do some cool stuff. */
func_name();
/* Going to turn brain into a meme name.
* Why?
* Because why not. That's why.
*/
std::cout << name_to_meme("Brain") << "\n";
}

View File

@@ -0,0 +1,20 @@
void foo() {
if (a == b) {
c();
} else {
d();
}
while (true) {
// ...
}
switch (a) {
case 1:
c();
break;
case 2:
d();
break;
}
}

View File

@@ -0,0 +1,5 @@
stuff{}
.add_stuff()
.add_stuff();
event.reply("This reply function isn't indented!");

View File

@@ -0,0 +1,18 @@
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;
}

View File

@@ -0,0 +1,2 @@
dpp::my_new_class nc;
nc.set_hats(3).set_clowns(9001);

View File

@@ -0,0 +1,3 @@
std::vector<std::string> clowns = { "pennywise", "bobo" };
evaluate_clown(clowns[0], evilness(2.5, factor));

View File

@@ -0,0 +1,5 @@
class DPP_EXPORT my_new_class {
public:
int hats;
int clowns;
};

View File

@@ -0,0 +1,46 @@
#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, dpp::snowflake id) : dpp::reaction_collector(cl, 20, id) { }
/* Override the "completed" event and then output the number of collected reactions as a message. */
virtual void completed(const std::vector<dpp::collected_reaction>& list) override {
if (list.size()) {
owner->message_create(dpp::message(list[0].react_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([&r, &bot](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,53 @@
#include <dpp/dpp.h>
int main() {
/* If your bot only uses the "/" prefix, you can remove the intents here. */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
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. Remove the .add_prefix(".") if you wish to only make it a slash command */
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 guild 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,53 @@
#include <dpp/dpp.h>
#include <dpp/unicode_emoji.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() == "button") {
/* Create a message */
dpp::message msg(event.command.channel_id, "this text has a button");
/* Add an action row, and then a button within the action row. */
msg.add_component(
dpp::component().add_component(
dpp::component()
.set_label("Click me!")
.set_type(dpp::cot_button)
.set_emoji(dpp::unicode_emoji::smile)
.set_style(dpp::cos_danger)
.set_id("myid")
)
);
/* Reply to the user with our message. */
event.reply(msg);
}
});
/* 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.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("button", "Send a message with a button!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,61 @@
#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() == "math") {
/* Create a message */
dpp::message msg(event.command.channel_id, "What is 5+5?");
/* Add an action row, and then 3 buttons within the action row. */
msg.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")
)
);
/* Reply to the user with our message. */
event.reply(msg);
}
});
bot.on_button_click([&bot](const dpp::button_click_t & event) {
if (event.custom_id == "10") {
event.reply(dpp::message("You got it right!").set_flags(dpp::m_ephemeral));
} else {
event.reply(dpp::message("Wrong! Try again.").set_flags(dpp::m_ephemeral));
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("math", "A quick maths question!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,53 @@
#include <dpp/dpp.h>
#include <dpp/unicode_emoji.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() == "select") {
/* Create a message */
dpp::message msg(event.command.channel_id, "This text has a select menu!");
/* Add an action row, and a select menu within the action row. */
msg.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(dpp::unicode_emoji::smile))
.add_select_option(dpp::select_option("label2","value2","description2").set_emoji(dpp::unicode_emoji::slight_smile))
.set_id("myselectid")
)
);
/* Reply to the user with our message. */
event.reply(msg);
}
});
/* 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.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("select", "Select something at random!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,46 @@
#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() == "select") {
/* Create a message */
dpp::message msg(event.command.channel_id, "This text has a select menu!");
/* Add an action row, and a select menu within the action row.
*
* By default, max values is 1, meaning people can only pick 1 option.
* We're changing this to two, so people can select multiple options!
* We'll also set the min_values to 2 so people have to pick another value!
*/
msg.add_component(
dpp::component().add_component(
dpp::component()
.set_type(dpp::cot_role_selectmenu)
.set_min_values(2)
.set_max_values(2)
.set_id("myselectid")
)
);
/* Reply to the user with our message. */
event.reply(msg);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("select", "Select something at random!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,38 @@
#include <dpp/dpp.h>
#include <iostream>
int main()
{
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* 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());
}
});
bot.on_ready([&bot](const dpp::ready_t &event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create the command */
dpp::slashcommand command;
command.set_name("High Five")
.set_application_id(bot.me.id)
.set_type(dpp::ctxm_user);
/* Register the command */
bot.guild_command_create(command, 857692897221033129); /* Replace this with the guild id you want */
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,46 @@
#include <dpp/dpp.h>
int main() {
dpp::cluster bot{"token"};
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "test") {
// Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it
dpp::message m{"Test"};
std::string id{event.command.id.str()};
m.add_component(
dpp::component{}.add_component(
dpp::component{}
.set_type(dpp::cot_button)
.set_label("Click me!")
.set_id(id)
)
);
co_await event.co_reply(m);
dpp::button_click_t click_event = co_await event.from->creator->on_button_click.when(
// Note!! Due to a bug in g++11 and g++12, id must be captured as a reference here or the compiler will destroy it twice. This is fixed in g++13
[&id] (dpp::button_click_t const &b) {
return b.custom_id == id;
}
);
// Acknowledge the click and edit the original response, removing the button
click_event.reply();
event.edit_original_response(dpp::message{"You clicked the button!"});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id};
bot.global_command_create(command);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,52 @@
#include <dpp/dpp.h>
int main() {
dpp::cluster bot{"token"};
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "test") {
// Make a message and add a button with its custom ID set to the command interaction's ID so we can identify it
dpp::message m{"Test"};
std::string id{event.command.id.str()};
m.add_component(
dpp::component{}.add_component(
dpp::component{}
.set_type(dpp::cot_button)
.set_label("Click me!")
.set_id(id)
)
);
co_await event.co_reply(m);
auto result = co_await dpp::when_any{ // Whichever completes first...
event.from->creator->on_button_click.when([&id](const dpp::button_click_t &b) { // Button clicked
return b.custom_id == id;
}),
event.from->creator->co_sleep(5) // Or sleep 5 seconds
};
// Note!! Due to a bug in g++11 and g++12, id must be captured as a reference above or the compiler will destroy it twice. This is fixed in g++13
if (result.index() == 0) { // Awaitable #0 completed first, that is the button click event
// Acknowledge the click and edit the original response, removing the button
const dpp::button_click_t &click_event = result.get<0>();
click_event.reply();
event.edit_original_response(dpp::message{"You clicked the button with the id " + click_event.custom_id});
} else { // Here index() is 1, the timer expired
event.edit_original_response(dpp::message{"I haven't got all day!"});
}
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command{"test", "Test awaiting for an event", bot.me.id};
bot.global_command_create(command);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,35 @@
#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([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "file") {
/* Request the image from the URL specified and co_await the response */
dpp::http_request_completion_t result = co_await event.from->creator->co_request("https://dpp.dev/DPP-Logo.png", dpp::m_get);
/* Create a message and attach the image on success */
dpp::message msg(event.command.channel_id, "This is my new attachment:");
if (result.status == 200) {
msg.add_file("logo.png", result.body);
}
/* Send the message, with our attachment. */
event.reply(msg);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand{"file", "Send a message with an image attached from the internet!", bot.me.id});
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,65 @@
#include <dpp/dpp.h>
int main() {
dpp::cluster bot{"token"};
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "addemoji") {
dpp::cluster *cluster = event.from->creator;
// Retrieve parameter values
dpp::snowflake file_id = std::get<dpp::snowflake>(event.get_parameter("file"));
std::string emoji_name = std::get<std::string>(event.get_parameter("name"));
// Get the attachment from the resolved list
const dpp::attachment &attachment = event.command.get_resolved_attachment(file_id);
// For simplicity for this example we only support PNG
if (attachment.content_type != "image/png") {
// While we could use event.co_reply, we can just use event.reply, as we will exit the command anyway and don't need to wait on the result
event.reply("Error: type " + attachment.content_type + " not supported");
co_return;
}
// Send a "<bot> is thinking..." message, to wait on later so we can edit
dpp::async thinking = event.co_thinking(false);
// Download and co_await the result
dpp::http_request_completion_t response = co_await cluster->co_request(attachment.url, dpp::m_get);
if (response.status != 200) { // Page didn't send the image
co_await thinking; // Wait for the thinking response to arrive so we can edit
event.edit_response("Error: could not download the attachment");
} else {
// Load the image data in a dpp::emoji
dpp::emoji emoji(emoji_name);
emoji.load_image(response.body, dpp::image_type::i_png);
// Create the emoji and co_await the response
dpp::confirmation_callback_t confirmation = co_await cluster->co_guild_emoji_create(event.command.guild_id, emoji);
co_await thinking; // Wait for the thinking response to arrive so we can edit
if (confirmation.is_error()) {
event.edit_response("Error: could not add emoji: " + confirmation.get_error().message);
} else { // Success
event.edit_response("Successfully added " + confirmation.get<dpp::emoji>().get_mention()); // Show the new emoji
}
}
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command("addemoji", "Add an emoji", bot.me.id);
// Add file and name as required parameters
command.add_option(dpp::command_option(dpp::co_attachment, "file", "Select an image", true));
command.add_option(dpp::command_option(dpp::co_string, "name", "Name of the emoji to add", true));
bot.global_command_create(command);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,85 @@
#include <dpp/dpp.h>
int main() {
dpp::cluster bot{"token"};
bot.on_log(dpp::utility::cout_logger());
bot.on_slashcommand([](const dpp::slashcommand_t& event) -> dpp::task<void> {
if (event.command.get_command_name() == "avatar") {
// Make a nested coroutine to fetch the guild member requested, that returns it as an optional
constexpr auto resolve_member = [](const dpp::slashcommand_t &event) -> dpp::task<std::optional<dpp::guild_member>> {
const dpp::command_value &user_param = event.get_parameter("user");
dpp::snowflake user_id;
if (std::holds_alternative<std::monostate>(user_param)) {
user_id = event.command.usr.id; // Parameter is empty so user is sender
}
else if (std::holds_alternative<dpp::snowflake>(user_param)) {
user_id = std::get<dpp::snowflake>(user_param); // Parameter has a user
}
// If we have the guild member in the command's resolved data, return it
const auto &member_map = event.command.resolved.members;
if (auto member = member_map.find(user_id); member != member_map.end()) {
co_return member->second;
}
// Try looking in guild cache
dpp::guild *guild = dpp::find_guild(event.command.guild_id);
if (guild) {
// Look in guild's member cache
if (auto member = guild->members.find(user_id); member != guild->members.end()) {
co_return member->second;
}
}
// Finally if everything else failed, request API
dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_guild_get_member(event.command.guild_id, user_id);
if (confirmation.is_error()) {
co_return std::nullopt; // Member not found, return empty
} else {
co_return confirmation.get<dpp::guild_member>();
}
};
// Send a "<bot> is thinking..." message, to wait on later so we can edit
dpp::async thinking = event.co_thinking(false);
// Call our coroutine defined above to retrieve the member requested
std::optional<dpp::guild_member> member = co_await resolve_member(event);
if (!member.has_value()) {
// Wait for the thinking response to arrive to make sure we can edit
co_await thinking;
event.edit_original_response(dpp::message{"User not found in this server!"});
co_return;
}
std::string avatar_url = member->get_avatar_url(512);
if (avatar_url.empty()) { // Member does not have a custom avatar for this server, get their user avatar
dpp::confirmation_callback_t confirmation = co_await event.from->creator->co_user_get_cached(member->user_id);
if (confirmation.is_error()) {
// Wait for the thinking response to arrive to make sure we can edit
co_await thinking;
event.edit_original_response(dpp::message{"User not found!"});
co_return;
}
avatar_url = confirmation.get<dpp::user_identified>().get_avatar_url(512);
}
// Wait for the thinking response to arrive to make sure we can edit
co_await thinking;
event.edit_original_response(dpp::message{avatar_url});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand command("avatar", "Get your or another user's avatar image", bot.me.id);
command.add_option(dpp::command_option(dpp::co_user, "user", "User to fetch the avatar from"));
bot.global_command_create(command);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,47 @@
#include <dpp/dpp.h>
#include <dpp/unicode_emoji.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() == "select") {
/* Create a message */
dpp::message msg(event.command.channel_id, "This text has a select menu!");
/* Add an action row, and a select menu within the action row.
*
* Your default values are limited to max_values,
* meaning you can't add more default values than the allowed max values.
*/
msg.add_component(
dpp::component().add_component(
dpp::component()
.set_type(dpp::cot_role_selectmenu)
.set_min_values(2)
.set_max_values(2)
.add_default_value(dpp::snowflake{667756886443163648}, dpp::cdt_role)
.set_id("myselectid")
)
);
/* Reply to the user with our message. */
event.reply(msg);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("select", "Select something at random!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,22 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot, but with our intents so we can use messages. */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when the bot detects a message in any server and any channel it has access to. */
bot.on_message_create([&bot](const dpp::message_create_t& event) {
/* See if the message contains the phrase we want to check for.
* If there's at least a single match, we reply and say it's not allowed.
*/
if (event.msg.content.find("bad word") != std::string::npos) {
event.reply("That is not allowed here. Please, mind your language!", true);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,77 @@
#include <dpp/dpp.h>
int main() {
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
/* the second argument is a bitmask of intents - i_message_content is needed to get messages */
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) {
if (event.command.get_command_name() == "msg-send") {
event.reply("That's a message");
} else if (event.command.get_command_name() == "msg-edit") {
const auto content = std::get<std::string>(event.get_parameter("content"));
/* get message to edit it after */
const dpp::snowflake msg_id = std::get<std::string>(event.get_parameter("msg-id"));
/* here string will automatically be converted to snowflake */
bot.message_get(msg_id, event.command.channel_id, [&bot, content, event](const dpp::confirmation_callback_t& callback) {
if (callback.is_error()) {
event.reply("error");
return;
}
auto message = callback.get<dpp::message>();
/* change the message content and edit the message itself */
message.set_content(content);
bot.message_edit(message);
event.reply("Message content is now `" + content + "`.");
});
} else if (event.command.get_command_name() == "channel-edit") {
const auto name = std::get<std::string>(event.get_parameter("name"));
/* get the channel to edit it after */
const auto channel_id = std::get<dpp::snowflake>(event.get_parameter("channel"));
bot.channel_get(channel_id, [&bot, name, event](const dpp::confirmation_callback_t& callback) {
if (callback.is_error()) {
event.reply("error");
return;
}
auto channel = callback.get<dpp::channel>();
/* change the channel name and edit the channel itself */
channel.set_name(name);
bot.channel_edit(channel);
event.reply("Channel name is now `" + name + "`.");
});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once <struct register_global_commands>()) {
dpp::slashcommand msg_edit("msg-edit", "Edit a message sent by the bot", bot.me.id);
msg_edit.add_option(dpp::command_option(dpp::co_string, "msg-id", "ID of the message to edit", true)); /* true for required option */
msg_edit.add_option(dpp::command_option(dpp::co_string, "content", "New content for the message", true)); /* same here */
dpp::slashcommand channel_edit("channel-edit", "Edit the name of channel specified", bot.me.id);
channel_edit.add_option(dpp::command_option(dpp::co_channel, "channel", "Channel to edit", true));
channel_edit.add_option(dpp::command_option(dpp::co_string, "name", "New name for the channel", true));
dpp::slashcommand msg_send("msg-send", "Send my message", bot.me.id);
bot.global_bulk_command_create({ msg_edit, channel_edit, msg_send });
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,59 @@
#include <dpp/dpp.h>
int main() {
/* Setup the bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
/* 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() == "embed") {
/* Create an 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));
/* Create a message with the content as our new embed. */
dpp::message msg(event.command.channel_id, embed);
/* Reply to the user with the message, containing our embed. */
event.reply(msg);
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register a command when the bot is ready */
bot.global_command_create(dpp::slashcommand("embed", "Send a test embed!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,28 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot */
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() == "hello") {
/* Reply to the user, but only let them see the response. */
event.reply(dpp::message("Hello! How are you today?").set_flags(dpp::m_ephemeral));
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and Register the command */
bot.global_command_create(dpp::slashcommand("hello", "Hello there!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,175 @@
/**
* 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 != dpp::snowflake(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 <dpp/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;
}

View File

@@ -0,0 +1,14 @@
#pragma once
/* This is the snowflake ID of the bot's developer.
* The eval command will be restricted to this user.
*/
#define MY_DEVELOPER 189759562910400512ULL
/* 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();

View File

@@ -0,0 +1,23 @@
#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);
}

View File

@@ -0,0 +1,4 @@
#include <dpp/dpp.h>
int main() {
}

View File

@@ -0,0 +1,7 @@
#include <dpp/dpp.h>
const std::string BOT_TOKEN = "add your token here";
int main() {
dpp::cluster bot(BOT_TOKEN);
}

View File

@@ -0,0 +1,13 @@
#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));
}
});
}

View File

@@ -0,0 +1,16 @@
#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));
}
});
}

View File

@@ -0,0 +1,19 @@
#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));
}
});
}

View File

@@ -0,0 +1,23 @@
#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);
}

View File

@@ -0,0 +1,30 @@
#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) {
// 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);
return 0;
}

View File

@@ -0,0 +1,85 @@
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
int main() {
/* Setup the bot */
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() == "join") {
/* Get the guild */
dpp::guild* g = dpp::find_guild(event.command.guild_id);
/* Get the voice channel that the bot is currently in from this server (will return nullptr if we're not in a voice channel!) */
auto current_vc = event.from->get_voice(event.command.guild_id);
bool join_vc = true;
/* Are we in a voice channel? If so, let's see if we're in the right channel. */
if (current_vc) {
/* Find the channel id that the user is currently in */
auto users_vc = g->voice_members.find(event.command.get_issuing_user().id);
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. We should leave it, then join the new one
* by falling through to the join_vc branch below.
*/
event.from->disconnect_voice(event.command.guild_id);
join_vc = true;
}
}
/* If we need to join a vc at all, join it here if join_vc == true */
if (join_vc) {
/* Attempt to connect to a voice channel, returns false if we fail to connect. */
/* The user issuing the command is not on any voice channel, we can't do anything */
if (!g->connect_member_voice(event.command.get_issuing_user().id)) {
event.reply("You don't seem to be in a voice channel!");
return;
}
/* 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!
*/
/* Tell the user we joined their channel. */
event.reply("Joined your channel!");
} else {
event.reply("Don't need to join your channel as i'm already there with you!");
}
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
bot.global_command_create(dpp::slashcommand("join", "Joins your voice channel.", bot.me.id));
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,35 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot */
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when the bot detects a message in any server and any channel it has access to. */
bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) {
/* Check which command they ran */
if (event.command.get_command_name() == "create-thread") {
/* Here we create a thread in the current channel. It will expire after 60 minutes of inactivity. We'll also allow other mods to join, and we won't add a slowdown timer. */
bot.thread_create("Cool thread!", event.command.channel_id, 60, dpp::channel_type::CHANNEL_PUBLIC_THREAD, true, 0, [event](const dpp::confirmation_callback_t& callback) {
if (callback.is_error()) {
event.reply("Failed to create a thread!");
return;
}
event.reply("Created a thread for you!");
});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register the command */
bot.global_command_create(dpp::slashcommand("create-thread", "Create a thread!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,56 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot */
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when the bot detects a message in any server and any channel it has access to. */
bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) {
/* Check which command they ran */
if (event.command.get_command_name() == "message-thread") {
/* Get all active threads in a guild. */
bot.threads_get_active(event.command.guild_id, [&bot, event](const dpp::confirmation_callback_t& callback) {
if (callback.is_error()) {
event.reply("Failed to get threads!");
return;
}
/* Get the list of active threads in the guild. */
auto threads = callback.get<dpp::active_threads>();
dpp::snowflake thread_id;
/* Loop through the threads, getting each value in the map. Then we get the first value and then break off.
* The reason we're getting only the first value is because, for this example, we'll just assume you've only got a single active thread (the one created by the bot)
*/
for (const auto& _thread : threads) {
thread_id = _thread.first;
break;
}
/* Send a message in the first thread we find. */
bot.message_create(dpp::message(thread_id, "Hey, I'm first to message in a cool thread!"), [event](const dpp::confirmation_callback_t& callback2) {
if (callback2.is_error()) {
event.reply("Failed to send a message in a thread.");
return;
}
event.reply("I've sent a message in the specified thread.");
});
});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register the command */
bot.global_command_create(dpp::slashcommand("message-thread", "Message a thread!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,49 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot */
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when the bot detects a message in any server and any channel it has access to. */
bot.on_slashcommand([&bot](const dpp::slashcommand_t& event) {
/* Check which command they ran */
if (event.command.get_command_name() == "lock-thread") {
/* Get this channel as a thread. */
bot.thread_get(event.command.channel_id, [&bot, event](const dpp::confirmation_callback_t& callback) {
if (callback.is_error()) {
event.reply("I failed to get the thread!");
return;
}
/* Get the thread from the callback. */
auto thread = callback.get<dpp::thread>();
/* Set the thread to locked. */
thread.metadata.locked = true;
/* Now we tell discord about our updates, meaning the thread will lock! */
bot.thread_edit(thread, [event](const dpp::confirmation_callback_t& callback2) {
if (callback2.is_error()) {
event.reply("I failed to lock the thread!");
return;
}
event.reply("I have locked the thread!");
});
});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create and register the command */
bot.global_command_create(dpp::slashcommand("lock-thread", "Lock the thread that you run this command in!", bot.me.id));
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,70 @@
#include <dpp/dpp.h>
#include <iostream>
int main() {
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
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);
});
bot.on_ready([&bot](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));
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,111 @@
#include <dpp/dpp.h>
#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() {
/* 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 (size_t 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");
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when someone issues your commands */
bot.on_slashcommand([&bot, &pcmdata](const dpp::slashcommand_t& event) {
/* Check which command they ran */
if (event.command.get_command_name() == "join") {
/* Get the guild */
dpp::guild* g = dpp::find_guild(event.command.guild_id);
/* Attempt to connect to a voice channel, returns false if we fail to connect. */
if (!g->connect_member_voice(event.command.get_issuing_user().id)) {
event.reply("You don't seem to be in a voice channel!");
return;
}
/* Tell the user we joined their channel. */
event.reply("Joined your channel!");
} else if (event.command.get_command_name() == "mp3") {
/* Get the voice channel the bot is in, in this current guild. */
dpp::voiceconn* v = event.from->get_voice(event.command.guild_id);
/* If the voice channel was invalid, or there is an issue with it, then tell the user. */
if (!v || !v->voiceclient || !v->voiceclient->is_ready()) {
event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!");
return;
}
/* 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());
event.reply("Played the mp3 file.");
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id);
dpp::slashcommand mp3command("mp3", "Plays an mp3 file.", bot.me.id);
bot.global_bulk_command_create({ joincommand, mp3command });
}
});
/* Start bot */
bot.start(dpp::st_wait);
/* Clean up */
mpg123_exit();
return 0;
}

View File

@@ -0,0 +1,172 @@
#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");
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() == "join") {
/* Get the guild */
dpp::guild* g = dpp::find_guild(event.command.guild_id);
/* Attempt to connect to a voice channel, returns false if we fail to connect. */
if (!g->connect_member_voice(event.command.get_issuing_user().id)) {
event.reply("You don't seem to be in a voice channel!");
return;
}
/* Tell the user we joined their channel. */
event.reply("Joined your channel!");
} else if (event.command.get_command_name() == "play") {
/* Get the voice channel the bot is in, in this current guild. */
dpp::voiceconn* v = event.from->get_voice(event.command.guild_id);
/* If the voice channel was invalid, or there is an issue with it, then tell the user. */
if (!v || !v->voiceclient || !v->voiceclient->is_ready()) {
event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!");
return;
}
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);
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);
event.reply("Finished playing the audio file!");
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id);
dpp::slashcommand playcommand("play", "Plays an ogg file.", bot.me.id);
bot.global_bulk_command_create({ joincommand, playcommand });
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,114 @@
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <oggz/oggz.h>
int main() {
/* 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
*/
/* Setup the bot */
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() == "join") {
/* Get the guild */
dpp::guild* g = dpp::find_guild(event.command.guild_id);
/* Attempt to connect to a voice channel, returns false if we fail to connect. */
if (!g->connect_member_voice(event.command.get_issuing_user().id)) {
event.reply("You don't seem to be in a voice channel!");
return;
}
/* Tell the user we joined their channel. */
event.reply("Joined your channel!");
} else if (event.command.get_command_name() == "play") {
/* Get the voice channel the bot is in, in this current guild. */
dpp::voiceconn* v = event.from->get_voice(event.command.guild_id);
/* If the voice channel was invalid, or there is an issue with it, then tell the user. */
if (!v || !v->voiceclient || !v->voiceclient->is_ready()) {
event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!");
return;
}
/* load the audio file with oggz */
OGGZ *track_og = oggz_open("/path/to/opus.ogg", OGGZ_READ);
/* If there was an issue reading the file, tell the user and stop */
if (!track_og) {
fprintf(stderr, "Error opening file\n");
event.reply("There was an issue opening the file!");
return;
}
/* 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;
}
}
/* Don't forget to free the memory */
oggz_close(track_og);
event.reply("Finished playing the audio file!");
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id);
dpp::slashcommand playcommand("play", "Plays an ogg file.", bot.me.id);
bot.global_bulk_command_create({ joincommand, playcommand });
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,62 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot */
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() == "pm") {
dpp::snowflake user;
/* If there was no specified user, we set the "user" variable to the command author (issuing user). */
if (event.get_parameter("user").index() == 0) {
user = event.command.get_issuing_user().id;
} else { /* Otherwise, we set it to the specified user! */
user = std::get<dpp::snowflake>(event.get_parameter("user"));
}
/* Send a message to the user set above. */
bot.direct_message_create(user, dpp::message("Here's a private message!"), [event, user](const dpp::confirmation_callback_t& callback){
/* If the callback errors, we want to send a message telling the author that something went wrong. */
if (callback.is_error()) {
/* Here, we want the error message to be different if the user we're trying to send a message to is the command author. */
if (user == event.command.get_issuing_user().id) {
event.reply(dpp::message("I couldn't send you a message.").set_flags(dpp::m_ephemeral));
} else {
event.reply(dpp::message("I couldn't send a message to that user. Please check that is a valid user!").set_flags(dpp::m_ephemeral));
}
return;
}
/* We do the same here, so the message is different if it's to the command author or if it's to a specified user. */
if (user == event.command.get_issuing_user().id) {
event.reply(dpp::message("I've sent you a private message.").set_flags(dpp::m_ephemeral));
} else {
event.reply(dpp::message("I've sent a message to that user.").set_flags(dpp::m_ephemeral));
}
});
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Register the command */
dpp::slashcommand command("pm", "Send a private message.", bot.me.id);
/* Add the option for a user mention that isn't required */
command.add_option(dpp::command_option(dpp::co_mentionable, "user", "The user to message", false));
/* Register the command */
bot.global_command_create(command);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,66 @@
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
int main() {
/* 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;
/* Setup the bot */
dpp::cluster bot("token");
FILE *fd;
fd = fopen("./me.pcm", "wb");
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when someone issues your commands */
bot.on_slashcommand([&bot, &fd](const dpp::slashcommand_t& event) {
/* Check which command they ran */
if (event.command.get_command_name() == "record") {
/* Get the guild */
dpp::guild* g = dpp::find_guild(event.command.guild_id);
/* Attempt to connect to a voice channel, returns false if we fail to connect. */
if (!g->connect_member_voice(event.command.get_issuing_user().id)) {
event.reply("You don't seem to be in a voice channel!");
return;
}
/* Tell the user we joined their channel. */
event.reply("Joined your channel, now recording!");
} else if (event.command.get_command_name() == "stop") {
event.from->disconnect_voice(event.command.guild_id);
fclose(fd);
event.reply("Stopped recording.");
}
});
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);
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
dpp::slashcommand recordcommand("record", "Joins your voice channel and records you.", bot.me.id);
dpp::slashcommand stopcommand("stop", "Stops recording you.", bot.me.id);
bot.global_bulk_command_create({ recordcommand, stopcommand });
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,43 @@
#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() == "addrole") {
/* Fetch a parameter value from the command options */
dpp::snowflake user_id = std::get<dpp::snowflake>(event.get_parameter("user"));
dpp::snowflake role_id = std::get<dpp::snowflake>(event.get_parameter("role"));
/* Get member object from resolved list */
dpp::guild_member resolved_member = event.command.get_resolved_member(user_id);
resolved_member.add_role(role_id);
bot.guild_edit_member(resolved_member);
event.reply("Added role");
}
});
/* Attach on_ready event */
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
dpp::slashcommand add_role("addrole", "Give user a role", bot.me.id);
/* Add user and role type command options to the slash command */
add_role.add_option(dpp::command_option(dpp::co_user, "user", "User to give role to", true));
add_role.add_option(dpp::command_option(dpp::co_role, "role", "Role to give", true));
bot.global_command_create(add_role);
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,19 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot */
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
bot.on_ready([&bot](const dpp::ready_t& event) {
/* We don't need the run_once here as we're not registering commands! */
/* Set the bot presence as online and "Playing..."! */
bot.set_presence(dpp::presence(dpp::ps_online, dpp::at_game, "games!"));
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,25 @@
#include <dpp/dpp.h>
int main() {
/* Create the bot */
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
bot.on_ready([&bot](const dpp::ready_t& event) {
/* We put our status updating inside "run_once" so that multiple shards don't try do this as "set_presence" updates all the shards. */
if (dpp::run_once<struct register_bot_commands>()) {
/* We update the presence now as the timer will do the first execution after the x amount of seconds we specify */
bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_game, "with " + std::to_string(event.guild_count) + " guilds!"));
/* Create a timer that runs every 120 seconds, that sets the status */
bot.start_timer([&bot](const dpp::timer& timer) {
bot.set_presence(dpp::presence(dpp::presence_status::ps_online, dpp::activity_type::at_game, "with " + std::to_string(dpp::get_guild_cache()->count()) + " guilds!"));
}, 120);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,41 @@
#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;
}

View File

@@ -0,0 +1,41 @@
#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.guild_command_create(newcommand, 857692897221033129); /* Replace this with the guild id you want */
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,38 @@
#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() == "ping") {
event.reply("Pong!");
} else if (event.command.get_command_name() == "pong") {
event.reply("Ping!");
} else if (event.command.get_command_name() == "ding") {
event.reply("Dong!");
} else if (event.command.get_command_name() == "dong") {
event.reply("Ding!");
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create some commands */
dpp::slashcommand pingcommand("ping", "Pong!", bot.me.id);
dpp::slashcommand pongcommand("pong", "Ping!", bot.me.id);
dpp::slashcommand dingcommand("ding", "Dong!", bot.me.id);
dpp::slashcommand dongcommand("dong", "Ding!", bot.me.id);
/* Register our commands in a list using bulk */
bot.global_bulk_command_create({ pingcommand, pongcommand, dingcommand, dongcommand });
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,38 @@
#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() == "ping") {
event.reply("Pong!");
} else if (event.command.get_command_name() == "pong") {
event.reply("Ping!");
} else if (event.command.get_command_name() == "ding") {
event.reply("Dong!");
} else if (event.command.get_command_name() == "dong") {
event.reply("Ding!");
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create some commands */
dpp::slashcommand pingcommand("ping", "Pong!", bot.me.id);
dpp::slashcommand pongcommand("pong", "Ping!", bot.me.id);
dpp::slashcommand dingcommand("ding", "Dong!", bot.me.id);
dpp::slashcommand dongcommand("dong", "Ding!", bot.me.id);
/* Register our commands in a list using bulk */
bot.guild_bulk_command_create({ pingcommand, pongcommand, dingcommand, dongcommand }, 857692897221033129);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,80 @@
#include <dpp/dpp.h>
#include <iomanip>
#include <sstream>
int main() {
/* 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");
bot.on_log(dpp::utility::cout_logger());
/* The event is fired when someone issues your commands */
bot.on_slashcommand([&bot, robot, robot_size](const dpp::slashcommand_t& event) {
/* Check which command they ran */
if (event.command.get_command_name() == "join") {
/* Get the guild */
dpp::guild* g = dpp::find_guild(event.command.guild_id);
/* Attempt to connect to a voice channel, returns false if we fail to connect. */
if (!g->connect_member_voice(event.command.get_issuing_user().id)) {
event.reply("You don't seem to be in a voice channel!");
return;
}
/* Tell the user we joined their channel. */
event.reply("Joined your channel!");
} else if (event.command.get_command_name() == "robot") {
/* Get the voice channel the bot is in, in this current guild. */
dpp::voiceconn* v = event.from->get_voice(event.command.guild_id);
/* If the voice channel was invalid, or there is an issue with it, then tell the user. */
if (!v || !v->voiceclient || !v->voiceclient->is_ready()) {
event.reply("There was an issue with getting the voice channel. Make sure I'm in a voice channel!");
return;
}
/* Tell the bot to play the sound file 'Robot.pcm' in the current voice channel. */
v->voiceclient->send_audio_raw((uint16_t*)robot, robot_size);
event.reply("Played robot.");
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
dpp::slashcommand joincommand("join", "Joins your voice channel.", bot.me.id);
dpp::slashcommand robotcommand("robot", "Plays a robot noise in your voice channel.", bot.me.id);
bot.global_bulk_command_create({ joincommand, robotcommand });
}
});
/* Start bot */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,66 @@
#include <dpp/dpp.h>
#include <iostream>
int main() {
dpp::cluster bot("token");
bot.on_log(dpp::utility::cout_logger());
/* Use the on_slashcommand event to look for commands */
bot.on_slashcommand([&bot](const dpp::slashcommand_t & event) {
dpp::command_interaction cmd_data = event.command.get_command_interaction();
/* Check if the command is the image command. */
if (event.command.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 = event.command.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");
}
} else if (subcommand.name == "cat") { /* Check if the subcommand is "cat". */
/* Checks if the subcommand has any options. */
if (!subcommand.options.empty()) {
/* Get the user from the parameter */
dpp::user user = event.command.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");
}
}
}
});
/* 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);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,39 @@
#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() == "show") {
/* Get the file id from the parameter attachment. */
dpp::snowflake file_id = std::get<dpp::snowflake>(event.get_parameter("file"));
/* Get the attachment that the user inputted from the file id. */
dpp::attachment att = event.command.get_resolved_attachment(file_id);
/* Reply with the file as a URL. */
event.reply(att.url);
}
});
bot.on_ready([&bot](const dpp::ready_t & event) {
if (dpp::run_once<struct register_bot_commands>()) {
/* Create a new command. */
dpp::slashcommand newcommand("show", "Show an uploaded file", bot.me.id);
/* Add a parameter option. */
newcommand.add_option(dpp::command_option(dpp::co_attachment, "file", "Select an image"));
/* Register the command */
bot.global_command_create(newcommand);
}
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,26 @@
#include <dpp/dpp.h>
int main() {
/* Create bot */
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_guild_members);
bot.on_log(dpp::utility::cout_logger());
/* This event is fired when someone removes their reaction from a message */
bot.on_message_reaction_remove([&bot](const dpp::message_reaction_remove_t& event) {
/* Find the user in the cache using his discord id */
dpp::user* reacting_user = dpp::find_user(event.reacting_user_id);
/* If user not found in cache, log and return */
if (!reacting_user) {
bot.log(dpp::ll_info, "User with the id " + std::to_string(event.reacting_user_id) + " was not found.");
return;
}
bot.log(dpp::ll_info, reacting_user->format_username() + " removed his reaction.");
});
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,29 @@
#include <dpp/dpp.h>
#include <dpp/unicode_emoji.h>
int main() {
dpp::cluster bot("Epic Token");
bot.on_log(dpp::utility::cout_logger());
/* We'll be using two emojis: shocked guy and animated mad face. */
dpp::emoji shocked("vahuyi", 1179366531856093214);
dpp::emoji mad("mad", 1117795317052616704, dpp::e_animated); /* We need this third argument, which is an emoji flag. */
bot.on_slashcommand([shocked, mad](const dpp::slashcommand_t& event) {
if (event.command.get_command_name() == "send-emojis") {
/* Here we send our very informative message: three epic emojis. */
event.reply(dpp::unicode_emoji::nerd + shocked.get_mention() + mad.get_mention());
}
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
bot.global_command_create(dpp::slashcommand("send-emojis", "Send the emojis", bot.me.id));
}
});
/* Start the bot! */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,35 @@
#include <dpp/dpp.h>
#include <dpp/unicode_emoji.h>
int main() {
dpp::cluster bot("Epic Token", dpp::i_default_intents | dpp::i_message_content);
/* The second argument is a bitmask of intents - i_message_content is needed to see the messages */
bot.on_log(dpp::utility::cout_logger());
/* We'll be using a shocked guy emoji */
dpp::emoji shocked("vahuyi", 1179366531856093214);
dpp::emoji mad("mad", 1117795317052616704, dpp::e_animated); /* We need this third argument, which is an emoji flag. */
bot.on_message_create([&bot, shocked, mad](const dpp::message_create_t& event) {
if (event.msg.content == "I'm hungry") {
/* But if they're hungry */
bot.message_add_reaction(event.msg.id, event.msg.channel_id, dpp::unicode_emoji::cut_of_meat);
/* Let's send some meat to the message, so they don't starve. They will thank us later. */
} else if (event.msg.content == "WHAT?") {
/* If some unknown content shocked the user */
bot.message_add_reaction(event.msg.id, event.msg.channel_id, shocked.format());
/* React to their message with a shocked guy */
} else if (event.msg.content == "I'm unsubscribing") {
/* They are angry! We should also be! */
bot.message_add_reaction(event.msg.id, event.msg.channel_id, mad.format());
/* React to their message with a mad emoji */
}
});
/* Start the bot! */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,45 @@
#include <dpp/dpp.h>
#include <dpp/unicode_emoji.h>
int main() {
dpp::cluster bot("Epic Token");
bot.on_log(dpp::utility::cout_logger());
/* We now have a new character! That's for the select menu. */
dpp::emoji walter("walter_black", 1179374919088361544);
dpp::emoji mad("mad", 1117795317052616704, dpp::e_animated); /* We need this third argument, which is an emoji flag. */
/* The event is fired when someone issues your commands */
bot.on_slashcommand([walter, mad](const dpp::slashcommand_t& event) {
if (event.command.get_command_name() == "select") {
dpp::message msg(event.command.channel_id, "Now.");
msg.add_component(
dpp::component().add_component(
dpp::component()
.set_type(dpp::cot_selectmenu)
.set_placeholder("Say my name.")
.add_select_option(dpp::select_option("Do what?", "Yeah, you do.", "I don't have a damn clue what you're talking about.").set_emoji(dpp::unicode_emoji::thinking))
.add_select_option(dpp::select_option("Heisenberg", "You're goddamn right!", "The one and only").set_emoji(walter.name, walter.id))
.add_select_option(dpp::select_option("I'm unsubscribing", "Wait what", "Pure cruelty").set_emoji(mad.name, mad.id, mad.is_animated())) /* Since our mad emoji is animated, we should tell that to the function */
.set_id("myselectid")
)
);
event.reply(msg);
}
});
bot.on_select_click([](const dpp::select_click_t& event) {
event.reply(event.values[0]);
});
bot.on_ready([&bot](const dpp::ready_t& event) {
if (dpp::run_once<struct register_bot_commands>()) {
bot.global_command_create(dpp::slashcommand("select", "Send the select menu", bot.me.id));
}
});
/* Start the bot! */
bot.start(dpp::st_wait);
return 0;
}

View File

@@ -0,0 +1,16 @@
#include <dpp/dpp.h>
int main()
{
dpp::cluster bot(""); /* Normally, you put your bot token in here, but its not required for webhooks. */
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;
}

View File

@@ -0,0 +1,10 @@
\page interactions-and-components Interactions And Components
Interactions are a unified way provided by Discord to handle \ref slashcommands "slash commands" and \ref components-menu "component interactions", such as \ref components "clicking a button". Slash commands are much better than message commands as they are not rate limited as much, meaning your bot can handle a lot more at once. They also contain a lot of data about a command, for example by making a command take a user as a parameter, \ref resolved-objects "the entire user object will be contained in the interaction" so you do not have to fetch it yourself, avoiding even more rate limits!
* \subpage slashcommands-menu
* \subpage user-only-messages
* \subpage resolved-objects
* \subpage components-menu
* \subpage modal-dialog-interactions
* \subpage context-menu

View File

@@ -0,0 +1,8 @@
\page components-menu Message Components
Components are anything that can be attached to a message or a \ref modal-dialog-interactions "modal" and interacted with, for example buttons and select menus. Due to being \ref interactions-and-components "interactions", they benefit from a low rate limit and are much more efficient than the old ways of using reactions for this purpose.
* \subpage components
* \subpage components2
* \subpage components3
* \subpage default_select_value

View File

@@ -0,0 +1,9 @@
\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 this code as in this example.
\include{cpp} components.cpp
When the feature is functioning, the code below will produce buttons on the reply message like in the image below:
\image html button.png

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