/* Copyright (C) 2025 awy stweath is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. stweath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with stweath. If not, see . */ #include #include #include #include #include #include #include #include "../lib/cjson/cJSON.h" #include "../lib/util.h" typedef struct { int code; const char *icon; } WeatherIcon; const char *geticon(int code) { static const WeatherIcon icons[] = { {113, ""}, {116, ""}, {119, ""}, {122, ""}, {143, ""}, {248, ""}, {260, ""}, {176, ""}, {179, ""}, {182, ""}, {185, ""}, {263, ""}, {266, ""}, {281, ""}, {284, ""}, {293, ""}, {296, ""}, {299, ""}, {302, ""}, {305, ""}, {308, ""}, {311, ""}, {314, ""}, {317, ""}, {350, ""}, {353, ""}, {356, ""}, {359, ""}, {362, ""}, {365, ""}, {368, ""}, {392, ""}, {200, ""}, {227, ""}, {230, ""}, {320, ""}, {323, ""}, {326, ""}, {374, ""}, {377, ""}, {386, ""}, {389, ""}, {329, ""}, {332, ""}, {335, ""}, {338, ""}, {371, ""}, {395, ""} }; size_t n = sizeof(icons) / sizeof(icons[0]); for (size_t i = 0; i < n; i++) { if (icons[i].code == code) { return icons[i].icon; } } return "unknown"; } int getforecast(const char *path) { FILE *fp; CURL* handle; CURLcode res; if (!(fp = fopen(path, "w"))) die("failed to open report:"); handle = curl_easy_init(); if (!handle) { fclose(fp); die("failed to create curl handle"); } curl_easy_setopt(handle, CURLOPT_URL, "https://wttr.in/?format=j1"); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(handle, CURLOPT_WRITEDATA, fp); res = curl_easy_perform(handle); if (res != CURLE_OK) { fclose(fp); die("curl failed"); } curl_easy_cleanup(handle); fclose(fp); return 0; } int showforecast(const char *path) { FILE *fp; long size; char *data; const char *icon; cJSON *root, *temp, *wcode, *rain; if (!(fp = fopen(path, "rb"))) die("fopen:"); fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); data = malloc(size + 1); if (!(data = malloc(size + 1))) { fclose(fp); die("failed to allocate memory"); } fread(data, 1, size, fp); data[size] = '\0'; fclose(fp); if (!(root = cJSON_Parse(data))) { free(data); die("failed to parse json"); } rain = cJSON_GetArrayItem(cJSON_GetObjectItem(root, "weather"), 0); rain = cJSON_GetArrayItem(cJSON_GetObjectItem(rain, "hourly"), 0); rain = cJSON_GetObjectItem(rain, "chanceofrain"); temp = cJSON_GetObjectItem(root, "current_condition"); temp = cJSON_GetArrayItem(temp, 0); wcode = cJSON_GetObjectItem(temp, "weatherCode"); temp = cJSON_GetObjectItem(temp, "temp_C"); icon = geticon(atoi(wcode->valuestring)); if (atoi(rain->valuestring) != 0) printf(" %s%% ", rain->valuestring); printf("%s %s°C", icon, temp->valuestring); cJSON_Delete(root); return 0; } int checkforecast(const char *path) { time_t now; char *buff; struct stat st; int tries, force; /* check if there is WEATHERFORCEUPDATE environment variable */ force = 0; buff = getenv("WEATHERFORCEUPDATE"); if (buff && buff[0] == '1') { force = 1; } if (access(path, F_OK) != -1) { if (stat(path, &st) != 0) die("failed to get modification time"); } else { force = 1; } now = time(NULL); if (now == ((time_t)-1)) { return 1; } /* check if 30 mins passed then get new forecast */ if (((now - st.st_mtime) >= 1800) || force) { tries = 0; /* try 100 fast retries */ while (tries < 100) { if(getforecast(path) == 0) { break; } tries++; sleep(1); } } showforecast(path); return 0; } int main(void) { char *env; char path[1024]; if (!(env = getenv("XDG_CACHE_HOME"))) die("XDG_CACHE_HOME is not set"); strcpy(path, env); strcat(path, "/weatherreport.json"); curl_global_init(CURL_GLOBAL_ALL); checkforecast(path); curl_global_cleanup(); return 0; }