diff options
Diffstat (limited to 'src')
47 files changed, 0 insertions, 3427 deletions
diff --git a/src/api/twitch/helix_client.cpp b/src/api/twitch/helix_client.cpp deleted file mode 100644 index 04d630b..0000000 --- a/src/api/twitch/helix_client.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "helix_client.hpp" - -#include <nlohmann/json.hpp> -#include <string> -#include <vector> - -#include "cpr/api.h" -#include "cpr/bearer.h" -#include "cpr/cprtypes.h" -#include "cpr/response.h" -#include "schemas/stream.hpp" -#include "schemas/user.hpp" - -namespace bot::api::twitch { - HelixClient::HelixClient(const std::string &token, - const std::string &client_id) { - this->token = token; - this->client_id = client_id; - } - - std::vector<schemas::User> HelixClient::get_users( - const std::vector<std::string> &logins) const { - std::string s; - - for (auto i = logins.begin(); i != logins.end(); i++) { - std::string start; - if (i == logins.begin()) { - start = "?"; - } else { - start = "&"; - } - - s += start + "login=" + *i; - } - - return this->get_users_by_query(s); - } - - std::vector<schemas::User> HelixClient::get_users( - const std::vector<int> &ids) const { - std::string s; - - for (auto i = ids.begin(); i != ids.end(); i++) { - std::string start; - if (i == ids.begin()) { - start = "?"; - } else { - start = "&"; - } - - s += start + "id=" + std::to_string(*i); - } - - return this->get_users_by_query(s); - } - - std::vector<schemas::User> HelixClient::get_users_by_query( - const std::string &query) const { - cpr::Response response = cpr::Get( - cpr::Url{this->base_url + "/users" + query}, cpr::Bearer{this->token}, - cpr::Header{{"Client-Id", this->client_id.c_str()}}); - - if (response.status_code != 200) { - return {}; - } - - std::vector<schemas::User> users; - - nlohmann::json j = nlohmann::json::parse(response.text); - - for (const auto &d : j["data"]) { - schemas::User u{std::stoi(d["id"].get<std::string>()), d["login"]}; - - users.push_back(u); - } - - return users; - } - - std::vector<schemas::User> HelixClient::get_chatters( - const int &broadcaster_id, const int &moderator_id) const { - cpr::Response response = - cpr::Get(cpr::Url{this->base_url + "/chat/chatters?broadcaster_id=" + - std::to_string(broadcaster_id) + - "&moderator_id=" + std::to_string(moderator_id)}, - cpr::Bearer{this->token}, - cpr::Header{{"Client-Id", this->client_id.c_str()}}); - - if (response.status_code != 200) { - return {}; - } - - std::vector<schemas::User> users; - - nlohmann::json j = nlohmann::json::parse(response.text); - - for (const auto &d : j["data"]) { - schemas::User u{std::stoi(d["user_id"].get<std::string>()), - d["user_login"]}; - - users.push_back(u); - } - - return users; - } - - std::vector<schemas::Stream> HelixClient::get_streams( - const std::vector<int> &ids) const { - std::string s; - - for (auto i = ids.begin(); i != ids.end(); i++) { - std::string start; - if (i == ids.begin()) { - start = "?"; - } else { - start = "&"; - } - - s += start + "user_id=" + std::to_string(*i); - } - - cpr::Response response = cpr::Get( - cpr::Url{this->base_url + "/streams" + s}, cpr::Bearer{this->token}, - cpr::Header{{"Client-Id", this->client_id.c_str()}}); - - if (response.status_code != 200) { - return {}; - } - - std::vector<schemas::Stream> streams; - - nlohmann::json j = nlohmann::json::parse(response.text); - - for (const auto &d : j["data"]) { - schemas::Stream u{std::stoi(d["user_id"].get<std::string>()), - d["user_login"], d["game_name"], d["title"], - d["started_at"]}; - - streams.push_back(u); - } - - return streams; - } -} diff --git a/src/api/twitch/helix_client.hpp b/src/api/twitch/helix_client.hpp deleted file mode 100644 index 27a9fa3..0000000 --- a/src/api/twitch/helix_client.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include <string> -#include <vector> - -#include "schemas/stream.hpp" -#include "schemas/user.hpp" - -namespace bot::api::twitch { - class HelixClient { - public: - HelixClient(const std::string &token, const std::string &client_id); - ~HelixClient() = default; - - std::vector<schemas::User> get_users( - const std::vector<std::string> &logins) const; - std::vector<schemas::User> get_users(const std::vector<int> &ids) const; - - std::vector<schemas::User> get_chatters(const int &broadcaster_id, - const int &moderator_id) const; - - std::vector<schemas::Stream> get_streams( - const std::vector<int> &ids) const; - - private: - std::vector<schemas::User> get_users_by_query( - const std::string &query) const; - std::string token, client_id; - const std::string base_url = "https://api.twitch.tv/helix"; - }; -} diff --git a/src/api/twitch/schemas/stream.hpp b/src/api/twitch/schemas/stream.hpp deleted file mode 100644 index e3d485e..0000000 --- a/src/api/twitch/schemas/stream.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include <chrono> -#include <string> - -#include "../../../utils/chrono.hpp" - -namespace bot::api::twitch::schemas { - class Stream { - public: - Stream(int user_id, std::string user_login, std::string game_name, - std::string title, std::string started_at) - : user_id(user_id), - user_login(user_login), - game_name(game_name), - title(title), - started_at(utils::chrono::string_to_time_point( - started_at, "%Y-%m-%dT%H:%M:%SZ")) {} - - Stream(int user_id) : user_id(user_id) {} - - const int &get_user_id() const { return this->user_id; } - const std::string &get_user_login() const { return this->user_login; } - const std::string &get_game_name() const { return this->game_name; } - const std::string &get_title() const { return this->title; } - const std::chrono::system_clock::time_point &get_started_at() const { - return this->started_at; - } - - private: - int user_id; - std::string user_login, game_name, title; - std::chrono::system_clock::time_point started_at; - }; -} diff --git a/src/api/twitch/schemas/user.hpp b/src/api/twitch/schemas/user.hpp deleted file mode 100644 index 288ec72..0000000 --- a/src/api/twitch/schemas/user.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include <string> - -namespace bot::api::twitch::schemas { - struct User { - int id; - std::string login; - }; -} diff --git a/src/bundle.hpp b/src/bundle.hpp deleted file mode 100644 index d30f5f8..0000000 --- a/src/bundle.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "api/twitch/helix_client.hpp" -#include "config.hpp" -#include "irc/client.hpp" -#include "localization/localization.hpp" - -namespace bot { - struct InstanceBundle { - irc::Client &irc_client; - const api::twitch::HelixClient &helix_client; - const bot::loc::Localization &localization; - const Configuration &configuration; - }; -} diff --git a/src/commands/command.cpp b/src/commands/command.cpp deleted file mode 100644 index e3b45b1..0000000 --- a/src/commands/command.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "command.hpp" - -#include <algorithm> -#include <chrono> -#include <ctime> -#include <memory> -#include <optional> -#include <pqxx/pqxx> -#include <string> - -#include "../bundle.hpp" -#include "../modules/custom_command.hpp" -#include "../modules/event.hpp" -#include "../modules/help.hpp" -#include "../modules/join.hpp" -#include "../modules/massping.hpp" -#include "../modules/notify.hpp" -#include "../modules/ping.hpp" -#include "../modules/timer.hpp" -#include "../utils/chrono.hpp" -#include "request.hpp" - -namespace bot { - namespace command { - CommandLoader::CommandLoader() { - this->add_command(std::make_unique<mod::Ping>()); - this->add_command(std::make_unique<mod::Massping>()); - this->add_command(std::make_unique<mod::Event>()); - this->add_command(std::make_unique<mod::Notify>()); - this->add_command(std::make_unique<mod::Join>()); - this->add_command(std::make_unique<mod::CustomCommand>()); - this->add_command(std::make_unique<mod::Timer>()); - this->add_command(std::make_unique<mod::Help>()); - } - - void CommandLoader::add_command(std::unique_ptr<Command> command) { - this->commands.push_back(std::move(command)); - } - - std::optional<std::variant<std::vector<std::string>, std::string>> - CommandLoader::run(const InstanceBundle &bundle, - const Request &request) const { - auto command = std::find_if( - this->commands.begin(), this->commands.end(), - [&](const auto &x) { return x->get_name() == request.command_id; }); - - if (command == this->commands.end()) { - return std::nullopt; - } - - if ((*command)->get_permission_level() > - request.user_rights.get_level()) { - return std::nullopt; - } - - pqxx::work work(request.conn); - - pqxx::result action_query = work.exec( - "SELECT sent_at FROM actions WHERE user_id = " + - std::to_string(request.user.get_id()) + - " AND channel_id = " + std::to_string(request.channel.get_id()) + - " AND command = '" + request.command_id + "' ORDER BY sent_at DESC"); - - if (!action_query.empty()) { - auto last_sent_at = utils::chrono::string_to_time_point( - action_query[0][0].as<std::string>()); - - auto now = std::chrono::system_clock::now(); - auto now_time_it = std::chrono::system_clock::to_time_t(now); - auto now_tm = std::gmtime(&now_time_it); - now = std::chrono::system_clock::from_time_t(std::mktime(now_tm)); - - auto difference = std::chrono::duration_cast<std::chrono::seconds>( - now - last_sent_at); - - if (difference.count() < command->get()->get_delay_seconds()) { - return std::nullopt; - } - } - - std::string arguments; - - if (request.subcommand_id.has_value()) { - arguments += request.subcommand_id.value() + " "; - } - - if (request.message.has_value()) { - arguments += request.message.value(); - } - - work.exec( - "INSERT INTO actions(user_id, channel_id, command, arguments, " - "full_message) VALUES (" + - std::to_string(request.user.get_id()) + ", " + - std::to_string(request.channel.get_id()) + ", '" + - request.command_id + "', '" + arguments + "', '" + - request.irc_message.message + "')"); - - work.commit(); - - return (*command)->run(bundle, request); - } - } -} diff --git a/src/commands/command.hpp b/src/commands/command.hpp deleted file mode 100644 index 40ec114..0000000 --- a/src/commands/command.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include <memory> -#include <optional> -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "request.hpp" - -namespace bot { - namespace command { - enum CommandArgument { - SUBCOMMAND, - MESSAGE, - INTERVAL, - NAME, - TARGET, - VALUE, - AMOUNT, - }; - - class Command { - public: - virtual std::string get_name() const = 0; - virtual std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, const Request &request) const = 0; - virtual schemas::PermissionLevel get_permission_level() const { - return schemas::PermissionLevel::USER; - } - virtual int get_delay_seconds() const { return 5; } - virtual std::vector<std::string> get_subcommand_ids() const { - return {}; - } - }; - - class CommandLoader { - public: - CommandLoader(); - ~CommandLoader() = default; - - void add_command(std::unique_ptr<Command> cmd); - std::optional<std::variant<std::vector<std::string>, std::string>> run( - const InstanceBundle &bundle, const Request &msg) const; - - const std::vector<std::unique_ptr<Command>> &get_commands() const { - return this->commands; - }; - - private: - std::vector<std::unique_ptr<Command>> commands; - }; - } -} diff --git a/src/commands/request.hpp b/src/commands/request.hpp deleted file mode 100644 index e2685f1..0000000 --- a/src/commands/request.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include <optional> -#include <pqxx/pqxx> -#include <string> - -#include "../irc/message.hpp" -#include "../schemas/channel.hpp" -#include "../schemas/user.hpp" - -namespace bot::command { - struct Request { - std::string command_id; - std::optional<std::string> subcommand_id; - std::optional<std::string> message; - const irc::Message<irc::MessageType::Privmsg> &irc_message; - - schemas::Channel channel; - schemas::ChannelPreferences channel_preferences; - schemas::User user; - schemas::UserRights user_rights; - - pqxx::connection &conn; - }; -} diff --git a/src/commands/request_util.cpp b/src/commands/request_util.cpp deleted file mode 100644 index 90750e5..0000000 --- a/src/commands/request_util.cpp +++ /dev/null @@ -1,205 +0,0 @@ -#include "request_util.hpp" - -#include <algorithm> -#include <optional> -#include <pqxx/pqxx> -#include <string> - -#include "../constants.hpp" -#include "../irc/message.hpp" -#include "../schemas/channel.hpp" -#include "command.hpp" -#include "request.hpp" - -namespace bot::command { - std::optional<Request> generate_request( - const command::CommandLoader &command_loader, - const irc::Message<irc::MessageType::Privmsg> &irc_message, - pqxx::connection &conn) { - pqxx::work *work; - - work = new pqxx::work(conn); - - std::vector<std::string> parts = - utils::string::split_text(irc_message.message, ' '); - - std::string command_id = parts[0]; - - if (command_id.substr(0, DEFAULT_PREFIX.length()) != DEFAULT_PREFIX) { - delete work; - return std::nullopt; - } - - command_id = - command_id.substr(DEFAULT_PREFIX.length(), command_id.length()); - - auto cmd = std::find_if( - command_loader.get_commands().begin(), - command_loader.get_commands().end(), - [&](const auto &command) { return command->get_name() == command_id; }); - - if (cmd == command_loader.get_commands().end()) { - delete work; - return std::nullopt; - } - - parts.erase(parts.begin()); - - pqxx::result query = work->exec("SELECT * FROM channels WHERE alias_id = " + - std::to_string(irc_message.source.id)); - - // Create new channel data in the database if it didn't exist b4 - if (query.empty()) { - work->exec("INSERT INTO channels (alias_id, alias_name) VALUES (" + - std::to_string(irc_message.source.id) + ", '" + - irc_message.source.login + "')"); - - work->commit(); - - delete work; - work = new pqxx::work(conn); - - query = work->exec("SELECT * FROM channels WHERE alias_id = " + - std::to_string(irc_message.source.id)); - } - - schemas::Channel channel(query[0]); - - if (channel.get_opted_out_at().has_value()) { - delete work; - return std::nullopt; - } - - query = work->exec("SELECT * FROM channel_preferences WHERE channel_id = " + - std::to_string(channel.get_id())); - - // Create new channel preference data in the database if it didn't exist b4 - if (query.empty()) { - work->exec( - "INSERT INTO channel_preferences (channel_id, prefix, locale) VALUES " - "(" + - std::to_string(channel.get_id()) + ", '" + DEFAULT_PREFIX + "', '" + - DEFAULT_LOCALE_ID + "')"); - - work->commit(); - - delete work; - work = new pqxx::work(conn); - - query = - work->exec("SELECT * FROM channel_preferences WHERE channel_id = " + - std::to_string(channel.get_id())); - } - - schemas::ChannelPreferences channel_preferences(query[0]); - - query = work->exec("SELECT * FROM users WHERE alias_id = " + - std::to_string(irc_message.sender.id)); - - // Create new user data in the database if it didn't exist before - if (query.empty()) { - work->exec("INSERT INTO users (alias_id, alias_name) VALUES (" + - std::to_string(irc_message.sender.id) + ", '" + - irc_message.sender.login + "')"); - - work->commit(); - - delete work; - work = new pqxx::work(conn); - - query = work->exec("SELECT * FROM users WHERE alias_id = " + - std::to_string(irc_message.sender.id)); - } - - schemas::User user(query[0]); - - if (user.get_alias_name() != irc_message.sender.login) { - work->exec("UPDATE users SET alias_name = '" + irc_message.sender.login + - "' WHERE id = " + std::to_string(user.get_id())); - work->commit(); - - delete work; - work = new pqxx::work(conn); - - user.set_alias_name(irc_message.sender.login); - } - - schemas::PermissionLevel level = schemas::PermissionLevel::USER; - const auto &badges = irc_message.sender.badges; - - if (user.get_alias_id() == channel.get_alias_id()) { - level = schemas::PermissionLevel::BROADCASTER; - } else if (std::any_of(badges.begin(), badges.end(), [&](const auto &x) { - return x.first == "moderator"; - })) { - level = schemas::PermissionLevel::MODERATOR; - } else if (std::any_of(badges.begin(), badges.end(), - [&](const auto &x) { return x.first == "vip"; })) { - level = schemas::PermissionLevel::VIP; - } - - query = work->exec("SELECT * FROM user_rights WHERE user_id = " + - std::to_string(user.get_id()) + - " AND channel_id = " + std::to_string(channel.get_id())); - - if (query.empty()) { - work->exec( - "INSERT INTO user_rights (user_id, channel_id, level) VALUES (" + - std::to_string(user.get_id()) + ", " + - std::to_string(channel.get_id()) + ", " + std::to_string(level) + - ")"); - - work->commit(); - - delete work; - work = new pqxx::work(conn); - - query = work->exec("SELECT * FROM user_rights WHERE user_id = " + - std::to_string(user.get_id()) + " AND channel_id = " + - std::to_string(channel.get_id())); - } - - schemas::UserRights user_rights(query[0]); - - if (user_rights.get_level() != level) { - work->exec("UPDATE user_rights SET level = " + std::to_string(level) + - " WHERE id = " + std::to_string(query[0][0].as<int>())); - - work->commit(); - - user_rights.set_level(level); - } - - delete work; - - if (parts.empty()) { - Request req{command_id, std::nullopt, std::nullopt, - irc_message, channel, channel_preferences, - user, user_rights, conn}; - - return req; - } - - std::optional<std::string> subcommand_id = parts[0]; - auto subcommand_ids = (*cmd)->get_subcommand_ids(); - - if (std::any_of( - subcommand_ids.begin(), subcommand_ids.end(), - [&](const auto &x) { return x == subcommand_id.value(); })) { - parts.erase(parts.begin()); - } else { - subcommand_id = std::nullopt; - } - - std::optional<std::string> message = utils::string::join_vector(parts, ' '); - - if (message->empty()) { - message = std::nullopt; - } - - Request req{command_id, subcommand_id, message, - irc_message, channel, channel_preferences, - user, user_rights, conn}; - return req; - } -} diff --git a/src/commands/request_util.hpp b/src/commands/request_util.hpp deleted file mode 100644 index dea6e12..0000000 --- a/src/commands/request_util.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#include <optional> -#include <pqxx/pqxx> - -#include "../irc/message.hpp" -#include "command.hpp" -#include "request.hpp" - -namespace bot::command { - std::optional<Request> generate_request( - const command::CommandLoader &command_loader, - const irc::Message<irc::MessageType::Privmsg> &irc_message, - pqxx::connection &conn); -} diff --git a/src/commands/response_error.hpp b/src/commands/response_error.hpp deleted file mode 100644 index ae2c3ee..0000000 --- a/src/commands/response_error.hpp +++ /dev/null @@ -1,222 +0,0 @@ -#pragma once - -#include <exception> -#include <optional> -#include <string> -#include <type_traits> -#include <vector> - -#include "command.hpp" -#include "request.hpp" - -namespace bot { - enum ResponseError { - NOT_ENOUGH_ARGUMENTS, - INCORRECT_ARGUMENT, - - INCOMPATIBLE_NAME, - NAMESAKE_CREATION, - NOT_FOUND, - - SOMETHING_WENT_WRONG, - - EXTERNAL_API_ERROR, - INSUFFICIENT_RIGHTS, - - ILLEGAL_COMMAND - }; - - template <ResponseError T, class Enable = void> - class ResponseException; - - template <ResponseError T> - class ResponseException< - T, typename std::enable_if< - T == INCORRECT_ARGUMENT || T == INCOMPATIBLE_NAME || - T == NAMESAKE_CREATION || T == NOT_FOUND>::type> - : public std::exception { - public: - ResponseException(const command::Request &request, - const loc::Localization &localizator, - const std::string &message) - : request(request), - localizator(localizator), - message(message), - error(T) { - loc::LineId line_id; - - switch (this->error) { - case INCORRECT_ARGUMENT: - line_id = loc::LineId::ErrorIncorrectArgument; - break; - case INCOMPATIBLE_NAME: - line_id = loc::LineId::ErrorIncompatibleName; - break; - case NAMESAKE_CREATION: - line_id = loc::LineId::ErrorNamesakeCreation; - break; - case NOT_FOUND: - line_id = loc::LineId::ErrorNotFound; - break; - default: - line_id = loc::LineId::ErrorSomethingWentWrong; - break; - }; - - this->line = - this->localizator - .get_formatted_line(this->request, line_id, {this->message}) - .value(); - } - ~ResponseException() = default; - - const char *what() const noexcept override { return this->line.c_str(); } - - private: - const command::Request &request; - const loc::Localization &localizator; - std::string message, line; - ResponseError error; - }; - - template <ResponseError T> - class ResponseException<T, - typename std::enable_if<T == SOMETHING_WENT_WRONG || - T == INSUFFICIENT_RIGHTS || - T == ILLEGAL_COMMAND>::type> - : public std::exception { - public: - ResponseException(const command::Request &request, - const loc::Localization &localizator) - : request(request), localizator(localizator), error(T) { - loc::LineId line_id; - - switch (this->error) { - case INSUFFICIENT_RIGHTS: - line_id = loc::LineId::ErrorInsufficientRights; - break; - case ILLEGAL_COMMAND: - line_id = loc::LineId::ErrorIllegalCommand; - break; - default: - line_id = loc::LineId::ErrorSomethingWentWrong; - break; - } - - this->line = - this->localizator.get_formatted_line(this->request, line_id, {}) - .value(); - } - ~ResponseException() = default; - - const char *what() const noexcept override { return this->line.c_str(); } - - private: - const command::Request &request; - const loc::Localization &localizator; - std::string line; - ResponseError error; - }; - - template <ResponseError T> - class ResponseException< - T, typename std::enable_if<T == EXTERNAL_API_ERROR>::type> - : public std::exception { - public: - ResponseException( - const command::Request &request, const loc::Localization &localizator, - const int &code, - const std::optional<std::string> &message = std::nullopt) - : request(request), - localizator(localizator), - code(code), - message(message), - error(T) { - loc::LineId line_id = loc::LineId::ErrorExternalAPIError; - std::vector<std::string> args = {std::to_string(this->code)}; - - if (this->message.has_value()) { - args.push_back(" " + this->message.value()); - } - - this->line = - this->localizator.get_formatted_line(this->request, line_id, args) - .value(); - } - ~ResponseException() = default; - - const char *what() const noexcept override { return this->line.c_str(); } - - private: - const command::Request &request; - const loc::Localization &localizator; - int code; - std::optional<std::string> message; - std::string line; - ResponseError error; - }; - - template <ResponseError T> - class ResponseException< - T, typename std::enable_if<T == NOT_ENOUGH_ARGUMENTS>::type> - : public std::exception { - public: - ResponseException(const command::Request &request, - const loc::Localization &localizator, - command::CommandArgument argument) - : request(request), - localizator(localizator), - argument(argument), - error(T) { - loc::LineId line_id = loc::LineId::ErrorNotEnoughArguments; - loc::LineId arg_id; - - switch (this->argument) { - case command::SUBCOMMAND: - arg_id = loc::LineId::ArgumentSubcommand; - break; - case command::MESSAGE: - arg_id = loc::LineId::ArgumentMessage; - break; - case command::INTERVAL: - arg_id = loc::LineId::ArgumentInterval; - break; - case command::NAME: - arg_id = loc::LineId::ArgumentName; - break; - case command::TARGET: - arg_id = loc::LineId::ArgumentTarget; - break; - case command::VALUE: - arg_id = loc::LineId::ArgumentValue; - break; - case command::AMOUNT: - arg_id = loc::LineId::ArgumentAmount; - break; - default: - break; - } - - auto arg = - this->localizator - .get_localized_line( - this->request.channel_preferences.get_locale(), arg_id) - .value(); - - this->line = - this->localizator.get_formatted_line(this->request, line_id, {arg}) - .value(); - } - ~ResponseException() = default; - - const char *what() const noexcept override { return this->line.c_str(); } - - private: - const command::Request &request; - const loc::Localization &localizator; - command::CommandArgument argument; - ResponseError error; - std::string line; - }; - -} diff --git a/src/config.cpp b/src/config.cpp deleted file mode 100644 index ec55913..0000000 --- a/src/config.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "config.hpp" - -#include <cctype> -#include <fstream> -#include <optional> -#include <sstream> -#include <string> - -#include "logger.hpp" - -namespace bot { - std::optional<Configuration> parse_configuration_from_file( - const std::string &file_path) { - std::ifstream ifs(file_path); - - if (!ifs.is_open()) { - log::error("Configuration", "Failed to open the file at " + file_path); - return std::nullopt; - } - - Configuration cfg; - TwitchCredentialsConfiguration ttv_crd_cfg; - DatabaseConfiguration db_cfg; - CommandConfiguration cmd_cfg; - OwnerConfiguration owner_cfg; - UrlConfiguration url_cfg; - - std::string line; - while (std::getline(ifs, line, '\n')) { - std::istringstream iss(line); - std::string key; - std::string value; - - std::getline(iss, key, '='); - std::getline(iss, value); - - for (char &c : key) { - c = tolower(c); - } - - if (key == "twitch_credentials.client_id") { - ttv_crd_cfg.client_id = value; - } else if (key == "twitch_credentials.token") { - ttv_crd_cfg.token = value; - } else if (key == "db_name") { - db_cfg.name = value; - } else if (key == "db_user") { - db_cfg.user = value; - } else if (key == "db_password") { - db_cfg.password = value; - } else if (key == "db_host") { - db_cfg.host = value; - } else if (key == "db_port") { - db_cfg.port = value; - } - - else if (key == "commands.join_allowed") { - cmd_cfg.join_allowed = std::stoi(value); - } else if (key == "commands.join_allow_from_other_chats") { - cmd_cfg.join_allow_from_other_chats = std::stoi(value); - } - - else if (key == "owner.name") { - owner_cfg.name = value; - } else if (key == "owner.id") { - owner_cfg.id = std::stoi(value); - } - - else if (key == "url.help") { - url_cfg.help = value; - } - } - - cfg.url = url_cfg; - cfg.owner = owner_cfg; - cfg.commands = cmd_cfg; - cfg.twitch_credentials = ttv_crd_cfg; - cfg.database = db_cfg; - - log::info("Configuration", - "Successfully loaded the file from '" + file_path + "'"); - return cfg; - } -} diff --git a/src/config.hpp b/src/config.hpp deleted file mode 100644 index 5c437d6..0000000 --- a/src/config.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include <optional> -#include <string> - -#define GET_DATABASE_CONNECTION_URL(c) \ - "dbname = " + c.database.name + " user = " + c.database.user + \ - " password = " + c.database.password + " host = " + c.database.host + \ - " port = " + c.database.port - -#define GET_DATABASE_CONNECTION_URL_POINTER(c) \ - "dbname = " + c->database.name + " user = " + c->database.user + \ - " password = " + c->database.password + " host = " + c->database.host + \ - " port = " + c->database.port - -namespace bot { - struct DatabaseConfiguration { - std::string name; - std::string user; - std::string password; - std::string host; - std::string port; - }; - - struct TwitchCredentialsConfiguration { - std::string client_id; - std::string token; - }; - - struct CommandConfiguration { - bool join_allowed = true; - bool join_allow_from_other_chats = false; - }; - - struct OwnerConfiguration { - std::optional<std::string> name = std::nullopt; - std::optional<int> id = std::nullopt; - }; - - struct UrlConfiguration { - std::optional<std::string> help = std::nullopt; - }; - - struct Configuration { - TwitchCredentialsConfiguration twitch_credentials; - DatabaseConfiguration database; - CommandConfiguration commands; - OwnerConfiguration owner; - UrlConfiguration url; - }; - - std::optional<Configuration> parse_configuration_from_file( - const std::string &file_path); -} diff --git a/src/constants.hpp b/src/constants.hpp deleted file mode 100644 index 3c3462b..0000000 --- a/src/constants.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include <chrono> -#include <string> - -#define DEFAULT_LOCALE_ID "english" - -#ifdef DEBUG_MODE -const std::string DEFAULT_PREFIX = "~"; -#else -const std::string DEFAULT_PREFIX = "!"; -#endif -const auto START_TIME = std::chrono::steady_clock::now(); diff --git a/src/handlers.cpp b/src/handlers.cpp deleted file mode 100644 index c7820b4..0000000 --- a/src/handlers.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "handlers.hpp" - -#include <exception> -#include <optional> -#include <pqxx/pqxx> -#include <string> -#include <vector> - -#include "bundle.hpp" -#include "commands/command.hpp" -#include "commands/request.hpp" -#include "commands/request_util.hpp" -#include "irc/message.hpp" -#include "localization/line_id.hpp" -#include "logger.hpp" -#include "utils/string.hpp" - -namespace bot::handlers { - void handle_private_message( - const InstanceBundle &bundle, - const command::CommandLoader &command_loader, - const irc::Message<irc::MessageType::Privmsg> &message, - pqxx::connection &conn) { - if (utils::string::string_contains_sql_injection(message.message)) { - log::warn("PrivateMessageHandler", - "Received the message in #" + message.source.login + - " with SQL injection: " + message.message); - return; - } - - std::optional<command::Request> request = - command::generate_request(command_loader, message, conn); - - if (request.has_value()) { - try { - auto response = command_loader.run(bundle, request.value()); - - if (response.has_value()) { - try { - auto str = std::get<std::string>(*response); - bundle.irc_client.say(message.source.login, str); - } catch (const std::exception &e) { - } - - try { - auto strs = std::get<std::vector<std::string>>(*response); - for (const std::string &str : strs) { - bundle.irc_client.say(message.source.login, str); - } - } catch (const std::exception &e) { - } - } - } catch (const std::exception &e) { - std::string line = - bundle.localization - .get_formatted_line(request.value(), loc::LineId::ErrorTemplate, - {e.what()}) - .value(); - - bundle.irc_client.say(message.source.login, line); - } - } - - pqxx::work work(conn); - pqxx::result channels = - work.exec("SELECT id FROM channels WHERE alias_id = " + - std::to_string(message.source.id)); - - if (!channels.empty()) { - int channel_id = channels[0][0].as<int>(); - pqxx::result cmds = - work.exec("SELECT message FROM custom_commands WHERE name = '" + - message.message + "' AND channel_id = '" + - std::to_string(channel_id) + "'"); - - if (!cmds.empty()) { - std::string msg = cmds[0][0].as<std::string>(); - - bundle.irc_client.say(message.source.login, msg); - } - } - - work.commit(); - } -} diff --git a/src/handlers.hpp b/src/handlers.hpp deleted file mode 100644 index a143f76..0000000 --- a/src/handlers.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "bundle.hpp" -#include "commands/command.hpp" -#include "irc/message.hpp" -#include "pqxx/pqxx" - -namespace bot::handlers { - void handle_private_message( - const InstanceBundle &bundle, - const command::CommandLoader &command_loader, - const irc::Message<irc::MessageType::Privmsg> &message, - pqxx::connection &conn); -} diff --git a/src/irc/client.cpp b/src/irc/client.cpp deleted file mode 100644 index 018736e..0000000 --- a/src/irc/client.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include "client.hpp" - -#include <ixwebsocket/IXWebSocketMessage.h> -#include <ixwebsocket/IXWebSocketMessageType.h> - -#include <algorithm> -#include <optional> -#include <string> -#include <vector> - -#include "../logger.hpp" -#include "cpr/api.h" -#include "cpr/cprtypes.h" -#include "cpr/response.h" -#include "message.hpp" -#include "nlohmann/json.hpp" - -using namespace bot::irc; - -Client::Client(std::string client_id, std::string token) { - this->client_id = client_id; - this->token = token; - - this->host = "wss://irc-ws.chat.twitch.tv"; - this->port = "443"; - - this->websocket.setUrl(this->host + ":" + this->port); - - // getting token owner - cpr::Response response = cpr::Get( - cpr::Url{"https://api.twitch.tv/helix/users"}, cpr::Bearer{this->token}, - cpr::Header{{"Client-Id", this->client_id}}); - - if (response.status_code != 200) { - log::warn("IRC", "Failed to get bot username from Twitch API: " + - std::to_string(response.status_code) + " " + - response.status_line); - } else { - nlohmann::json j = nlohmann::json::parse(response.text); - - auto d = j["data"][0]; - this->id = std::stoi(d["id"].get<std::string>()); - this->username = d["login"]; - } -} - -void Client::run() { - this->websocket.setOnMessageCallback( - [this](const ix::WebSocketMessagePtr &msg) { - switch (msg->type) { - case ix::WebSocketMessageType::Message: { - log::debug("IRC", "Received message: " + msg->str); - - std::vector<std::string> lines = - utils::string::split_text(msg->str, '\n'); - - for (std::string &line : lines) { - line.erase(std::remove_if(line.begin(), line.end(), - [](char c) { - return c == '\n' || c == '\r' || - c == '\t'; - }), - line.end()); - - std::optional<MessageType> type = define_message_type(line); - - if (!type.has_value()) { - break; - } - - MessageType m_type = type.value(); - - if (m_type == MessageType::Privmsg) { - std::optional<Message<MessageType::Privmsg>> message = - parse_message<MessageType::Privmsg>(line); - - if (message.has_value()) { - this->onPrivmsg(message.value()); - } - } - } - - break; - } - case ix::WebSocketMessageType::Open: { - log::info("IRC", "Connected to Twitch IRC"); - this->is_connected = true; - this->authorize(); - for (const auto &msg : this->pool) { - this->websocket.send(msg); - } - this->pool.clear(); - break; - } - case ix::WebSocketMessageType::Close: { - log::info("IRC", "Twitch IRC connection closed"); - this->is_connected = false; - - for (const auto &x : this->joined_channels) { - this->raw("JOIN #" + x); - } - - break; - } - default: { - break; - } - } - }); - - this->websocket.start(); -} - -void Client::say(const std::string &channel_login, const std::string &message) { - this->raw("PRIVMSG #" + channel_login + " :" + message); - log::debug("IRC", "Sent '" + message + "' in #" + channel_login); -} - -bool Client::join(const std::string &channel_login) { - auto already_joined = - std::any_of(this->joined_channels.begin(), this->joined_channels.end(), - [&](const auto &x) { return x == channel_login; }); - - if (!already_joined) { - this->raw("JOIN #" + channel_login); - this->joined_channels.push_back(channel_login); - log::info("IRC", "Joined #" + channel_login); - } - - return !already_joined; -} - -void Client::raw(const std::string &raw_message) { - std::string msg = raw_message + "\r\n"; - if (this->is_connected) { - this->websocket.send(msg); - } else { - this->pool.push_back(msg); - } -} - -void Client::authorize() { - if (this->username.empty() || this->token.empty()) { - log::error("IRC", "Bot username and token must be set for authorization!"); - return; - } - - log::info("IRC", "Authorizing on Twitch IRC servers..."); - - this->raw("PASS oauth:" + this->token); - this->raw("NICK " + this->username); - this->raw("CAP REQ :twitch.tv/membership"); - this->raw("CAP REQ :twitch.tv/commands"); - this->raw("CAP REQ :twitch.tv/tags"); -} diff --git a/src/irc/client.hpp b/src/irc/client.hpp deleted file mode 100644 index cff867f..0000000 --- a/src/irc/client.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include <ixwebsocket/IXWebSocket.h> - -#include <string> -#include <vector> - -#include "message.hpp" - -namespace bot { - namespace irc { - class Client { - public: - Client(std::string client_id, std::string token); - ~Client() = default; - - void run(); - - void say(const std::string &channel_login, const std::string &message); - bool join(const std::string &channel_login); - void raw(const std::string &raw_message); - - template <MessageType T> - void on(typename MessageHandler<T>::fn function) { - switch (T) { - case Privmsg: - this->onPrivmsg = function; - break; - default: - break; - } - } - - const std::string &get_bot_username() const { return this->username; }; - const int &get_bot_id() const { return this->id; } - - private: - void authorize(); - - std::string client_id, token, username; - - std::string host; - std::string port; - - int id; - - ix::WebSocket websocket; - - bool is_connected = false; - std::vector<std::string> pool; - - std::vector<std::string> joined_channels; - - // Message handlers - typename MessageHandler<MessageType::Privmsg>::fn onPrivmsg; - }; - } -} diff --git a/src/irc/message.cpp b/src/irc/message.cpp deleted file mode 100644 index 569e691..0000000 --- a/src/irc/message.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "message.hpp" - -#include <optional> -#include <string> -#include <vector> - -namespace bot { - namespace irc { - std::optional<MessageType> define_message_type(const std::string &msg) { - std::vector<std::string> parts = utils::string::split_text(msg, ' '); - int i; - - if (msg[0] == '@') { - i = 2; - } else if (msg[0] == ':') { - i = 1; - } else { - return std::nullopt; - } - - if (parts[i] == "NOTICE") { - return MessageType::Notice; - } else if (parts[i] == "PRIVMSG") { - return MessageType::Privmsg; - } - - return std::nullopt; - } - } -} diff --git a/src/irc/message.hpp b/src/irc/message.hpp deleted file mode 100644 index 164d7ca..0000000 --- a/src/irc/message.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once - -#include <functional> -#include <map> -#include <optional> -#include <sstream> -#include <string> -#include <vector> - -#include "../utils/string.hpp" - -namespace bot { - namespace irc { - enum MessageType { Privmsg, Notice }; - std::optional<MessageType> define_message_type(const std::string &msg); - - struct MessageSender { - std::string login; - std::string display_name; - int id; - - std::map<std::string, int> badges; - - // More fields will be here - }; - - struct MessageSource { - std::string login; - int id; - }; - - template <MessageType T> - struct Message; - - template <> - struct Message<MessageType::Privmsg> { - MessageSender sender; - MessageSource source; - std::string message; - }; - - template <MessageType T> - std::optional<Message<T>> parse_message(const std::string &msg) { - std::vector<std::string> parts = utils::string::split_text(msg, ' '); - - if (T == MessageType::Privmsg) { - MessageSender sender; - MessageSource source; - - Message<MessageType::Privmsg> message; - - std::string tags = parts[0]; - tags = tags.substr(1, tags.length()); - parts.erase(parts.begin()); - - std::string user = parts[0]; - user = user.substr(1, user.length()); - - std::vector<std::string> user_parts = - utils::string::split_text(user, '!'); - - sender.login = user_parts[0]; - - parts.erase(parts.begin(), parts.begin() + 2); - - std::string channel_login = parts[0]; - source.login = channel_login.substr(1, channel_login.length()); - - parts.erase(parts.begin()); - - std::string chat_message = utils::string::join_vector(parts, ' '); - message.message = chat_message.substr(1, chat_message.length()); - - std::vector<std::string> tags_parts = - utils::string::split_text(tags, ';'); - - for (const std::string &tag : tags_parts) { - std::istringstream iss(tag); - std::string key; - std::string value; - - std::getline(iss, key, '='); - std::getline(iss, value); - - if (key == "display-name") { - sender.display_name = value; - } else if (key == "room-id") { - source.id = std::stoi(value); - } else if (key == "user-id") { - sender.id = std::stoi(value); - } else if (key == "badges") { - std::vector<std::string> badges = - utils::string::split_text(value, ','); - - std::map<std::string, int> map; - - for (const auto &badge : badges) { - std::istringstream iss2(badge); - std::string name; - std::string value; - - std::getline(iss2, name, '/'); - std::getline(iss2, value); - - map.insert({name, std::stoi(value)}); - } - - sender.badges = map; - } - } - - message.sender = sender; - message.source = source; - - return message; - } - - return std::nullopt; - } - - template <MessageType T> - struct MessageHandler; - - template <> - struct MessageHandler<MessageType::Privmsg> { - using fn = std::function<void(Message<Privmsg> message)>; - }; - - } -} diff --git a/src/localization/line_id.cpp b/src/localization/line_id.cpp deleted file mode 100644 index 567a3ba..0000000 --- a/src/localization/line_id.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "line_id.hpp" - -#include <optional> -#include <string> - -namespace bot { - namespace loc { - std::optional<LineId> string_to_line_id(const std::string &str) { - if (str == "ping.response") { - return LineId::PingResponse; - } - - else if (str == "msg.owner") { - return LineId::MsgOwner; - } - - else if (str == "argument.subcommand") { - return LineId::ArgumentSubcommand; - } else if (str == "argument.message") { - return LineId::ArgumentMessage; - } else if (str == "argument.interval") { - return LineId::ArgumentInterval; - } else if (str == "argument.name") { - return LineId::ArgumentName; - } else if (str == "argument.target") { - return LineId::ArgumentTarget; - } else if (str == "argument.value") { - return LineId::ArgumentValue; - } else if (str == "argument.amount") { - return LineId::ArgumentAmount; - } - - else if (str == "error.template") { - return LineId::ErrorTemplate; - } else if (str == "error.not_enough_arguments") { - return LineId::ErrorNotEnoughArguments; - } else if (str == "error.incorrect_argument") { - return LineId::ErrorIncorrectArgument; - } else if (str == "error.incompatible_name") { - return LineId::ErrorIncompatibleName; - } else if (str == "error.namesake_creation") { - return LineId::ErrorNamesakeCreation; - } else if (str == "error.not_found") { - return LineId::ErrorNotFound; - } else if (str == "error.something_went_wrong") { - return LineId::ErrorSomethingWentWrong; - } else if (str == "error.insufficient_rights") { - return LineId::ErrorInsufficientRights; - } else if (str == "error.illegal_command") { - return LineId::ErrorIllegalCommand; - } - - else if (str == "event.on") { - return LineId::EventOn; - } else if (str == "event.off") { - return LineId::EventOff; - } - - else if (str == "notify.sub") { - return LineId::NotifySub; - } else if (str == "notify.unsub") { - return LineId::NotifyUnsub; - } - - else if (str == "join.response") { - return LineId::JoinResponse; - } else if (str == "join.response_in_chat") { - return LineId::JoinResponseInChat; - } else if (str == "join.already_in") { - return LineId::JoinAlreadyIn; - } else if (str == "join.rejoined") { - return LineId::JoinRejoined; - } else if (str == "join.from_other_chat") { - return LineId::JoinFromOtherChat; - } else if (str == "join.not_allowed") { - return LineId::JoinNotAllowed; - } - - else if (str == "custom_command.new") { - return LineId::CustomcommandNew; - } else if (str == "custom_command.delete") { - return LineId::CustomcommandDelete; - } - - else if (str == "timer.new") { - return LineId::TimerNew; - } else if (str == "timer.delete") { - return LineId::TimerDelete; - } - - else if (str == "help.response") { - return LineId::HelpResponse; - } - - else { - return std::nullopt; - } - } - } -} diff --git a/src/localization/line_id.hpp b/src/localization/line_id.hpp deleted file mode 100644 index 41ceec6..0000000 --- a/src/localization/line_id.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include <optional> -#include <string> - -namespace bot { - namespace loc { - enum LineId { - MsgOwner, - - ArgumentSubcommand, - ArgumentMessage, - ArgumentInterval, - ArgumentName, - ArgumentTarget, - ArgumentValue, - ArgumentAmount, - - ErrorTemplate, - ErrorNotEnoughArguments, - ErrorIncorrectArgument, - ErrorIncompatibleName, - ErrorNamesakeCreation, - ErrorNotFound, - ErrorSomethingWentWrong, - ErrorExternalAPIError, - ErrorInsufficientRights, - ErrorIllegalCommand, - - PingResponse, - - EventOn, - EventOff, - - NotifySub, - NotifyUnsub, - - JoinResponse, - JoinResponseInChat, - JoinAlreadyIn, - JoinRejoined, - JoinFromOtherChat, - JoinNotAllowed, - - CustomcommandNew, - CustomcommandDelete, - - TimerNew, - TimerDelete, - - HelpResponse - }; - - std::optional<LineId> string_to_line_id(const std::string &str); - } -} diff --git a/src/localization/localization.cpp b/src/localization/localization.cpp deleted file mode 100644 index 2742602..0000000 --- a/src/localization/localization.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "localization.hpp" - -#include <algorithm> -#include <filesystem> -#include <fstream> -#include <map> -#include <nlohmann/json.hpp> -#include <optional> -#include <string> -#include <unordered_map> -#include <vector> - -#include "../utils/string.hpp" -#include "line_id.hpp" - -namespace bot { - namespace loc { - Localization::Localization(const std::string &folder_path) { - for (const auto &entry : - std::filesystem::directory_iterator(folder_path)) { - std::vector<std::string> file_name_parts = - utils::string::split_text(entry.path(), '/'); - std::string file_name = file_name_parts[file_name_parts.size() - 1]; - file_name = file_name.substr(0, file_name.length() - 5); - - std::unordered_map<LineId, std::string> lines = - this->load_from_file(entry.path()); - - this->localizations[file_name] = lines; - } - } - - std::unordered_map<LineId, std::string> Localization::load_from_file( - const std::string &file_path) { - std::ifstream ifs(file_path); - - std::unordered_map<LineId, std::string> map; - - nlohmann::json json; - ifs >> json; - - for (auto it = json.begin(); it != json.end(); ++it) { - std::optional<LineId> line_id = string_to_line_id(it.key()); - - if (line_id.has_value()) { - map[line_id.value()] = it.value(); - } - } - - ifs.close(); - return map; - } - - std::optional<std::string> Localization::get_localized_line( - const std::string &locale_id, const LineId &line_id) const { - auto locale_it = - std::find_if(this->localizations.begin(), this->localizations.end(), - [&](const auto &x) { return x.first == locale_id; }); - - if (locale_it == this->localizations.end()) { - return std::nullopt; - } - - auto line_it = - std::find_if(locale_it->second.begin(), locale_it->second.end(), - [&](const auto &x) { return x.first == line_id; }); - - if (line_it == locale_it->second.end()) { - return std::nullopt; - } - - return line_it->second; - } - - std::optional<std::string> Localization::get_formatted_line( - const std::string &locale_id, const LineId &line_id, - const std::vector<std::string> &args) const { - std::optional<std::string> o_line = - this->get_localized_line(locale_id, line_id); - - if (!o_line.has_value()) { - return std::nullopt; - } - - std::string line = o_line.value(); - - int pos = 0; - int index = 0; - - while ((pos = line.find("%s", pos)) != std::string::npos) { - line.replace(pos, 2, args[index]); - pos += args[index].size(); - ++index; - - if (index >= args.size()) { - break; - } - } - - return line; - } - - std::optional<std::string> Localization::get_formatted_line( - const command::Request &request, const LineId &line_id, - const std::vector<std::string> &args) const { - std::optional<std::string> o_line = this->get_formatted_line( - request.channel_preferences.get_locale(), line_id, args); - - if (!o_line.has_value()) { - return std::nullopt; - } - - std::string line = o_line.value(); - - std::map<std::string, std::string> token_map = { - {"{sender.alias_name}", request.user.get_alias_name()}, - {"{source.alias_name}", request.channel.get_alias_name()}, - {"{default.prefix}", DEFAULT_PREFIX}}; - - for (const auto &pair : token_map) { - int pos = line.find(pair.first); - - while (pos != std::string::npos) { - line.replace(pos, pair.first.length(), pair.second); - pos = line.find(pair.first, pos + pair.second.length()); - } - } - - return line; - } - } -} diff --git a/src/localization/localization.hpp b/src/localization/localization.hpp deleted file mode 100644 index 4626c68..0000000 --- a/src/localization/localization.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include <optional> -#include <string> -#include <unordered_map> -#include <vector> - -#include "../commands/request.hpp" -#include "line_id.hpp" - -namespace bot { - namespace loc { - class Localization { - public: - Localization(const std::string &folder_path); - ~Localization() = default; - - std::optional<std::string> get_localized_line( - const std::string &locale_id, const LineId &line_id) const; - - std::optional<std::string> get_formatted_line( - const std::string &locale_id, const LineId &line_id, - const std::vector<std::string> &args) const; - - std::optional<std::string> get_formatted_line( - const command::Request &request, const LineId &line_id, - const std::vector<std::string> &args) const; - - private: - std::unordered_map<LineId, std::string> load_from_file( - const std::string &file_path); - std::unordered_map<std::string, std::unordered_map<LineId, std::string>> - localizations; - }; - } - -} diff --git a/src/logger.cpp b/src/logger.cpp deleted file mode 100644 index 3d142a2..0000000 --- a/src/logger.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "logger.hpp" - -#include <ctime> -#include <filesystem> -#include <fstream> -#include <iomanip> -#include <iostream> -#include <sstream> -#include <stdexcept> - -namespace bot::log { - void log(const LogLevel &level, const std::string &source, - const std::string &message) { - std::string dir_name = "logs"; - if (!std::filesystem::exists(dir_name)) { - std::filesystem::create_directory(dir_name); - } - - if (std::filesystem::exists(dir_name) && - !std::filesystem::is_directory(dir_name)) { - throw std::runtime_error("The path '" + dir_name + - "' is not a directory!"); - return; - } - - std::ostringstream line; - - // getting time - std::time_t current_time = std::time(nullptr); - std::tm *local_time = std::localtime(¤t_time); - - line << "[" << std::put_time(local_time, "%H:%M:%S") << "] "; - - std::string level_str; - - switch (level) { - case DEBUG: - level_str = "DEBUG"; - break; - case WARN: - level_str = "WARN"; - break; - case ERROR: - level_str = "ERROR"; - break; - default: - level_str = "INFO"; - break; - } - - line << level_str << " - "; - - line << source << ": " << message << "\n"; - -#ifdef DEBUG_MODE - std::cout << line.str(); -#else - if (level != LogLevel::DEBUG) { - std::cout << line.str(); - } -#endif - - // saving into the log file - std::ostringstream file_name_oss; - file_name_oss << dir_name << "/"; - file_name_oss << "log_"; - file_name_oss << std::put_time(local_time, "%Y-%m-%d"); - file_name_oss << ".log"; - - std::ofstream ofs; - ofs.open(file_name_oss.str(), std::ios::app); - - if (ofs.is_open()) { - ofs << line.str(); - ofs.close(); - } else { - std::cerr << "Failed to write to the log file!\n"; - } - } - - void info(const std::string &source, const std::string &message) { - log(LogLevel::INFO, source, message); - } - - void debug(const std::string &source, const std::string &message) { - log(LogLevel::DEBUG, source, message); - } - - void warn(const std::string &source, const std::string &message) { - log(LogLevel::WARN, source, message); - } - - void error(const std::string &source, const std::string &message) { - log(LogLevel::ERROR, source, message); - } -} diff --git a/src/logger.hpp b/src/logger.hpp deleted file mode 100644 index 91b4757..0000000 --- a/src/logger.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include <string> - -namespace bot::log { - enum LogLevel { INFO, DEBUG, WARN, ERROR }; - - void log(const LogLevel &level, const std::string &source, - const std::string &message); - - // just shorthands - void info(const std::string &source, const std::string &message); - void debug(const std::string &source, const std::string &message); - void warn(const std::string &source, const std::string &message); - void error(const std::string &source, const std::string &message); -} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 3c8f5e7..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include <optional> -#include <pqxx/pqxx> -#include <string> -#include <vector> - -#include "api/twitch/helix_client.hpp" -#include "bundle.hpp" -#include "commands/command.hpp" -#include "config.hpp" -#include "handlers.hpp" -#include "irc/client.hpp" -#include "irc/message.hpp" -#include "localization/localization.hpp" -#include "logger.hpp" -#include "stream.hpp" -#include "timer.hpp" - -int main(int argc, char *argv[]) { - bot::log::info("Main", "Starting up..."); - - std::optional<bot::Configuration> o_cfg = - bot::parse_configuration_from_file(".env"); - - if (!o_cfg.has_value()) { - return 1; - } - - bot::Configuration cfg = o_cfg.value(); - - if (cfg.twitch_credentials.client_id.empty() || - cfg.twitch_credentials.token.empty()) { - bot::log::error("Main", - "TWITCH_CREDENTIALS.CLIENT_ID and TWITCH_CREDENTIALS.TOKEN " - "must be set in environmental file!"); - return 1; - } - - if (cfg.database.name.empty() || cfg.database.user.empty() || - cfg.database.password.empty() || cfg.database.host.empty() || - cfg.database.port.empty()) { - bot::log::error("Main", - "DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT " - "must be set in environmental file!"); - return 1; - } - - bot::irc::Client client(cfg.twitch_credentials.client_id, - cfg.twitch_credentials.token); - bot::command::CommandLoader command_loader; - bot::loc::Localization localization("localization"); - bot::api::twitch::HelixClient helix_client(cfg.twitch_credentials.token, - cfg.twitch_credentials.client_id); - - client.join(client.get_bot_username()); - - pqxx::connection conn(GET_DATABASE_CONNECTION_URL(cfg)); - pqxx::work *work = new pqxx::work(conn); - - pqxx::result rows = work->exec( - "SELECT alias_id FROM channels WHERE opted_out_at is null AND alias_id " - "!= " + - std::to_string(client.get_bot_id())); - - std::vector<int> ids; - - for (const auto &row : rows) { - ids.push_back(row[0].as<int>()); - } - - auto helix_channels = helix_client.get_users(ids); - - // it could be optimized - for (const auto &helix_channel : helix_channels) { - auto channel = - work->exec("SELECT id, alias_name FROM channels WHERE alias_id = " + - std::to_string(helix_channel.id)); - - if (!channel.empty()) { - std::string name = channel[0][1].as<std::string>(); - - if (name != helix_channel.login) { - work->exec("UPDATE channels SET alias_name = '" + helix_channel.login + - "' WHERE id = " + std::to_string(channel[0][0].as<int>())); - work->commit(); - - delete work; - work = new pqxx::work(conn); - } - - client.join(helix_channel.login); - } - } - - work->commit(); - delete work; - - conn.close(); - - bot::stream::StreamListenerClient stream_listener_client(helix_client, client, - cfg); - - client.on<bot::irc::MessageType::Privmsg>( - [&client, &command_loader, &localization, &cfg, &helix_client]( - const bot::irc::Message<bot::irc::MessageType::Privmsg> &message) { - bot::InstanceBundle bundle{client, helix_client, localization, cfg}; - - pqxx::connection conn(GET_DATABASE_CONNECTION_URL(cfg)); - - bot::handlers::handle_private_message(bundle, command_loader, message, - conn); - - conn.close(); - }); - - client.run(); - - std::vector<std::thread> threads; - threads.push_back(std::thread(bot::create_timer_thread, &client, &cfg)); - threads.push_back(std::thread(&bot::stream::StreamListenerClient::run, - &stream_listener_client)); - - for (auto &thread : threads) { - thread.join(); - } - - return 0; -} diff --git a/src/modules/custom_command.hpp b/src/modules/custom_command.hpp deleted file mode 100644 index 50b3692..0000000 --- a/src/modules/custom_command.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" -#include "../commands/response_error.hpp" - -namespace bot { - namespace mod { - class CustomCommand : public command::Command { - std::string get_name() const override { return "scmd"; } - - schemas::PermissionLevel get_permission_level() const override { - return schemas::PermissionLevel::MODERATOR; - } - - std::vector<std::string> get_subcommand_ids() const override { - return {"new", "remove"}; - } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - if (!request.subcommand_id.has_value()) { - throw ResponseException<NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::SUBCOMMAND); - } - - const std::string &subcommand_id = request.subcommand_id.value(); - - if (!request.message.has_value()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::CommandArgument::NAME); - } - - const std::string &message = request.message.value(); - std::vector<std::string> s = utils::string::split_text(message, ' '); - - std::string name = s[0]; - s.erase(s.begin()); - - pqxx::work work(request.conn); - pqxx::result cmds = work.exec( - "SELECT id FROM custom_commands WHERE name = '" + name + - "' AND channel_id = " + std::to_string(request.channel.get_id())); - - if (subcommand_id == "new") { - if (!cmds.empty()) { - throw ResponseException<ResponseError::NAMESAKE_CREATION>( - request, bundle.localization, name); - } - - if (s.empty()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, - command::CommandArgument::MESSAGE); - } - - std::string m = utils::string::str(s.begin(), s.end(), ' '); - - work.exec( - "INSERT INTO custom_commands(channel_id, name, message) VALUES " - "(" + - std::to_string(request.channel.get_id()) + ", '" + name + - "', '" + m + "')"); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::CustomcommandNew, - {name}) - .value(); - } else if (subcommand_id == "remove") { - if (cmds.empty()) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, name); - } - - work.exec("DELETE FROM custom_commands WHERE id = " + - std::to_string(cmds[0][0].as<int>())); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::CustomcommandDelete, - {name}) - .value(); - } - - throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>( - request, bundle.localization); - } - }; - } -} diff --git a/src/modules/event.hpp b/src/modules/event.hpp deleted file mode 100644 index 4242f07..0000000 --- a/src/modules/event.hpp +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" -#include "../commands/response_error.hpp" -#include "../schemas/stream.hpp" - -namespace bot { - namespace mod { - class Event : public command::Command { - std::string get_name() const override { return "event"; } - - schemas::PermissionLevel get_permission_level() const override { - return schemas::PermissionLevel::MODERATOR; - } - - std::vector<std::string> get_subcommand_ids() const override { - return {"on", "off"}; - } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - if (!request.subcommand_id.has_value()) { - throw ResponseException<NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::SUBCOMMAND); - } - - const std::string &subcommand_id = request.subcommand_id.value(); - - if (!request.message.has_value()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::CommandArgument::TARGET); - } - - const std::string &message = request.message.value(); - std::vector<std::string> s = utils::string::split_text(message, ' '); - - std::string target; - schemas::EventType type; - - std::vector<std::string> target_and_type = - utils::string::split_text(s[0], ':'); - - if (target_and_type.size() != 2) { - throw ResponseException<ResponseError::INCORRECT_ARGUMENT>( - request, bundle.localization, s[0]); - } - - s.erase(s.begin()); - - target = target_and_type[0]; - type = schemas::string_to_event_type(target_and_type[1]); - - std::string t = target_and_type[0] + ":" + target_and_type[1]; - - auto channels = bundle.helix_client.get_users({target}); - api::twitch::schemas::User channel; - - if (channels.empty() && type != schemas::EventType::CUSTOM) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, t); - } - - pqxx::work work(request.conn); - std::string query; - - if (type != schemas::CUSTOM) { - channel = channels[0]; - - query = "SELECT id FROM events WHERE channel_id = " + - std::to_string(request.channel.get_id()) + - " AND target_alias_id = " + std::to_string(channel.id) + - " AND event_type = " + std::to_string(type); - } else { - query = "SELECT id FROM events WHERE channel_id = " + - std::to_string(request.channel.get_id()) + - " AND custom_alias_id = '" + target + - "' AND event_type = " + std::to_string(type); - } - - pqxx::result event = work.exec(query); - - if (subcommand_id == "on") { - if (!event.empty()) { - throw ResponseException<ResponseError::NAMESAKE_CREATION>( - request, bundle.localization, t); - } - - if (s.empty()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, - command::CommandArgument::MESSAGE); - } - - std::string m = utils::string::str(s.begin(), s.end(), ' '); - - if (type != schemas::CUSTOM) { - query = - "INSERT INTO events (channel_id, target_alias_id, " - "event_type, " - "message) VALUES (" + - std::to_string(request.channel.get_id()) + ", " + - std::to_string(channel.id) + ", " + std::to_string(type) + - ", '" + m + "')"; - } else { - query = - "INSERT INTO events (channel_id, custom_alias_id, " - "event_type, " - "message) VALUES (" + - std::to_string(request.channel.get_id()) + ", '" + target + - "', " + std::to_string(type) + ", '" + m + "')"; - } - - work.exec(query); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::EventOn, {t}) - .value(); - } else if (subcommand_id == "off") { - if (event.empty()) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, t); - } - - work.exec("DELETE FROM events WHERE id = " + - std::to_string(event[0][0].as<int>())); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::EventOff, {t}) - .value(); - } - - throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>( - request, bundle.localization); - } - }; - } -} diff --git a/src/modules/help.hpp b/src/modules/help.hpp deleted file mode 100644 index 13af228..0000000 --- a/src/modules/help.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" -#include "../commands/response_error.hpp" - -namespace bot { - namespace mod { - class Help : public command::Command { - std::string get_name() const override { return "help"; } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - if (!bundle.configuration.url.help.has_value()) { - throw ResponseException<ResponseError::ILLEGAL_COMMAND>( - request, bundle.localization); - } - - return bundle.localization - .get_formatted_line(request, loc::LineId::HelpResponse, - {*bundle.configuration.url.help}) - .value(); - } - }; - } -} diff --git a/src/modules/join.hpp b/src/modules/join.hpp deleted file mode 100644 index 16e8b4a..0000000 --- a/src/modules/join.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include <pqxx/pqxx> -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" -#include "../schemas/channel.hpp" - -namespace bot { - namespace mod { - class Join : public command::Command { - std::string get_name() const override { return "join"; } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - if (!bundle.configuration.commands.join_allowed) { - std::string owner = ""; - - if (bundle.configuration.owner.name.has_value()) { - owner = " " + bundle.localization - .get_formatted_line( - request, loc::LineId::MsgOwner, - {*bundle.configuration.owner.name}) - .value(); - } - - return bundle.localization - .get_formatted_line(request, loc::LineId::JoinNotAllowed, - {owner}) - .value(); - } - - if (!bundle.configuration.commands.join_allow_from_other_chats && - request.channel.get_alias_name() != - bundle.irc_client.get_bot_username()) { - return bundle.localization - .get_formatted_line(request, loc::LineId::JoinFromOtherChat, - {bundle.irc_client.get_bot_username()}) - .value(); - } - - pqxx::work work(request.conn); - - pqxx::result channels = - work.exec("SELECT * FROM channels WHERE alias_id = " + - std::to_string(request.user.get_alias_id())); - - if (!channels.empty()) { - schemas::Channel channel(channels[0]); - - if (channel.get_opted_out_at().has_value()) { - work.exec("UPDATE channels SET opted_out_at = null WHERE id = " + - std::to_string(channel.get_id())); - work.commit(); - - bundle.irc_client.join(channel.get_alias_name()); - - return bundle.localization - .get_formatted_line(request, loc::LineId::JoinRejoined, {}) - .value(); - } - - return bundle.localization - .get_formatted_line(request, loc::LineId::JoinAlreadyIn, {}) - .value(); - } - - work.exec("INSERT INTO channels(alias_id, alias_name) VALUES (" + - std::to_string(request.user.get_alias_id()) + ", '" + - request.user.get_alias_name() + "')"); - work.commit(); - - bundle.irc_client.join(request.user.get_alias_name()); - bundle.irc_client.say( - request.user.get_alias_name(), - bundle.localization - .get_formatted_line(request, loc::LineId::JoinResponseInChat, - {}) - .value()); - - return bundle.localization - .get_formatted_line(request, loc::LineId::JoinResponse, {}) - .value(); - } - }; - } -} diff --git a/src/modules/massping.hpp b/src/modules/massping.hpp deleted file mode 100644 index 2957e34..0000000 --- a/src/modules/massping.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" - -namespace bot { - namespace mod { - class Massping : public command::Command { - std::string get_name() const override { return "massping"; } - - schemas::PermissionLevel get_permission_level() const override { - return schemas::PermissionLevel::MODERATOR; - } - - int get_delay_seconds() const override { return 1; } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - auto chatters = bundle.helix_client.get_chatters( - request.channel.get_alias_id(), bundle.irc_client.get_bot_id()); - - std::string m; - - if (request.message.has_value()) { - m = request.message.value() + " ·"; - } - - std::string base = "📣 " + m + " "; - std::vector<std::string> msgs = {""}; - int index = 0; - - for (const auto &chatter : chatters) { - const std::string ¤t_msg = msgs.at(index); - std::string x = "@" + chatter.login; - - if (base.length() + current_msg.length() + 1 + x.length() >= 500) { - index += 1; - } - - if (index > msgs.size() - 1) { - msgs.push_back(x); - } else { - msgs[index] = current_msg + " " + x; - } - } - - std::vector<std::string> msgs2; - - for (const auto &m : msgs) { - msgs2.push_back(base + m); - } - - return msgs2; - } - }; - } -} diff --git a/src/modules/notify.hpp b/src/modules/notify.hpp deleted file mode 100644 index 3587e73..0000000 --- a/src/modules/notify.hpp +++ /dev/null @@ -1,131 +0,0 @@ -#pragma once - -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" -#include "../commands/response_error.hpp" -#include "../schemas/stream.hpp" - -namespace bot { - namespace mod { - class Notify : public command::Command { - std::string get_name() const override { return "notify"; } - - std::vector<std::string> get_subcommand_ids() const override { - return {"sub", "unsub"}; - } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - if (!request.subcommand_id.has_value()) { - throw ResponseException<NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::SUBCOMMAND); - } - - const std::string &subcommand_id = request.subcommand_id.value(); - - if (!request.message.has_value()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::CommandArgument::TARGET); - } - - const std::string &message = request.message.value(); - std::vector<std::string> s = utils::string::split_text(message, ' '); - - std::string target; - schemas::EventType type; - - std::vector<std::string> target_and_type = - utils::string::split_text(s[0], ':'); - - if (target_and_type.size() != 2) { - throw ResponseException<ResponseError::INCORRECT_ARGUMENT>( - request, bundle.localization, s[0]); - } - - s.erase(s.begin()); - - target = target_and_type[0]; - type = schemas::string_to_event_type(target_and_type[1]); - - std::string t = target_and_type[0] + ":" + target_and_type[1]; - - auto channels = bundle.helix_client.get_users({target}); - api::twitch::schemas::User channel; - - if (channels.empty() && type != schemas::EventType::CUSTOM) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, t); - } - - pqxx::work work(request.conn); - std::string query; - - if (type != schemas::CUSTOM) { - channel = channels[0]; - - query = "SELECT id FROM events WHERE channel_id = " + - std::to_string(request.channel.get_id()) + - " AND target_alias_id = " + std::to_string(channel.id) + - " AND event_type = " + std::to_string(type); - } else { - query = "SELECT id FROM events WHERE channel_id = " + - std::to_string(request.channel.get_id()) + - " AND custom_alias_id = '" + target + - "' AND event_type = " + std::to_string(type); - } - - pqxx::result events = work.exec(query); - - if (events.empty()) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, t); - } - - pqxx::row event = events[0]; - - pqxx::result subs = - work.exec("SELECT id FROM event_subscriptions WHERE event_id = " + - std::to_string(event[0].as<int>()) + " AND user_id = " + - std::to_string(request.user.get_id())); - - if (subcommand_id == "sub") { - if (!subs.empty()) { - throw ResponseException<ResponseError::NAMESAKE_CREATION>( - request, bundle.localization, t); - } - - work.exec( - "INSERT INTO event_subscriptions(event_id, user_id) VALUES (" + - std::to_string(event[0].as<int>()) + ", " + - std::to_string(request.user.get_id())); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::NotifySub, {t}) - .value(); - } else if (subcommand_id == "unsub") { - if (subs.empty()) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, t); - } - - work.exec("DELETE FROM event_subscriptions WHERE id = " + - std::to_string(subs[0][0].as<int>())); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::NotifyUnsub, {t}) - .value(); - } - - throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>( - request, bundle.localization); - } - }; - } -} diff --git a/src/modules/ping.hpp b/src/modules/ping.hpp deleted file mode 100644 index 836917d..0000000 --- a/src/modules/ping.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include <sys/resource.h> -#include <sys/types.h> -#include <unistd.h> - -#include <chrono> -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" -#include "../utils/chrono.hpp" - -namespace bot { - namespace mod { - class Ping : public command::Command { - std::string get_name() const override { return "ping"; } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - auto now = std::chrono::steady_clock::now(); - auto duration = now - START_TIME; - auto seconds = - std::chrono::duration_cast<std::chrono::seconds>(duration); - std::string uptime = utils::chrono::format_timestamp(seconds.count()); - - struct rusage usage; - getrusage(RUSAGE_SELF, &usage); - - int used_memory = usage.ru_maxrss / 1024; - - std::string cpp_info; - -#ifdef __cplusplus - cpp_info.append("C++" + std::to_string(__cplusplus).substr(2, 2)); -#endif - -#ifdef __VERSION__ - cpp_info.append(" (gcc " + - bot::utils::string::split_text(__VERSION__, ' ')[0] + - ")"); -#endif - - if (!cpp_info.empty()) { - cpp_info.append(" · "); - } - - return bundle.localization - .get_formatted_line( - request, loc::LineId::PingResponse, - {cpp_info, uptime, std::to_string(used_memory)}) - .value(); - } - }; - } -} diff --git a/src/modules/timer.hpp b/src/modules/timer.hpp deleted file mode 100644 index 36c3982..0000000 --- a/src/modules/timer.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#pragma once - -#include <string> -#include <variant> -#include <vector> - -#include "../bundle.hpp" -#include "../commands/command.hpp" -#include "../commands/response_error.hpp" - -namespace bot { - namespace mod { - class Timer : public command::Command { - std::string get_name() const override { return "timer"; } - - schemas::PermissionLevel get_permission_level() const override { - return schemas::PermissionLevel::MODERATOR; - } - - std::vector<std::string> get_subcommand_ids() const override { - return {"new", "remove"}; - } - - std::variant<std::vector<std::string>, std::string> run( - const InstanceBundle &bundle, - const command::Request &request) const override { - if (!request.subcommand_id.has_value()) { - throw ResponseException<NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::SUBCOMMAND); - } - - const std::string &subcommand_id = request.subcommand_id.value(); - - if (!request.message.has_value()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, command::CommandArgument::NAME); - } - - const std::string &message = request.message.value(); - std::vector<std::string> s = utils::string::split_text(message, ' '); - - std::string name = s[0]; - s.erase(s.begin()); - - pqxx::work work(request.conn); - pqxx::result timers = work.exec( - "SELECT id FROM timers WHERE name = '" + name + - "' AND channel_id = " + std::to_string(request.channel.get_id())); - - if (subcommand_id == "new") { - if (!timers.empty()) { - throw ResponseException<ResponseError::NAMESAKE_CREATION>( - request, bundle.localization, name); - } - - if (s.empty()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, - command::CommandArgument::INTERVAL); - } - - int interval_s; - - try { - interval_s = std::stoi(s[0]); - } catch (std::exception e) { - throw ResponseException<ResponseError::INCORRECT_ARGUMENT>( - request, bundle.localization, s[0]); - } - - s.erase(s.begin()); - - if (s.empty()) { - throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>( - request, bundle.localization, - command::CommandArgument::MESSAGE); - } - - std::string m = utils::string::str(s.begin(), s.end(), ' '); - - work.exec( - "INSERT INTO timers(channel_id, name, message, interval_sec) " - "VALUES " - "(" + - std::to_string(request.channel.get_id()) + ", '" + name + - "', '" + m + "', " + std::to_string(interval_s) + ")"); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::TimerNew, {name}) - .value(); - } else if (subcommand_id == "remove") { - if (timers.empty()) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, name); - } - - work.exec("DELETE FROM timers WHERE id = " + - std::to_string(timers[0][0].as<int>())); - work.commit(); - - return bundle.localization - .get_formatted_line(request, loc::LineId::TimerDelete, {name}) - .value(); - } - - throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>( - request, bundle.localization); - } - }; - } -} diff --git a/src/schemas/channel.hpp b/src/schemas/channel.hpp deleted file mode 100644 index 2560331..0000000 --- a/src/schemas/channel.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include <chrono> -#include <optional> -#include <pqxx/pqxx> -#include <string> - -#include "../constants.hpp" -#include "../utils/chrono.hpp" - -namespace bot::schemas { - class Channel { - public: - Channel(const pqxx::row &row) { - this->id = row[0].as<int>(); - this->alias_id = row[1].as<int>(); - this->alias_name = row[2].as<std::string>(); - - this->joined_at = - utils::chrono::string_to_time_point(row[3].as<std::string>()); - - if (!row[4].is_null()) { - this->opted_out_at = - utils::chrono::string_to_time_point(row[4].as<std::string>()); - } - } - - ~Channel() = default; - - const int &get_id() const { return this->id; } - const int &get_alias_id() const { return this->alias_id; } - const std::string &get_alias_name() const { return this->alias_name; } - const std::chrono::system_clock::time_point &get_joined_at() const { - return this->joined_at; - } - const std::optional<std::chrono::system_clock::time_point> & - get_opted_out_at() const { - return this->opted_out_at; - } - - private: - int id, alias_id; - std::string alias_name; - std::chrono::system_clock::time_point joined_at; - std::optional<std::chrono::system_clock::time_point> opted_out_at; - }; - - class ChannelPreferences { - public: - ChannelPreferences(const pqxx::row &row) { - this->channel_id = row[0].as<int>(); - - if (!row[2].is_null()) { - this->prefix = row[1].as<std::string>(); - } else { - this->prefix = DEFAULT_PREFIX; - } - - if (!row[3].is_null()) { - this->locale = row[2].as<std::string>(); - } else { - this->locale = DEFAULT_LOCALE_ID; - } - } - - ~ChannelPreferences() = default; - - const int &get_channel_id() const { return this->channel_id; } - const std::string &get_prefix() const { return this->prefix; } - const std::string &get_locale() const { return this->locale; } - - private: - int channel_id; - std::string prefix, locale; - }; -} diff --git a/src/schemas/stream.cpp b/src/schemas/stream.cpp deleted file mode 100644 index 6ef10dc..0000000 --- a/src/schemas/stream.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "stream.hpp" - -namespace bot::schemas { - EventType string_to_event_type(const std::string &type) { - if (type == "live") { - return EventType::LIVE; - } else if (type == "offline") { - return EventType::OFFLINE; - } else if (type == "title") { - return EventType::TITLE; - } else if (type == "game") { - return EventType::GAME; - } else { - return EventType::CUSTOM; - } - } -} diff --git a/src/schemas/stream.hpp b/src/schemas/stream.hpp deleted file mode 100644 index a636ea5..0000000 --- a/src/schemas/stream.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include <string> - -namespace bot::schemas { - enum EventType { LIVE, OFFLINE, TITLE, GAME, CUSTOM = 99 }; - EventType string_to_event_type(const std::string &type); - - enum EventFlag { MASSPING }; - -} diff --git a/src/schemas/user.hpp b/src/schemas/user.hpp deleted file mode 100644 index 0bd1368..0000000 --- a/src/schemas/user.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include <chrono> -#include <optional> -#include <pqxx/pqxx> -#include <string> - -#include "../utils/chrono.hpp" - -namespace bot::schemas { - class User { - public: - User(const pqxx::row &row) { - this->id = row[0].as<int>(); - this->alias_id = row[1].as<int>(); - this->alias_name = row[2].as<std::string>(); - - this->joined_at = - utils::chrono::string_to_time_point(row[3].as<std::string>()); - - if (!row[4].is_null()) { - this->opted_out_at = - utils::chrono::string_to_time_point(row[4].as<std::string>()); - } - } - - ~User() = default; - - const int &get_id() const { return this->id; } - const int &get_alias_id() const { return this->alias_id; } - const std::string &get_alias_name() const { return this->alias_name; } - void set_alias_name(const std::string &alias_name) { - this->alias_name = alias_name; - } - const std::chrono::system_clock::time_point &get_joined_at() const { - return this->joined_at; - } - const std::optional<std::chrono::system_clock::time_point> & - get_opted_out_at() const { - return this->opted_out_at; - } - - private: - int id, alias_id; - std::string alias_name; - std::chrono::system_clock::time_point joined_at; - std::optional<std::chrono::system_clock::time_point> opted_out_at; - }; - - enum PermissionLevel { SUSPENDED, USER, VIP, MODERATOR, BROADCASTER }; - - class UserRights { - public: - UserRights(const pqxx::row &row) { - this->id = row[0].as<int>(); - this->user_id = row[1].as<int>(); - this->channel_id = row[2].as<int>(); - this->level = static_cast<PermissionLevel>(row[3].as<int>()); - } - - ~UserRights() = default; - - const int &get_id() const { return this->id; } - const int &get_user_id() const { return this->user_id; } - const int &get_channel_id() const { return this->channel_id; } - const PermissionLevel &get_level() const { return this->level; } - void set_level(PermissionLevel level) { this->level = level; } - - private: - int id, user_id, channel_id; - PermissionLevel level; - }; -} diff --git a/src/stream.cpp b/src/stream.cpp deleted file mode 100644 index 6e48fb8..0000000 --- a/src/stream.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include "stream.hpp" - -#include <algorithm> -#include <chrono> -#include <pqxx/pqxx> -#include <set> -#include <string> -#include <thread> -#include <utility> -#include <vector> - -#include "api/twitch/schemas/stream.hpp" -#include "config.hpp" -#include "logger.hpp" -#include "schemas/stream.hpp" -#include "utils/string.hpp" - -namespace bot::stream { - void StreamListenerClient::listen_channel(const int &id) { - this->ids.push_back(id); - } - void StreamListenerClient::unlisten_channel(const int &id) { - auto x = std::find_if(this->ids.begin(), this->ids.end(), - [&](const auto &x) { return x == id; }); - - if (x != this->ids.end()) { - this->ids.erase(x); - } - - auto y = std::find_if(this->online_ids.begin(), this->online_ids.end(), - [&](const auto &x) { return x == id; }); - - if (y != this->online_ids.end()) { - this->online_ids.erase(y); - } - } - void StreamListenerClient::run() { - while (true) { - this->update_channel_ids(); - this->check(); - std::this_thread::sleep_for(std::chrono::seconds(5)); - } - } - void StreamListenerClient::check() { - auto streams = this->helix_client.get_streams(this->ids); - auto now = std::chrono::system_clock::now(); - auto now_time_it = std::chrono::system_clock::to_time_t(now); - auto now_tm = std::gmtime(&now_time_it); - now = std::chrono::system_clock::from_time_t(std::mktime(now_tm)); - - // adding new ids - for (const auto &stream : streams) { - bool is_already_live = - std::any_of(this->online_ids.begin(), this->online_ids.end(), - [&](const auto &x) { return x == stream.get_user_id(); }); - - if (!is_already_live) { - this->online_ids.insert(stream.get_user_id()); - - auto difference = now - stream.get_started_at(); - auto difference_min = - std::chrono::duration_cast<std::chrono::minutes>(difference); - - if (difference_min.count() < 1) { - this->handler(schemas::EventType::LIVE, stream); - } - } - } - - // removing old ids - for (auto i = this->online_ids.begin(); i != this->online_ids.end();) { - auto stream = - std::find_if(streams.begin(), streams.end(), - [&](const auto &x) { return x.get_user_id() == *i; }); - - if (stream == streams.end()) { - this->handler(schemas::EventType::OFFLINE, - api::twitch::schemas::Stream{*i}); - i = this->online_ids.erase(i); - } else { - ++i; - } - } - } - void StreamListenerClient::handler( - const schemas::EventType &type, - const api::twitch::schemas::Stream &stream) { - pqxx::connection conn(GET_DATABASE_CONNECTION_URL(this->configuration)); - pqxx::work work(conn); - - pqxx::result events = work.exec( - "SELECT id, channel_id, message, flags FROM events WHERE event_type " - "= " + - std::to_string(type) + - " AND target_alias_id = " + std::to_string(stream.get_user_id())); - - for (const auto &event : events) { - pqxx::row channel = work.exec1( - "SELECT alias_id, alias_name, opted_out_at FROM channels WHERE id " - "= " + - std::to_string(event[1].as<int>())); - - if (!channel[2].is_null()) { - continue; - } - - pqxx::result subs = work.exec( - "SELECT user_id FROM event_subscriptions WHERE event_id = " + - std::to_string(event[0].as<int>())); - - std::set<std::string> user_ids; - if (!subs.empty()) { - for (const auto &sub : subs) { - user_ids.insert(std::to_string(sub[0].as<int>())); - } - - pqxx::result users = work.exec( - "SELECT alias_name FROM users WHERE id IN (" + - utils::string::str(user_ids.begin(), user_ids.end(), ',') + ")"); - - user_ids.clear(); - - for (const auto &user : users) { - user_ids.insert(user[0].as<std::string>()); - } - } - - auto flags = event[3].as_array(); - std::pair<pqxx::array_parser::juncture, std::string> elem; - - do { - elem = flags.get_next(); - if (elem.first == pqxx::array_parser::juncture::string_value) { - if (std::stoi(elem.second) == schemas::EventFlag::MASSPING) { - auto chatters = this->helix_client.get_chatters( - channel[0].as<int>(), this->irc_client.get_bot_id()); - - for (const auto &chatter : chatters) { - user_ids.insert(chatter.login); - } - } - } - } while (elem.first != pqxx::array_parser::juncture::done); - - std::string base = "⚡ " + event[2].as<std::string>(); - std::vector<std::string> msgs = {""}; - int index = 0; - - if (!user_ids.empty()) { - base.append(" · "); - } - - for (const auto &user_id : user_ids) { - const std::string ¤t_msg = msgs.at(index); - std::string x = "@" + user_id; - - if (base.length() + current_msg.length() + 1 + x.length() >= 500) { - index += 1; - } - - if (index > msgs.size() - 1) { - msgs.push_back(x); - } else { - msgs[index] = current_msg + " " + x; - } - } - - for (const auto &msg : msgs) { - this->irc_client.say(channel[1].as<std::string>(), base + msg); - } - } - - work.commit(); - conn.close(); - } - void StreamListenerClient::update_channel_ids() { - pqxx::connection conn(GET_DATABASE_CONNECTION_URL(this->configuration)); - pqxx::work work(conn); - - pqxx::result ids = - work.exec("SELECT target_alias_id FROM events WHERE event_type < 99"); - - for (const auto &row : ids) { - int id = row[0].as<int>(); - - if (std::any_of(this->ids.begin(), this->ids.end(), - [&](const auto &x) { return x == id; })) { - continue; - } - - log::info("TwitchStreamListener", - "Listening stream events for ID " + std::to_string(id)); - - this->ids.push_back(id); - } - - work.commit(); - conn.close(); - } -} diff --git a/src/stream.hpp b/src/stream.hpp deleted file mode 100644 index 73313ed..0000000 --- a/src/stream.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include <set> -#include <vector> - -#include "api/twitch/helix_client.hpp" -#include "api/twitch/schemas/stream.hpp" -#include "config.hpp" -#include "irc/client.hpp" -#include "schemas/stream.hpp" - -namespace bot::stream { - class StreamListenerClient { - public: - StreamListenerClient(const api::twitch::HelixClient &helix_client, - irc::Client &irc_client, - const Configuration &configuration) - : helix_client(helix_client), - irc_client(irc_client), - configuration(configuration){}; - ~StreamListenerClient() = default; - - void run(); - void listen_channel(const int &id); - void unlisten_channel(const int &id); - - private: - void check(); - void handler(const schemas::EventType &type, - const api::twitch::schemas::Stream &stream); - void update_channel_ids(); - - const api::twitch::HelixClient &helix_client; - irc::Client &irc_client; - const Configuration &configuration; - - std::vector<int> ids; - - std::set<int> online_ids; - }; -} diff --git a/src/timer.cpp b/src/timer.cpp deleted file mode 100644 index 055dde0..0000000 --- a/src/timer.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "timer.hpp" - -#include <chrono> -#include <pqxx/pqxx> -#include <string> -#include <thread> - -#include "config.hpp" -#include "irc/client.hpp" -#include "utils/chrono.hpp" - -namespace bot { - void create_timer_thread(irc::Client *irc_client, - Configuration *configuration) { - while (true) { - pqxx::connection conn(GET_DATABASE_CONNECTION_URL_POINTER(configuration)); - pqxx::work *work = new pqxx::work(conn); - - pqxx::result timers = work->exec( - "SELECT id, interval_sec, message, channel_id, last_executed_at FROM " - "timers"); - - for (const auto &timer : timers) { - int id = timer[0].as<int>(); - int interval_sec = timer[1].as<int>(); - std::string message = timer[2].as<std::string>(); - int channel_id = timer[3].as<int>(); - - // it could be done in sql query - std::chrono::system_clock::time_point last_executed_at = - utils::chrono::string_to_time_point(timer[4].as<std::string>()); - auto now = std::chrono::system_clock::now(); - auto now_time_it = std::chrono::system_clock::to_time_t(now); - auto now_tm = std::gmtime(&now_time_it); - now = std::chrono::system_clock::from_time_t(std::mktime(now_tm)); - - auto difference = std::chrono::duration_cast<std::chrono::seconds>( - now - last_executed_at); - - if (difference.count() > interval_sec) { - pqxx::result channels = work->exec( - "SELECT alias_name, opted_out_at FROM channels WHERE id = " + - std::to_string(channel_id)); - - if (!channels.empty() && channels[0][1].is_null()) { - std::string alias_name = channels[0][0].as<std::string>(); - - irc_client->say(alias_name, message); - } - - work->exec( - "UPDATE timers SET last_executed_at = timezone('utc', now()) " - "WHERE " - "id = " + - std::to_string(id)); - - work->commit(); - - delete work; - work = new pqxx::work(conn); - } - } - - delete work; - conn.close(); - - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - } -} diff --git a/src/timer.hpp b/src/timer.hpp deleted file mode 100644 index 40a52ee..0000000 --- a/src/timer.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "config.hpp" -#include "irc/client.hpp" - -namespace bot { - void create_timer_thread(irc::Client *irc_client, - Configuration *configuration); -} diff --git a/src/utils/chrono.cpp b/src/utils/chrono.cpp deleted file mode 100644 index 7a7f2c9..0000000 --- a/src/utils/chrono.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "chrono.hpp" - -#include <chrono> -#include <cmath> -#include <ctime> -#include <iomanip> -#include <sstream> -#include <string> - -namespace bot::utils::chrono { - std::string format_timestamp(int seconds) { - int d = round(seconds / (60 * 60 * 24)); - int h = round(seconds / (60 * 60) % 24); - int m = round(seconds % (60 * 60) / 60); - int s = round(seconds % 60); - - // Only seconds: - if (d == 0 && h == 0 && m == 0) { - return std::to_string(s) + "s"; - } - // Minutes and seconds: - else if (d == 0 && h == 0) { - return std::to_string(m) + "m" + std::to_string(s) + "s"; - } - // Hours and minutes: - else if (d == 0) { - return std::to_string(h) + "h" + std::to_string(m) + "m"; - } - // Days and hours: - else { - return std::to_string(d) + "d" + std::to_string(h) + "h"; - } - } - - std::chrono::system_clock::time_point string_to_time_point( - const std::string &value, const std::string &format) { - std::tm tm = {}; - std::stringstream ss(value); - - ss >> std::get_time(&tm, format.c_str()); - - if (ss.fail()) { - throw std::invalid_argument("Invalid time format"); - } - - return std::chrono::system_clock::from_time_t(std::mktime(&tm)); - } -} diff --git a/src/utils/chrono.hpp b/src/utils/chrono.hpp deleted file mode 100644 index 7e85e70..0000000 --- a/src/utils/chrono.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include <chrono> -#include <string> - -namespace bot::utils::chrono { - std::string format_timestamp(int seconds); - std::chrono::system_clock::time_point string_to_time_point( - const std::string &value, - const std::string &format = "%Y-%m-%d %H:%M:%S"); -} diff --git a/src/utils/string.cpp b/src/utils/string.cpp deleted file mode 100644 index 71c06bf..0000000 --- a/src/utils/string.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "string.hpp" - -#include <sstream> -#include <string> -#include <vector> - -namespace bot { - namespace utils { - namespace string { - std::vector<std::string> split_text(const std::string &text, - char delimiter) { - std::vector<std::string> parts; - - std::istringstream iss(text); - std::string part; - - while (std::getline(iss, part, delimiter)) { - parts.push_back(part); - } - - return parts; - } - - std::string join_vector(const std::vector<std::string> &vec, - char delimiter) { - if (vec.empty()) { - return ""; - } - - std::string str; - - for (auto i = vec.begin(); i != vec.end() - 1; i++) { - str += *i + delimiter; - } - - str += vec[vec.size() - 1]; - - return str; - } - - std::string join_vector(const std::vector<std::string> &vec) { - std::string str; - - for (const auto &e : vec) { - str += e; - } - - return str; - } - - bool string_contains_sql_injection(const std::string &input) { - std::string forbidden_strings[] = {";", "--", "'", "\"", - "/*", "*/", "xp_", "exec", - "sp_", "insert", "select", "delete"}; - - for (const auto &str : forbidden_strings) { - if (input.find(str) != std::string::npos) { - return true; - } - } - - return false; - } - } - } -} diff --git a/src/utils/string.hpp b/src/utils/string.hpp deleted file mode 100644 index c8385ad..0000000 --- a/src/utils/string.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include <sstream> -#include <string> -#include <vector> - -namespace bot { - namespace utils { - namespace string { - std::vector<std::string> split_text(const std::string &text, - char delimiter); - std::string join_vector(const std::vector<std::string> &vec, - char delimiter); - std::string join_vector(const std::vector<std::string> &vec); - - template <typename T> - std::string str(T begin, T end, char delimiter) { - std::stringstream ss; - bool first = true; - - for (; begin != end; begin++) { - if (!first) ss << delimiter; - ss << *begin; - first = false; - } - return ss.str(); - } - - bool string_contains_sql_injection(const std::string &input); - } - } -} |
