commit 53453872f24c6ba75a02cc58f3c36786cc1f32af Author: Green Sky Date: Tue Dec 12 17:46:04 2023 +0100 mvp irc client for solanaceae with plugin non exhausitve list of missing stuff: - notices (only channel partly implemented) - invites - initiating private chat - channel membership status and other flags diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56f48bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +.vs/ +*.o +*.swp +~* +*~ +.idea/ +cmake-build-debug/ +cmake-build-debugandtest/ +cmake-build-release/ +*.stackdump +*.coredump +compile_commands.json +/build* +/result* +.clangd +.cache + +.DS_Store +.AppleDouble +.LSOverride + +CMakeLists.txt.user* +CMakeCache.txt + +*.tox +imgui.ini diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3e4c4d1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,75 @@ +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +# cmake setup begin +project(solanaceae_ircclient) + +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(SOLANACEAE_IRCCLIENT_STANDALONE ON) + # why the f do i need this >:( + set(NOT_SOLANACEAE_IRCCLIENT_STANDALONE OFF) +else() + set(SOLANACEAE_IRCCLIENT_STANDALONE OFF) + set(NOT_SOLANACEAE_IRCCLIENT_STANDALONE ON) +endif() +message("II SOLANACEAE_IRCCLIENT_STANDALONE " ${SOLANACEAE_IRCCLIENT_STANDALONE}) + +option(SOLANACEAE_IRCCLIENT_BUILD_PLUGINS "Build the ircclient plugins" ${SOLANACEAE_IRCCLIENT_STANDALONE}) + +if (SOLANACEAE_IRCCLIENT_STANDALONE) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + + # defaulting to debug mode, if not specified + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") + endif() + + # setup my vim ycm :D + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + + # more paths + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +endif() + +# external libs +add_subdirectory(./external EXCLUDE_FROM_ALL) # before increasing warn levels, sad :( + +if (SOLANACEAE_IRCCLIENT_STANDALONE) + set(CMAKE_CXX_EXTENSIONS OFF) + + # bump up warning levels appropriately for clang, gcc & msvc + if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + add_compile_options( + -Wall -Wextra # Reasonable and standard + -Wpedantic # Warn if non-standard C++ is used + -Wunused # Warn on anything being unused + #-Wconversion # Warn on type conversions that may lose data + #-Wsign-conversion # Warn on sign conversions + -Wshadow # Warn if a variable declaration shadows one from a parent context + ) + + if (NOT WIN32) + #link_libraries(-fsanitize=address) + #link_libraries(-fsanitize=address,undefined) + #link_libraries(-fsanitize-address-use-after-scope) + #link_libraries(-fsanitize=undefined) + endif() + elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") + if (CMAKE_CXX_FLAGS MATCHES "/W[0-4]") + string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() + endif() + +endif() + +# cmake setup end + +add_subdirectory(./src) + +if (SOLANACEAE_IRCCLIENT_BUILD_PLUGINS) + add_subdirectory(./plugins) +endif() + diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt new file mode 100644 index 0000000..20b730b --- /dev/null +++ b/external/CMakeLists.txt @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 3.24 FATAL_ERROR) + +include(FetchContent) + +# TODO: move entt dep into solanaceae_contact +if (NOT TARGET EnTT::EnTT) + FetchContent_Declare(EnTT + GIT_REPOSITORY https://github.com/skypjack/entt.git + GIT_TAG v3.12.2 + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable(EnTT) +endif() + +if (NOT TARGET solanaceae_util) + FetchContent_Declare(solanaceae_util + GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_util.git + GIT_TAG master + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable(solanaceae_util) +endif() + +if (NOT TARGET solanaceae_contact) + FetchContent_Declare(solanaceae_contact + GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_contact.git + GIT_TAG master + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable(solanaceae_contact) +endif() + +if (NOT TARGET solanaceae_message3) + FetchContent_Declare(solanaceae_message3 + GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_message3.git + GIT_TAG master + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable(solanaceae_message3) +endif() + +if (NOT TARGET solanaceae_plugin) + FetchContent_Declare(solanaceae_plugin + GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_plugin.git + GIT_TAG master + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable(solanaceae_plugin) +endif() + +if (NOT TARGET libircclient) + #add_subdirectory(./libircclient) + FetchContent_Declare(libircclient + GIT_REPOSITORY https://github.com/Green-Sky/libircclient.git + GIT_TAG master + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable(libircclient) +endif() + +if ( + NOT TARGET libsodium AND + NOT TARGET unofficial-sodium::sodium AND + NOT TARGET unofficial-sodium::sodium_config_public AND + NOT TARGET sodium +) + # for find sodium + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + + find_package(unofficial-sodium CONFIG QUIET) + find_package(sodium QUIET) + if(unofficial-sodium_FOUND) # vcpkg + if(TARGET unofficial-sodium::sodium) + #TODO: alias can not target another alias + #target_link_libraries(toxcore unofficial-sodium::sodium) + #add_library(libsodium ALIAS unofficial-sodium::sodium) + + add_library(libsodium INTERFACE) + target_link_libraries(libsodium INTERFACE unofficial-sodium::sodium) + endif() + if(TARGET unofficial-sodium::sodium_config_public) + #TODO: alias can not target another alias + #target_link_libraries(toxcore unofficial-sodium::sodium_config_public) + #add_library(libsodium ALIAS unofficial-sodium::sodium_config_public) + + add_library(libsodium INTERFACE) + target_link_libraries(libsodium INTERFACE unofficial-sodium::sodium_config_public) + endif() + elseif(sodium_FOUND) + #add_library(libsodium ALIAS sodium) + add_library(libsodium INTERFACE) + target_link_libraries(libsodium INTERFACE sodium) + else() + message(SEND_ERROR "missing libsodium") + endif() +endif() + diff --git a/external/cmake/Findsodium.cmake b/external/cmake/Findsodium.cmake new file mode 100644 index 0000000..a210c00 --- /dev/null +++ b/external/cmake/Findsodium.cmake @@ -0,0 +1,297 @@ +# Written in 2016 by Henrik Steffen Gaßmann +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# +# http://creativecommons.org/publicdomain/zero/1.0/ +# +######################################################################## +# Tries to find the local libsodium installation. +# +# On Windows the sodium_DIR environment variable is used as a default +# hint which can be overridden by setting the corresponding cmake variable. +# +# Once done the following variables will be defined: +# +# sodium_FOUND +# sodium_INCLUDE_DIR +# sodium_LIBRARY_DEBUG +# sodium_LIBRARY_RELEASE +# +# +# Furthermore an imported "sodium" target is created. +# + +if (CMAKE_C_COMPILER_ID STREQUAL "GNU" + OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(_GCC_COMPATIBLE 1) +endif() + +# static library option +if (NOT DEFINED sodium_USE_STATIC_LIBS) + option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF) +endif() +if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST)) + unset(sodium_LIBRARY CACHE) + unset(sodium_LIBRARY_DEBUG CACHE) + unset(sodium_LIBRARY_RELEASE CACHE) + unset(sodium_DLL_DEBUG CACHE) + unset(sodium_DLL_RELEASE CACHE) + set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable") +endif() + + +######################################################################## +# UNIX +if (UNIX) + # import pkg-config + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(sodium_PKG QUIET libsodium) + endif() + + if(sodium_USE_STATIC_LIBS) + foreach(_libname ${sodium_PKG_STATIC_LIBRARIES}) + if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a + list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a") + endif() + endforeach() + list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES) + + # if pkgconfig for libsodium doesn't provide + # static lib info, then override PKG_STATIC here.. + if (NOT sodium_PKG_STATIC_FOUND) + set(sodium_PKG_STATIC_LIBRARIES libsodium.a) + endif() + + set(XPREFIX sodium_PKG_STATIC) + else() + if (NOT sodium_PKG_FOUND) + set(sodium_PKG_LIBRARIES sodium) + endif() + + set(XPREFIX sodium_PKG) + endif() + + find_path(sodium_INCLUDE_DIR sodium.h + HINTS ${${XPREFIX}_INCLUDE_DIRS} + ) + find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} + HINTS ${${XPREFIX}_LIBRARY_DIRS} + ) + find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} + HINTS ${${XPREFIX}_LIBRARY_DIRS} + ) + + +######################################################################## +# Windows +elseif (WIN32) + set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory") + mark_as_advanced(sodium_DIR) + + find_path(sodium_INCLUDE_DIR sodium.h + HINTS ${sodium_DIR} + PATH_SUFFIXES include + ) + + if (MSVC) + # detect target architecture + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" [=[ + #if defined _M_IX86 + #error ARCH_VALUE x86_32 + #elif defined _M_X64 + #error ARCH_VALUE x86_64 + #endif + #error ARCH_VALUE unknown + ]=]) + try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" + OUTPUT_VARIABLE _COMPILATION_LOG + ) + string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}") + + # construct library path + if (_TARGET_ARCH STREQUAL "x86_32") + string(APPEND _PLATFORM_PATH "Win32") + elseif(_TARGET_ARCH STREQUAL "x86_64") + string(APPEND _PLATFORM_PATH "x64") + else() + message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.") + endif() + string(APPEND _PLATFORM_PATH "/$$CONFIG$$") + + if (MSVC_VERSION LESS 1900) + math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60") + else() + math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50") + endif() + string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}") + + if (sodium_USE_STATIC_LIBS) + string(APPEND _PLATFORM_PATH "/static") + else() + string(APPEND _PLATFORM_PATH "/dynamic") + endif() + + string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}") + string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}") + + find_library(sodium_LIBRARY_DEBUG libsodium.lib + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} + ) + find_library(sodium_LIBRARY_RELEASE libsodium.lib + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} + ) + if (NOT sodium_USE_STATIC_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") + find_library(sodium_DLL_DEBUG libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} + ) + find_library(sodium_DLL_RELEASE libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} + ) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK}) + endif() + + elseif(_GCC_COMPATIBLE) + if (sodium_USE_STATIC_LIBS) + find_library(sodium_LIBRARY_DEBUG libsodium.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + find_library(sodium_LIBRARY_RELEASE libsodium.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + else() + find_library(sodium_LIBRARY_DEBUG libsodium.dll.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + find_library(sodium_LIBRARY_RELEASE libsodium.dll.a + HINTS ${sodium_DIR} + PATH_SUFFIXES lib + ) + + file(GLOB _DLL + LIST_DIRECTORIES false + RELATIVE "${sodium_DIR}/bin" + "${sodium_DIR}/bin/libsodium*.dll" + ) + find_library(sodium_DLL_DEBUG ${_DLL} libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES bin + ) + find_library(sodium_DLL_RELEASE ${_DLL} libsodium + HINTS ${sodium_DIR} + PATH_SUFFIXES bin + ) + endif() + else() + message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") + endif() + + +######################################################################## +# unsupported +else() + message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") +endif() + + +######################################################################## +# common stuff + +# extract sodium version +if (sodium_INCLUDE_DIR) + set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h") + if (EXISTS _VERSION_HEADER) + file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT) + string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1" + sodium_VERSION "${_VERSION_HEADER_CONTENT}") + set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE) + endif() +endif() + +# communicate results +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + sodium # The name must be either uppercase or match the filename case. + REQUIRED_VARS + sodium_LIBRARY_RELEASE + sodium_LIBRARY_DEBUG + sodium_INCLUDE_DIR + VERSION_VAR + sodium_VERSION +) + +if(Sodium_FOUND) + set(sodium_LIBRARIES + optimized ${sodium_LIBRARY_RELEASE} debug ${sodium_LIBRARY_DEBUG}) +endif() + +# mark file paths as advanced +mark_as_advanced(sodium_INCLUDE_DIR) +mark_as_advanced(sodium_LIBRARY_DEBUG) +mark_as_advanced(sodium_LIBRARY_RELEASE) +if (WIN32) + mark_as_advanced(sodium_DLL_DEBUG) + mark_as_advanced(sodium_DLL_RELEASE) +endif() + +# create imported target +if(sodium_USE_STATIC_LIBS) + set(_LIB_TYPE STATIC) +else() + set(_LIB_TYPE SHARED) +endif() + +if(NOT TARGET sodium) + add_library(sodium ${_LIB_TYPE} IMPORTED) +endif() + +set_target_properties(sodium PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" +) + +if (sodium_USE_STATIC_LIBS) + set_target_properties(sodium PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC" + IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" + ) +else() + if (UNIX) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" + IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" + ) + elseif (WIN32) + set_target_properties(sodium PROPERTIES + IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}" + IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}" + ) + if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND")) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}" + ) + endif() + if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND")) + set_target_properties(sodium PROPERTIES + IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}" + IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}" + IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}" + ) + endif() + endif() +endif() diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 0000000..9dd4304 --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR) + +add_library(plugin_ircclient SHARED + ./plugin_ircclient.cpp +) + +target_link_libraries(plugin_ircclient PUBLIC + solanaceae_plugin + solanaceae_ircclient_contacts + solanaceae_ircclient_messages +) + diff --git a/plugins/plugin_ircclient.cpp b/plugins/plugin_ircclient.cpp new file mode 100644 index 0000000..478d9be --- /dev/null +++ b/plugins/plugin_ircclient.cpp @@ -0,0 +1,92 @@ +#include + +#include +#include +#include + +#include +#include + +#define RESOLVE_INSTANCE(x) static_cast(solana_api->resolveInstance(#x)) +#define PROVIDE_INSTANCE(x, p, v) solana_api->provideInstance(#x, p, static_cast(v)) + +static std::unique_ptr g_ircc = nullptr; +static std::unique_ptr g_ircccm = nullptr; +static std::unique_ptr g_irccmm = nullptr; + +extern "C" { + +SOLANA_PLUGIN_EXPORT const char* solana_plugin_get_name(void) { + return "IRCClient"; +} + +SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_get_version(void) { + return SOLANA_PLUGIN_VERSION; +} + +SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api) { + std::cout << "PLUGIN IRCC START()\n"; + + if (solana_api == nullptr) { + return 1; + } + + Contact3Registry* cr; + RegistryMessageModel* rmm = nullptr; + ConfigModelI* conf = nullptr; + + { // make sure required types are loaded + cr = RESOLVE_INSTANCE(Contact3Registry); + rmm = RESOLVE_INSTANCE(RegistryMessageModel); + conf = RESOLVE_INSTANCE(ConfigModelI); + + if (cr == nullptr) { + std::cerr << "PLUGIN IRCC missing Contact3Registry\n"; + return 2; + } + + if (rmm == nullptr) { + std::cerr << "PLUGIN IRCC missing RegistryMessageModel\n"; + return 2; + } + + if (conf == nullptr) { + std::cerr << "PLUGIN IRCC missing ConfigModelI\n"; + return 2; + } + } + + // static store, could be anywhere tho + // construct with fetched dependencies + g_ircc = std::make_unique(*conf); + + // register types + PROVIDE_INSTANCE(IRCClient1, "IRCClient", g_ircc.get()); + + g_ircccm = std::make_unique(*cr, *conf, *g_ircc); + + // register types + PROVIDE_INSTANCE(IRCClientContactModel, "IRCClient", g_ircccm.get()); + + g_irccmm = std::make_unique(*rmm, *cr, *conf, *g_ircc, *g_ircccm); + + // register types + PROVIDE_INSTANCE(IRCClientMessageManager, "IRCClient", g_irccmm.get()); + + return 0; +} + +SOLANA_PLUGIN_EXPORT void solana_plugin_stop(void) { + std::cout << "PLUGIN IRCC STOP()\n"; + + g_ircc.reset(); +} + +SOLANA_PLUGIN_EXPORT void solana_plugin_tick(float delta) { + (void)delta; + //std::cout << "PLUGIN IRCC TICK()\n"; + g_ircc->iterate(); +} + +} // extern C + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..fdde0d2 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR) + +project(solanaceae) + +add_library(solanaceae_ircclient + ./solanaceae/ircclient/ircclient.hpp + ./solanaceae/ircclient/ircclient.cpp +) + +target_include_directories(solanaceae_ircclient PUBLIC .) +target_compile_features(solanaceae_ircclient PRIVATE cxx_std_20) +target_compile_features(solanaceae_ircclient INTERFACE cxx_std_17) +target_link_libraries(solanaceae_ircclient PUBLIC + solanaceae_util + libircclient + libsodium +) + +######################################## + +add_library(solanaceae_ircclient_contacts + ./solanaceae/ircclient_contacts/components.hpp + ./solanaceae/ircclient_contacts/components_id.inl + + ./solanaceae/ircclient_contacts/ircclient_contact_model.hpp + ./solanaceae/ircclient_contacts/ircclient_contact_model.cpp +) + +target_include_directories(solanaceae_ircclient_contacts PUBLIC .) +target_compile_features(solanaceae_ircclient_contacts PRIVATE cxx_std_20) +target_compile_features(solanaceae_ircclient_contacts INTERFACE cxx_std_17) +target_link_libraries(solanaceae_ircclient_contacts PUBLIC + solanaceae_util + solanaceae_contact + solanaceae_ircclient +) + +######################################## + +add_library(solanaceae_ircclient_messages + ./solanaceae/ircclient_messages/ircclient_message_manager.hpp + ./solanaceae/ircclient_messages/ircclient_message_manager.cpp +) + +target_include_directories(solanaceae_ircclient_messages PUBLIC .) +target_compile_features(solanaceae_ircclient_messages PRIVATE cxx_std_20) +target_compile_features(solanaceae_ircclient_messages INTERFACE cxx_std_17) +target_link_libraries(solanaceae_ircclient_messages PUBLIC + solanaceae_ircclient_contacts + solanaceae_message3 +) + +######################################## + +add_executable(test2 + test2.cpp +) + +target_link_libraries(test2 PUBLIC + solanaceae_ircclient + solanaceae_ircclient_contacts + solanaceae_ircclient_messages +) diff --git a/src/solanaceae/ircclient/ircclient.cpp b/src/solanaceae/ircclient/ircclient.cpp new file mode 100644 index 0000000..38ad394 --- /dev/null +++ b/src/solanaceae/ircclient/ircclient.cpp @@ -0,0 +1,181 @@ +#include "./ircclient.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void IRCClient1::on_event_numeric(irc_session_t* session, unsigned int event, const char* origin, const char** params, unsigned int count) { + std::vector params_view; + for (size_t i = 0; i < count; i++) { + params_view.push_back(params[i]); + } + +#if 0 + std::cout << "IRC: event_numeric " << event << " " << origin << "\n"; + + for (const auto it : params_view) { + std::cout << " " << it << "\n"; + } +#endif + + auto ircc = static_cast(irc_get_ctx(session)); + ircc->dispatch(IRCClient_Event::NUMERIC, IRCClient::Events::Numeric{event, origin, params_view}); +} + +IRCClient1::IRCClient1( + ConfigModelI& conf +) : _conf(conf) { + + static irc_callbacks_t cb{}; + + cb.event_numeric = on_event_numeric; + +#define IRC_CB_G(x0, x1, x2) cb.x0 = on_event_generic_new; + + //cb.event_connect = on_event_generic_new; + + IRC_CB_G(event_connect, CONNECT, Connect); + IRC_CB_G(event_nick, NICK, Nick); + IRC_CB_G(event_quit, QUIT, Quit); + IRC_CB_G(event_join, JOIN, Join); + IRC_CB_G(event_part, PART, Part); + IRC_CB_G(event_mode, MODE, Mode); + IRC_CB_G(event_umode, UMODE, UMode); + IRC_CB_G(event_topic, TOPIC, Topic); + IRC_CB_G(event_kick, KICK, Kick); + + IRC_CB_G(event_channel, CHANNEL, Channel); + + IRC_CB_G(event_privmsg, PRIVMSG, PrivMSG); + IRC_CB_G(event_notice, NOTICE, Notice); + IRC_CB_G(event_channel_notice, CHANNELNOTICE, ChannelNotice); + IRC_CB_G(event_invite, INVITE, Invite); + + IRC_CB_G(event_ctcp_req, CTCP_REQ, CTCP_Req); + IRC_CB_G(event_ctcp_rep, CTCP_REP, CTCP_Rep); + IRC_CB_G(event_ctcp_action, CTCP_ACTION, CTCP_Action); + + IRC_CB_G(event_unknown, UNKNOWN, Unknown); + +#undef IRC_CB_G + + // TODO: dcc + //irc_event_dcc_chat_t event_dcc_chat_req; + //irc_event_dcc_send_t event_dcc_send_req; + + + _irc_session = irc_create_session(&cb); + irc_set_ctx(_irc_session, this); + + irc_option_set(_irc_session, LIBIRC_OPTION_DEBUG); + irc_option_set(_irc_session, LIBIRC_OPTION_STRIPNICKS); + irc_option_set(_irc_session, LIBIRC_OPTION_SSL_NO_VERIFY); // why + + + if (!_conf.has_string("IRCClient", "server")) { + std::cerr << "IRCC error: no irc server in config!!\n"; + throw std::runtime_error("missing server in config"); + } + + // if server is prefixed with '#', its ssl + std::string server = _conf.get_string("IRCClient", "server").value(); + _server_name = server; // TODO: find a better solution + int64_t port = _conf.get_int("IRCClient", "port").value_or(6660); + // TODO: password + + std::string nick; + if (_conf.has_string("IRCClient", "nick")) { + nick = _conf.get_string("IRCClient", "nick").value(); + } else { + nick = "solanaceae_guest_" + std::to_string(std::random_device{}() % 10'000); + } + + std::string username; + if (_conf.has_string("IRCClient", "username")) { + username = _conf.get_string("IRCClient", "username").value(); + } else { + username = nick + "_"; + } + + std::string realname; + if (_conf.has_string("IRCClient", "realname")) { + realname = _conf.get_string("IRCClient", "realname").value(); + } else { + realname = username + "_"; + } + + if (irc_connect(_irc_session, server.c_str(), port, nullptr, nick.c_str(), username.c_str(), realname.c_str()) != 0) { + std::cerr << "error failed to connect: (" << irc_errno(_irc_session) << ") " << irc_strerror(irc_errno(_irc_session)) << "\n"; + throw std::runtime_error("failed to connect to irc"); + } +} + +IRCClient1::~IRCClient1(void) { + irc_destroy_session(_irc_session); +} + +// tmp +void IRCClient1::run(void) { + if (irc_run(_irc_session) != 0) { + std::cerr << "error failed to run: " << irc_strerror(irc_errno(_irc_session)) << "\n"; + } +} + +void IRCClient1::iterate(void) { + //if ( session->state != LIBIRC_STATE_CONNECTING ) + //{ + //session->lasterror = LIBIRC_ERR_STATE; + //return 1; + //} + + if (!irc_is_connected(_irc_session)) { + return; + } + + struct timeval tv; + fd_set in_set, out_set; + int maxfd = 0; + + //tv.tv_usec = 20000; // 20ms + tv.tv_usec = 1000; // 1ms + tv.tv_sec = 0; + + // Init sets + FD_ZERO (&in_set); + FD_ZERO (&out_set); + + irc_add_select_descriptors(_irc_session, &in_set, &out_set, &maxfd); + + if (select(maxfd + 1, &in_set, &out_set, 0, &tv) < 0) + { +#if 0 + if (socket_error() == EINTR) { + //continue; + return; + } +#endif + + //session->lasterror = LIBIRC_ERR_TERMINATED; + //return 1; + return; + } + + if (irc_process_select_descriptors(_irc_session, &in_set, &out_set)) { + //return 1; + } +} + +irc_session_t* IRCClient1::getSession(void) { + return _irc_session; +} + +const std::string_view IRCClient1::getServerName(void) const { + return _server_name; +} diff --git a/src/solanaceae/ircclient/ircclient.hpp b/src/solanaceae/ircclient/ircclient.hpp new file mode 100644 index 0000000..ec5e007 --- /dev/null +++ b/src/solanaceae/ircclient/ircclient.hpp @@ -0,0 +1,225 @@ +#pragma once + +#include +#include + +#include // tmp + +// fwd +struct irc_session_s; +using irc_session_t = irc_session_s; +extern "C" void* irc_get_ctx(irc_session_t* session); + +namespace IRCClient::Events { + + // TODO: proper param seperation + + struct Numeric { + unsigned int event; + std::string_view origin; + std::vector params; + }; + + struct Connect { + std::string_view origin; + std::vector params; + }; + + struct Nick { + std::string_view origin; + std::vector params; + }; + + struct Quit { + std::string_view origin; + std::vector params; + }; + + struct Join { + std::string_view origin; + std::vector params; + }; + + struct Part { + std::string_view origin; + std::vector params; + }; + + struct Mode { + std::string_view origin; + std::vector params; + }; + + struct UMode { + std::string_view origin; + std::vector params; + }; + + struct Topic { + std::string_view origin; + std::vector params; + }; + + struct Kick { + std::string_view origin; + std::vector params; + }; + + struct Channel { + std::string_view origin; + std::vector params; + }; + + struct PrivMSG { + std::string_view origin; + std::vector params; + }; + + struct Notice { + std::string_view origin; + std::vector params; + }; + + struct ChannelNotice { + std::string_view origin; + std::vector params; + }; + + struct Invite { + std::string_view origin; + std::vector params; + }; + + struct CTCP_Req { + std::string_view origin; + std::vector params; + }; + + struct CTCP_Rep { + std::string_view origin; + std::vector params; + }; + + struct CTCP_Action { + std::string_view origin; + std::vector params; + }; + + struct Unknown { + std::string_view origin; + std::vector params; + }; + +} // Events + +enum class IRCClient_Event : uint32_t { + NUMERIC, + CONNECT, + NICK, + QUIT, + JOIN, + PART, + MODE, + UMODE, + TOPIC, + KICK, + CHANNEL, + PRIVMSG, + NOTICE, + CHANNELNOTICE, + INVITE, + + CTCP_REQ, + CTCP_REP, + CTCP_ACTION, + + UNKNOWN, + + MAX +}; + +struct IRCClientEventI { + using enumType = IRCClient_Event; + + virtual ~IRCClientEventI(void) {} + + virtual bool onEvent(const IRCClient::Events::Numeric&) { return false; } + virtual bool onEvent(const IRCClient::Events::Connect&) { return false; } + virtual bool onEvent(const IRCClient::Events::Nick&) { return false; } + virtual bool onEvent(const IRCClient::Events::Quit&) { return false; } + virtual bool onEvent(const IRCClient::Events::Join&) { return false; } + virtual bool onEvent(const IRCClient::Events::Part&) { return false; } + virtual bool onEvent(const IRCClient::Events::Mode&) { return false; } + virtual bool onEvent(const IRCClient::Events::UMode&) { return false; } + virtual bool onEvent(const IRCClient::Events::Topic&) { return false; } + virtual bool onEvent(const IRCClient::Events::Kick&) { return false; } + virtual bool onEvent(const IRCClient::Events::Channel&) { return false; } + virtual bool onEvent(const IRCClient::Events::PrivMSG&) { return false; } + virtual bool onEvent(const IRCClient::Events::Notice&) { return false; } + virtual bool onEvent(const IRCClient::Events::ChannelNotice&) { return false; } + virtual bool onEvent(const IRCClient::Events::Invite&) { return false; } + virtual bool onEvent(const IRCClient::Events::CTCP_Req&) { return false; } + virtual bool onEvent(const IRCClient::Events::CTCP_Rep&) { return false; } + virtual bool onEvent(const IRCClient::Events::CTCP_Action&) { return false; } + virtual bool onEvent(const IRCClient::Events::Unknown&) { return false; } +}; + +using IRCClientEventProviderI = EventProviderI; + +// one network per instance only +class IRCClient1 : public IRCClientEventProviderI { + ConfigModelI& _conf; + + irc_session_t* _irc_session = nullptr; + + std::string _server_name; // name of the irc network this iirc is connected to + + public: + IRCClient1( + ConfigModelI& conf + ); + + ~IRCClient1(void); + + // tmp + void run(void); + void iterate(void); + + // raw access + irc_session_t* getSession(void); + + const std::string_view getServerName(void) const; + + // join + void join(std::string_view channel); + + private: // callbacks for libircclient + static void on_event_numeric(irc_session_t* session, unsigned int event, const char* origin, const char** params, unsigned int count); + + template + static void on_event_generic_new(irc_session_t* session, const char* event, const char* origin, const char** params, unsigned int count) { + std::vector params_view; + for (size_t i = 0; i < count; i++) { + params_view.push_back(params[i]); + } + + std::cout << "IRC: event " << event << " " << origin << "\n"; + +#if 0 + if (std::string_view{event} == "ACTION") { + std::cout << " -action is " << typeid(EventType).name() << "\n"; + std::cout << " -enum is " << (int)event_type_enum << "\n"; + } +#endif + + for (const auto it : params_view) { + std::cout << " " << it << "\n"; + } + + auto* ircc = static_cast(irc_get_ctx(session)); + assert(ircc != nullptr); + + ircc->dispatch(event_type_enum, EventType{origin, params_view}); + } +}; + diff --git a/src/solanaceae/ircclient_contacts/components.hpp b/src/solanaceae/ircclient_contacts/components.hpp new file mode 100644 index 0000000..c4483ec --- /dev/null +++ b/src/solanaceae/ircclient_contacts/components.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace Contact::Components::IRC { + + struct ServerName { + std::string name; + }; + + struct ChannelName { + std::string name; + }; + + struct UserName { + std::string name; + }; + + // TODO: + // - membership level in channels + // - dcc stuff + // - tags for server channel user? + +} // Contact::Components::IRC + +#include "./components_id.inl" + diff --git a/src/solanaceae/ircclient_contacts/components_id.inl b/src/solanaceae/ircclient_contacts/components_id.inl new file mode 100644 index 0000000..818ada6 --- /dev/null +++ b/src/solanaceae/ircclient_contacts/components_id.inl @@ -0,0 +1,22 @@ +#pragma once + +#include "./components.hpp" + +#include + +// TODO: move more central +#define DEFINE_COMP_ID(x) \ +template<> \ +constexpr entt::id_type entt::type_hash::value() noexcept { \ + using namespace entt::literals; \ + return #x##_hs; \ +} + +// cross compiler stable ids + +DEFINE_COMP_ID(Contact::Components::IRC::ServerName) +DEFINE_COMP_ID(Contact::Components::IRC::ChannelName) +DEFINE_COMP_ID(Contact::Components::IRC::UserName) + +#undef DEFINE_COMP_ID + diff --git a/src/solanaceae/ircclient_contacts/ircclient_contact_model.cpp b/src/solanaceae/ircclient_contacts/ircclient_contact_model.cpp new file mode 100644 index 0000000..b5ca2b0 --- /dev/null +++ b/src/solanaceae/ircclient_contacts/ircclient_contact_model.cpp @@ -0,0 +1,412 @@ +#include "./ircclient_contact_model.hpp" + +#include "./components.hpp" + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + + +IRCClientContactModel::IRCClientContactModel( + Contact3Registry& cr, + ConfigModelI& conf, + IRCClient1& ircc +) : _cr(cr), _conf(conf), _ircc(ircc) { + _ircc.subscribe(this, IRCClient_Event::CONNECT); + + _ircc.subscribe(this, IRCClient_Event::NUMERIC); + + _ircc.subscribe(this, IRCClient_Event::JOIN); + _ircc.subscribe(this, IRCClient_Event::PART); + _ircc.subscribe(this, IRCClient_Event::QUIT); + + _ircc.subscribe(this, IRCClient_Event::CTCP_REQ); + + // dont create server self etc until connect event comes + + for (const auto& [channel, should_join] : _conf.entries_bool("IRCClient", "autojoin")) { + if (should_join) { + std::cout << "IRCCCM: autojoining " << channel << "\n"; + join(channel); + } + } +} + +IRCClientContactModel::~IRCClientContactModel(void) { +} + +void IRCClientContactModel::join(const std::string& channel) { + if (_connected) { + irc_cmd_join( + _ircc.getSession(), + channel.c_str(), + "" + ); + std::cout << "IRCCCM: connected joining channel...\n"; + } else { + _join_queue.push(channel); + std::cout << "IRCCCM: not connected yet, queued join...\n"; + } +} + +std::vector IRCClientContactModel::getHash(std::string_view value) { + assert(!value.empty()); + + std::vector hash(crypto_hash_sha256_bytes(), 0x00); + crypto_hash_sha256(hash.data(), reinterpret_cast(value.data()), value.size()); + return hash; +} + +std::vector IRCClientContactModel::getIDHash(std::string_view name) { + assert(!_server_hash.empty()); + assert(!name.empty()); + + std::vector data = _server_hash; + data.insert(data.end(), name.begin(), name.end()); + return getHash(std::string_view{reinterpret_cast(data.data()), data.size()}); +} + +Contact3Handle IRCClientContactModel::getC(std::string_view channel) { + const auto server_name = _ircc.getServerName(); + // TODO: this needs a better way + for (const auto e : _cr.view()) { + if (_cr.get(e).name == server_name && _cr.get(e).name == channel) { + return {_cr, e}; + } + } + + return {_cr, entt::null}; +} + +Contact3Handle IRCClientContactModel::getU(std::string_view nick) { + const auto server_name = _ircc.getServerName(); + // TODO: this needs a better way + for (const auto e : _cr.view()) { + if (_cr.get(e).name == server_name && _cr.get(e).name == nick) { + return {_cr, e}; + } + } + + return {_cr, entt::null}; +} + +Contact3Handle IRCClientContactModel::getCU(std::string_view name) { + if (name.empty()) { + return {_cr, entt::null}; + } + + static constexpr std::string_view channel_prefixes{ + // rfc 1459 1.3 + "&" // local + "#" // regular + + // rfc 2812 1.3 + "+" + "!" + }; + + if (channel_prefixes.find(name.front()) != std::string_view::npos) { + return getC(name); + } else { + return getU(name); + } +} + +bool IRCClientContactModel::onEvent(const IRCClient::Events::Connect& e) { + _server_hash = getHash(_ircc.getServerName()); + _connected = true; + + { // server + if (!_cr.valid(_server)) { + _server = _cr.create(); + } + + _cr.emplace_or_replace(_server, this); + _cr.emplace_or_replace(_server, std::string{_ircc.getServerName()}); // really? + _cr.emplace_or_replace(_server, std::string{_ircc.getServerName()}); // TODO: add special string? + _cr.emplace_or_replace(_server, _server_hash); + + // does this make sense ? + _cr.emplace_or_replace(_server, Contact::Components::ConnectionState::State::direct); + + _cr.emplace_or_replace(_server); + } + + { // self + if (!_cr.valid(_self)) { + _self = _cr.create(); + } + _cr.emplace_or_replace(_self, this); + _cr.emplace_or_replace(_self); + _cr.emplace_or_replace(_self, std::string{_ircc.getServerName()}); // really? + if (!e.params.empty()) { + _cr.emplace_or_replace(_self, std::string{e.params.front()}); + _cr.emplace_or_replace(_self, std::string{e.params.front()}); + // make id hash(hash(ServerName)+UserName) + // or irc name format, but those might cause collisions + _cr.emplace_or_replace(_self, getIDHash(e.params.front())); + } + + _cr.emplace_or_replace(_self, Contact::Components::ConnectionState::State::cloud); + + // add self to server + _cr.emplace_or_replace(_server, _self); + } + + // join queued + while (!_join_queue.empty()) { + irc_cmd_join( + _ircc.getSession(), + _join_queue.front().c_str(), + "" + ); + _join_queue.pop(); + } + + return false; +} + +bool IRCClientContactModel::onEvent(const IRCClient::Events::Numeric& e) { + if (e.event == LIBIRC_RFC_RPL_NAMREPLY) { + // user list + // e.origin is the server + // e.params.at(0) user (self) + // e.params.at(1) = + // e.params.at(2) channel + // e.params.at(3) list of users space seperated with power prefixed + if (e.params.size() != 4) { + // error + return false; + } + + if (e.params.at(1) != "=") { + // error, unexpected + return false; + } + + const auto& channel_name = e.params.at(2); + auto channel = getC(channel_name); + if (!channel.valid()) { + std::cerr << "IRCCM error: name list for unknown channel\n"; + return false; + } + + std::string_view user_list = e.params.at(3); + + std::string_view::size_type space_pos; + do { + space_pos = user_list.find_first_of(' '); + auto user_str = user_list.substr(0, space_pos); + { // handle user + // rfc 2812 5.1 + // The '@' and '+' characters next to the channel name + // indicate whether a client is a channel operator or + // has been granted permission to speak on a moderated + // channel. + + if (user_str.empty()) { + std::cerr << "IRCCCM error: empty user\n"; + break; + } + + // https://modern.ircdocs.horse/#channel-membership-prefixes + static constexpr std::string_view membership_prefixes{ + "~" // founder + "&" // protected + "@" // operator + "%" // half operator + "+" // voice + }; + if (membership_prefixes.find(user_str.front()) != std::string_view::npos) { + switch (user_str.front()) { + // TODO: use this info + case '~': break; + case '&': break; + case '@': break; + case '%': break; + case '+': break; + } + user_str = user_str.substr(1); + } + + if (user_str.empty()) { + std::cerr << "IRCCCM error: empty user after removing membership prefix\n"; + break; + } + + //std::cout << "u: " << user_str << "\n"; + + auto user = getU(user_str); + if (!user.valid()) { + user = {_cr, _cr.create()}; + + user.emplace(this); + user.emplace(std::string{_ircc.getServerName()}); + // channel list? + // add to channel? + user.emplace(std::string{user_str}); + user.emplace(std::string{user_str}); + user.emplace(getIDHash(user_str)); + } + + if (user.entity() != _self) { + user.emplace_or_replace(Contact::Components::ConnectionState::State::cloud); + user.emplace_or_replace(_self); + } + + { // add user to channel + auto& channel_user_list = channel.get_or_emplace().subs; + if (std::find(channel_user_list.begin(), channel_user_list.end(), user) == channel_user_list.end()) { + //std::cout << "!!!!!!!! new user in channel!\n"; + channel_user_list.push_back(user); + } + } + } + + if (space_pos == std::string_view::npos) { + break; + } + + // trim user + user_list = user_list.substr(space_pos); + const auto next_non_space = user_list.find_first_not_of(' '); + if (next_non_space == std::string_view::npos) { + break; + } + user_list = user_list.substr(next_non_space); + } while (space_pos != std::string_view::npos); + } + return false; +} + +bool IRCClientContactModel::onEvent(const IRCClient::Events::Join& e) { + if (e.params.empty()) { + return false; + } + + const auto& joined_channel_name = e.params.front(); + + //std::cout << "JOIN!!!! " << e.origin << " in " << joined_channel_name << "\n"; + + auto channel = getC(e.params.front()); + if (!channel.valid()) { + channel = {_cr, _cr.create()}; + channel.emplace_or_replace(this); + channel.emplace_or_replace(std::string{_ircc.getServerName()}); + channel.emplace_or_replace(std::string{joined_channel_name}); + channel.emplace_or_replace(std::string{joined_channel_name}); + + channel.emplace_or_replace(getIDHash(joined_channel_name)); + + channel.emplace_or_replace(Contact::Components::ConnectionState::State::cloud); + + channel.emplace(); + + // add self to channel + channel.emplace_or_replace(_self); + } + + auto user = getU(e.origin); + if (!user.valid()) { + user = {_cr, _cr.create()}; + + user.emplace(this); + user.emplace(std::string{_ircc.getServerName()}); + // channel list? + // add to channel? + user.emplace(std::string{e.origin}); + user.emplace(std::string{e.origin}); + user.emplace(getIDHash(e.origin)); + } + + if (user.entity() != _self) { + user.emplace_or_replace(Contact::Components::ConnectionState::State::cloud); + user.emplace_or_replace(_self); + } + + { // add user to channel + auto& channel_user_list = channel.get_or_emplace().subs; + if (std::find(channel_user_list.begin(), channel_user_list.end(), user) == channel_user_list.end()) { + //std::cout << "!!!!!!!! new user in channel!\n"; + channel_user_list.push_back(user); + } + } + + return false; +} + +bool IRCClientContactModel::onEvent(const IRCClient::Events::Part& e) { + if (e.params.size() < 1) { + // error + return false; + } + + // e.origin // is the parting user + auto user = getU(e.origin); + if (!user.valid()) { + // ignoring unknown users, might be caused by a bug + std::cerr << "ignoring unknown users, might be caused by a bug\n"; + return false; + } + + // e.params.front() is the channel + auto channel = getC(e.params.front()); + if (!channel.valid()) { + // ignoring unknown channel, might be caused by a bug + std::cerr << "ignoring unknown channel, might be caused by a bug\n"; + return false; + } + + { // remove user from channel + auto& channel_user_list = channel.get_or_emplace().subs; + if (auto it = std::find(channel_user_list.begin(), channel_user_list.end(), user); it != channel_user_list.end()) { + //std::cout << "!!!!!!!! removing user from channel!\n"; + channel_user_list.erase(it); + } else { + //std::cout << "!!!!!!!! unknown user leaving channel!\n"; + } + } + + return false; +} + +bool IRCClientContactModel::onEvent(const IRCClient::Events::Quit& e) { + // e.origin // is the quitting user + + // e.params.front() is the quit reason + + auto user = getU(e.origin); + if (!user.valid()) { + // ignoring unknown users, might be caused by a bug + return false; + } + + if (user.entity() != _self) { + user.emplace_or_replace(Contact::Components::ConnectionState::State::disconnected); + } + + // should we remove the user from the channel? + + return false; +} + +bool IRCClientContactModel::onEvent(const IRCClient::Events::CTCP_Req& e) { + if (e.params.size() < 1) { + return false; + } + + if (e.params.front() == "VERSION") { + return false; + } + + return false; +} diff --git a/src/solanaceae/ircclient_contacts/ircclient_contact_model.hpp b/src/solanaceae/ircclient_contacts/ircclient_contact_model.hpp new file mode 100644 index 0000000..7333a4f --- /dev/null +++ b/src/solanaceae/ircclient_contacts/ircclient_contact_model.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include + +#include // tmp + +class IRCClientContactModel : public IRCClientEventI, public ContactModel3I { + Contact3Registry& _cr; + ConfigModelI& _conf; + IRCClient1& _ircc; + + // cm needs the connect event to happen + bool _connected {false}; + + std::vector _server_hash; // cached for id gen + Contact3 _server = entt::null; + Contact3 _self = entt::null; + + // used if not connected + std::queue _join_queue; + + public: + IRCClientContactModel( + Contact3Registry& cr, + ConfigModelI& conf, + IRCClient1& ircc + ); + + virtual ~IRCClientContactModel(void); + + void join(const std::string& channel); + + private: + // just the hash algo + std::vector getHash(std::string_view value); + + public: + // the actually ID is a chain containing the server+channel or server+name + // eg: hash(hash(ServerName)+ChannelName) + std::vector getIDHash(std::string_view name); + + Contact3Handle getC(std::string_view channel); + Contact3Handle getU(std::string_view nick); + // user or channel using channel prefix + Contact3Handle getCU(std::string_view name); + + private: // ircclient + bool onEvent(const IRCClient::Events::Connect& e) override; + bool onEvent(const IRCClient::Events::Numeric& e) override; + bool onEvent(const IRCClient::Events::Join& e) override; + bool onEvent(const IRCClient::Events::Part& e) override; + bool onEvent(const IRCClient::Events::Quit& e) override; + bool onEvent(const IRCClient::Events::CTCP_Req&) override; +}; diff --git a/src/solanaceae/ircclient_messages/ircclient_message_manager.cpp b/src/solanaceae/ircclient_messages/ircclient_message_manager.cpp new file mode 100644 index 0000000..067f8c1 --- /dev/null +++ b/src/solanaceae/ircclient_messages/ircclient_message_manager.cpp @@ -0,0 +1,296 @@ +#include "./ircclient_message_manager.hpp" + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +//namespace Components { + //struct ServerName { + //std::string name; + //}; +//} // Components + +IRCClientMessageManager::IRCClientMessageManager( + RegistryMessageModel& rmm, + Contact3Registry& cr, + ConfigModelI& conf, + IRCClient1& ircc, + IRCClientContactModel& ircccm +) : _rmm(rmm), _cr(cr), _conf(conf), _ircc(ircc), _ircccm(ircccm) { + _ircc.subscribe(this, IRCClient_Event::CHANNEL); + _ircc.subscribe(this, IRCClient_Event::PRIVMSG); + _ircc.subscribe(this, IRCClient_Event::NOTICE); + _ircc.subscribe(this, IRCClient_Event::CHANNELNOTICE); + _ircc.subscribe(this, IRCClient_Event::CTCP_ACTION); + + _rmm.subscribe(this, RegistryMessageModel_Event::send_text); +} + +IRCClientMessageManager::~IRCClientMessageManager(void) { +} + +bool IRCClientMessageManager::processMessage(Contact3Handle from, Contact3Handle to, std::string_view message_text, bool action) { + const uint64_t ts = Message::getTimeMS(); + + Message3Registry* reg_ptr = nullptr; + if (to.all_of()) { + reg_ptr = _rmm.get(from); + } else { + reg_ptr = _rmm.get(to); + } + if (reg_ptr == nullptr) { + std::cerr << "IRCCMM error: cant find reg\n"; + return false; + } + + // TODO: check for existence, hs or other syncing mechanics might have sent it already (or like, it arrived 2x or whatever) + auto new_msg = Message3Handle{*reg_ptr, reg_ptr->create()}; + + { // contact + // from + new_msg.emplace(from); + + // to + new_msg.emplace(to); + } + + // no message id :( + + new_msg.emplace(message_text); + + if (action) { + new_msg.emplace(); + } + + new_msg.emplace(ts); + new_msg.emplace(ts); // reactive? + + new_msg.emplace(); + + _rmm.throwEventConstruct(new_msg); + return false; +} + +bool IRCClientMessageManager::sendText(const Contact3 c, std::string_view message, bool action) { + if (!_cr.valid(c)) { + return false; + } + + if (message.empty()) { + return false; // TODO: empty messages allowed? + } + + const uint64_t ts = Message::getTimeMS(); + + if (_cr.all_of(c)) { + return false; // message to self? not with irc + } + + // test for contact irc specific components + // TODO: what about commands and server messages? + if (!_cr.any_of(c)) { + return false; + } + + std::string to_str; + if (_cr.all_of(c)) { + to_str = _cr.get(c).name; + } else { + to_str = _cr.get(c).name; + } + + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + return false; // nope + } + + if (!_cr.all_of(c)) { + std::cerr << "IRCCMM error: cant get self\n"; + return false; + } + + { // actually send + std::string tmp_message{message}; // string_view might not be nul terminated + if (action) { + if (irc_cmd_me(_ircc.getSession(), to_str.c_str(), tmp_message.c_str()) != 0) { + std::cerr << "IRCCMM error: failed to send action\n"; + + // we dont have offline messaging in irc + return false; + } + } else { + if (irc_cmd_msg(_ircc.getSession(), to_str.c_str(), tmp_message.c_str()) != 0) { + std::cerr << "IRCCMM error: failed to send message\n"; + + // we dont have offline messaging in irc + return false; + } + } + } + + const Contact3 c_self = _cr.get(c).self; + + auto new_msg = Message3Handle{*reg_ptr, reg_ptr->create()}; + + new_msg.emplace(c_self); + new_msg.emplace(c); + + new_msg.emplace(message); + + if (action) { + new_msg.emplace(); + } + + new_msg.emplace(ts); + new_msg.emplace(ts); // reactive? + new_msg.emplace(ts); + + // mark as read + new_msg.emplace(ts); // reactive? + + _rmm.throwEventConstruct(new_msg); + return true; +} + +bool IRCClientMessageManager::onEvent(const IRCClient::Events::Channel& e) { + if (e.params.size() < 2) { + std::cerr << "IRCCMM error: channel event too few params\n"; + return false; + } + + + // e.origin is sender + auto sender = _ircccm.getU(e.origin); // assuming its always a user // aka ContactFrom + if (!sender.valid()) { + std::cerr << "IRCCMM error: channel event unknown sender\n"; + return false; + } + + // e.params.at(0) is channel + auto channel = _ircccm.getC(e.params.at(0)); // aka ContactTo + if (!channel.valid()) { + std::cerr << "IRCCMM error: channel event unknown channel\n"; + return false; + } + + // e.params.at(1) is message + const auto& message_text = e.params.at(1); + + return processMessage(sender, channel, message_text, false); +} + +bool IRCClientMessageManager::onEvent(const IRCClient::Events::PrivMSG& e) { + if (e.params.size() < 2) { + std::cerr << "IRCCMM error: privmsg event too few params\n"; + return false; + } + + // e.origin is sender + auto from = _ircccm.getU(e.origin); // assuming its always a user // aka ContactFrom + if (!from.valid()) { + std::cerr << "IRCCMM error: channel event unknown sender\n"; + return false; + } + + // e.params.at(0) is receiver (us?) + auto to = _ircccm.getU(e.params.at(0)); // aka ContactTo + if (!to.valid()) { + std::cerr << "IRCCMM error: channel event unknown channel\n"; + return false; + } + + // upgrade contact to big + from.emplace_or_replace(); // could be like an invite? + + return processMessage(from, to, e.params.at(1), false); +} + +bool IRCClientMessageManager::onEvent(const IRCClient::Events::Notice& e) { + if (e.params.size() < 2) { + std::cerr << "IRCCMM error: notice event too few params\n"; + return false; + } + + // server message type 1 + // e.origin is server host (not network name) + // e.params.at(0) is '*' + // server message type 2 + // e.origin is server host (not network name) + // e.params.at(0) is user (us) + // server message type 3 + // e.origin is "Global" + // e.params.at(0) is user (us) + // user message (private) + // e.origin is sending user + // e.params.at(0) is user (us) + + // e.params.at(1) is message + + return false; +} + +bool IRCClientMessageManager::onEvent(const IRCClient::Events::ChannelNotice& e) { + if (e.params.size() < 2) { + std::cerr << "IRCCMM error: channel notice event too few params\n"; + return false; + } + + // e.origin is sending user (probably) + auto from = _ircccm.getU(e.origin); + if (!from.valid()) { + std::cerr << "IRCCMM error: channel notice event unknown sender\n"; + return false; + } + + // e.params.at(0) is channel + auto to = _ircccm.getC(e.params.at(0)); + if (!to.valid()) { + std::cerr << "IRCCMM error: unknown receiver\n"; + return false; + } + + // TODO: add notice tag + + // e.params.at(1) is message + return processMessage(from, to, e.params.at(1), false); +} + +bool IRCClientMessageManager::onEvent(const IRCClient::Events::CTCP_Action& e) { + if (e.params.size() < 2) { + std::cerr << "IRCCMM error: action event too few params\n"; + return false; + } + + // e.origin is sender + auto from = _ircccm.getU(e.origin); // assuming its always a user // aka ContactFrom + if (!from.valid()) { + std::cerr << "IRCCMM error: channel event unknown sender\n"; + return false; + } + + // e.params.at(0) is receiver (self if pm or channel if channel) + auto receiver = _ircccm.getCU(e.params.at(0)); + if (!receiver.valid()) { + std::cerr << "IRCCMM error: unknown receiver\n"; + return false; + } + // e.params.at(1) is message + + // upgrade contact to big + if (receiver.all_of()) { + from.emplace_or_replace(); // could be like an invite? + } + + return processMessage(from, receiver, e.params.at(1), true); +} + diff --git a/src/solanaceae/ircclient_messages/ircclient_message_manager.hpp b/src/solanaceae/ircclient_messages/ircclient_message_manager.hpp new file mode 100644 index 0000000..d82da89 --- /dev/null +++ b/src/solanaceae/ircclient_messages/ircclient_message_manager.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "solanaceae/contact/contact_model3.hpp" +#include +#include +#include + +class IRCClientMessageManager : public IRCClientEventI, public RegistryMessageModelEventI { + protected: + RegistryMessageModel& _rmm; + Contact3Registry& _cr; + ConfigModelI& _conf; + IRCClient1& _ircc; + IRCClientContactModel& _ircccm; + + public: + IRCClientMessageManager( + RegistryMessageModel& rmm, + Contact3Registry& cr, + ConfigModelI& conf, + IRCClient1& ircc, + IRCClientContactModel& ircccm + ); + + virtual ~IRCClientMessageManager(void); + + // bring event overloads into scope + using IRCClientEventI::onEvent; + using RegistryMessageModelEventI::onEvent; + private: + bool processMessage(Contact3Handle from, Contact3Handle to, std::string_view message_text, bool action); + + private: // mm3 + bool sendText(const Contact3 c, std::string_view message, bool action = false) override; + + private: // ircclient + bool onEvent(const IRCClient::Events::Channel& e) override; + bool onEvent(const IRCClient::Events::PrivMSG& e) override; + bool onEvent(const IRCClient::Events::Notice& e) override; + bool onEvent(const IRCClient::Events::ChannelNotice& e) override; + bool onEvent(const IRCClient::Events::CTCP_Action& e) override; +}; diff --git a/src/test2.cpp b/src/test2.cpp new file mode 100644 index 0000000..67adee7 --- /dev/null +++ b/src/test2.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +int main(void) { + SimpleConfigModel conf; + + conf.set("IRCClient", "server", std::string_view{"#irc.rizon.net"}); + conf.set("IRCClient", "port", int64_t(6697)); + conf.set("IRCClient", "autojoin", "#HorribleSubs", true); + conf.set("IRCClient", "autojoin", "#green_testing", true); + + Contact3Registry cr; + RegistryMessageModel rmm{cr}; + + IRCClient1 ircc{conf}; + + IRCClientContactModel ircccm{cr, conf, ircc}; + IRCClientMessageManager irccmm{rmm, cr, conf, ircc, ircccm}; + + //ircccm.join("#green_testing"); + + while (irc_is_connected(ircc.getSession())) { + ircc.iterate(); + } + + return 0; +}