diff options
| author | ilotterytea <iltsu@alright.party> | 2025-04-06 17:28:47 +0400 |
|---|---|---|
| committer | ilotterytea <iltsu@alright.party> | 2025-04-06 17:28:47 +0400 |
| commit | 2a49844a95593ac98e919c18651320e62f276fa7 (patch) | |
| tree | 01b7e2ebb1dc7a9ac92e7c3105edfd098271f29a /bot | |
| parent | a1a36cf4d4999b5ce89dce95364c9fd839b54b5d (diff) | |
feat: implementing lua coding
Diffstat (limited to 'bot')
| -rw-r--r-- | bot/CMakeLists.txt | 21 | ||||
| -rw-r--r-- | bot/src/commands/command.cpp | 30 | ||||
| -rw-r--r-- | bot/src/commands/command.hpp | 5 | ||||
| -rw-r--r-- | bot/src/commands/lua.cpp | 68 | ||||
| -rw-r--r-- | bot/src/commands/lua.hpp | 45 | ||||
| -rw-r--r-- | bot/src/commands/request.cpp | 28 | ||||
| -rw-r--r-- | bot/src/commands/request.hpp | 5 | ||||
| -rw-r--r-- | bot/src/main.cpp | 6 | ||||
| -rw-r--r-- | bot/src/schemas/channel.cpp | 60 | ||||
| -rw-r--r-- | bot/src/schemas/channel.hpp | 7 | ||||
| -rw-r--r-- | bot/src/schemas/user.cpp | 42 | ||||
| -rw-r--r-- | bot/src/schemas/user.hpp | 5 |
12 files changed, 321 insertions, 1 deletions
diff --git a/bot/CMakeLists.txt b/bot/CMakeLists.txt index 1806c03..a01cc7f 100644 --- a/bot/CMakeLists.txt +++ b/bot/CMakeLists.txt @@ -4,6 +4,7 @@ include(FetchContent) # Creating symbolic links create_symlink_if_exists("${CMAKE_SOURCE_DIR}/localization" "${CMAKE_CURRENT_BINARY_DIR}/localization") create_symlink_if_exists("${CMAKE_SOURCE_DIR}/luamods" "${CMAKE_CURRENT_BINARY_DIR}/luamods") + if (EXISTS "${CMAKE_SOURCE_DIR}/.env") create_symlink_if_exists("${CMAKE_SOURCE_DIR}/.env" "${CMAKE_CURRENT_BINARY_DIR}/.env") endif() @@ -53,6 +54,7 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(pqxx) +# websockets FetchContent_Declare( ixwebsocket GIT_REPOSITORY https://github.com/machinezone/IXWebSocket @@ -60,5 +62,22 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(ixwebsocket) -target_link_libraries(Bot PRIVATE ixwebsocket::ixwebsocket pqxx nlohmann_json::nlohmann_json cpr::cpr) +set(SOL2_LUA_VERSION "5.4.8" CACHE STRING "The version of Lua used") +set(SOL2_BUILD_LUA FALSE CACHE BOOL "Always build Lua, do not search for it in the system") + +FetchContent_Declare( + sol + GIT_REPOSITORY https://github.com/ThePhD/sol2.git + GIT_TAG v3.5.0 +) +FetchContent_MakeAvailable(sol) + +target_link_libraries(Bot PRIVATE + ixwebsocket::ixwebsocket + pqxx + nlohmann_json::nlohmann_json + cpr::cpr + lua + sol2::sol2 +) 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 <algorithm> #include <chrono> #include <ctime> +#include <fstream> #include <memory> #include <optional> #include <pqxx/pqxx> +#include <sol/state.hpp> +#include <sol/types.hpp> +#include <stdexcept> #include <string> #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<mod::Settings>()); this->add_command(std::make_unique<mod::User>()); this->add_command(std::make_unique<mod::MinecraftServerCheck>()); + + this->luaState = std::make_shared<sol::state>(); + 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<lua::LuaCommand>(this->luaState, content)); + } } void CommandLoader::add_command(std::unique_ptr<Command> 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 <memory> #include <optional> +#include <sol/sol.hpp> +#include <sol/state.hpp> #include <string> #include <vector> @@ -41,6 +43,7 @@ namespace bot { ~CommandLoader() = default; void add_command(std::unique_ptr<Command> cmd); + void load_lua_directory(const std::string &folder_path); std::optional<Response> run(const InstanceBundle &bundle, const Request &msg) const; @@ -50,6 +53,8 @@ namespace bot { private: std::vector<std::unique_ptr<Command>> commands; + + std::shared_ptr<sol::state> 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 <sol/table.hpp> +#include <string> + +#include "bundle.hpp" +#include "commands/request.hpp" +#include "commands/response.hpp" +#include "schemas/user.hpp" + +namespace bot::command::lua { + LuaCommand::LuaCommand(std::shared_ptr<sol::state> 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<std::string>()) { + this->subcommands.push_back(value.as<std::string>()); + } + } + + 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<std::string>()) { + return {response.as<std::string>()}; + } else if (response.is<sol::table>()) { + sol::table tbl = response.as<sol::table>(); + std::vector<std::string> items; + + for (auto &kv : tbl) { + sol::object value = kv.second; + if (value.is<std::string>()) { + items.push_back(value.as<std::string>()); + } + } + + 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 <sol/sol.hpp> +#include <sol/state.hpp> +#include <sol/table.hpp> +#include <sol/types.hpp> +#include <string> +#include <vector> + +#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<sol::state> 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<std::string> get_subcommand_ids() const override { + return this->subcommands; + } + + private: + std::string name; + int delay; + schemas::PermissionLevel level; + std::vector<std::string> subcommands; + + sol::function handle; + + std::shared_ptr<sol::state> 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 <sol/types.hpp> + +namespace bot::command { + sol::table Request::as_lua_table(std::shared_ptr<sol::state> 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 <memory> #include <optional> #include <pqxx/pqxx> +#include <sol/state.hpp> +#include <sol/table.hpp> #include <string> #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<sol::state> 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 <memory> #include <optional> #include <pqxx/pqxx> +#include <sol/state.hpp> #include <string> #include <thread> #include <vector> @@ -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 <optional> + namespace bot::schemas { std::optional<ChannelFeature> string_to_channel_feature( const std::string &value) { @@ -11,4 +13,62 @@ namespace bot::schemas { return std::nullopt; } } + + std::optional<std::string> 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<sol::state> 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<long>(std::chrono::duration_cast<std::chrono::seconds>( + this->joined_at.time_since_epoch()) + .count()); + if (this->opted_out_at.has_value()) { + o["opted_out_at"] = + static_cast<long>(std::chrono::duration_cast<std::chrono::seconds>( + 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<sol::state> 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<std::string> 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 <chrono> #include <optional> #include <pqxx/pqxx> +#include <sol/sol.hpp> #include <string> #include <vector> @@ -40,6 +41,8 @@ namespace bot::schemas { return this->opted_out_at; } + sol::table as_lua_table(std::shared_ptr<sol::state> 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<ChannelFeature> string_to_channel_feature( const std::string &value); + std::optional<std::string> 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<sol::state> 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 <chrono> +#include <sol/types.hpp> + +namespace bot::schemas { + sol::table User::as_lua_table(std::shared_ptr<sol::state> 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<long>(std::chrono::duration_cast<std::chrono::seconds>( + this->joined_at.time_since_epoch()) + .count()); + if (this->opted_out_at.has_value()) { + o["opted_out_at"] = + static_cast<long>(std::chrono::duration_cast<std::chrono::seconds>( + 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<sol::state> 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 <chrono> #include <optional> #include <pqxx/pqxx> +#include <sol/sol.hpp> #include <string> #include "../utils/chrono.hpp" @@ -40,6 +41,8 @@ namespace bot::schemas { return this->opted_out_at; } + sol::table as_lua_table(std::shared_ptr<sol::state> 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<sol::state> luaState) const; + private: int id, user_id, channel_id; PermissionLevel level; |
