diff options
| author | ilotterytea <iltsu@alright.party> | 2024-05-18 14:48:12 +0500 |
|---|---|---|
| committer | ilotterytea <iltsu@alright.party> | 2024-05-18 14:48:12 +0500 |
| commit | d1793df1eda463b10107d41785ad1d7f055ed476 (patch) | |
| tree | fd3e41c3b4a05924748ae4b762e1ae55a0bc815c /bot/src/modules | |
| parent | d7a2de17e9b7931f68b5b4079b1c36866a19d343 (diff) | |
upd: moved the bot part to a relative subfolder
Diffstat (limited to 'bot/src/modules')
| -rw-r--r-- | bot/src/modules/custom_command.hpp | 96 | ||||
| -rw-r--r-- | bot/src/modules/event.hpp | 145 | ||||
| -rw-r--r-- | bot/src/modules/help.hpp | 31 | ||||
| -rw-r--r-- | bot/src/modules/join.hpp | 91 | ||||
| -rw-r--r-- | bot/src/modules/massping.hpp | 62 | ||||
| -rw-r--r-- | bot/src/modules/notify.hpp | 131 | ||||
| -rw-r--r-- | bot/src/modules/ping.hpp | 59 | ||||
| -rw-r--r-- | bot/src/modules/timer.hpp | 112 |
8 files changed, 727 insertions, 0 deletions
diff --git a/bot/src/modules/custom_command.hpp b/bot/src/modules/custom_command.hpp new file mode 100644 index 0000000..50b3692 --- /dev/null +++ b/bot/src/modules/custom_command.hpp @@ -0,0 +1,96 @@ +#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/bot/src/modules/event.hpp b/bot/src/modules/event.hpp new file mode 100644 index 0000000..4242f07 --- /dev/null +++ b/bot/src/modules/event.hpp @@ -0,0 +1,145 @@ +#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/bot/src/modules/help.hpp b/bot/src/modules/help.hpp new file mode 100644 index 0000000..13af228 --- /dev/null +++ b/bot/src/modules/help.hpp @@ -0,0 +1,31 @@ +#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/bot/src/modules/join.hpp b/bot/src/modules/join.hpp new file mode 100644 index 0000000..16e8b4a --- /dev/null +++ b/bot/src/modules/join.hpp @@ -0,0 +1,91 @@ +#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/bot/src/modules/massping.hpp b/bot/src/modules/massping.hpp new file mode 100644 index 0000000..2957e34 --- /dev/null +++ b/bot/src/modules/massping.hpp @@ -0,0 +1,62 @@ +#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/bot/src/modules/notify.hpp b/bot/src/modules/notify.hpp new file mode 100644 index 0000000..3587e73 --- /dev/null +++ b/bot/src/modules/notify.hpp @@ -0,0 +1,131 @@ +#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/bot/src/modules/ping.hpp b/bot/src/modules/ping.hpp new file mode 100644 index 0000000..836917d --- /dev/null +++ b/bot/src/modules/ping.hpp @@ -0,0 +1,59 @@ +#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/bot/src/modules/timer.hpp b/bot/src/modules/timer.hpp new file mode 100644 index 0000000..36c3982 --- /dev/null +++ b/bot/src/modules/timer.hpp @@ -0,0 +1,112 @@ +#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); + } + }; + } +} |
