Created
April 15, 2026 21:14
-
-
Save vlaleli/305bc6aa89befc2ca489416c9a024afb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <curl/curl.h> | |
| #include <cctype> | |
| #include <ctime> | |
| #include <iomanip> | |
| #include <iostream> | |
| #include <limits> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| using namespace std; | |
| struct ZodiacSign { | |
| string uaName; | |
| string apiName; | |
| }; | |
| size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { | |
| size_t totalSize = size * nmemb; | |
| static_cast<string*>(userp)->append(static_cast<char*>(contents), totalSize); | |
| return totalSize; | |
| } | |
| string trim(const string& s) { | |
| const string whitespace = " \n\r\t"; | |
| size_t start = s.find_first_not_of(whitespace); | |
| if (start == string::npos) { | |
| return ""; | |
| } | |
| size_t end = s.find_last_not_of(whitespace); | |
| return s.substr(start, end - start + 1); | |
| } | |
| string unicodeCodePointToUtf8(unsigned int codepoint) { | |
| string result; | |
| if (codepoint <= 0x7F) { | |
| result += static_cast<char>(codepoint); | |
| } else if (codepoint <= 0x7FF) { | |
| result += static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)); | |
| result += static_cast<char>(0x80 | (codepoint & 0x3F)); | |
| } else if (codepoint <= 0xFFFF) { | |
| result += static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)); | |
| result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)); | |
| result += static_cast<char>(0x80 | (codepoint & 0x3F)); | |
| } else { | |
| result += static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07)); | |
| result += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)); | |
| result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)); | |
| result += static_cast<char>(0x80 | (codepoint & 0x3F)); | |
| } | |
| return result; | |
| } | |
| string unescapeJsonString(const string& s) { | |
| string result; | |
| result.reserve(s.size()); | |
| for (size_t i = 0; i < s.size(); ++i) { | |
| if (s[i] == '\\' && i + 1 < s.size()) { | |
| char next = s[i + 1]; | |
| switch (next) { | |
| case 'n': | |
| result += '\n'; | |
| ++i; | |
| break; | |
| case 'r': | |
| ++i; | |
| break; | |
| case 't': | |
| result += '\t'; | |
| ++i; | |
| break; | |
| case '"': | |
| result += '"'; | |
| ++i; | |
| break; | |
| case '\\': | |
| result += '\\'; | |
| ++i; | |
| break; | |
| case '/': | |
| result += '/'; | |
| ++i; | |
| break; | |
| case 'u': { | |
| if (i + 5 < s.size()) { | |
| string hexCode = s.substr(i + 2, 4); | |
| unsigned int codepoint = 0; | |
| stringstream ss; | |
| ss << hex << hexCode; | |
| ss >> codepoint; | |
| result += unicodeCodePointToUtf8(codepoint); | |
| i += 5; | |
| } else { | |
| result += s[i]; | |
| } | |
| break; | |
| } | |
| default: | |
| result += s[i]; | |
| break; | |
| } | |
| } else { | |
| result += s[i]; | |
| } | |
| } | |
| return result; | |
| } | |
| string extractJsonField(const string& json, const string& fieldName) { | |
| string key = "\"" + fieldName + "\""; | |
| size_t keyPos = json.find(key); | |
| if (keyPos == string::npos) { | |
| return ""; | |
| } | |
| size_t colonPos = json.find(':', keyPos); | |
| if (colonPos == string::npos) { | |
| return ""; | |
| } | |
| size_t firstQuote = json.find('"', colonPos + 1); | |
| if (firstQuote == string::npos) { | |
| return ""; | |
| } | |
| size_t secondQuote = firstQuote + 1; | |
| while (true) { | |
| secondQuote = json.find('"', secondQuote); | |
| if (secondQuote == string::npos) { | |
| return ""; | |
| } | |
| if (json[secondQuote - 1] != '\\') { | |
| break; | |
| } | |
| ++secondQuote; | |
| } | |
| return unescapeJsonString(json.substr(firstQuote + 1, secondQuote - firstQuote - 1)); | |
| } | |
| class HttpClient { | |
| public: | |
| HttpClient() { | |
| curl_global_init(CURL_GLOBAL_DEFAULT); | |
| } | |
| ~HttpClient() { | |
| curl_global_cleanup(); | |
| } | |
| bool get(const string& url, string& response, string& errorMessage) { | |
| CURL* curl = curl_easy_init(); | |
| if (!curl) { | |
| errorMessage = "Не вдалося ініціалізувати libcurl."; | |
| return false; | |
| } | |
| response.clear(); | |
| curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
| curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); | |
| curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
| curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
| curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); | |
| curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L); | |
| curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0"); | |
| CURLcode res = curl_easy_perform(curl); | |
| long httpCode = 0; | |
| curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); | |
| curl_easy_cleanup(curl); | |
| if (res != CURLE_OK) { | |
| errorMessage = "Помилка HTTP-запиту: " + string(curl_easy_strerror(res)); | |
| return false; | |
| } | |
| if (httpCode != 200) { | |
| errorMessage = "Сервер повернув HTTP-код: " + to_string(httpCode); | |
| return false; | |
| } | |
| if (response.empty()) { | |
| errorMessage = "Сервер повернув порожню відповідь."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| }; | |
| class HoroscopeClient { | |
| private: | |
| HttpClient http; | |
| public: | |
| bool getHoroscope(const string& sign, const string& day, string& response, string& errorMessage) { | |
| string url = "https://horoscope-app-api.vercel.app/api/v1/get-horoscope/daily?sign=" + sign + "&day=" + day; | |
| return http.get(url, response, errorMessage); | |
| } | |
| bool translateToUkrainian(const string& text, string& translatedText, string& errorMessage) { | |
| CURL* curl = curl_easy_init(); | |
| if (!curl) { | |
| errorMessage = "Не вдалося ініціалізувати libcurl для перекладу."; | |
| return false; | |
| } | |
| char* encodedText = curl_easy_escape(curl, text.c_str(), static_cast<int>(text.length())); | |
| if (!encodedText) { | |
| curl_easy_cleanup(curl); | |
| errorMessage = "Не вдалося закодувати текст для перекладу."; | |
| return false; | |
| } | |
| string url = "https://api.mymemory.translated.net/get?q=" + string(encodedText) + "&langpair=en|uk"; | |
| curl_free(encodedText); | |
| curl_easy_cleanup(curl); | |
| string response; | |
| if (!http.get(url, response, errorMessage)) { | |
| return false; | |
| } | |
| translatedText = extractJsonField(response, "translatedText"); | |
| if (translatedText.empty()) { | |
| errorMessage = "Не вдалося отримати переклад із відповіді API."; | |
| return false; | |
| } | |
| translatedText = trim(translatedText); | |
| return true; | |
| } | |
| }; | |
| string normalizeHoroscopeText(const string& response) { | |
| string cleaned = trim(response); | |
| string horoscope = extractJsonField(cleaned, "horoscope"); | |
| if (!horoscope.empty()) { | |
| return trim(horoscope); | |
| } | |
| string horoscopeData = extractJsonField(cleaned, "horoscope_data"); | |
| if (!horoscopeData.empty()) { | |
| return trim(horoscopeData); | |
| } | |
| return cleaned; | |
| } | |
| string formatDate(int addDays) { | |
| time_t now = time(nullptr); | |
| now += static_cast<time_t>(addDays) * 24 * 60 * 60; | |
| tm localTime{}; | |
| #if defined(_WIN32) | |
| localtime_s(&localTime, &now); | |
| #else | |
| localtime_r(&now, &localTime); | |
| #endif | |
| ostringstream out; | |
| out << put_time(&localTime, "%d.%m.%Y"); | |
| return out.str(); | |
| } | |
| void printLine(char ch = '=', int width = 74) { | |
| for (int i = 0; i < width; ++i) { | |
| cout << ch; | |
| } | |
| cout << '\n'; | |
| } | |
| void printTitle() { | |
| printLine('='); | |
| cout << " ПРОГРАМА \"ГОРОСКОП НА СЬОГОДНІ ТА ЗАВТРА\"\n"; | |
| printLine('='); | |
| cout << '\n'; | |
| } | |
| void printMenu() { | |
| cout << "Меню:\n"; | |
| cout << "1. Отримати гороскоп\n"; | |
| cout << "0. Вийти\n\n"; | |
| } | |
| int getIntInRange(const string& prompt, int minValue, int maxValue) { | |
| int value; | |
| while (true) { | |
| cout << prompt; | |
| if (cin >> value && value >= minValue && value <= maxValue) { | |
| cin.ignore(numeric_limits<streamsize>::max(), '\n'); | |
| return value; | |
| } | |
| cout << "Некоректне введення. Спробуйте ще раз.\n"; | |
| cin.clear(); | |
| cin.ignore(numeric_limits<streamsize>::max(), '\n'); | |
| } | |
| } | |
| char getYesNo(const string& prompt) { | |
| char answer; | |
| while (true) { | |
| cout << prompt; | |
| if (cin >> answer) { | |
| cin.ignore(numeric_limits<streamsize>::max(), '\n'); | |
| answer = static_cast<char>(tolower(static_cast<unsigned char>(answer))); | |
| if (answer == 'y' || answer == 'n') { | |
| return answer; | |
| } | |
| } | |
| cout << "Введіть тільки y або n.\n"; | |
| cin.clear(); | |
| cin.ignore(numeric_limits<streamsize>::max(), '\n'); | |
| } | |
| } | |
| void printSigns(const vector<ZodiacSign>& signs) { | |
| cout << "Оберіть знак зодіаку:\n\n"; | |
| for (size_t i = 0; i < signs.size(); ++i) { | |
| cout << setw(2) << (i + 1) << ". " << signs[i].uaName << '\n'; | |
| } | |
| cout << '\n'; | |
| } | |
| void printHoroscopeBlock(const string& title, const string& dateText, const string& horoscopeText) { | |
| printLine('-'); | |
| cout << title << " (" << dateText << ")\n"; | |
| printLine('-'); | |
| cout << horoscopeText << "\n\n"; | |
| } | |
| void runHoroscopeProgram(HoroscopeClient& client, const vector<ZodiacSign>& signs) { | |
| printSigns(signs); | |
| int choice = getIntInRange("Ваш вибір: ", 1, 12); | |
| ZodiacSign selected = signs[choice - 1]; | |
| cout << "\nЗавантаження гороскопу для знака: " << selected.uaName << "...\n\n"; | |
| string todayResponse; | |
| string tomorrowResponse; | |
| string errorMessage; | |
| bool okToday = client.getHoroscope(selected.apiName, "today", todayResponse, errorMessage); | |
| if (!okToday) { | |
| cout << "Не вдалося отримати гороскоп на сьогодні.\n"; | |
| cout << errorMessage << "\n\n"; | |
| return; | |
| } | |
| bool okTomorrow = client.getHoroscope(selected.apiName, "tomorrow", tomorrowResponse, errorMessage); | |
| if (!okTomorrow) { | |
| cout << "Не вдалося отримати гороскоп на завтра.\n"; | |
| cout << errorMessage << "\n\n"; | |
| return; | |
| } | |
| string todayText = normalizeHoroscopeText(todayResponse); | |
| string tomorrowText = normalizeHoroscopeText(tomorrowResponse); | |
| string translatedToday; | |
| string translatedTomorrow; | |
| string translationError; | |
| if (!client.translateToUkrainian(todayText, translatedToday, translationError)) { | |
| translatedToday = todayText + "\n\n[Переклад не спрацював, показано оригінальний текст]"; | |
| } | |
| if (!client.translateToUkrainian(tomorrowText, translatedTomorrow, translationError)) { | |
| translatedTomorrow = tomorrowText + "\n\n[Переклад не спрацював, показано оригінальний текст]"; | |
| } | |
| printLine('='); | |
| cout << "ЗНАК ЗОДІАКУ: " << selected.uaName << '\n'; | |
| printLine('='); | |
| cout << '\n'; | |
| printHoroscopeBlock("Гороскоп на сьогодні", formatDate(0), translatedToday); | |
| printHoroscopeBlock("Гороскоп на завтра", formatDate(1), translatedTomorrow); | |
| printLine('='); | |
| cout << "Гороскоп успішно отримано.\n"; | |
| printLine('='); | |
| cout << '\n'; | |
| } | |
| int main() { | |
| vector<ZodiacSign> signs = { | |
| {"Овен", "aries"}, | |
| {"Телець", "taurus"}, | |
| {"Близнюки", "gemini"}, | |
| {"Рак", "cancer"}, | |
| {"Лев", "leo"}, | |
| {"Діва", "virgo"}, | |
| {"Терези", "libra"}, | |
| {"Скорпіон", "scorpio"}, | |
| {"Стрілець", "sagittarius"}, | |
| {"Козоріг", "capricorn"}, | |
| {"Водолій", "aquarius"}, | |
| {"Риби", "pisces"} | |
| }; | |
| HoroscopeClient client; | |
| while (true) { | |
| printTitle(); | |
| printMenu(); | |
| int menuChoice = getIntInRange("Оберіть пункт меню: ", 0, 1); | |
| cout << '\n'; | |
| if (menuChoice == 0) { | |
| cout << "Програму завершено.\n"; | |
| break; | |
| } | |
| runHoroscopeProgram(client, signs); | |
| char again = getYesNo("Бажаєте отримати ще один гороскоп? (y/n): "); | |
| cout << '\n'; | |
| if (again == 'n') { | |
| cout << "Дякуємо за використання програми.\n"; | |
| break; | |
| } | |
| cout << '\n'; | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment