1
0
Fork 0
mirror of https://gitlab.com/niansa/SomeBot.git synced 2025-03-06 20:48:26 +01:00
SomeBot/modules/globalchat.cpp
2023-10-06 22:53:39 +02:00

485 lines
24 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "globalchat.hpp"
#include "../bot.hpp"
#include "../util.hpp"
#include <string_view>
#include <optional>
#include <tuple>
#include <thread>
#include <chrono>
#include <exception>
namespace GlobalchatExternal {
std::string MessageInfo::encode() const {
return Util::int_as_hex(author)+'-'+Util::int_as_hex(message)+'-'+Util::int_as_hex(channel)+'-'+Util::int_as_hex(guild);
}
MessageInfo MessageInfo::decode(std::string_view str) {
// Find start message code
auto pos_start = str.rfind(message_code_idenfitication);
if (pos_start == str.npos) {
throw DecodeError();
}
pos_start += message_code_idenfitication.size();
// Find end of message code
auto pos_end = str.find(' ', pos_start);
if (pos_end == str.npos) {
pos_end = str.size()-1;
}
// Get message code as string
std::string_view message_code{str.data()+pos_start, str.size()-pos_start-(str.size()-pos_end)};
// Find all IDs
auto fres = Util::split_str(message_code, '-', 3);
if (fres.size() != 4) {
throw DecodeError();
}
return MessageInfo{Util::hex_as_int<uint64_t>(fres[0]), Util::hex_as_int<uint64_t>(fres[1]), Util::hex_as_int<uint64_t>(fres[2]), Util::hex_as_int<uint64_t>(fres[3])};
}
MessageInfo MessageInfo::decode_message(const dpp::message& msg) {
// Check that message has embeds
if (msg.embeds.empty()) {
throw DecodeError();
}
// Return message info
return decode(msg.embeds[0].description);
}
}
class Globalchat {
Bot *bot;
std::unordered_map<dpp::snowflake, dpp::guild> guildCache;
bool is_bad_message(std::string message) {
// Lowercase message
for (auto& c : message) {
c = tolower(c);
}
// String blacklist
const static std::vector<std::string_view> blacklist = {"https://", "http://", "discord.gg/", "]("};
for (const auto word : blacklist) {
if (message.find(word) != message.npos) {
return true;
}
}
// Message seems alright
return false;
}
void broadcast_message(const dpp::message_create_t& event, bool origin = true) {
using namespace GlobalchatExternal;
auto original_msg = event.msg;
// Filter bad messages
bool is_bad = is_bad_message(original_msg.content);
if (is_bad) {
original_msg.content = "*Nachricht entspricht nicht den Richtlinien*";
}
// Get user info
unsigned color = Util::get_color_of_user(original_msg.author);
bool banned = false;
bot->db << "SELECT color, banned FROM globalchat_user_settings "
"WHERE id = ?;"
<< std::to_string(original_msg.author.id)
>> [&](int _color, int _banned) {
if (_color != -1) color = _color;
banned = _banned;
};
// Make sure user isn't banned
if (banned) {
return;
}
// Create embed
dpp::message globalchat_msg;
std::string guild_name;
try {
guild_name = guildCache.at(original_msg.guild_id).name;
} catch (...) {
guild_name = "Unbekannt (extern?)";
auto [bots, bots_lock] = bot->get_locked_property<std::vector<Bot*>>("main_all_instances");
for (auto bot : *bots) {
try {
guild_name = bot->get_loaded_module<Globalchat>("Globalchat")->guildCache.at(original_msg.guild_id).name + " (extern)";
break;
} catch (...) {}
}
}
auto messageCode = MessageInfo{original_msg.author.id, original_msg.id, original_msg.channel_id, original_msg.guild_id}.encode();
globalchat_msg.add_embed(dpp::embed()
.set_title(":earth_africa: Globalchat")
.set_description(":speaking_head: ["+original_msg.author.format_username()+"]("+std::string(MessageInfo::message_code_idenfitication)+messageCode+" 'Inspection Data')")
.set_thumbnail(original_msg.author.get_avatar_url())
.set_color(color)
.add_field(original_msg.member.get_nickname().empty()?original_msg.author.username:original_msg.member.get_nickname(), original_msg.content+"\n")
.set_footer(dpp::embed_footer().set_text("Gesendet von: "+guild_name))
);
// Process message reference
std::string replied_to_primary_id;
if (original_msg.message_reference.message_id) {
// Get primary message ID
bot->db << "SELECT primary_id FROM globalchat_messages "
"WHERE id = ?;"
<< std::to_string(original_msg.message_reference.message_id)
>> [&](std::string primary_id) {
replied_to_primary_id = primary_id;
};
// Get message content
auto [bots, bots_lock] = bot->get_locked_property<std::vector<Bot*>>("main_all_instances");
for (auto& bot : *bots) {
try {
auto replied_to_message = bot->cluster.message_get_sync(original_msg.message_reference.message_id, original_msg.channel_id);
if (!replied_to_message.embeds.empty()) {
const auto& embed = replied_to_message.embeds[0];
if (!embed.fields.empty()) {
auto content = embed.fields[0].value;
content.erase(content.size()-3, 3);
globalchat_msg.set_content("> "+content);
}
}
break;
} catch (...) {}
}
}
// Broadcast to all globalchat channels
bot->db << "SELECT globalchat_channel, id FROM globalchat_guild_settings;"
>> [&](std::string channel_id_str, std::string guild_id_str) {
if (channel_id_str.empty())
return;
globalchat_msg.set_channel_id(std::stoul(channel_id_str));
// Set reply
if (original_msg.message_reference.message_id) {
bot->db << "SELECT id FROM globalchat_messages "
"WHERE primary_id = ? AND guild_id = ?;"
<< replied_to_primary_id << guild_id_str
>> [&](std::string id) {
globalchat_msg.set_reference(id, guild_id_str, channel_id_str);
};
}
// Send message
bot->cluster.message_create(globalchat_msg, [=](const dpp::confirmation_callback_t& ccb) {
if (ccb.is_error()) {
// Unset global chat
unset_globalchat_channel(guild_id_str);
return;
}
// Special treatment for bad messages
if (!is_bad) {
// Add this message to database
bot->db << "INSERT INTO globalchat_messages (primary_id, id, guild_id, author_id) VALUES (?, ?, ?, ?);"
<< std::to_string(original_msg.id) << std::to_string(ccb.get<dpp::message>().id) << guild_id_str << std::to_string(original_msg.author.id);
} else {
auto msg = ccb.get<dpp::message>();
// Add reaction
bot->cluster.message_add_reaction(msg, "💣", [this, msg](const dpp::confirmation_callback_t&) {
// Delete message soon (random delay to avoid deleting many messages at once)
std::thread([this, msg]() {
std::this_thread::sleep_for(std::chrono::seconds((msg.id%10)+6));
bot->cluster.message_delete(msg.id, msg.channel_id);
}).detach();
});
}
});
};
// Broadcast to other instances
if (origin) {
// We NEED to avoid having the lock held when broadcasting!!!
std::vector<Globalchat*> globalchat_instances;
{
auto [bots, bots_lock] = bot->get_locked_property<std::vector<Bot*>>("main_all_instances");
for (auto bot : *bots) {
if (bot == this->bot) {
continue;
}
try {
globalchat_instances.push_back(bot->get_loaded_module<Globalchat>("Globalchat"));
} catch (...) {}
}
}
for (auto globalchat : globalchat_instances) {
globalchat->broadcast_message(event, false);
}
}
}
void db_add_guild(const dpp::snowflake& guild_id) {
bot->db << "INSERT OR IGNORE INTO globalchat_guild_settings (id) VALUES (?);"
<< std::to_string(guild_id);
}
void db_add_user(const dpp::snowflake& user_id) {
bot->db << "INSERT OR IGNORE INTO globalchat_user_settings (id, color, banned) VALUES (?, -1, 0);"
<< std::to_string(user_id);
}
void set_globalchat_channel(dpp::snowflake guild_id, dpp::snowflake channel_id) {
// Unset global chat
bot->db << "UPDATE globalchat_guild_settings "
"SET globalchat_channel = ? "
"WHERE id = ?;"
<< std::to_string(channel_id)
<< std::to_string(guild_id);
return;
}
void unset_globalchat_channel(dpp::snowflake guild_id) {
// Unset global chat
bot->db << "UPDATE globalchat_guild_settings "
"SET globalchat_channel = NULL "
"WHERE id = ?;"
<< std::to_string(guild_id);
return;
}
void ban_user(dpp::snowflake user_id) {
bot->db << "UPDATE globalchat_user_settings "
"SET banned = TRUE "
"WHERE id = ?;"
<< std::to_string(user_id);
}
void unban_user(dpp::snowflake user_id) {
bot->db << "UPDATE globalchat_user_settings "
"SET banned = FALSE "
"WHERE id = ?;"
<< std::to_string(user_id);
}
void set_globalchat_channel_topic(dpp::channel channel) {
channel.set_topic("Dies ist der Globalchat des *"+bot->cluster.me.username+"* Bots. Viel Spaß!\n"
"\n"
"**Regeln:**\n"
" **0.** Common Sense\n"
" **1.** Keine Links\n"
" **2.** Kein Trolling\n"
" **3.** Keine Diskussionen über Moderationsentscheidungen\n"
" **4.** Keine hitzigen religiösen Diskussionen\n"
" **5.** Keine ernsthaften Beleidigungen");
bot->cluster.channel_edit(channel);
}
public:
Globalchat(Bot *_bot) : bot(_bot) {
bot->cluster.intents |= dpp::intents::i_message_content | dpp::intents::i_guilds;
bot->db << "CREATE TABLE IF NOT EXISTS globalchat_guild_settings ("
" id TEXT PRIMARY KEY NOT NULL,"
" globalchat_channel TEXT,"
" UNIQUE(id)"
");";
bot->db << "CREATE TABLE IF NOT EXISTS globalchat_user_settings ("
" id TEXT PRIMARY KEY NOT NULL,"
" color INTEGER,"
" banned INTEGER,"
" UNIQUE(id)"
");";
bot->db << "CREATE TABLE IF NOT EXISTS globalchat_messages ("
" primary_id TEXT,"
" id TEXT,"
" guild_id TEXT,"
" author_id TEXT"
");";
bot->cluster.on_message_create([&](const dpp::message_create_t& message) {
// Make sure sender isn't a bot
if (message.msg.author.is_bot())
return;
// Get current globalchat channel for server
dpp::snowflake globalchat_channel;
{
std::string globalchat_channel_str;
bot->db << "SELECT globalchat_channel FROM globalchat_guild_settings "
"WHERE id = ?;"
<< std::to_string(message.msg.guild_id)
>> std::tie(globalchat_channel_str);
if (globalchat_channel_str.empty()) {
return;
}
globalchat_channel = std::stoul(globalchat_channel_str);
}
// If this channel is globalchat channel, broadcast message
if (message.msg.channel_id == globalchat_channel) {
std::thread([this, message]() {
broadcast_message(message);
bot->cluster.message_delete(message.msg.id, message.msg.channel_id);
}).detach();
return;
}
});
bot->cluster.on_guild_create([&](const dpp::guild_create_t& guild) {
// Make sure guild is in database
db_add_guild(guild.created->id);
// Add guild to cache
guildCache[guild.created->id] = *guild.created;
// Set topic in its globalchat channel
try {
// Get globalchat channel ID
std::string globalchat_channel_str;
bot->db << "SELECT globalchat_channel FROM globalchat_guild_settings "
"WHERE id = ?;"
<< std::to_string(guild.created->id)
>> std::tie(globalchat_channel_str);
// Get channel itself
bot->cluster.channel_get(globalchat_channel_str, [this, guild_id = guild.created->id](const dpp::confirmation_callback_t& ccb) {
if (ccb.is_error()) {
// Unset globalchat channel
unset_globalchat_channel(guild_id);
return;
}
// Set channel topic
set_globalchat_channel_topic(ccb.get<dpp::channel>());
});
} catch (...) {}
});
bot->cluster.on_guild_delete([&](const dpp::guild_delete_t& guild) {
// Delete guild from database
bot->db << "DELETE FROM globalchat_guild_settings "
"WHERE id = ?;"
<< std::to_string(guild.deleted->id);
});
bot->cluster.on_guild_member_remove([&](const dpp::guild_member_remove_t& guild) {
// Delete empty guilds
if (guild.removing_guild->member_count == 1) {
bot->cluster.guild_delete(guild.removing_guild->id);
}
});
bot->add_chatcommand(Bot::ChatCommand({"set_gc", "globalchat"}, "Verwende den Globalchat", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_channel, "channel", "Der Kanal in dem der Globalchat laufen soll", true))), [&](const dpp::slashcommand_t& event) {
auto channel_id = std::get<dpp::snowflake>(event.get_parameter("channel"));
// Check that user has the correct permissions
if (!event.command.get_guild().base_permissions(event.command.member).has(dpp::permissions::p_manage_channels)) {
event.reply(dpp::message("Du brauchst die Kanalverwaltungsberechtigung, um dieses Kommando zu verwenden.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Update globalchat ID
set_globalchat_channel(event.command.guild_id, channel_id);
// Set channel topic
bot->cluster.channel_get(channel_id, [=](const dpp::confirmation_callback_t& ccb) {
if (ccb.is_error()) {
return;
}
set_globalchat_channel_topic(ccb.get<dpp::channel>());
});
// Reply
event.reply("Der Globalchat läuft jetzt in <#"+std::to_string(channel_id)+">");
});
bot->add_chatcommand(Bot::ChatCommand({"reset_gc", "globalchat_stop"}, "Stoppe den Globalchat"), [&](const dpp::slashcommand_t& event) {
// Check that user has the correct permissions
if (!event.command.get_guild().base_permissions(event.command.member).has(dpp::permissions::p_manage_channels)) {
event.reply("Du brauchst die Kanalverwaltungsberechtigung, um dieses Kommando zu verwenden.");
return;
}
// Update globalchat ID
unset_globalchat_channel(event.command.guild_id);
// Reply
event.reply("Der Globalchat wurde gestoppt");
});
bot->add_chatcommand(Bot::ChatCommand({"gc_color", "globalchat_color", "globalchat_farbe"}, "Setze Farbe im Globalchat", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_string, "color", "Farbcode oder Name", true))), [&](const dpp::slashcommand_t& event) {
auto color_str = std::get<std::string>(event.get_parameter("color"));
auto color_code = Util::str_to_color(color_str);
if (color_code == -1U) {
event.reply(dpp::message("Farbe ist ungültig. Versuche es mit einem Farbcode.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Set user color in database
db_add_user(event.command.usr.id);
bot->db << "UPDATE globalchat_user_settings "
"SET color = ? "
"WHERE id = ?;"
<< color_code
<< std::to_string(event.command.usr.id);
// Send success reply
event.reply(dpp::message().add_embed(dpp::embed().set_title("Deine Farbe im Globalchat wurde festgelegt!").set_color(color_code).set_image("https://singlecolorimage.com/get/"+Util::int_as_hex(color_code)+"/400x100")).set_flags(dpp::message_flags::m_ephemeral));
});
bot->add_chatcommand(Bot::ChatCommand({"gc_ban", "globalchat_ban"}, "Verbanne jemanden aus dem Globalchat", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_user, "user", "Benutzer", true))), [&](const dpp::slashcommand_t& event) {
auto user_id = std::get<dpp::snowflake>(event.get_parameter("user"));
// Set user to banned
db_add_user(user_id);
ban_user(user_id);
// Report success
event.reply(dpp::message("Okay!").set_flags(dpp::message_flags::m_ephemeral));
}, bot->config.management_guild_id);
bot->add_chatcommand(Bot::ChatCommand({"gc_unban", "globalchat_unban"}, "Entbanne jemanden aus dem Globalchat", dpp::slashcommand().add_option(dpp::command_option(dpp::command_option_type::co_user, "user", "Benutzer", true))), [&](const dpp::slashcommand_t& event) {
auto user_id = std::get<dpp::snowflake>(event.get_parameter("user"));
// Set user to non-banned
unban_user(user_id);
// Report success
event.reply(dpp::message("Okay!").set_flags(dpp::message_flags::m_ephemeral));
}, bot->config.management_guild_id);
bot->add_messagecommand(Bot::MessageCommand({"GC Ban"}, "Verbanne den Sender der Nachricht"), [&](const dpp::message_context_menu_t& event) {
const auto& target_message = event.get_message();
// Get message info
auto messageinfo = GlobalchatExternal::MessageInfo::decode_message(target_message);
// Ban sender
db_add_user(messageinfo.author);
ban_user(messageinfo.author);
// Report success
event.reply(dpp::message("Okay!").set_flags(dpp::message_flags::m_ephemeral));
}, bot->config.management_guild_id);
bot->add_messagecommand(Bot::MessageCommand({"GC Unban"}, "Entbanne den Sender der Nachricht"), [&](const dpp::message_context_menu_t& event) {
const auto& target_message = event.get_message();
// Get message info
auto messageinfo = GlobalchatExternal::MessageInfo::decode_message(target_message);
// Ban sender
unban_user(messageinfo.author);
// Report success
event.reply(dpp::message("Okay!").set_flags(dpp::message_flags::m_ephemeral));
}, bot->config.management_guild_id);
bot->add_messagecommand(Bot::MessageCommand({"GC Nachricht löschen"}, "Lösche eine Nachricht im Globalchat"), [&](const dpp::message_context_menu_t& event) {
const auto& target_message = event.get_message();
// Get message ID
GlobalchatExternal::MessageInfo messageinfo;
try {
messageinfo = GlobalchatExternal::MessageInfo::decode_message(target_message);
} catch (GlobalchatExternal::MessageInfo::DecodeError& e) {
event.reply(dpp::message("Fehler beim Dekodieren der Nachricht. Ist sie wirklich Teil des Globalchats?").set_flags(dpp::message_flags::m_ephemeral));
return;
}
const auto& [author_id, primary_id, channel_id, guild_id] = messageinfo;
// Check for error
if (primary_id.empty()) {
event.reply(dpp::message("Ich konnte diese Nachricht nicht identifizieren. Möglicherweise ist sie kein Teil des Globalchats, das Embed wurde gelöscht oder sie ist zu alt.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Check if user is allowed to delete this message
if (event.command.guild_id != bot->config.management_guild_id && dpp::snowflake(author_id) != event.command.get_issuing_user().id) {
event.reply(dpp::message("Du darfst diese Nachricht nicht löschen.").set_flags(dpp::message_flags::m_ephemeral));
return;
}
// Delete message everywhere
auto [bots, bots_lock] = bot->get_locked_property<std::vector<Bot*>>("main_all_instances");
for (auto bot : *bots) {
// Make sure module is enabled
if (!bot->is_module_enabled("Globalchat")) {
continue;
}
// Get all messages broadcasted
bot->db << "SELECT id, guild_id FROM globalchat_messages "
"WHERE primary_id = ?;"
<< std::to_string(primary_id)
>> [&, bot](std::string message_id_str, std::string guild_id_str) {
std::string channel_id_str;
bot->db << "SELECT globalchat_channel FROM globalchat_guild_settings "
"WHERE id = ?;"
<< guild_id_str
>> channel_id_str;
// If guild is management guild, only mark it as deleted, otherwise delete
if (guild_id_str != std::to_string(bot->config.management_guild_id)) {
bot->cluster.message_delete(message_id_str, channel_id_str);
} else {
bot->cluster.message_get(message_id_str, channel_id_str, [bot](const dpp::confirmation_callback_t& ccb) {
if (ccb.is_error()) {
return;
}
auto msg = ccb.get<dpp::message>();
msg.set_content(msg.content+"\n*(gelöscht)*");
bot->cluster.message_edit(msg);
});
}
};
}
// Report success
event.reply(dpp::message("Okay!").set_flags(dpp::message_flags::m_ephemeral));
});
}
};
BOT_ADD_MODULE(Globalchat);