/* * ______________ * //````````````\\ 😀 Thank you for choosing Pudding Customs for your business 😀 * //~~~~~~~~~~~~~~\\ * //================\\ Custom pudding code provided by 0eggxactly ........... [http://0eggxactly.itch.io] * // \\ Available for copy, modification and redistribution .. [http://git.shampoo.ooo/pudding] * // ''CUSTOM PUDDING'' \\ Updates .............................................. [http://twitter.com/diskmem] * //______________________\\ * `````````````````````````` * * Use this application to generate a custom pudding from a series of UPC scans, helping a pair of rats leverage their * extraterrestrial trash symbiosis, using it to take over the video game industry with performance enhancing drugged * puddings that enable business professionals to predict the stock market with supernatural accuracy. */ #include "Pudding.hpp" int main() { Pudding pudding = Pudding(); pudding.run(); pudding.quit(); return 0; } Pudding::Pudding() { get_delegate().subscribe(&Pudding::respond, this); load_sdl_context(); } void Pudding::respond(SDL_Event& event) { if (get_delegate().compare(event, "up")) { increment_item_index(); } else if (get_delegate().compare(event, "right")) { get_current_item().increment_image_index(); } else if (get_delegate().compare(event, "down")) { increment_item_index(-1); } else if (get_delegate().compare(event, "left")) { get_current_item().increment_image_index(-1); } } void Pudding::load_sdl_context() { super::load_sdl_context(); } /* Build an Item object by submitting upc parameter to various APIs and incorporating * relevant results from each. Result JSON will be saved if saving is enabled in the global * configuration. */ void Pudding::add_item(const std::string& upc) { Item item; item.set_upc(upc); incorporate_open_food_api(item); incorporate_nutronix_api(item); incorporate_edamam_api(item); items.push_back(item); } /* Look for item upc in the Open Food API, and use result to fill out item properties if found */ void Pudding::incorporate_open_food_api(Item& item) { SDL_Log("checking Open Food API"); nlohmann::json json = json_from_url(OPEN_FOOD_API_URL + item.get_upc()); // test that should determine if an Open Food API response is not empty if (json.value("status", 0) && json.contains("product")) { if (json["product"].value("image_url", "") != "") { std::string url = json["product"]["image_url"]; SDL_Texture* texture = texture_from_image_url(url); if (texture != nullptr) { item.add_image_texture(texture); } } item.set_brand_name(json["product"].value("brands", "")); item.set_product_name(json["product"].value("product_name", "")); save_item_json(json, item, "Open_Food_API"); } else { SDL_Log("no results from Open Food API"); } } /* Look for item upc in the Nutronix API, and use result to fill out item properties if found */ void Pudding::incorporate_nutronix_api(Item& item) { SDL_Log("checking Nutronix API"); // Nutronix requires API keys in headers for validation nlohmann::json json = json_from_url( NUTRONIX_API_URL + item.get_upc(), { "x-app-id: " + get_configuration()["api"]["nutronix-app-id"].get(), "x-app-key: " + get_configuration()["api"]["nutronix-app-key"].get() }); // test that should determine if a Nutronix response is not empty if (!(json.contains("message") && json["message"] == NUTRONIX_NOT_FOUND)) { nlohmann::json food = json["foods"][0]; if (food.contains("photo") && food["photo"].value("thumb", "") != "") { std::string url = food["photo"]["thumb"]; SDL_Log("adding image listed in Nutronix API at %s", url.c_str()); SDL_Texture* texture = texture_from_image_url(url); if (texture != nullptr) { item.add_image_texture(texture); } } item.set_brand_name(food.value("brand_name", "")); item.set_product_name(food.value("food_name", "")); save_item_json(json, item, "Nutronix_API"); } else { SDL_Log("no results from Nutronix API"); } } /* Submit a query to Edamam API and insert relevant results into supplied Item object */ void Pudding::incorporate_edamam_api(Item& item) { SDL_Log("checking Edamam API"); // build API url by concatenating relevant values into query string std::stringstream url; url << "https://api.edamam.com/api/food-database/v2/parser?upc=" << item.get_upc() << "&app_id=" << get_configuration()["api"]["edamam-app-id"].get() << "&app_key=" << get_configuration()["api"]["edamam-app-key"].get(); nlohmann::json json = json_from_url(url.str()); // test that should determine if a Edamam response has food data if (json.contains("hints") && json["hints"][0].contains("food")) { nlohmann::json food = json["hints"][0]["food"]; if (food.value("image", "") != "") { std::string url = food["image"]; SDL_Texture* texture = texture_from_image_url(url); if (texture != nullptr) { item.add_image_texture(texture); } item.set_product_name(food.value("label", "")); } save_item_json(json, item, "Edamam_API"); } } /* Write submitted JSON to file, creating parent directories if necessary, and using item and * api_name to determine file name prefix */ void Pudding::save_item_json(const nlohmann::json& json, const Item& item, const std::string& api_name) { if (get_configuration()["scan"]["json-save"]) { fs::path path = get_configuration()["scan"]["json-save-directory"]; if (!fs::exists(path)) { fs::create_directories(path); } std::string prefix = api_name; if (item.get_full_name() != "") { prefix += "_" + item.get_full_name(); } else { prefix += "_Unknown"; } std::replace_if(prefix.begin(), prefix.end(), [](char c) { return !std::isalnum(c); }, '_'); path /= prefix + "_" + item.get_upc() + ".json"; std::ofstream out(path); out << std::setw(4) << json << std::endl; SDL_Log("Saved JSON to %s", path.c_str()); } else { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "not saving JSON, saving disabled by configuration"); } } nlohmann::json Pudding::json_from_url(const std::string& url, const std::vector& headers) { std::vector storage; curl_get_bytes(url, storage, headers); nlohmann::json json = nlohmann::json::parse(storage); std::stringstream json_formatted; json_formatted << std::setw(4) << json << std::endl; SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", json_formatted.str().c_str()); return json; } void Pudding::curl_get_bytes(const std::string& url, std::vector& storage, const std::vector& headers) { CURL *curl; CURLcode result; result = curl_global_init(CURL_GLOBAL_DEFAULT); if (result != CURLE_OK) { std::cout << "curl initialization failed " << curl_easy_strerror(result) << std::endl; } else { curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Pudding::curl_write_response); std::vector food_barcode_response; curl_easy_setopt(curl, CURLOPT_WRITEDATA, &storage); curl_easy_setopt(curl, CURLOPT_USERAGENT, get_configuration()["api"]["user-agent"].get().c_str()); struct curl_slist* list = nullptr; if (headers.size() > 0) { for (const std::string& header : headers) { list = curl_slist_append(list, header.c_str()); } } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); result = curl_easy_perform(curl); curl_slist_free_all(list); if (result != CURLE_OK) { std::cout << "curl request failed " << curl_easy_strerror(result) << std::endl; } } else { std::cout << "curl initialization failed" << std::endl; } curl_easy_cleanup(curl); } curl_global_cleanup(); } size_t Pudding::curl_write_response(std::uint8_t* buffer, size_t size, size_t count, std::vector* storage) { size_t total_size = size * count; storage->insert(storage->end(), buffer, buffer + total_size); return total_size; } SDL_Texture* Pudding::texture_from_image_url(const std::string& url) { SDL_Log("looking up image at %s", url.c_str()); std::vector storage; curl_get_bytes(url, storage); if (!storage.empty()) { SDL_RWops* rw = SDL_RWFromConstMem(storage.data(), storage.size()); SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "received image data"); return IMG_LoadTexture_RW(get_renderer(), rw, 0); } else { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "image url returned no data"); return nullptr; } } void Pudding::increment_item_index(int increment) { current_item_index = sfw::mod(current_item_index + increment, static_cast(items.size())); } Item& Pudding::get_current_item() { return items[current_item_index]; } void Pudding::update() { get_root()->configuration.load("config.json"); get_root()->configuration.merge(); if (current_barcode != get_configuration()["scan"]["barcode"]) { current_barcode = get_configuration()["scan"]["barcode"]; add_item(current_barcode); } if (items.size() > 0) { SDL_RenderCopyF(get_renderer(), get_current_item().get_active_image_texture().get(), nullptr, nullptr); } }