mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2026-05-08 11:47:20 +02:00
Add D++ library.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
\page caching-messages Caching Messages
|
||||
|
||||
By default D++ does not cache messages. The example program below demonstrates how to instantiate a custom cache using dpp::cache which will allow you to cache messages and query the cache for messages by ID.
|
||||
|
||||
This can be adjusted to cache any type derived from dpp::managed including types you define yourself.
|
||||
|
||||
@note This example will cache and hold onto messages forever! In a real world situation this would be bad. If you do use this,
|
||||
you should use the dpp::cache::remove() method periodically to remove stale items. This is left out of this example as a learning
|
||||
exercise to the reader. For further reading please see the documentation of dpp::cache
|
||||
|
||||
~~~~~~~~~~{.cpp}
|
||||
#include <dpp/dpp.h>
|
||||
#include <sstream>
|
||||
|
||||
int main() {
|
||||
/* Create bot */
|
||||
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
|
||||
|
||||
/* Create a cache to contain types of dpp::message */
|
||||
dpp::cache<dpp::message> message_cache;
|
||||
|
||||
bot.on_log(dpp::utility::cout_logger());
|
||||
|
||||
/* Message handler */
|
||||
bot.on_message_create([&](const dpp::message_create_t &event) {
|
||||
|
||||
/* Make a permanent pointer using new, for each message to be cached */
|
||||
dpp::message* m = new dpp::message();
|
||||
/* Store the message into the pointer by copying it */
|
||||
*m = event.msg;
|
||||
/* Store the new pointer to the cache using the store() method */
|
||||
message_cache.store(m);
|
||||
|
||||
/* Simple ghetto command handler. In the real world, use slashcommand or commandhandler here. */
|
||||
std::stringstream ss(event.msg.content);
|
||||
std::string cmd;
|
||||
dpp::snowflake msg_id;
|
||||
ss >> cmd;
|
||||
|
||||
/* Look for our command */
|
||||
if (cmd == "!get") {
|
||||
ss >> msg_id;
|
||||
/* Search our cache for a cached message */
|
||||
dpp::message* find_msg = message_cache.find(msg_id);
|
||||
if (find_msg != nullptr) {
|
||||
/* Found a cached message, echo it out */
|
||||
bot.message_create(dpp::message(event.msg.channel_id, "This message had the following content: " + find_msg->content));
|
||||
} else {
|
||||
/* Nothing like that here. Have you checked under the carpet? */
|
||||
bot.message_create(dpp::message(event.msg.channel_id, "There is no message cached with this ID"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Start bot */
|
||||
bot.start(dpp::st_wait);
|
||||
|
||||
return 0;
|
||||
}
|
||||
~~~~~~~~~~
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
\page collecting-reactions Collecting Reactions
|
||||
|
||||
D++ comes with many useful helper classes, but amongst these is something called dpp::collector. Collector is a template which can be specialised to automatically collect objects of a pre-determined type from events for a specific interval of time. Once this time period is up, or the class is otherwise signalled, a method is called with the complete set of collected objects.
|
||||
|
||||
In the example below we will use it to collect all reactions on a message.
|
||||
|
||||
~~~~~~~~~~{.cpp}
|
||||
#include <dpp/dpp.h>
|
||||
|
||||
/* To create a collector we must derive from dpp::collector. As dpp::collector is a complicated template,
|
||||
* various pre-made forms exist such as this one, reaction_collector.
|
||||
*/
|
||||
class react_collector : public dpp::reaction_collector {
|
||||
public:
|
||||
/* Collector will run for 20 seconds */
|
||||
react_collector(dpp::cluster* cl, snowflake id) : dpp::message_collector(cl, 20, id) { }
|
||||
|
||||
/* On completion just output number of collected reactions to as a message. */
|
||||
virtual void completed(const std::vector<dpp::collected_reaction>& list) {
|
||||
if (list.size()) {
|
||||
owner->message_create(dpp::message(list[0].channel_id, "I collected " + std::to_string(list.size()) + " reactions!"));
|
||||
} else {
|
||||
owner->message_create(dpp::message("... I got nothin'."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int main() {
|
||||
/* Create bot */
|
||||
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
|
||||
|
||||
/* Pointer to reaction collector */
|
||||
react_collector* r = nullptr;
|
||||
|
||||
bot.on_log(dpp::utility::cout_logger());
|
||||
|
||||
/* Message handler */
|
||||
bot.on_message_create([&](const dpp::message_create_t &event) {
|
||||
|
||||
/* If someone sends a message that has the text 'collect reactions!' start a reaction collector */
|
||||
if (event.msg.content == "collect reactions!" && r == nullptr) {
|
||||
/* Create a new reaction collector to collect reactions */
|
||||
r = new react_collector(&bot, event.msg.id);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/* Start bot */
|
||||
bot.start(dpp::st_wait);
|
||||
|
||||
return 0;
|
||||
}
|
||||
~~~~~~~~~~
|
||||
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
\page cpp-eval-command-discord Making an eval command in C++
|
||||
|
||||
### What is an eval command anyway?
|
||||
|
||||
Many times people will ask: "how do i make a command like 'eval' in C++". For the uninitiated, an `eval` command is a command often found in interpreted languages such as Javascript and Python, which allows the developer to pass in raw interpreter statements which are then executed within the context of the running program, without any sandboxing. Eval commands are plain **evil**, if not properly coded in.
|
||||
|
||||
Needless to say, this is very dangerous. If you are asking how to do this, and want to put this into your bot, we trust that you have a very good reason to do so and have considered alternatives before resorting to this. The code below is for educational purposes only and makes several assumptions:
|
||||
|
||||
1. This code will only operate on UNIX-like systems such as Linux (not **Darwin**)
|
||||
2. It assumes you use GCC, and have `g++` installed on your server and in your $PATH
|
||||
3. The program will attempt to write to the current directory
|
||||
4. No security checks will be done against the code, except for to check that it is being run by the bot's developer by snowflake id. It is entirely possible to send an `!eval exit(0);` and make the bot quit, for example, or delete files from the host operating system, if misused or misconfigured.
|
||||
5. You're willing to wait a few seconds for compilation before your evaluated code runs. There isn't a way around this, as C++ is a compiled language.
|
||||
|
||||
To create this program you must create two files, `eval.h` and `eval.cpp`. The header file lists forward declarations of functions that you will be able to use directly within your `eval` code. As well as this the entire of D++ will be available to the eval command via the local variable `bot`, and the entire `on_message_create` event variable via a local variable called `event`.
|
||||
|
||||
The evaluated code will run within its own thread, so can execute for as long as it needs (but use common sense, don't go spawning a tight `while` loop that runs forever, you'll lock a thread at 100% CPU that won't ever end!).
|
||||
|
||||
### Implementation details
|
||||
|
||||
This code operates by outputting your provided code to be evaluated into a simple boilerplate program which can be compiled to a
|
||||
shared object library (.so file). This .so file is then compiled with g++, using the `-shared` and `-fPIC` flags. If the program can be successfully compiled, it is then loaded using `dlopen()`, and the symbol `so_exec()` searched for within it, and called. This `so_exec()` function will contain the body of the code given to the eval command. Once this has been called and it has returned,
|
||||
the `dlclose()` function is called to unload the library, and finally any temporary files (such as the .so file and its corresponding .cpp file) are cleaned up.
|
||||
Docker is definitely recommended if you code on Windows/Mac OS, because docker desktop still uses a linux VM, so your code can easily use `.so` file and your code runs the same on your vps (if it also uses Linux distro)
|
||||
|
||||
### Source code
|
||||
|
||||
\warning If you manage to get your system, network, or anything else harmed by use or misuse of this code, we are not responsible. Don't say we didn't warn you! Find another way to solve your problem!
|
||||
|
||||
#### eval.h
|
||||
|
||||
Remember that eval.h contains forward-declarations of any functions you want to expose to the eval command. It is included both by the bot itself, and by any shared object files compiled for evaluation.
|
||||
|
||||
~~~~~~~~~~~~~~~~{.cpp}
|
||||
#pragma once
|
||||
|
||||
/* This is the snowflake ID of the bot's developer.
|
||||
* The eval command will be restricted to this user.
|
||||
*/
|
||||
#define MY_DEVELOPER 189759562910400512
|
||||
|
||||
/* Any functions you want to be usable from within an eval,
|
||||
* that are not part of D++ itself or the message event, you
|
||||
* can put here as forward declarations. The test_function()
|
||||
* serves as an example.
|
||||
*/
|
||||
|
||||
int test_function();
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
#### eval.cpp
|
||||
|
||||
This is the main body of the example program.
|
||||
|
||||
~~~~~~~~~~~~~~~~{.cpp}
|
||||
/**
|
||||
* D++ eval command example.
|
||||
* This is dangerous and for educational use only, here be dragons!
|
||||
*/
|
||||
|
||||
#include <dpp/dpp.h>
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
/* We have to define this to make certain functions visible */
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <link.h>
|
||||
#include <dlfcn.h>
|
||||
#include "eval.h"
|
||||
|
||||
/* This is an example function you can expose to your eval command */
|
||||
int test_function() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
/* Important: This code is for UNIX-like systems only, e.g.
|
||||
* Linux, BSD, OSX. It will NOT work on Windows!
|
||||
* Note for OSX you'll probably have to change all references
|
||||
* from .so to .dylib.
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
dpp::cluster bot("token", dpp::i_default_intents | dpp::i_message_content);
|
||||
|
||||
bot.on_log(dpp::utility::cout_logger());
|
||||
|
||||
/* This won't work in a slash command very well yet, as there is not yet
|
||||
* a multi-line slash command input type.
|
||||
*/
|
||||
bot.on_message_create([&bot](const auto & event) {
|
||||
if (dpp::utility::utf8substr(event.msg.content, 0, 5) == "!eval") {
|
||||
|
||||
/**
|
||||
* THIS IS CRITICALLY IMPORTANT!
|
||||
* Never EVER make an eval command that isn't restricted to a specific developer by user id.
|
||||
* With access to this command the person who invokes it has at best full control over
|
||||
* your bot's user account and at worst, full control over your entire network!!!
|
||||
* Eval commands are Evil (pun intended) and could even be considered a security
|
||||
* vulnerability. YOU HAVE BEEN WARNED!
|
||||
*/
|
||||
if (event.msg.author.id != MY_DEVELOPER) {
|
||||
bot.message_create(dpp::message(event.msg.channel_id, "On the day i do this for you, Satan will be ice skating to work."));
|
||||
return;
|
||||
}
|
||||
|
||||
/* We start by creating a string that contains a cpp program for a simple library.
|
||||
* The library will contain one exported function called so_exec() that is called
|
||||
* containing the raw C++ code to eval.
|
||||
*/
|
||||
std::string code = "#include <iostream>\n\
|
||||
#include <string>\n\
|
||||
#include <map>\n\
|
||||
#include <unordered_map>\n\
|
||||
#include <stdint.h>\n\
|
||||
#include <dpp/dpp.h>\n\
|
||||
#include <nlohmann/json.hpp>\n\
|
||||
#include <fmt/format.h>\n\
|
||||
#include \"eval.h\"\n\
|
||||
extern \"C\" void so_exec(dpp::cluster& bot, dpp::message_create_t event) {\n\
|
||||
" + dpp::utility::utf8substr(
|
||||
event.msg.content,
|
||||
6,
|
||||
dpp::utility::utf8len(event.msg.content)
|
||||
) + ";\n\
|
||||
return;\n\
|
||||
}";
|
||||
|
||||
/* Next we output this string full of C++ to a cpp file on disk.
|
||||
* This code assumes the current directory is writeable. The file will have a
|
||||
* unique name made from the user's id and the message id.
|
||||
*/
|
||||
std::string source_filename = std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp";
|
||||
std::fstream code_file(source_filename, std::fstream::binary | std::fstream::out);
|
||||
if (!code_file.is_open()) {
|
||||
bot.message_create(dpp::message(event.msg.channel_id, "Unable to create source file for `eval`"));
|
||||
return;
|
||||
}
|
||||
code_file << code;
|
||||
code_file.close();
|
||||
|
||||
/* Now to actually compile the file. We use dpp::utility::exec to
|
||||
* invoke a compiler. This assumes you are using g++, and it is in your path.
|
||||
*/
|
||||
double compile_start = dpp::utility::time_f();
|
||||
dpp::utility::exec("g++", {
|
||||
"-std=c++17",
|
||||
"-shared", /* Build the output as a .so file */
|
||||
"-fPIC",
|
||||
std::string("-o") + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so",
|
||||
std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp",
|
||||
"-ldpp",
|
||||
"-ldl"
|
||||
}, [event, &bot, source_filename, compile_start](const std::string &output) {
|
||||
|
||||
/* After g++ is ran we end up inside this lambda with the output as a string */
|
||||
double compile_time = dpp::utility::time_f() - compile_start;
|
||||
|
||||
/* Delete our cpp file, we don't need it any more */
|
||||
std::string del_file = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".cpp";
|
||||
unlink(del_file.c_str());
|
||||
|
||||
/* On successful compilation g++ outputs nothing, so any output here is error output */
|
||||
if (output.length()) {
|
||||
bot.message_create(dpp::message(event.msg.channel_id, "Compile error: ```\n" + output + "\n```"));
|
||||
} else {
|
||||
|
||||
/* Now for the meat of the function. To actually load
|
||||
* our shared object we use dlopen() to load it into the
|
||||
* memory space of our bot. If dlopen() returns a nullptr,
|
||||
* the shared object could not be loaded. The user probably
|
||||
* did something odd with the symbols inside their eval.
|
||||
*/
|
||||
std::string dl = std::string(getenv("PWD")) + std::to_string(event.msg.author.id) + "_" + std::to_string(event.msg.id) + ".so";
|
||||
auto shared_object_handle = dlopen(dl.c_str(), RTLD_NOW);
|
||||
if (!shared_object_handle) {
|
||||
const char *dlsym_error = dlerror();
|
||||
bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" +
|
||||
std::string(dlsym_error ? dlsym_error : "Unknown error") +"\n```"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* This type represents the "void so_exec()" function inside
|
||||
* the shared object library file.
|
||||
*/
|
||||
using function_pointer = void(*)(dpp::cluster&, dpp::message_create_t);
|
||||
|
||||
/* Attempt to find the function called so_exec() inside the
|
||||
* library we just loaded. If we can't find it, then the user
|
||||
* did something really strange in their eval. Also note it's
|
||||
* important we call dlerror() here to reset it before trying
|
||||
* to use it a second time. It's weird-ass C code and is just
|
||||
* like that.
|
||||
*/
|
||||
dlerror();
|
||||
function_pointer exec_run = (function_pointer)dlsym(shared_object_handle, "so_exec");
|
||||
const char *dlsym_error = dlerror();
|
||||
if (dlsym_error) {
|
||||
bot.message_create(dpp::message(event.msg.channel_id, "Shared object load error: ```\n" + std::string(dlsym_error) +"\n```"));
|
||||
dlclose(shared_object_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now we have a function pointer to our actual exec code in
|
||||
* 'exec_run', so lets call it, and pass it a reference to
|
||||
* the cluster, and also a copy of the message_create_t.
|
||||
*/
|
||||
double run_start = dpp::utility::time_f();
|
||||
exec_run(bot, event);
|
||||
double run_time = dpp::utility::time_f() - run_start;
|
||||
|
||||
/* When we're done with a .so file we must always dlclose() it */
|
||||
dlclose(shared_object_handle);
|
||||
|
||||
/* We are now done with the compiled code too */
|
||||
unlink(dl.c_str());
|
||||
|
||||
/* Output some statistics */
|
||||
bot.message_create(dpp::message(event.msg.channel_id,
|
||||
"Execution completed. Compile time: " + std::to_string(compile_time) +
|
||||
"s, execution time " + std::to_string(run_time) + "s"));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
bot.start(dpp::st_wait);
|
||||
return 0;
|
||||
}
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
### Compilation
|
||||
|
||||
To compile this program you must link against `libdl`. It is also critically important to include the `-rdynamic` flag. For example:
|
||||
|
||||
```
|
||||
g++ -std=c++17 -rdynamic -oeval eval.cpp -ldpp -ldl
|
||||
```
|
||||
|
||||
### Example usage
|
||||
|
||||
\image html eval_example.png
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
\page making_a_http_request Making arbitrary HTTP requests using D++
|
||||
|
||||
If you wish to make arbitrary HTTP(S) requests to websites and APIs, e.g. to update statistics on bot lists, you can use code similar to the code below. You may pass any arbitrary POST data:
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
|
||||
#include <dpp/dpp.h>
|
||||
|
||||
int main() {
|
||||
dpp::cluster bot("TOKEN GOES HERE");
|
||||
|
||||
bot.on_log(dpp::utility::cout_logger());
|
||||
|
||||
bot.on_ready([&bot](const dpp::ready_t& event) {
|
||||
// Arbitrary post data as a string
|
||||
std::string mypostdata = "{\"value\": 42}";
|
||||
// Make a HTTP POST request. HTTP and HTTPS are supported here.
|
||||
bot.request(
|
||||
"http://www.somebotlist.com/api/servers", dpp::m_post, [](const dpp::http_request_completion_t & cc) {
|
||||
// This callback is called when the HTTP request completes. See documentation of
|
||||
// dpp::http_request_completion_t for information on the fields in the parameter.
|
||||
std::cout << "I got reply: " << cc.body << " with HTTP status code: " << cc.status << "\n";
|
||||
},
|
||||
mypostdata,
|
||||
"application/json",
|
||||
{
|
||||
{"Authorization", "Bearer tokengoeshere"}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
bot.start(dpp::st_wait);
|
||||
}
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -0,0 +1,63 @@
|
||||
\page spdlog Integrating with spdlog
|
||||
|
||||
If you want to make your bot use spdlog, like aegis does, you can attach it to the on_log event. You can do this as follows:
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/async.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/sinks/rotating_file_sink.h>
|
||||
#include <iomanip>
|
||||
#include <dpp/dpp.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
dpp::cluster bot("token");
|
||||
|
||||
const std::string log_name = "mybot.log";
|
||||
|
||||
/* Set up spdlog logger */
|
||||
std::shared_ptr<spdlog::logger> log;
|
||||
spdlog::init_thread_pool(8192, 2);
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >();
|
||||
auto rotating = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_name, 1024 * 1024 * 5, 10);
|
||||
sinks.push_back(stdout_sink);
|
||||
sinks.push_back(rotating);
|
||||
log = std::make_shared<spdlog::async_logger>("logs", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
|
||||
spdlog::register_logger(log);
|
||||
log->set_pattern("%^%Y-%m-%d %H:%M:%S.%e [%L] [th#%t]%$ : %v");
|
||||
log->set_level(spdlog::level::level_enum::debug);
|
||||
|
||||
/* Integrate spdlog logger to D++ log events */
|
||||
bot.on_log([&bot, &log](const dpp::log_t & event) {
|
||||
switch (event.severity) {
|
||||
case dpp::ll_trace:
|
||||
log->trace("{}", event.message);
|
||||
break;
|
||||
case dpp::ll_debug:
|
||||
log->debug("{}", event.message);
|
||||
break;
|
||||
case dpp::ll_info:
|
||||
log->info("{}", event.message);
|
||||
break;
|
||||
case dpp::ll_warning:
|
||||
log->warn("{}", event.message);
|
||||
break;
|
||||
case dpp::ll_error:
|
||||
log->error("{}", event.message);
|
||||
break;
|
||||
case dpp::ll_critical:
|
||||
default:
|
||||
log->critical("{}", event.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
/* Add the rest of your events */
|
||||
|
||||
bot.start(dpp::st_wait);
|
||||
return 0;
|
||||
}
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Reference in New Issue
Block a user