#include "okapi.h" #include "debug.h" #include #include #include #include #include using json = nlohmann::json; static const std::string OKAPI_logs = "services/logs/userlogs"; static const std::string OKAPI_cache = "services/caches/geocache"; static const std::string OKAPI_caches = "services/caches/geocaches"; static const std::string OKAPI_username = "services/users/by_username"; static const std::string OKAPI_user = "services/users/user"; static const std::string OKAPI_changelog = "services/replicate/changelog"; Okapi::Okapi(std::string server_url, std::string consumer_key) : url(server_url), key(consumer_key) { if (url.find(".pl/") != std::string::npos) service = "OC.pl"; if (url.find(".de/") != std::string::npos) service = "OC.de"; if (url.find(".us/") != std::string::npos) service = "OC.us"; if (url.find(".nl/") != std::string::npos) service = "OC.nl"; if (url.find(".ro/") != std::string::npos) service = "OC.ro"; if (url.find(".uk/") != std::string::npos) service = "OC.uk"; } // Callback for cURL easy interface used to save received output to std::string object size_t Okapi::write_cb(char* ptr, __attribute__((unused)) size_t size, size_t nmemb, void* userdata) { std::string* str = reinterpret_cast(userdata); str->append(ptr, nmemb); return nmemb; } std::string Okapi::curl_post(std::string url, std::string post) { CURL* curl; CURLcode res; std::string output; Debug(5) << "API query: " << post; curl = curl_easy_init(); if (!curl) { curl_global_cleanup(); throw 0; } curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&output); res = curl_easy_perform(curl); if (res != CURLE_OK) { Debug(1) << "curl_easy_perform() failed: " << curl_easy_strerror(res) << '\n'; curl_easy_cleanup(curl); std::exit(EXIT_FAILURE); } curl_easy_cleanup(curl); Debug(5) << "API query result: " << output; if (output.starts_with("{\"error\"")) { json j = json::parse(output); Debug(1) << "OKAPI error: " << j["error"]["developer_message"]; std::exit(EXIT_FAILURE); } return output; } std::string Okapi::get_user_caches_json(std::string uuid, int count, int offset) { std::string service = url + OKAPI_logs; std::string query = "consumer_key=" + key + "&user_uuid=" + uuid + "&fields=cache_code|type|date&limit=" + std::to_string(count) + "&offset=" + std::to_string(offset); return curl_post(service, query); } // std::string Okapi::get_cache_json(std::string code) { // std::string service = url + OKAPI_cache; // std::string query = "consumer_key=" + key + "&cache_code=" + code + "&fields=code|name|location|type|status|difficulty|terrain"; // return curl_post(service, query); // } std::string Okapi::get_caches_json(std::string codes) { std::string service = url + OKAPI_caches; std::string query = "consumer_key=" + key + "&cache_codes=" + codes + "&fields=code|name|location|type|status|difficulty|terrain|owner|region|size2|date_hidden|recommendations|founds"; return curl_post(service, query); } // Cache Okapi::get_cache(std::string code) { // std::string output = get_cache_json(code); // json j = json::parse(output); // // Cache c; // c.code = code; // c.name = j["name"]; // c.type = j["type"]; // c.diff = j["difficulty"].get(); // c.terr = j["terrain"].get(); // // std::cout << j["difficulty"] << '\n'; // // std::cout << j["difficulty"].get() << '\n'; // // std::cout << j["difficulty"].get() << '\n'; // // std::cout << j["difficulty"].get() << '\n'; // // std::string loc = j["location"]; // int pos = loc.find("|"); // //c.pos.lat = stof(loc.substr(0, pos)); // //c.pos.lon = stof(loc.substr(pos + 1)); // // return c; // } Caches Okapi::get_caches(std::map codes) { Cache c; Caches cc; uint k = 0; std::string codes_list; codes_list.reserve(500 * 7); // maximum of 500 codes, 6 chars per code plus a separator auto it = codes.begin(); while (it != codes.end()) { k = 0; while (it != codes.end() && k < 500) { codes_list += it->first; codes_list += '|'; it++; k++; } codes_list.pop_back(); // remove trailing '|' std::string output = get_caches_json(codes_list); json j = json::parse(output); codes_list.clear(); for (auto& el : j.items()) { c.code = el.value()["code"]; c.name = el.value()["name"]; c.type = el.value()["type"]; c.size = el.value()["size2"]; c.region = el.value()["region"]; c.diff = el.value()["difficulty"]; c.terr = el.value()["terrain"]; c.owner = el.value()["owner"]["username"]; c.owner_uuid = el.value()["owner"]["uuid"]; c.pos = get_lat_lon(el.value()["location"]); c.origin = service; c.fav = el.value()["recommendations"]; c.founds = el.value()["founds"]; std::tm tmp; std::stringstream ss(el.value()["date_hidden"].get()); ss >> std::get_time(&tmp, "%Y-%m-%dT%H:%M:%S+"); c.set_date_hidden(tmp); //ugly way to handle date... might produce bad results if a cache appears twice in the list for (auto& el2 : codes) { if (el2.first == c.code) { c.set_date(el2.second); break; } } cc.insert(c); } } return cc; } Caches Okapi::get_user_caches(std::string uuid, int count) { Caches cc; std::map codes; json j; std::tm date; int off = 0; if (count == 0) do { std::string output = get_user_caches_json(uuid, 1000, off); j = json::parse(output); for (auto& el : j.items()) { if (el.value()["type"] == "Found it") { std::stringstream ss(el.value()["date"].get()); // TODO need to take care of the time zone :/ ss >> std::get_time(&date, "%Y-%m-%dT%H:%M:%S+"); codes[el.value()["cache_code"].get()] = date; } } off += j.size(); } while (j.size() > 0); else { int count_req = (count > 1000) ? 1000 : count; do { std::string output = get_user_caches_json(uuid, count_req, off); j = json::parse(output); for (auto& el : j.items()) { if (el.value()["type"] == "Found it") { std::stringstream ss(el.value()["date"].get()); ss >> std::get_time(&date, "%Y-%m-%dT%H-%M-%S"); codes[el.value()["cache_code"].get()] = date; } } off += j.size(); count -= count_req; } while (j.size() > 0 && count > 0); } cc = get_caches(codes); Debug(2) << "Caches read from OC: " << cc.size() << '\n'; return cc; } std::string Okapi::get_uuid(std::string username) { std::string service = url + OKAPI_username; std::string query = "consumer_key=" + key + "&username=" + username + "&fields=uuid"; json j = json::parse(curl_post(service, query)); return j["uuid"]; } std::string Okapi::get_profile_url(std::string uuid) { std::string service = url + OKAPI_user; std::string query = "consumer_key=" + key + "&user_uuid=" + uuid + "&fields=profile_url"; json j = json::parse(curl_post(service, query)); return j["profile_url"]; } std::string Okapi::get_changelog_json(int revision) { std::string service = url + OKAPI_changelog; std::string query = "consumer_key=" + key + "&since=" + std::to_string(revision); return curl_post(service, query); }