From 2a49844a95593ac98e919c18651320e62f276fa7 Mon Sep 17 00:00:00 2001 From: ilotterytea Date: Sun, 6 Apr 2025 17:28:47 +0400 Subject: feat: implementing lua coding --- bot/src/commands/command.cpp | 30 +++++++++++++++++++ bot/src/commands/command.hpp | 5 ++++ bot/src/commands/lua.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++++ bot/src/commands/lua.hpp | 45 +++++++++++++++++++++++++++++ bot/src/commands/request.cpp | 28 ++++++++++++++++++ bot/src/commands/request.hpp | 5 ++++ bot/src/main.cpp | 6 ++++ bot/src/schemas/channel.cpp | 60 ++++++++++++++++++++++++++++++++++++++ bot/src/schemas/channel.hpp | 7 +++++ bot/src/schemas/user.cpp | 42 +++++++++++++++++++++++++++ bot/src/schemas/user.hpp | 5 ++++ 11 files changed, 301 insertions(+) create mode 100644 bot/src/commands/lua.cpp create mode 100644 bot/src/commands/lua.hpp create mode 100644 bot/src/commands/request.cpp create mode 100644 bot/src/schemas/user.cpp (limited to 'bot/src') diff --git a/bot/src/commands/command.cpp b/bot/src/commands/command.cpp index bd33c68..97dd88b 100644 --- a/bot/src/commands/command.cpp +++ b/bot/src/commands/command.cpp @@ -3,9 +3,13 @@ #include #include #include +#include #include #include #include +#include +#include +#include #include #include "../bundle.hpp" @@ -23,6 +27,8 @@ #include "../modules/timer.hpp" #include "../modules/user.hpp" #include "../utils/chrono.hpp" +#include "commands/lua.hpp" +#include "logger.hpp" #include "request.hpp" #include "response.hpp" @@ -42,6 +48,30 @@ namespace bot { this->add_command(std::make_unique()); this->add_command(std::make_unique()); this->add_command(std::make_unique()); + + this->luaState = std::make_shared(); + this->luaState->open_libraries(sol::lib::base, sol::lib::string); + } + + void CommandLoader::load_lua_directory(const std::string &folder_path) { + for (const auto &entry : + std::filesystem::directory_iterator(folder_path)) { + std::ifstream ifs(entry.path()); + if (!ifs.is_open()) { + throw new std::runtime_error("Failed to open the Lua file at " + + entry.path().string()); + } + std::string content, line; + + while (std::getline(ifs, line)) { + content += line + '\n'; + } + + ifs.close(); + + this->add_command( + std::make_unique(this->luaState, content)); + } } void CommandLoader::add_command(std::unique_ptr command) { diff --git a/bot/src/commands/command.hpp b/bot/src/commands/command.hpp index 793446e..c2398ec 100644 --- a/bot/src/commands/command.hpp +++ b/bot/src/commands/command.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -41,6 +43,7 @@ namespace bot { ~CommandLoader() = default; void add_command(std::unique_ptr cmd); + void load_lua_directory(const std::string &folder_path); std::optional run(const InstanceBundle &bundle, const Request &msg) const; @@ -50,6 +53,8 @@ namespace bot { private: std::vector> commands; + + std::shared_ptr luaState; }; } } diff --git a/bot/src/commands/lua.cpp b/bot/src/commands/lua.cpp new file mode 100644 index 0000000..cd7bc84 --- /dev/null +++ b/bot/src/commands/lua.cpp @@ -0,0 +1,68 @@ +#include "commands/lua.hpp" + +#include +#include + +#include "bundle.hpp" +#include "commands/request.hpp" +#include "commands/response.hpp" +#include "schemas/user.hpp" + +namespace bot::command::lua { + LuaCommand::LuaCommand(std::shared_ptr luaState, + const std::string &script) { + this->luaState = luaState; + + sol::table data = luaState->script(script); + this->name = data["name"]; + this->delay = data["delay_sec"]; + + sol::table subcommands = data["subcommands"]; + for (auto &k : subcommands) { + sol::object value = k.second; + if (value.is()) { + this->subcommands.push_back(value.as()); + } + } + + std::string rights_text = data["minimal_rights"]; + if (rights_text == "suspended") { + this->level = schemas::PermissionLevel::SUSPENDED; + } else if (rights_text == "user") { + this->level = schemas::PermissionLevel::USER; + } else if (rights_text == "vip") { + this->level = schemas::PermissionLevel::VIP; + } else if (rights_text == "moderator") { + this->level = schemas::PermissionLevel::MODERATOR; + } else if (rights_text == "broadcaster") { + this->level = schemas::PermissionLevel::BROADCASTER; + } else { + this->level = schemas::PermissionLevel::USER; + } + + this->handle = data["run"]; + } + + Response LuaCommand::run(const InstanceBundle &bundle, + const Request &request) const { + sol::object response = this->handle(request.as_lua_table(this->luaState)); + + if (response.is()) { + return {response.as()}; + } else if (response.is()) { + sol::table tbl = response.as(); + std::vector items; + + for (auto &kv : tbl) { + sol::object value = kv.second; + if (value.is()) { + items.push_back(value.as()); + } + } + + return items; + } + + return {}; + } +} \ No newline at end of file diff --git a/bot/src/commands/lua.hpp b/bot/src/commands/lua.hpp new file mode 100644 index 0000000..11e98d3 --- /dev/null +++ b/bot/src/commands/lua.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "commands/command.hpp" +#include "commands/response.hpp" +#include "schemas/user.hpp" + +void print_lua_object_type(const sol::object &obj); + +namespace bot::command::lua { + class LuaCommand : public Command { + public: + LuaCommand(std::shared_ptr luaState, + const std::string &content); + ~LuaCommand() = default; + + Response run(const InstanceBundle &bundle, + const Request &request) const override; + + std::string get_name() const override { return this->name; } + int get_delay_seconds() const override { return this->delay; } + schemas::PermissionLevel get_permission_level() const override { + return this->level; + } + std::vector get_subcommand_ids() const override { + return this->subcommands; + } + + private: + std::string name; + int delay; + schemas::PermissionLevel level; + std::vector subcommands; + + sol::function handle; + + std::shared_ptr luaState; + }; +} \ No newline at end of file diff --git a/bot/src/commands/request.cpp b/bot/src/commands/request.cpp new file mode 100644 index 0000000..7acc107 --- /dev/null +++ b/bot/src/commands/request.cpp @@ -0,0 +1,28 @@ +#include "commands/request.hpp" + +#include + +namespace bot::command { + sol::table Request::as_lua_table(std::shared_ptr luaState) const { + sol::table o = luaState->create_table(); + + o["command_id"] = this->command_id; + if (this->subcommand_id.has_value()) { + o["subcommand_id"] = this->subcommand_id.value(); + } else { + o["subcommand_id"] = sol::lua_nil; + } + if (this->message.has_value()) { + o["message"] = this->message.value(); + } else { + o["message"] = sol::lua_nil; + } + + o["sender"] = this->user.as_lua_table(luaState); + o["channel"] = this->channel.as_lua_table(luaState); + o["channel_preference"] = this->channel_preferences.as_lua_table(luaState); + o["rights"] = this->user_rights.as_lua_table(luaState); + + return o; + } +} \ No newline at end of file diff --git a/bot/src/commands/request.hpp b/bot/src/commands/request.hpp index e2685f1..b6ed534 100644 --- a/bot/src/commands/request.hpp +++ b/bot/src/commands/request.hpp @@ -1,7 +1,10 @@ #pragma once +#include #include #include +#include +#include #include #include "../irc/message.hpp" @@ -21,5 +24,7 @@ namespace bot::command { schemas::UserRights user_rights; pqxx::connection &conn; + + sol::table as_lua_table(std::shared_ptr luaState) const; }; } diff --git a/bot/src/main.cpp b/bot/src/main.cpp index a95e4a8..212d190 100644 --- a/bot/src/main.cpp +++ b/bot/src/main.cpp @@ -1,5 +1,7 @@ +#include #include #include +#include #include #include #include @@ -7,6 +9,8 @@ #include "api/twitch/helix_client.hpp" #include "bundle.hpp" #include "commands/command.hpp" +#include "commands/lua.hpp" +#include "commands/response.hpp" #include "config.hpp" #include "github.hpp" #include "handlers.hpp" @@ -49,6 +53,8 @@ int main(int argc, char *argv[]) { bot::irc::Client client(cfg.twitch_credentials.client_id, cfg.twitch_credentials.token); bot::command::CommandLoader command_loader; + command_loader.load_lua_directory("luamods"); + bot::loc::Localization localization("localization"); bot::api::twitch::HelixClient helix_client(cfg.twitch_credentials.token, cfg.twitch_credentials.client_id); diff --git a/bot/src/schemas/channel.cpp b/bot/src/schemas/channel.cpp index ae4ea53..540b7c0 100644 --- a/bot/src/schemas/channel.cpp +++ b/bot/src/schemas/channel.cpp @@ -1,5 +1,7 @@ #include "channel.hpp" +#include + namespace bot::schemas { std::optional string_to_channel_feature( const std::string &value) { @@ -11,4 +13,62 @@ namespace bot::schemas { return std::nullopt; } } + + std::optional channelfeature_to_string( + const ChannelFeature &value) { + switch (value) { + case MARKOV_RESPONSES: + return "markov_responses"; + case RANDOM_MARKOV_RESPONSES: + return "random_markov_responses"; + default: + std::nullopt; + } + } + + sol::table Channel::as_lua_table(std::shared_ptr luaState) const { + sol::table o = luaState->create_table(); + + o["id"] = this->id; + o["alias_id"] = this->alias_id; + o["alias_name"] = this->alias_name; + + o["joined_at"] = + static_cast(std::chrono::duration_cast( + this->joined_at.time_since_epoch()) + .count()); + if (this->opted_out_at.has_value()) { + o["opted_out_at"] = + static_cast(std::chrono::duration_cast( + this->opted_out_at->time_since_epoch()) + .count()); + } else { + o["opted_out_at"] = sol::lua_nil; + } + + return o; + } + + sol::table ChannelPreferences::as_lua_table( + std::shared_ptr luaState) const { + sol::table o = luaState->create_table(); + + o["id"] = this->channel_id; // TODO: remove it later too. + o["channel_id"] = this->channel_id; + o["prefix"] = this->prefix; + o["language"] = this->locale; + + sol::table f = luaState->create_table(); + + for (const ChannelFeature &feature : this->features) { + std::optional ff = channelfeature_to_string(feature); + if (ff.has_value()) { + f.add(ff.value()); + } + } + + o["features"] = f; + + return o; + } } \ No newline at end of file diff --git a/bot/src/schemas/channel.hpp b/bot/src/schemas/channel.hpp index 08ed8ad..d2f13eb 100644 --- a/bot/src/schemas/channel.hpp +++ b/bot/src/schemas/channel.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,8 @@ namespace bot::schemas { return this->opted_out_at; } + sol::table as_lua_table(std::shared_ptr luaState) const; + private: int id, alias_id; std::string alias_name; @@ -50,6 +53,8 @@ namespace bot::schemas { enum ChannelFeature { MARKOV_RESPONSES, RANDOM_MARKOV_RESPONSES }; std::optional string_to_channel_feature( const std::string &value); + std::optional channelfeature_to_string( + const ChannelFeature &value); class ChannelPreferences { public: @@ -90,6 +95,8 @@ namespace bot::schemas { return this->features; } + sol::table as_lua_table(std::shared_ptr luaState) const; + private: int channel_id; std::string prefix, locale; diff --git a/bot/src/schemas/user.cpp b/bot/src/schemas/user.cpp new file mode 100644 index 0000000..bbf5711 --- /dev/null +++ b/bot/src/schemas/user.cpp @@ -0,0 +1,42 @@ +#include "schemas/user.hpp" + +#include +#include + +namespace bot::schemas { + sol::table User::as_lua_table(std::shared_ptr luaState) const { + sol::table o = luaState->create_table(); + + o["id"] = this->id; + o["alias_id"] = this->alias_id; + o["alias_name"] = this->alias_name; + + o["joined_at"] = + static_cast(std::chrono::duration_cast( + this->joined_at.time_since_epoch()) + .count()); + if (this->opted_out_at.has_value()) { + o["opted_out_at"] = + static_cast(std::chrono::duration_cast( + this->opted_out_at->time_since_epoch()) + .count()); + } else { + o["opted_out_at"] = sol::lua_nil; + } + + return o; + } + + sol::table UserRights::as_lua_table( + std::shared_ptr luaState) const { + sol::table o = luaState->create_table(); + + o["id"] = this->id; + o["user_id"] = this->user_id; + o["channel_id"] = this->channel_id; + o["level"] = this->level; + o["is_fixed"] = false; // TODO: remove it later + + return o; + } +} \ No newline at end of file diff --git a/bot/src/schemas/user.hpp b/bot/src/schemas/user.hpp index 0bd1368..e9d7e0f 100644 --- a/bot/src/schemas/user.hpp +++ b/bot/src/schemas/user.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "../utils/chrono.hpp" @@ -40,6 +41,8 @@ namespace bot::schemas { return this->opted_out_at; } + sol::table as_lua_table(std::shared_ptr luaState) const; + private: int id, alias_id; std::string alias_name; @@ -66,6 +69,8 @@ namespace bot::schemas { const PermissionLevel &get_level() const { return this->level; } void set_level(PermissionLevel level) { this->level = level; } + sol::table as_lua_table(std::shared_ptr luaState) const; + private: int id, user_id, channel_id; PermissionLevel level; -- cgit v1.2.3