1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2025-09-18 02:07:18 +02:00

Add D++ library.

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

View File

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

View File

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

View File

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

View File

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

View File

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