#include #include #include #include #include #include #include "./tox_private_impl.hpp" #include #include #include #include "./tox_client.hpp" #include "./auto_dirty.hpp" #include "./message_cleanser.hpp" #include "./message_command_dispatcher.hpp" #include #include #include #include #include #include #include #include #include #include #include #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) #include #include #elif defined (_WIN32) #include #endif std::atomic_bool quit = false; #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) || defined (_WIN32) void sigint_handler(int signo) { if (signo == SIGINT) { quit = true; } } #endif bool load_json_into_config(const nlohmann::ordered_json& config_json, SimpleConfigModel& conf) { if (!config_json.is_object()) { std::cout << "TOTATO error: config file is not an json object!!!\n"; return false; } for (const auto& [mod, cats] : config_json.items()) { for (const auto& [cat, cat_v] : cats.items()) { if (cat_v.is_object()) { if (cat_v.contains("default")) { const auto& value = cat_v["default"]; if (value.is_string()) { conf.set(mod, cat, value.get_ref()); } else if (value.is_boolean()) { conf.set(mod, cat, value.get_ref()); } else if (value.is_number_float()) { conf.set(mod, cat, value.get_ref()); } else if (value.is_number_integer()) { conf.set(mod, cat, value.get_ref()); } else { std::cerr << "JSON error: wrong value type in " << mod << "::" << cat << " = " << value << "\n"; return false; } } if (cat_v.contains("entries")) { for (const auto& [ent, ent_v] : cat_v["entries"].items()) { if (ent_v.is_string()) { conf.set(mod, cat, ent, ent_v.get_ref()); } else if (ent_v.is_boolean()) { conf.set(mod, cat, ent, ent_v.get_ref()); } else if (ent_v.is_number_float()) { conf.set(mod, cat, ent, ent_v.get_ref()); } else if (ent_v.is_number_integer()) { conf.set(mod, cat, ent, ent_v.get_ref()); } else { std::cerr << "JSON error: wrong value type in " << mod << "::" << cat << "::" << ent << " = " << ent_v << "\n"; return false; } } } } else { if (cat_v.is_string()) { conf.set(mod, cat, cat_v.get_ref()); } else if (cat_v.is_boolean()) { conf.set(mod, cat, cat_v.get_ref()); } else if (cat_v.is_number_float()) { conf.set(mod, cat, cat_v.get_ref()); } else if (cat_v.is_number_integer()) { conf.set(mod, cat, cat_v.get_ref()); } else { std::cerr << "JSON error: wrong value type in " << mod << "::" << cat << " = " << cat_v << "\n"; return false; } } } } return true; } int main(int argc, char** argv) { #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) struct sigaction sigint_action; sigint_action.sa_handler = sigint_handler; sigemptyset (&sigint_action.sa_mask); sigint_action.sa_flags = 0; sigaction(SIGINT, &sigint_action, NULL); #elif defined (_WIN32) signal(SIGINT, sigint_handler); #endif auto last_time = std::chrono::steady_clock::now(); std::string config_path {"config.json"}; // totato -p // TODO: parse arg if (argc == 2) { config_path = argv[1]; } SimpleConfigModel conf; { // read conf from json TODO: refactor extract this for reuse auto config_file = std::ifstream(config_path); if (!config_file.is_open()) { std::cerr << "TOTATO error: failed to open config file '" << config_path << "', exiting...\n"; return -1; } auto config_json = nlohmann::ordered_json::parse(std::ifstream(config_path)); if (!load_json_into_config(config_json, conf)) { std::cerr << "TOTATO error in config json, exiting...\n"; return -1; } } { // fill in defaults // config file folder const auto config_path_base = std::filesystem::path{config_path}.parent_path(); if (!conf.has_string("tox", "save_file_path")) { // default to totato.tox relative to config file conf.set("tox", "save_file_path", (config_path_base / "totato.tox").u8string()); } else { // transform relative to config to absolute const auto tox_conf_path = std::filesystem::path{static_cast(conf.get_string("tox", "save_file_path").value())}; if (tox_conf_path.is_relative()) { // is relative to config conf.set("tox", "save_file_path", std::filesystem::canonical(config_path_base / tox_conf_path).u8string()); } } // TODO: name } Contact3Registry cr; RegistryMessageModel rmm{cr}; MessageTimeSort mts{rmm}; MessageCleanser mc{cr, rmm}; MessageCommandDispatcher mcd{cr, rmm, conf}; { // setup basic commands for bot mcd.registerCommand( "host", "", "info", [](std::string_view, Message3Handle m) -> bool { std::cout << "INFO got called :)\n"; return true; }, "Get basic information about this bot" ); } PluginManager pm; ToxEventLogger tel{std::cout}; // TODO: config // TODO: password? ToxClient tc{conf.get_string("tox", "save_file_path").value(), ""}; tel.subscribeAll(tc); { // name stuff auto name = tc.toxSelfGetName(); if (name.empty()) { name = "totato"; } conf.set("tox", "name", name); tc.setSelfName(name); // TODO: this is ugly } std::cout << "TOTATO: own address: " << tc.toxSelfGetAddressStr() << "\n"; ToxPrivateImpl tpi{tc.getTox()}; AutoDirty ad{tc}; ToxContactModel2 tcm{cr, tc, tc}; ToxMessageManager tmm{rmm, cr, tcm, tc, tc}; ToxTransferManager ttm{rmm, cr, tcm, tc, tc}; { // setup more advanced commands mcd.registerCommand( "tox", "tox", "status", [&](std::string_view, Message3Handle m) -> bool { const auto tox_self_status = tc.toxSelfGetConnectionStatus(); const auto contact_from = m.get().c; std::string reply{"dht:"}; if (tox_self_status == Tox_Connection::TOX_CONNECTION_UDP) { reply += "upd-direct"; } else if (tox_self_status == Tox_Connection::TOX_CONNECTION_TCP) { reply += "tcp-relayed"; } if (cr.all_of(contact_from)) { const auto con_opt = tc.toxFriendGetConnectionStatus(cr.get(contact_from).friend_number); if (!con_opt.has_value() || con_opt.value() == Tox_Connection::TOX_CONNECTION_NONE) { reply += "\nfriend:offline"; } else if (con_opt.value() == Tox_Connection::TOX_CONNECTION_UDP) { reply += "\nfriend:udp-direct"; } else if (con_opt.value() == Tox_Connection::TOX_CONNECTION_TCP) { reply += "\nfriend:tcp-relayed"; } } else if (cr.all_of(contact_from)) { const auto [group_number, peer_number] = cr.get(contact_from); const auto [con_opt, _] = tc.toxGroupPeerGetConnectionStatus(group_number, peer_number); if (!con_opt.has_value() || con_opt.value() == Tox_Connection::TOX_CONNECTION_NONE) { reply += "\ngroup-peer:offline"; } else if (con_opt.value() == Tox_Connection::TOX_CONNECTION_UDP) { reply += "\ngroup-peer:udp-direct"; } else if (con_opt.value() == Tox_Connection::TOX_CONNECTION_TCP) { reply += "\ngroup-peer:tcp-relayed"; } } else if (cr.any_of(contact_from)) { reply += "\noffline"; } else { reply += "\nunk"; } rmm.sendText( contact_from, reply ); return true; }, "Query the tox status of dht and to you." ); mcd.registerCommand( "tox", "tox", "add", [&](std::string_view params, Message3Handle m) -> bool { const auto contact_from = m.get().c; if (params.size() != 38*2) { rmm.sendText( contact_from, "error adding friend, id is not the correct size!" ); return true; } // TODO: add tcm interface const auto [_, err] = tc.toxFriendAdd(hex2bin(std::string{params}), "Add me, I am totato"); if (err == Tox_Err_Friend_Add::TOX_ERR_FRIEND_ADD_OK) { rmm.sendText( contact_from, "freind request sent" ); } else { rmm.sendText( contact_from, "error adding friend, error code " + std::to_string(err) ); } return true; }, "add a tox friend by id" ); mcd.registerCommand( "tox", "tox", "join", [&](std::string_view params, Message3Handle m) -> bool { const auto contact_from = m.get().c; if (params.size() != 32*2) { rmm.sendText( contact_from, "error joining group, id is not the correct size!" ); return true; } auto name_opt = conf.get_string("tox", "name"); // TODO: add tcm interface const auto [_, err] = tc.toxGroupJoin(hex2bin(std::string{params}), std::string{name_opt.value_or("no-name-found")}, ""); if (err == Tox_Err_Group_Join::TOX_ERR_GROUP_JOIN_OK) { rmm.sendText( contact_from, "joining group..." ); } else { rmm.sendText( contact_from, "error joining group, error code " + std::to_string(err) ); } return true; }, "join a tox group by id" ); } { // setup plugin instances g_provideInstance("ConfigModelI", "host", &conf); g_provideInstance("Contact3Registry", "host", &cr); g_provideInstance("RegistryMessageModel", "host", &rmm); g_provideInstance("MessageCommandDispatcher", "host", &mcd); g_provideInstance("ToxI", "host", &tc); g_provideInstance("ToxPrivateI", "host", &tpi); g_provideInstance("ToxEventProviderI", "host", &tc); g_provideInstance("ToxContactModel2", "host", &tcm); // TODO: pm? } // load from config!!! // HACK: we cheat and directly access the members // TODO: add api to iterate if (conf._map_bool.count("PluginManager") && conf._map_bool.at("PluginManager").count("autoload")) { const auto config_path_base = std::filesystem::path{config_path}.parent_path(); for (const auto& [plugin_path, plugin_autoload] : conf._map_bool.at("PluginManager").at("autoload").second) { if (plugin_autoload) { std::filesystem::path real_plugin_path = plugin_path; if (real_plugin_path.is_relative()) { real_plugin_path = config_path_base / real_plugin_path; } if (!pm.add(real_plugin_path.u8string())) { std::cerr << "TOTATO error: loading plugin '" << real_plugin_path << "' failed!\n"; // thow? assert(false && "failed to load plugin"); } } } } conf.dump(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); // at startup, just to be safe while (!quit) { //auto new_time = std::chrono::steady_clock::now(); quit = !tc.iterate(); tcm.iterate(/*time_delta*/0.02f); ttm.iterate(); mts.iterate(); pm.tick(/*time_delta*/0.02f); mc.iterate(0.02f); //std::this_thread::sleep_for( // time left to get to 60fps //std::chrono::duration(0.0166f) // 60fps frame duration //- std::chrono::duration(std::chrono::steady_clock::now() - new_time) // time used for rendering //); std::this_thread::sleep_for(std::chrono::milliseconds(20)); // HACK: until i figure out the best main loop } std::cout << "\nTOTATO shutting down...\n"; return 0; }