diff options
| -rw-r--r-- | bot/src/commands/command.cpp | 2 | ||||
| -rw-r--r-- | bot/src/modules/event.hpp | 145 | ||||
| -rw-r--r-- | luamods/event.lua | 376 |
3 files changed, 376 insertions, 147 deletions
diff --git a/bot/src/commands/command.cpp b/bot/src/commands/command.cpp index fa266de..55fac64 100644 --- a/bot/src/commands/command.cpp +++ b/bot/src/commands/command.cpp @@ -13,7 +13,6 @@ #include <string> #include "../bundle.hpp" -#include "../modules/event.hpp" #include "../modules/notify.hpp" #include "../modules/timer.hpp" #include "../utils/chrono.hpp" @@ -25,7 +24,6 @@ namespace bot { namespace command { CommandLoader::CommandLoader() { - this->add_command(std::make_unique<mod::Event>()); this->add_command(std::make_unique<mod::Notify>()); this->add_command(std::make_unique<mod::Timer>()); diff --git a/bot/src/modules/event.hpp b/bot/src/modules/event.hpp deleted file mode 100644 index f2d33b8..0000000 --- a/bot/src/modules/event.hpp +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -#include <string> -#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"}; - } - - command::Response 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::GITHUB) { - throw ResponseException<ResponseError::NOT_FOUND>( - request, bundle.localization, t); - } - - pqxx::work work(request.conn); - std::string query; - - if (type < schemas::GITHUB) { - 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::GITHUB) { - 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 command::Response( - 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 command::Response( - bundle.localization - .get_formatted_line(request, loc::LineId::EventOff, {t}) - .value()); - } - - throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>( - request, bundle.localization); - } - }; - } -} diff --git a/luamods/event.lua b/luamods/event.lua new file mode 100644 index 0000000..be27622 --- /dev/null +++ b/luamods/event.lua @@ -0,0 +1,376 @@ +local function parse_target(value) + local parts = str_split(value, ':') + if #parts ~= 2 then + return nil + end + + local data = { + target = parts[1], + type = str_to_event_type(parts[2]) + } + + if data.type == nil then + return data + end + + if data.type < 10 then + local users = twitch_get_users({ logins = { data.target } }) + if #users == 0 then + data.target = nil + return data + end + + data.target = users[1] + end + + return data +end + +local lines = { + english = { + ["no_subcommand"] = + "{sender.alias_name}: No subcommand provided. Use {channel.prefix}help event for more information.", + ["no_message"] = "{sender.alias_name}: No message provided.", + ["not_parseable"] = "{sender.alias_name}: This value cannot be parsed. (%s)", + ["unknown_type"] = "{sender.alias_name}: Unknown event type. (%s)", + ["user_not_found"] = "{sender.alias_name}: User not found. (%s)", + ["not_found"] = "{sender.alias_name}: Event %s not found.", + ["no_target"] = "{sender.alias_name}: Next event target must be provided.", + ["namesake"] = "{sender.alias_name}: Same event already exists.", + ["list"] = "{sender.alias_name}: %s", + ["empty_list"] = "{sender.alias_name}: There are no events in this channel.", + ["on"] = + "{sender.alias_name}: Successfully created a new event %s. Use '{channel.prefix}notify sub %s' to subscribe.", + ["off"] = "{sender.alias_name}: Successfully deleted event %s", + ["edit"] = "{sender.alias_name}: Edited a message for event %s", + ["settarget"] = "{sender.alias_name}: Changed event target from %s to %s", + ["flag_disabled"] = "{sender.alias_name}: Flag %s has been disabled for event %s", + ["flag_enabled"] = "{sender.alias_name}: Flag %s has been enabled for event %s", + ["view"] = "{sender.alias_name}: ID %s | %s | %s subs | Flags: %s | %s" + }, + russian = { + ["no_subcommand"] = + "{sender.alias_name}: Подкоманда не предоставлена. Используйте {channel.prefix}help event для большей информации.", + ["no_message"] = "{sender.alias_name}: Сообщение не предоставлено.", + ["not_parseable"] = "{sender.alias_name}: Это значение не может быть использовано. (%s)", + ["unknown_type"] = "{sender.alias_name}: Неизвестный тип события. (%s)", + ["user_not_found"] = "{sender.alias_name}: Пользователь не найден. (%s)", + ["not_found"] = "{sender.alias_name}: Событие %s не найдено.", + ["no_target"] = "{sender.alias_name}: Следующие значение события должно быть предоставлено.", + ["namesake"] = "{sender.alias_name}: Такое же событие уже существует.", + ["list"] = "{sender.alias_name}: %s", + ["empty_list"] = "{sender.alias_name}: На этом канале нету событий.", + ["on"] = + "{sender.alias_name}: Успешно создано событие %s. Используйте '{channel.prefix}notify sub %s' для подписки.", + ["off"] = "{sender.alias_name}: Успешно удалено событие %s", + ["edit"] = "{sender.alias_name}: Сообщение для события %s было отредактировано.", + ["settarget"] = "{sender.alias_name}: Цель события было изменено с %s на %s", + ["flag_disabled"] = "{sender.alias_name}: Флаг %s был отключен для события %s", + ["flag_enabled"] = "{sender.alias_name}: Флаг %s был включен для события %s", + ["view"] = "{sender.alias_name}: ID %s | %s | %s подписчиков | Флаги: %s | %s" + }, +} + +return { + name = "event", + description = [[ +> This command is for broadcaster and moderators only. + + +The `!event` command gives the ability to manage events. + + +## Event types + ++ live ++ offline ++ title ++ game ++ github (Placeholders for messages: `%0` - Commit hash, `%1` - Committer name, `%2` - Commit message) ++ custom + +## Syntax + +### Create a new event + +`!event on [name]:[type] [message...]` + ++ `[name]` - Twitch username or event name *(custom type only)*. ++ `[type]` - [Event type](#event-types). ++ `[message]` - The message that will be sent with the event. + + +> Events with types *category* and *title* use *{0}* and *{1}* placeholders +> for old and new values respectively. +> This means that the bot can show changes if you set them +> (e.g. *forsen changed the title from **{0}** to **{1}*** will replace +> with *forsen changed the game from **Just Chatting** to **PUBG***). + + +### Delete the event + +`!event off [name]:[type]` + ++ `[name]` - Twitch username or event name *(custom type only)*. ++ `[type]` - [Event type](#event-types). + +### Make the event massping everytime + +`!event setmassping [name]:[type]` + ++ `[name]` - Twitch username or event name *(custom type only)*. ++ `[type]` - [Event type](#event-types). + +### Edit the event message + +`!event edit [name]:[type] [message...]` + ++ `[name]` - Twitch username or event name *(custom type only)*. ++ `[type]` - [Event type](#event-types). ++ `[message]` - New message. + +### Set a new target for the event + +`!event settarget [name]:[type] [new_name]:[new_type]` + ++ `[name]` - Twitch username or event name *(custom type only)*. ++ `[type]` - [Event type](#event-types). ++ `[new_name]` - New Twitch username or event name *(custom type only)*. ++ `[new_type]` - [New event type](#event-types). + + +### Call the event + + +> The bot requires moderator privileges on events with the **"massping"** flag. + + +`!event call [name]:[type]` + ++ `[name]` - Twitch username or event name *(custom type only)*. ++ `[type]` - [Event type](#event-types). + +### View the event + +`!event view [name]:[type]` + ++ `[name]` - Twitch username or event name *(custom type only)*. ++ `[type]` - [Event type](#event-types). +]], + delay_sec = 1, + options = {}, + subcommands = { "on", "off", "list", "edit", "settarget", "setmassping", "call", "view" }, + minimal_rights = "moderator", + handle = function(request) + if request.subcommand_id == nil then + return l10n_custom_formatted_line_request(request, lines, "no_subcommand", {}) + end + + local scid = request.subcommand_id + + if scid == "list" then + local names = {} + + local events = db_query('SELECT name, event_type FROM events WHERE channel_id = $1', + { request.channel.id }) + local user_ids = {} + for i = 1, #events, 1 do + local e = events[i] + local t = tonumber(e.event_type) + if t < 10 then + local id = tonumber(e.name) + table.insert(names, { name = id, type = t }) + table.insert(user_ids, id) + print(id) + else + table.insert(names, { name = e.name, type = event_type_to_str(t) }) + end + end + if #user_ids > 0 then + local users = twitch_get_users({ ids = user_ids }) + for i = 1, #users, 1 do + local user = users[i] + for j = 1, #names, 1 do + if type(names[j].name) == "number" and + type(names[j].type) == "number" and + names[j].type < 10 and names[j].name == user.id + then + names[j].name = user.login + names[j].type = event_type_to_str(names[j].type) + end + end + end + end + + -- finalizing + local n = {} + for i = 1, #names, 1 do + table.insert(n, names[i].name .. ':' .. names[i].type) + end + + local line_id = "list" + if #n == 0 then + line_id = "empty_list" + end + + return l10n_custom_formatted_line_request(request, lines, line_id, { table.concat(n, ', ') }) + end + + if request.message == nil then + return l10n_custom_formatted_line_request(request, lines, "no_message", {}) + end + + local parts = str_split(request.message, ' ') + + local data_original = parts[1] + local data = parse_target(data_original) + table.remove(parts, 1) + local data_name = nil + + if data == nil then + return l10n_custom_formatted_line_request(request, lines, "not_parseable", { data_original }) + elseif data.type == nil then + return l10n_custom_formatted_line_request(request, lines, "unknown_type", { data_original }) + elseif type(data.target) == "nil" then + return l10n_custom_formatted_line_request(request, lines, "user_not_found", { data_original }) + elseif type(data.target) == "string" then + data_name = data.target + elseif type(data.target) == "table" then + data_name = data.target.id + end + + local events = db_query( + 'SELECT id, message, array_to_json(flags) as flags FROM events WHERE name = $1 AND event_type = $2', + { data_name, data.type }) + + if scid == "on" then + if #events > 0 then + return l10n_custom_formatted_line_request(request, lines, "namesake", { data_original }) + end + + local message = table.concat(parts, ' ') + if #message == 0 then + return l10n_custom_formatted_line_request(request, lines, "no_message", {}) + end + + db_execute('INSERT INTO events(channel_id, name, event_type, message) VALUES ($1, $2, $3, $4)', + { request.channel.id, data_name, data.type, message }) + + return l10n_custom_formatted_line_request(request, lines, "on", { data_original, data_original }) + end + + if #events == 0 then + return l10n_custom_formatted_line_request(request, lines, "not_found", { data_original }) + end + + local event = events[1] + + if scid == "off" then + db_execute('DELETE FROM events WHERE id = $1', { event.id }) + return l10n_custom_formatted_line_request(request, lines, "off", { data_original }) + elseif scid == "edit" then + local message = table.concat(parts, ' ') + if #message == 0 then + return l10n_custom_formatted_line_request(request, lines, "no_message", {}) + end + + db_execute('UPDATE events SET message = $1 WHERE id = $2', { message, event.id }) + + return l10n_custom_formatted_line_request(request, lines, "edit", { data_original }) + elseif scid == "settarget" then + if #parts == 0 then + return l10n_custom_formatted_line_request(request, lines, "no_target", {}) + end + + local new_data_original = parts[1] + local data = parse_target(new_data_original) + local data_name = nil + if data == nil then + return l10n_custom_formatted_line_request(request, lines, "not_parseable", { new_data_original }) + elseif data.type == nil then + return l10n_custom_formatted_line_request(request, lines, "unknown_type", { new_data_original }) + elseif type(data.target) == "nil" then + return l10n_custom_formatted_line_request(request, lines, "user_not_found", { new_data_original }) + elseif type(data.target) == "string" then + data_name = data.target + elseif type(data.target) == "table" then + data_name = data.target.id + end + + local existing_events = db_query( + 'SELECT id FROM events WHERE channel_id = $1 AND name = $2 AND event_type = $3', + { request.channel.id, data_name, data.type }) + + if #existing_events > 0 then + return l10n_custom_formatted_line_request(request, lines, "namesake", { new_data_original }) + end + + db_execute('UPDATE events SET name = $1, event_type = $2 WHERE id = $3', + { data_name, data.type, event.id }) + + return l10n_custom_formatted_line_request(request, lines, "settarget", { data_original, new_data_original }) + elseif scid == "setmassping" then + local flags = json_parse(event.flags) + + local line_id = "" + local query = "" + if array_contains_int(flags, 0) then + line_id = "flag_disabled" + query = "UPDATE events SET flags = array_remove(flags, $1) WHERE id = $2" + else + line_id = "flag_enabled" + query = "UPDATE events SET flags = array_append(flags, $1) WHERE id = $2" + end + + db_execute(query, { 0, event.id }) + + return l10n_custom_formatted_line_request(request, lines, line_id, { event_flag_to_str(0), data_original }) + elseif scid == "call" then + local flags = json_parse(event.flags) + local names = {} + + if array_contains_int(flags, 0) then + local chatters = twitch_get_chatters() + for i = 1, #chatters, 1 do + table.insert(names, chatters[i].login) + end + else + local subscriptions = db_query([[ +SELECT u.alias_name FROM users u +INNER JOIN event_subscriptions es ON es.user_id = u.id +INNER JOIN events e ON e.id = es.event_id +WHERE e.id = $1 +]], { event.id }) + + for i = 1, #subscriptions, 1 do + table.insert(names, subscriptions[i].alias_name) + end + end + + local base = '⚡️ ' .. event.message + if #names > 0 then + base = base .. ' · ' + end + + return str_make_parts(base, names, "@", " ", 500) + elseif scid == "view" then + local subscription_count = db_query([[ +SELECT COUNT(es.id) AS count FROM event_subscriptions es +INNER JOIN events e ON e.id = es.event_id +WHERE e.id = $1 +]], { event.id }) + + local f = json_parse(event.flags) + local flags = {} + for i = 1, #f, 1 do + table.insert(flags, event_type_to_str(f[i])) + end + if #flags == 0 then + table.insert(flags, '-') + end + + return l10n_custom_formatted_line_request(request, lines, "view", + { event.id, data_original, subscription_count[1].count, table.concat(flags, ', '), event.message }) + end + end +} |
