mirror of
https://gitlab.com/niansa/dpplogger.git
synced 2025-03-06 20:48:29 +01:00
Swapped out discord library
This commit is contained in:
parent
ad0f2f3b76
commit
c4dd7eae25
6 changed files with 281 additions and 105 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "DPP"]
|
||||
path = DPP
|
||||
url = https://github.com/brainboxdotcc/DPP.git
|
||||
[submodule "dcboost"]
|
||||
path = dcboost
|
||||
url = https://gitlab.com/chatfuse/libs/dcboost
|
||||
|
|
|
@ -2,13 +2,13 @@ cmake_minimum_required(VERSION 3.5)
|
|||
|
||||
project(dpplogger LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_subdirectory(DPP)
|
||||
add_subdirectory(dcboost)
|
||||
|
||||
add_executable(dpplogger main.cpp)
|
||||
target_link_libraries(dpplogger PUBLIC dpp sqlite3)
|
||||
target_link_libraries(dpplogger PUBLIC sqlite3 dcboost jsoncpp)
|
||||
|
||||
install(TARGETS dpplogger
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
|
1
DPP
1
DPP
|
@ -1 +0,0 @@
|
|||
Subproject commit 788377d2b4fef949db3debd02d9acaea37344eff
|
43
Random.hpp
Normal file
43
Random.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef _RANDOM_HPP
|
||||
#define _RANDOM_HPP
|
||||
#include <random>
|
||||
|
||||
|
||||
|
||||
class RandomGenerator {
|
||||
std::mt19937 rng;
|
||||
uint32_t initialSeed;
|
||||
|
||||
public:
|
||||
void seed() {
|
||||
rng.seed(initialSeed = std::random_device{}());
|
||||
}
|
||||
void seed(uint32_t customSeed) {
|
||||
rng.seed(initialSeed = customSeed);
|
||||
}
|
||||
|
||||
unsigned getUInt() {
|
||||
std::uniform_int_distribution<unsigned> dist;
|
||||
return dist(rng);
|
||||
}
|
||||
unsigned getUInt(unsigned max) {
|
||||
std::uniform_int_distribution<unsigned> dist(0, max);
|
||||
return dist(rng);
|
||||
}
|
||||
unsigned getUInt(unsigned min, unsigned max) {
|
||||
std::uniform_int_distribution<unsigned> dist(min, max);
|
||||
return dist(rng);
|
||||
}
|
||||
double getDouble(double max) {
|
||||
std::uniform_real_distribution<double> dist(0.0, max);
|
||||
return dist(rng);
|
||||
}
|
||||
double getDouble(double min, double max) {
|
||||
std::uniform_real_distribution<double> dist(min, max);
|
||||
return dist(rng);
|
||||
}
|
||||
bool getBool(float chance) {
|
||||
return getDouble(1.0) <= chance && chance != 0.0f;
|
||||
}
|
||||
};
|
||||
#endif
|
1
dcboost
Submodule
1
dcboost
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 53b8c9ac5e7c670f0af03cc94aa6ba02f906ab03
|
329
main.cpp
329
main.cpp
|
@ -1,121 +1,241 @@
|
|||
#include "Random.hpp"
|
||||
#include "sqlite_modern_cpp/sqlite_modern_cpp.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <dpp/dpp.h>
|
||||
#include <stdexcept>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <dcboost/discord.hpp>
|
||||
#include <dcboost/snowflake.hpp>
|
||||
|
||||
using namespace ChatFuse;
|
||||
using namespace QLog;
|
||||
|
||||
|
||||
|
||||
class Cache {
|
||||
using Object = std::variant<std::monostate, dpp::guild, dpp::user, dpp::guild_member>;
|
||||
std::unordered_map<dpp::snowflake, Object> cache;
|
||||
struct Miss : public std::runtime_error {
|
||||
Miss() : std::runtime_error("Object cache miss") {}
|
||||
};
|
||||
|
||||
Logger<Cache> logger;
|
||||
std::unordered_map<Discord::Snowflake, Json::Value> cache;
|
||||
|
||||
public:
|
||||
void store(dpp::snowflake id, const Object& object) {
|
||||
cache[id] = object;
|
||||
Cache() {
|
||||
logger.inst_ptr = this;
|
||||
}
|
||||
const Object& fetch(dpp::snowflake id) const {
|
||||
auto entry = cache.find(id);
|
||||
|
||||
const Json::Value& store(const Json::Value& object) {
|
||||
const auto& id_str = object["id"].asString();
|
||||
logger.log(Loglevel::verbose, "Stored "+id_str+" in cache");
|
||||
return cache.insert_or_assign(std::move(id_str), object).first->second;
|
||||
}
|
||||
const Json::Value& fetch(Discord::Snowflake id) const {
|
||||
const auto& entry = cache.find(id);
|
||||
if (entry == cache.end()) {
|
||||
return std::monostate();
|
||||
logger.log(Loglevel::warn, "Missing "+id.str());
|
||||
throw Miss();
|
||||
}
|
||||
return entry->second;
|
||||
}
|
||||
bool has(Discord::Snowflake id) const {
|
||||
return cache.find(id) != cache.end();
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
|
||||
struct MyClient final : public Discord::Client {
|
||||
sqlite::database db;
|
||||
Cache cache;
|
||||
|
||||
// Initialize Sqlite3
|
||||
sqlite::database db("log.sqlite3");
|
||||
Json::Value *me;
|
||||
|
||||
// Create tables
|
||||
public:
|
||||
MyClient(boost::asio::io_service& io, const ChatFuse::Discord::Settings& settings = {})
|
||||
: ChatFuse::Discord::Client(io, settings),
|
||||
db("log.sqlite3")
|
||||
{
|
||||
db << "CREATE TABLE IF NOT EXISTS messages ("
|
||||
" id TEXT PRIMARY KEY NOT NULL,"
|
||||
" type INTEGER NOT NULL,"
|
||||
" channel_id TEXT NOT NULL,"
|
||||
" author_id TEXT NOT NULL,"
|
||||
" replied_to_id TEXT,"
|
||||
" is_deleted INTEGER DEFAULT 0 NOT NULL,"
|
||||
" is_edited INTEGER DEFAULT 0 NOT NULL,"
|
||||
" creation_timestamp TEXT NOT NULL,"
|
||||
" update_timestamp TEXT DEFAULT '0' NOT NULL,"
|
||||
" UNIQUE(id)"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS message_contents ("
|
||||
" message_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" content TEXT NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS users ("
|
||||
" id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" full_username TEXT NOT NULL," // Username#Discriminator
|
||||
" avatar TEXT,"
|
||||
" bio TEXT,"
|
||||
" has_nitro INTEGER NOT NULL,"
|
||||
" is_bot INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS user_statuses ("
|
||||
" user_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" status TEXT," // JSON array or null if not updated: [int:online/dnd/afk/offline enum, ?:emoji (null/empty if none), string:text (empty if none)]
|
||||
" presence TEXT" // Discord JSON presence object
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS members ("
|
||||
" user_id TEXT NOT NULL,"
|
||||
" guild_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" nickname TEXT,"
|
||||
" in_guild INTEGER NOT NULL,"
|
||||
" is_untracked INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS member_voice_connections ("
|
||||
" channel_id TEXT NOT NULL,"
|
||||
" user_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" voice_channel_id TEXT,"
|
||||
" self_muted INTEGER NOT NULL,"
|
||||
" self_deafened INTEGER NOT NULL,"
|
||||
" server_muted INTEGER NOT NULL,"
|
||||
" server_deafened INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS channels ("
|
||||
" id TEXT NOT NULL,"
|
||||
" guild_id TEXT NOT NULL,"
|
||||
" category_channel_id TEXT NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" type INTEGER NOT NULL,"
|
||||
" name TEXT NOT NULL,"
|
||||
" topic TEXT,"
|
||||
" has_access INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS guilds ("
|
||||
" id TEXT NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" name TEXT NOT NULL,"
|
||||
" owner_user_id TEXT NOT NULL,"
|
||||
" in_guild INTEGER DEFAULT 1 NOT NULL"
|
||||
");";
|
||||
// Create tables
|
||||
{
|
||||
db << "CREATE TABLE IF NOT EXISTS messages ("
|
||||
" id TEXT PRIMARY KEY NOT NULL,"
|
||||
" type INTEGER NOT NULL,"
|
||||
" channel_id TEXT NOT NULL,"
|
||||
" author_id TEXT NOT NULL,"
|
||||
" replied_to_id TEXT,"
|
||||
" is_deleted INTEGER DEFAULT 0 NOT NULL,"
|
||||
" is_edited INTEGER DEFAULT 0 NOT NULL,"
|
||||
" creation_timestamp TEXT NOT NULL,"
|
||||
" update_timestamp TEXT DEFAULT '0' NOT NULL,"
|
||||
" UNIQUE(id)"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS message_contents ("
|
||||
" message_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" content TEXT NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS users ("
|
||||
" id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" full_username TEXT NOT NULL," // Username#Discriminator
|
||||
" avatar TEXT,"
|
||||
" bio TEXT,"
|
||||
" has_nitro INTEGER NOT NULL,"
|
||||
" is_bot INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS user_statuses ("
|
||||
" user_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" status TEXT," // JSON array or null if not updated: [int:online/dnd/afk/offline enum, ?:emoji (null/empty if none), string:text (empty if none)]
|
||||
" presence TEXT" // Discord JSON presence object
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS members ("
|
||||
" user_id TEXT NOT NULL,"
|
||||
" guild_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" nickname TEXT,"
|
||||
" in_guild INTEGER NOT NULL,"
|
||||
" is_untracked INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS member_voice_connections ("
|
||||
" channel_id TEXT NOT NULL,"
|
||||
" user_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" voice_channel_id TEXT,"
|
||||
" self_muted INTEGER NOT NULL,"
|
||||
" self_deafened INTEGER NOT NULL,"
|
||||
" server_muted INTEGER NOT NULL,"
|
||||
" server_deafened INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS channels ("
|
||||
" id TEXT NOT NULL,"
|
||||
" guild_id TEXT NOT NULL,"
|
||||
" category_channel_id TEXT NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" type INTEGER NOT NULL,"
|
||||
" name TEXT NOT NULL,"
|
||||
" topic TEXT,"
|
||||
" has_access INTEGER NOT NULL"
|
||||
");";
|
||||
db << "CREATE TABLE IF NOT EXISTS guilds ("
|
||||
" id TEXT NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" name TEXT NOT NULL,"
|
||||
" owner_user_id TEXT NOT NULL,"
|
||||
" in_guild INTEGER DEFAULT 1 NOT NULL"
|
||||
");";
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize bot
|
||||
dpp::cluster cluster("token", dpp::i_all_intents);
|
||||
boost::asio::awaitable<void> fetchAllGuilds(const Json::Value& unavailable_guild_array) {
|
||||
RandomGenerator rng;
|
||||
rng.seed();
|
||||
for (const auto& incomplete_data : unavailable_guild_array) {
|
||||
Discord::Snowflake guild_id = incomplete_data["id"];
|
||||
|
||||
// Make sure guild isn't cached yet
|
||||
if (cache.has(guild_id)) continue;
|
||||
|
||||
// Make sure data is actually incomplete
|
||||
if (incomplete_data["name"].isString()) {
|
||||
// We'll just use it as-is
|
||||
cache.store(incomplete_data);
|
||||
insertGuildUpdate(incomplete_data, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch guild from API and store it in cache
|
||||
auto data = co_await api.call(boost::beast::http::verb::get, "/guilds/"+guild_id.str());
|
||||
cache.store(data);
|
||||
|
||||
// Insert guild into database
|
||||
insertGuildUpdate(data, true);
|
||||
|
||||
// Delay
|
||||
co_await asyncSleep(rng.getUInt(6000, 15000));
|
||||
}
|
||||
}
|
||||
|
||||
void insertGuildUpdate(const Json::Value& data, bool is_initial) {
|
||||
db << "INSERT OR IGNORE INTO guilds (id, timestamp, is_initial, name, owner_user_id)"
|
||||
" VALUES (?, ?, ?, ?, ? );"
|
||||
<< data["id"].asString() << std::to_string(time(nullptr)) << is_initial << data["name"].asString() << data["owner_id"].asString();
|
||||
cache.store(data);
|
||||
}
|
||||
void insertGuildLeave(const Json::Value& data) {
|
||||
auto cached_data = cache.fetch(data["id"]);
|
||||
db << "INSERT OR IGNORE INTO guilds (id, timestamp, is_initial, name, owner_user_id, in_guild)"
|
||||
" VALUES (?, ?, 0, ?, ?, 0 );"
|
||||
<< data["id"].asString() << std::to_string(time(nullptr)) << cached_data["name"].asString() << cached_data["owner_id"].asString();
|
||||
}
|
||||
|
||||
virtual boost::asio::awaitable<void> intentHandler(const std::string& intent, const Json::Value& data) override {
|
||||
if (intent == "READY") [[unlikely]] {
|
||||
const auto& user = cache.store(data["user"]);
|
||||
logger.log(Loglevel::info, "Connected to Discord as: "+user["username"].asString()+'#'+user["discriminator"].asString());
|
||||
settings.is_bot = user["bot"].asBool();
|
||||
co_await fetchAllGuilds(data["guilds"]);
|
||||
} else if (intent == "GUILD_CREATE") [[unlikely]] {
|
||||
insertGuildUpdate(data, true);
|
||||
} else if (intent == "GUILD_UPDATE") [[unlikely]] {
|
||||
insertGuildUpdate(data, false);
|
||||
} else if (intent == "GUILD_DELETE") [[unlikely]] {
|
||||
insertGuildLeave(data);
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Check args
|
||||
if (argc == 1) {
|
||||
std::cout << "Usage: " << argv[0] << " <token>" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Create io service
|
||||
boost::asio::io_service io;
|
||||
|
||||
// Set up client
|
||||
auto client = std::make_shared<MyClient>(io, ChatFuse::Discord::Settings{
|
||||
.bot_token = argv[1],
|
||||
.intents = ChatFuse::Discord::intents::all
|
||||
});
|
||||
client->detach();
|
||||
|
||||
// Erase bot token from argv (so it's no longer visible in /proc/{pid}/cmdline)
|
||||
memset(argv[1], 0, strlen(argv[1]));
|
||||
|
||||
// Run!!!
|
||||
io.run();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
|
||||
|
||||
/*// Initialize bot
|
||||
dpp::cluster cluster("ODAyODYzNTU0MjY4NjI2OTY1.GsYaeE.Mzu8hrICXwcJMfDKnYssRMkRtDfX-r7zrQOCRI", dpp::i_all_intents);
|
||||
|
||||
// Set up callbacks
|
||||
// Useful: https://discord.com/developers/docs/topics/gateway-events
|
||||
cluster.on_ready([&](const dpp::ready_t& event) {
|
||||
std::cout << "Connected to Discord as " << cluster.me.format_username() << std::endl;
|
||||
});
|
||||
|
||||
cluster.on_guild_create([&](const dpp::guild_create_t& event) {
|
||||
std::cout << "Logging guild " << event.created->name << std::endl;
|
||||
const auto guild = event.created;
|
||||
db << "INSERT OR IGNORE INTO levels (id, timestamp, name, owner_user_id)"
|
||||
" VALUES (?, ?, ?, ? );"
|
||||
|
@ -136,23 +256,36 @@ int main() {
|
|||
<< std::to_string(guild.id) << std::to_string(time(nullptr)) << guild.name << std::to_string(guild.owner_id);
|
||||
});
|
||||
|
||||
db << "CREATE TABLE IF NOT EXISTS message_contents ("
|
||||
" message_id TEXT NOT NULL,"
|
||||
" is_initial INTEGER DEFAULT 1 NOT NULL,"
|
||||
" timestamp TEXT NOT NULL,"
|
||||
" content TEXT NOT NULL"
|
||||
");";
|
||||
auto on_message_content = [&](const dpp::message& msg, bool is_initial) {
|
||||
db << "INSERT OR IGNORE INTO message_contents (message_id, is_initial, timestamp, content)"
|
||||
" VALUES (?, ?, ?, ? );"
|
||||
<< std::to_string(msg.id) << is_initial << time(nullptr) << msg.content;
|
||||
<< std::to_string(msg.id) << is_initial << std::to_string(msg.edited?msg.edited:msg.sent) << msg.content;
|
||||
};
|
||||
cluster.on_message_create([&](const dpp::message_create_t& event) {
|
||||
const auto msg = event.msg;
|
||||
const auto& msg = event.msg;
|
||||
db << "INSERT OR IGNORE INTO messages (id, type, channel_id, author_id, replied_to_id, creation_timestamp)"
|
||||
" VALUES (?, ?, ?, ?, ?, ? );"
|
||||
<< std::to_string(msg.id) << std::to_string(msg.type) << std::to_string(msg.channel_id) << std::to_string(msg.author.id) << (msg.message_reference.message_id?std::optional<std::string>(std::to_string(msg.message_reference.message_id)):nullptr) << time(nullptr);
|
||||
<< std::to_string(msg.id) << std::to_string(msg.type) << std::to_string(msg.channel_id) << std::to_string(msg.author.id) << (msg.message_reference.message_id?std::optional<std::string>(std::to_string(msg.message_reference.message_id)):nullptr) << std::to_string(msg.sent);
|
||||
if (!msg.content.empty()) on_message_content(msg, true);
|
||||
cache.store(msg.author.id, msg.author);
|
||||
});
|
||||
cluster.on_message_update([&](const dpp::message_update_t& event) {
|
||||
const auto& msg = event.msg;
|
||||
if (!msg.content.empty()) {
|
||||
on_message_content(msg, false);
|
||||
db << "UPDATE messages "
|
||||
"SET is_edited = 1 "
|
||||
"WHERE id = ?;"
|
||||
<< std::to_string(msg.id);;
|
||||
}
|
||||
});
|
||||
cluster.on_message_delete([&](const dpp::message_delete_t& event) {
|
||||
db << "UPDATE messages "
|
||||
"SET is_deleted = 1 "
|
||||
"WHERE id = ?;"
|
||||
<< std::to_string(event.deleted->id);;
|
||||
});
|
||||
|
||||
// Run
|
||||
cluster.start(false);*/
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue