#include "snote_thread.hpp" #include "irc_parse_thread.hpp" #include "connection.hpp" #include #include #include #include #include namespace { struct SnotePattern { SnotePattern(SnoteTag tag, char const* expression, unsigned flags = 0) : tag{tag} , expression{expression} , regex{expression, std::regex_constants::ECMAScript | std::regex_constants::optimize} { } SnoteTag tag; char const* expression; std::regex regex; }; SnotePattern const patterns[] = { {SnoteTag::ClientConnecting, R"(^Client connecting: ([^ ]+) \(([^@ ]+)@([^) ]+)\) \[(.*)\] \{([^ ]*)\} <([^ ]*)> \[(.*)\]$)"}, {SnoteTag::ClientExiting, R"(^Client exiting: ([^ ]+) \(([^@ ]+)@([^) ]+)\) \[(.*)\] \[(.*)\]$)"}, {SnoteTag::RejectingKlined, R"(^Rejecting K-Lined user ([^ ]+)\[([^@]+)@([^\]]+)\] \[([^\] ]+)\] \((.*)\)$)"}, {SnoteTag::NickChange, R"(^Nick change: From ([^ ]+) to ([^ ]+) \[([^@]+)@([^ ]+)\]$)"}, {SnoteTag::CreateChannel, R"(^([^ ]+) is creating new channel ([^ ]+)$)"}, {SnoteTag::TemporaryKlineExpired, R"(^Temporary K-line for \[([^ ]+)\] expired$)"}, {SnoteTag::DisconnectingKlined, R"(^Disconnecting K-Lined user ([^ ]+)\[([^@]+)@([^ ]+)\] \((.*)\)$)"}, }; auto setup_database() -> hs_database_t* { auto const n = std::size(patterns); std::vector expressions; std::vector flags(n, 0); std::vector ids; expressions.reserve(std::size(patterns)); ids.reserve(std::size(patterns)); for (std::size_t i = 0; i < n; i++) { expressions.push_back(patterns[i].expression); flags.push_back(0); ids.push_back(i); } hs_database_t* db; hs_compile_error *error; hs_platform_info_t *platform = nullptr; // target current platform switch (hs_compile_multi(expressions.data(), flags.data(), ids.data(), expressions.size(), HS_MODE_BLOCK, platform, &db, &error)) { case HS_COMPILER_ERROR: { std::string msg = error->message; hs_free_compile_error(error); throw std::runtime_error{std::move(msg)}; } case HS_SUCCESS: break; default: abort(); } return db; } } // namespace auto SnoteThread::start(Connection& connection) -> std::shared_ptr { auto thread = std::make_shared(); thread->db_.reset(setup_database()); hs_scratch_t* scratch = nullptr; if (HS_SUCCESS != hs_alloc_scratch(thread->db_.get(), &scratch)) { abort(); } thread->scratch_.reset(scratch); static char const* const prefix = "*** Notice -- "; connection.add_listener([&connection, thread](IrcMsgEvent& event) { auto& args = event.irc.args; if (IrcCommand::NOTICE == event.command && "*" == args[0] && args[1].starts_with(prefix)) { event.handled_ = true; auto message = args[1].substr(strlen(prefix)); unsigned int match_id = -1; auto const scan_result = hs_scan(thread->db_.get(), message.data(), message.size(), 0, thread->scratch_.get(), [](unsigned int id, unsigned long long from, unsigned long long to, unsigned int flags, void *context) -> int { int* const match_id = static_cast(context); *match_id = id; return 1; // stop scanning } , &match_id); if (scan_result != HS_SUCCESS && scan_result != HS_SCAN_TERMINATED) { abort(); } if (match_id == -1) { std::cout << "Unknown snote: " << message << std::endl; } else { auto& pattern = patterns[match_id]; std::match_results results; if (not std::regex_match(message.begin(), message.end(), results, pattern.regex)) { // something went wrong - hyperscan disagrees with std::regex abort(); } std::vector parts; for (auto const sub : results) { parts.push_back(std::string_view{sub.first, sub.second}); } connection.make_event(pattern.tag, std::move(parts)); } } }); return thread; }