Skip to content

Instantly share code, notes, and snippets.

@vlaleli
Created April 15, 2026 21:14
Show Gist options
  • Select an option

  • Save vlaleli/305bc6aa89befc2ca489416c9a024afb to your computer and use it in GitHub Desktop.

Select an option

Save vlaleli/305bc6aa89befc2ca489416c9a024afb to your computer and use it in GitHub Desktop.
#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