ADDED tenki/make.sh Index: tenki/make.sh ================================================================== --- tenki/make.sh +++ tenki/make.sh @@ -0,0 +1,6 @@ +#!/bin/bash +if test -e apikey; then + cc -Ofast -Dds_apikey="\"$(cat apikey)\"" tenki.c -lssl -otenki +else + echo "tenki requires an API key. get one from dark sky and put it in a file in this directory named 'apikey' before you try to build" +fi DELETED tenki/makefile Index: tenki/makefile ================================================================== --- tenki/makefile +++ tenki/makefile @@ -1,2 +0,0 @@ -tenki: tenki.cc - $(CC) -Ofast tenki.cc -o tenki -lssl ADDED tenki/tenki.c Index: tenki/tenki.c ================================================================== --- tenki/tenki.c +++ tenki/tenki.c @@ -0,0 +1,387 @@ +/* [ʞ] tenki.c + * ~ lexi hale + * > ./make.sh + * © affero general public license + + * tenki is a client for the Dark Sky API. it is a + * bail-early stream parser which retrieves a fixed set + * of keys (this set may be easily changed in the code) + * and prints them as a formatted string to stdout. it + * was designed purely as a polybar plugin to display my + * local weather, but it could be just as easily be + * reworked for many other purposes + * + * you need to get an api key from dark sky before you + * compile tenki, and place it in the file apikey - it's + * segregated from the main code this way to allow it to + * be excluded from version control. + * + * the default language of the results is Japanese; you + * can change this by changing the macro ds_lang from + * "ja" to your language code of choice. the ds_exclude + * macro is used to minimize the bandwidth tenki uses by + * instructing the dark sky server not to send those + * fields back; if you want to access information in any + * of them, you need to remove them from ds_exclude. + * delete the #define entirely if you wish to access all + * of the excluded fields. + * + * the function main() contains most of the code you + * will likely wish to alter. it contains a variable + * "paths" which is used to specify which fields of the + * returned json object you wish the parser to extract. + * each path is a "p-string of p-strings" - that is, its + * first character is an integer which specifies the + * depth of the path, and each "directory" is specified + * with a string prefixed with the number of characters + * in it. so the path "a/bc/def" would be encoded + * "\3\1a\2bc\3def" - \3 for the three constituents "a" + * "bc" and "def", \1 for the strlen of "a", \2 for the + * strlen of "bc", etc. shoutout to the C standards + * committee for forcing me to do this horrible thing. + * + * in conclusion, i should have written this in scheme. + **/ + +#define ds_lang "ja" +#define ds_exclude "minutely,hourly,daily,alerts,flags" + +// posix includes +#include +#include +#include +#include + +// libc includes +#include +#include + +// networking includes +#include +#include +#include +#include + +#ifndef ds_apikey +# error missing api key; compile with make.sh +# define ds_apikey "" // tidy up errors +#endif + +#ifdef ds_exclude +# define _http "?exclude=" ds_exclude "&" +#else +# define _http "?" +#endif +#define http _http "lang=" ds_lang " HTTP/1.1\r\nHost: api.darksky.net\r\n\r\n" +#define start "GET /forecast/" ds_apikey "/" + +#define min(x,y) (x>y?y:x) +#define len(x) (sizeof(x)/sizeof(*x)) + +typedef enum bool { false, true } bool; + +SSL_CTX* sc; +bool head(size_t* len, char* txt, char** body) { + char* hend = strstr(txt, "\r\n\r\n"); + if (hend == NULL) return false; + #define ctlhead "\r\nContent-Length:" + char* ctl = strstr(txt, ctlhead); + if (ctl == NULL || ctl > hend) return false; + ctl += len(ctlhead); + *len = 0; + while(*ctl != '\r') { + if (*ctl >= '0' && *ctl <= '9') *len*=10,*len+=*ctl-'0'; + ++ctl; + } + *len+=(hend-txt)+4; + *body = hend+4; + return true; +} +size_t get(char* resp, size_t max, const char* msg, size_t total, char** body){ + + int port = 443; + const char* host = "api.darksky.net"; + //printf("req: %s\n", msg); + struct hostent* api; + struct sockaddr_in api_addr; + int sockfd, bytes; + size_t sent, recd; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) exit(2); + SSL *con = SSL_new(sc); + SSL_set_fd(con, sockfd); + + api = gethostbyname(host); + if (api == NULL) exit(3); + + memset(&api_addr, 0, sizeof(api_addr)); + api_addr.sin_family = AF_INET; + api_addr.sin_port = htons(port); + memcpy(&api_addr.sin_addr.s_addr, api -> h_addr, api -> h_length); + + if (connect(sockfd,(struct sockaddr*)&api_addr,sizeof(api_addr))) + exit(4); + int err = SSL_connect(con); + + + sent = 0; + do { + bytes = SSL_write(con,msg+sent,total-sent); + if (bytes < 0) exit (4); + if (bytes == 0) break; + ++sent; + } while (sent < total); + recd = 0; + size_t resplen = max; + bool rhead = false; + do { + bytes = SSL_read(con,resp+recd,min(resplen-recd,15000)); + if (bytes < 0) exit (5); + if (bytes == 0) break; + if(!rhead)rhead=head(&resplen, resp, body); + recd += bytes; + } while (recd < resplen - 1); + + close(sockfd); + SSL_shutdown(con); + + return recd; +} +size_t pos(char* msg, const char* lat, const char* lng) { + char* cur = msg + len(start) - 1; + size_t latl = strlen(lat); + size_t longl = strlen(lng); + strcpy(cur, lat); cur += latl; + *cur++=','; + strcpy(cur, lng); cur += longl; + strcpy(cur, http); cur += len(http); + *cur--=0; + return cur-msg; +} +bool estrc(size_t sz, const char* str1, const char* str2) { + for(size_t i = 0;i keyend) return; + if (str > end) return; + /* print_path: { + printf("current path: /"); + for (size_t i = 0; i < depth; ++i) { + printf("%.*s/",strchr(path[i],'"')-path[i],path[i]); + } + printf("\n"); + printf("current key: "); + printkey(*keys); + } */ + parse_char: { + switch(*str++) { + case ' ': case '\t': case '\n': + goto parse_char; + case '"': + path[depth] = str; + goto path_find_quote_end; + case '}': + if (depth == 0) return; + depth--; + while(*str!=',') ++str; ++str; + goto init; + default: + printf("found illegal char %c\n", *(str-1)); + exit(6); + } + } + path_find_quote_end: { + switch (*str++) { + case '\\': ++str; goto path_find_quote_end; + case '"': goto read_value; + default: goto path_find_quote_end; + } + } + read_value: { + if (*str++!=':') { + printf("illegal char\n"); + exit(6); + } + bool iskey; + const char* key = *keys; + jsv* value; + if (depth + 1 != *key++) { + iskey = false; + goto comp; + } else for (size_t i = 0; i <= depth; ++i) { + if (estrc(*key, key+1, path[i])) { + key += *key + 1; + } else { + iskey = false; + goto comp; + } + } + iskey = true; + value = values; + ++keys; ++values; + comp: if (*str == '{') { + ++depth; ++str; + goto init; + } else if (*str == '"') { + goto copy_str_value; + } else if (*str == '[') { + goto skip_array; + } else if ((*str >= '0' && *str <= '9') || *str=='-') { + goto copy_int_value; + } else { + printf("illegal character found in value %c\n", *str); + exit(6); + } + + copy_str_value: { + fld = ++str; + while (*++str != '"') if (*str == '\\') { ++str; continue; } + if (iskey) { + value -> s.sz = str - fld; + value -> s.a = fld; + } + while (*++str != ','); + ++str; goto init; + } + skip_array: { + size_t ard = 0; + skip_loop: switch (*++str) { + case '{': case '[': ++ard; goto skip_loop; + case '"': goto skip_str; + case '}': if (ard == 0) { + printf("bad json\n"); + exit(7); + } else { --ard; goto skip_loop; } + case ']': if (ard == 0) { + if (*++str == ',') { ++str; goto init; } + else goto init; + } else { --ard; } + default: goto skip_loop; + } + skip_str: while (*str != '"') if (*str == '\\') { str += 2; continue; } else ++str; goto skip_loop; + } + copy_int_value: { + if (!iskey) { + while(*str++!=',') if (str > end) return; + goto init; + } + value -> f = 0; + bool neg; + if (*str == '-') { neg=true; ++str; } else neg=false; + while(*str >= '0' && *str <= '9') { + value -> f *= 10; + value -> f += *str - '0'; + ++str; + } + if(*str == '.') { + float fac = 0.1; + float val = 0; + while(*++str >= '0' && *str <= '9') { + val += (((float)(*str - '0')) * fac); + fac *= 0.1; + } + value -> f += val; + } + if(neg) value -> f *= -1; + if (*str == ',') { + ++str; goto init; + } else { + printf("illegal character %.*s\n",15,str); + exit(6); + } + } + } + } +} + +int main(int argc, char** argv) { + if (argc != 3) exit(1); + SSL_load_error_strings(); + SSL_library_init(); + sc = SSL_CTX_new(SSLv23_client_method()); + + char msg[256] = start; + char resp[32368]; + char* body; + + // name the fields to extract from the json stream + // note: MUST BE LISTED IN ORDER THEY APPEAR + // TODO: performance worth lack of flexibility? + const char* paths[] = { + "\2\x9""currently\7summary", + "\2\x9""currently\xfprecipIntensity", + "\2\x9""currently\xbtemperature", + "\2\x9""currently\x9windspeed", + }; + + //copy the latitude and longitude into the request + size_t msgsz = pos(msg, argv[1], argv[2]); + //msgsz = size of what we're going to transmit + + //for (;;) { + size_t rspsz = get(resp, sizeof(resp), msg, msgsz, &body); + // if paths contains the keys, this contains the values + jsv values[len(paths)]; + + parse: { + pplt(body, body + (rspsz - (resp - body)), paths, paths+len(paths), values); + + pstr summary = values[0].s; + float pcpint = values[1].f, + temp = values[2].f, + windspd = values[3].f; + + printf("%.1f° %%{F#ff9bbb}%.*s\n", + summary.sz, + summary.a, + temp); + } + // dark sky's API allows us to make 1000 free requests a day. + // let's try not to get too near that. + // hours in day 24 - minutes in day 24 * 60 = 1440 + // so once a minute is almost half again too many + // 1440 / 2 = 720 leaving reasonable refresh time without + // going too near the limit. so we aim for one update + // every two minutes. + // sleep(60 * 2); +// } + + return 0; +} DELETED tenki/tenki.cc Index: tenki/tenki.cc ================================================================== --- tenki/tenki.cc +++ tenki/tenki.cc @@ -1,332 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ds_apikey "a6aa0337fafd8b8fcfafe84a403ed24f" -#define ds_lang "ja" -#define ds_exclude "minutely,hourly,daily,alerts,flags" - -#define req "GET /forecast/" -#define http "?exclude=" ds_exclude "&lang=" ds_lang " HTTP/1.1\r\nHost: api.darksky.net\r\n\r\n" - -#define len(x) (sizeof(x)/sizeof(*x)) -#define start req ds_apikey "/" -#define min(x,y) (x>y?y:x) - -SSL_CTX* sc; -bool head(size_t* len, char* txt, char** body) { - char* hend = strstr(txt, "\r\n\r\n"); - if (hend == nullptr) return false; - #define ctlhead "\r\nContent-Length:" - char* ctl = strstr(txt, ctlhead); - if (ctl == nullptr or ctl > hend) return false; - ctl += len(ctlhead); - *len = 0; - while(*ctl != '\r') { - if (*ctl >= '0' and *ctl <= '9') *len*=10,*len+=*ctl-'0'; - ++ctl; - } - *len+=(hend-txt)+4; - *body = hend+4; - return true; -} -size_t get(char* resp, size_t max, const char* msg, size_t total, char** body){ - - int port = 443; - const char* host = "api.darksky.net"; - //printf("req: %s\n", msg); - struct hostent* api; - struct sockaddr_in api_addr; - int sockfd, bytes; - size_t sent, recd; - - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) exit(2); - SSL *con = SSL_new(sc); - SSL_set_fd(con, sockfd); - - //printf("getting host\n"); - api = gethostbyname(host); - if (api == nullptr) exit(3); - - memset(&api_addr, 0, sizeof(api_addr)); - api_addr.sin_family = AF_INET; - api_addr.sin_port = htons(port); - memcpy(&api_addr.sin_addr.s_addr, api -> h_addr, api -> h_length); - - //printf("connecting\n"); - if (connect(sockfd,(struct sockaddr*)&api_addr,sizeof(api_addr))) - exit(4); - int err = SSL_connect(con); - - - sent = 0; -// printf("writing\n"); - do { - // printf("writing byte %x\n",*(msg+sent)); - bytes = SSL_write(con,msg+sent,total-sent); - if (bytes < 0) exit (4); - if (bytes == 0) break; - ++sent; - } while (sent < total); -// printf("receiving\n"); - recd = 0; - size_t resplen = max; - bool rhead = false; - do { - // printf("\n\nreading - addr %x; max %d\n",resp+recd,resplen-recd); - bytes = SSL_read(con,resp+recd,min(resplen-recd,15000)); - if (bytes < 0) exit (5); - if (bytes == 0) break; - if(!rhead)rhead=head(&resplen, resp, body); - // printf("got %d bytes: %s", bytes, resp+recd); - recd += bytes; - } while (recd < resplen - 1); - - close(sockfd); - SSL_shutdown(con); - - return recd; -} -size_t pos(char* msg, const char* lat, const char* lng) { - char* cur = msg + len(start) - 1; - size_t latl = strlen(lat); - size_t longl = strlen(lng); - strcpy(cur, lat); cur += latl; - *cur++=','; - strcpy(cur, lng); cur += longl; - strcpy(cur, http); cur += len(http); - *cur--=0; - return cur-msg; -} -bool estrc(size_t sz, const char* str1, const char* str2) { - for(size_t i = 0;i keyend) return; - if (str > end) return; - /* print_path: { - printf("current path: /"); - for (size_t i = 0; i < depth; ++i) { - printf("%.*s/",strchr(path[i],'"')-path[i],path[i]); - } - printf("\n"); - printf("current key: "); - printkey(*keys); - } */ - parse_char: { - switch(*str++) { - case ' ': case '\t': case '\n': - goto parse_char; - case '"': - path[depth] = str; - goto path_find_quote_end; - case '}': - if (depth == 0) return; - depth--; - while(*str!=',') ++str; ++str; - goto init; - default: - printf("found illegal char %c\n", *(str-1)); - exit(6); - } - } - path_find_quote_end: { - switch (*str++) { - case '\\': ++str; goto path_find_quote_end; - case '"': goto read_value; - default: goto path_find_quote_end; - } - } - read_value: { - if (*str++!=':') { - printf("illegal char\n"); - exit(6); - } - bool iskey; - const char* key = *keys; - jsv* value; - if (depth + 1 != *key++) { - iskey = false; - goto comp; - } else for (size_t i = 0; i <= depth; ++i) { - if (estrc(*key, key+1, path[i])) { - key += *key + 1; - } else { - iskey = false; - goto comp; - } - } - iskey = true; - value = values; - ++keys; ++values; - comp: if (*str == '{') { - ++depth; ++str; - goto init; - } else if (*str == '"') { - goto copy_str_value; - } else if (*str == '[') { - goto skip_array; - } else if ((*str >= '0' and *str <= '9') or *str=='-') { - goto copy_int_value; - } else { - printf("illegal character found in value %c\n", *str); - exit(6); - } - - copy_str_value: { - fld = ++str; - while (*++str != '"') if (*str == '\\') { ++str; continue; } - if (iskey) { - value -> s.sz = str - fld; - value -> s.a = fld; - } - while (*++str != ','); - ++str; goto init; - } - skip_array: { - size_t ard = 0; - skip_loop: switch (*++str) { - case '{': case '[': ++ard; goto skip_loop; - case '"': goto skip_str; - case '}': if (ard == 0) { - printf("bad json\n"); - exit(7); - } else { --ard; goto skip_loop; } - case ']': if (ard == 0) { - if (*++str == ',') { ++str; goto init; } - else goto init; - } else { --ard; } - default: goto skip_loop; - } - skip_str: while (*str != '"') if (*str == '\\') { str += 2; continue; } else ++str; goto skip_loop; - } - copy_int_value: { - if (!iskey) { - while(*str++!=',') if (str > end) return; - goto init; - } - value -> f = 0; - bool neg; - if (*str == '-') { neg=true; ++str; } else neg=false; - while(*str >= '0' and *str <= '9') { - value -> f *= 10; - value -> f += *str - '0'; - ++str; - } - if(*str == '.') { - float fac = 0.1; - float val = 0; - while(*++str >= '0' and *str <= '9') { - val += (((float)(*str - '0')) * fac); - fac *= 0.1; - } - value -> f += val; - } - if(neg) value -> f *= -1; - if (*str == ',') { - ++str; goto init; - } else { - printf("illegal character %.*s\n",15,str); - exit(6); - } - } - } - } -} -int main(int argc, char** argv) { - if (argc != 3) exit(1); - SSL_load_error_strings(); - SSL_library_init(); - sc = SSL_CTX_new(SSLv23_client_method()); - - char msg[256] = start; - char resp[32368]; - char* body; - - // name the fields to extract from the json stream - // note: MUST BE LISTED IN ORDER THEY APPEAR - // paths are encoded as sequences of pstrings - // initial value indicates depth of path - // TODO: performance worth lack of flexibility? - const char* paths[] = { - "\2\x9""currently\7summary", - "\2\x9""currently\xfprecipIntensity", - "\2\x9""currently\xbtemperature", - "\2\x9""currently\x9windspeed", - }; - - size_t msgsz = pos(msg, argv[1], argv[2]); - //msgsz = size of transmit - //for (;;) { - size_t rspsz = get(resp, sizeof(resp), msg, msgsz, &body); - // if paths contains the keys, this contains the values - jsv values[len(paths)]; - - parse: { - pplt(body, body + (rspsz - (resp - body)), paths, paths+len(paths), values); - - pstr summary = values[0].s; - float pcpint = values[1].f, - temp = values[2].f, - windspd = values[3].f; - - printf("%.1f° %%{F#ff9bbb}%.*s\n", - summary.sz, - summary.a, - temp); - } - // dark sky's API allows us to make 1000 free requests a day. - // let's try not to get too near that. - // hours in day 24 - minutes in day 24 * 60 = 1440 - // so once a minute is almost half again too many - // 1440 / 2 = 720 leaving reasonable refresh time without - // going too near the limit. so we aim for one update - // every two minutes. - // sleep(60 * 2); -// } - - return 0; -}