From 2a5e31c42603f38e9933e0f0cc1857da618f98c5 Mon Sep 17 00:00:00 2001 From: sijanec Date: Wed, 17 Mar 2021 23:21:37 +0100 Subject: =?UTF-8?q?ve=C4=8D=20napisanega,=20manj=20delujo=C4=8Dega=20(:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 4 +- misc/openssl.supp | 17 +++ misc/valgrind-supp-extractor.c | 16 +++ src/api.c | 242 +++++++++++++++++++++-------------------- src/i18n.h | 8 ++ src/main.c | 53 +++++---- src/ui.c | 188 ++++++++++++++++++++++++++++---- 7 files changed, 371 insertions(+), 157 deletions(-) create mode 100644 misc/openssl.supp create mode 100644 misc/valgrind-supp-extractor.c diff --git a/Makefile b/Makefile index de9b211..5d5f623 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ default: - gcc -Wall -pedantic -g -Ilib -Isrc -I. -pthread src/api.c -lcurl -lncurses -lform -odiscord.c + gcc -Wall -pedantic -g -Ilib -Isrc -I. -pthread src/main.c -lcurl -lncursesw -lformw -lm -odiscord.c prepare: wget https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.c -O lib/cJSON.c wget https://raw.githubusercontent.com/DaveGamble/cJSON/master/cJSON.h -O lib/cJSON.h clean: rm lib/cJSON.c lib/cJSON.h +valgrind: + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=valgrind-out.txt --suppressions=/usr/lib/valgrind/ncurses.supp --suppressions=misc/openssl.supp ./discord.c diff --git a/misc/openssl.supp b/misc/openssl.supp new file mode 100644 index 0000000..21d47cd --- /dev/null +++ b/misc/openssl.supp @@ -0,0 +1,17 @@ +{ + Ignore OpenSSL malloc + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + ... + obj:*libcrypto* +} + +{ + Ignore OpenSSL realloc + Memcheck:Leak + fun:realloc + fun:CRYPTO_realloc + ... + obj:*libcrypto* +} diff --git a/misc/valgrind-supp-extractor.c b/misc/valgrind-supp-extractor.c new file mode 100644 index 0000000..0b56a91 --- /dev/null +++ b/misc/valgrind-supp-extractor.c @@ -0,0 +1,16 @@ +#include +int main (int argc, char ** argv) { + char c = fgetc(stdin); + int s = 0; /* 0: not in supp, 1: in supp */ + while (!feof(stdin)) { + if (c == '}') + s = 0; + if (s) + fputc(c, stdout); + if (c == '{') + s = 1; + c = fgetc(stdin); + } + fflush(stdout); + return 0; +} diff --git a/src/api.c b/src/api.c index a90b8ac..f798aff 100644 --- a/src/api.c +++ b/src/api.c @@ -1,8 +1,4 @@ -#if __INCLUDE_LEVEL__ != 0 #pragma once -#endif -#define _XOPEN_SOURCE 600 -#define _POSIX_C_SOURCE 200809L #include #include #include @@ -18,12 +14,14 @@ #include #include #include +#include #define DC_API_PREFIX "https://discord.com/api/v8/" /* this can be a format string, DO NOT use format characters inside */ #define DC_LOGIN_FORMAT "{\"login\":\"%s\",\"password\":\"%s\",\"undelete\":false,\"captcha_key\":null,\"login_source\":null,\"gift_code_sku_id\":null}" #define DC_USER_AGENT "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36" -#define DC_ERROR(e, s, l, m, ...) dc_push_error(e, s, l, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__) +/* #define DC_ERROR(e, s, l, m, ...) (dc_push_error(e, s, l, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__)) */ +#define DC_ERROR(e, s, l, m, ...) (fprintf(stderr, "%s()@" __FILE__ ":%d: " m "\n", __func__, __LINE__ __VA_OPT__(,) __VA_ARGS__)) #define DC_CLIENT_ERROR(c, m, ...) DC_ERROR(c->errors, &c->errors_sizeof, c->errors_lock, m __VA_OPT__(,) __VA_ARGS__) /* yeah, that m is not a typo */ -#define DC_API(curl, body, endpoint, ...) dc_api(curl, body, 0##__VA_OPT__(1), endpoint __VA_OPT__(,) __VA_ARGS__) +#define DC_CAPI(c, body, endpoint, ...) dc_api(c, body, 0##__VA_OPT__(1), endpoint __VA_OPT__(,) __VA_ARGS__) #define cJSON_GetObjectItem2(root, name1, name2) (cJSON_GetObjectItem(root, name1) ? cJSON_GetObjectItem(cJSON_GetObjectItem(root, name1), name2) : NULL) #define DC_TIMESTAMP_FORMAT "%Y-%m-%dT%H:%M:%S.XXXXXX%z" #define DC_CWLE(c, name) (pthread_rwlock_wrlock(name) ? (DC_CLIENT_ERROR(c, DC_I18N_LOCKING " " #name " " DC_I18N_FAILED) || 1) : 0) @@ -159,26 +157,19 @@ struct dc_client { struct dc_client * dc_client_init () { /* gives you a prepared dc_client */ struct dc_client * c = calloc(1, sizeof(struct dc_client)); c->discriminator = -1; -#define DC_CILI(name) /* Client Init Lock Init */ if (pthread_rwlock_init(name##_lock, NULL)) goto fuck; +#define DC_CILI(name) /* Client Init Lock Init */ do { name##_lock = malloc(sizeof(pthread_rwlock_t)); pthread_rwlock_init(name##_lock, NULL); } while(0) DC_CILI(c->authorization); DC_CILI(c->username); DC_CILI(c->guilds); DC_CILI(c->errors); DC_CILI(c->sent_messages); return c; - fuck: -#define DC_CILD(name) if (name##_lock) pthread_rwlock_destroy(name##_lock) - DC_CILD(c->authorization); - DC_CILD(c->username); - DC_CILD(c->guilds); - DC_CILD(c->errors); - DC_CILD(c->sent_messages); - free(c); - return NULL; /* don't even check for this, if pthread_rwlock_init fails we'll segfault */ } void dc_client_free (struct dc_client * c) { /* noui, noapi, nolock - only called by main on exit */ curl_easy_cleanup(c->curl); + c->curl = NULL; curl_slist_free_all(c->curl_headers); + c->curl_headers = NULL; free(c->authorization); c->authorization = NULL; free(c->email); c->email = NULL; free(c->password); c->password = NULL; @@ -197,6 +188,12 @@ void dc_client_free (struct dc_client * c) { /* noui, noapi, nolock - only calle dc_message_free(c->sent_messages[i]); free(c->sent_messages); c->sent_messages_sizeof = 0; +#define DC_CFLD(name) do { pthread_rwlock_destroy(name##_lock); free(name##_lock); } while(0) + DC_CFLD(c->authorization); + DC_CFLD(c->username); + DC_CFLD(c->guilds); + DC_CFLD(c->errors); + DC_CFLD(c->sent_messages); free(c); } int dc_push_error (struct dc_error ** e, _Atomic size_t * s, pthread_rwlock_t * lock, const char * c, char * f, size_t l, unsigned short int isfmt, char * m, ...) { @@ -228,38 +225,90 @@ int dc_push_error (struct dc_error ** e, _Atomic size_t * s, pthread_rwlock_t * return -2; return 1; } -cJSON * dc_api (CURL * curl, char * body, int isfmt, char * endpoint, ...) { /* note: format arguments are evaluated twice */ - if (!curl) +cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, ...) { /* note: format arguments are evaluated twice */ + if (!c) return NULL; - if (!endpoint) + if (!c->curl) { + DC_CLIENT_ERROR(c, "!c->curl"); + } + if (!c->curl) { + DC_CLIENT_ERROR(c, "!c->curl"); return NULL; + } + if (!endpoint) { + DC_CLIENT_ERROR(c, "!endpoint"); + return NULL; + } cJSON * json = NULL; - struct writefunc_string s; - init_writefunc_string(&s); + struct writefunc_string s, h; size_t va_count = parse_printf_format(endpoint, 0, NULL); char * endpoint_formatted = NULL; - if (isfmt && va_count > 0) { + int retried = 0; + long response_code = 0; + retry: + init_writefunc_string(&s); + init_writefunc_string(&h); + if (isfmt && va_count > 0 && endpoint_formatted == NULL) { va_list ap, ap2; va_start(ap, endpoint); va_copy(ap2, ap); size_t strlenm = vsnprintf(NULL, 0, endpoint, ap); endpoint_formatted = malloc(sizeof(char)*strlenm+1); - vsnprintf(endpoint_formatted, strlenm, endpoint, ap2); /* sn instead of s because double evaulation may produce */ + vsnprintf(endpoint_formatted, strlenm+1, endpoint, ap2); /* sn instead of s because double evaulation may produce */ va_end(ap); /* larger output the next time and lead to overflow */ va_end(ap2); } - curl_easy_setopt(curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint); + curl_easy_setopt(c->curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint); if (!body) - curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); - else - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); - if (curl_easy_perform(curl) != CURLE_OK) + curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(c->curl, CURLOPT_POSTFIELDS, body); /* yes, even null is okay, it's actually a must as it makes sure curl clears old pointers and does not read freed memory. see https://github.com/curl/curl/issues/3214#issuecomment-435335974 */ + curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(c->curl, CURLOPT_HEADERDATA, &h); + curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, writefunc); + if (curl_easy_perform(c->curl) != CURLE_OK) { + DC_CLIENT_ERROR(c, "curl_easy_perform(curl) != CURLE_OK"); goto rc; + } else { + curl_easy_getinfo(c->curl, CURLINFO_RESPONSE_CODE, &response_code); + } + fprintf(netreq, "%s\n%s\n%s\n%s====================================\n", endpoint_formatted ? endpoint_formatted : endpoint, body ? body : "GET", h.ptr, s.ptr); + fflush(netreq); + char * cp = strstr(h.ptr, "\nx-ratelimit-reset-after: "); + if (cp == NULL) + goto norlheaders; + cp += strlen("\nx-ratelimit-reset-after: "); + double retry_after = strtod(cp, NULL); + cp = strstr(h.ptr, "\nx-ratelimit-remaining: "); + if (cp == NULL) + goto norlheaders; + cp += strlen("\nx-ratelimit-remaining: "); + if (cp[0] >= '0' && cp[0] <= '9' && atoi(cp) == 0) { /* X-RateLimit-Remaining: 0 */ + DC_CLIENT_ERROR(c, DC_I18N_HITRL " %lfs. endpoint = %s", retry_after, endpoint_formatted ? endpoint_formatted : endpoint); + sleep(ceil(retry_after)+1); /* TODO: prevent hanging entire thread just for this */ + free(s.ptr); s.ptr = NULL; + free(h.ptr); h.ptr = NULL; + cJSON_Delete(json); + json = NULL; + if (retried++) + return NULL; + goto retry; + } + norlheaders: json = cJSON_Parse(s.ptr); + if (!json) { + const char * error_ptr = cJSON_GetErrorPtr(); + if (error_ptr) { + DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %.21s s.ptr = %s", error_ptr, s.ptr); + } else { + DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ". s.ptr = %s", s.ptr ? s.ptr : "NULL"); + } + goto rc; + } rc: + curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, NULL); /* for usages that don't use headerfunction */ free(endpoint_formatted); free(s.ptr); s.ptr = NULL; + free(h.ptr); h.ptr = NULL; return json; } int dc_login (struct dc_client * c) { /* noui */ @@ -280,9 +329,9 @@ int dc_login (struct dc_client * c) { /* noui */ return -3; } init_writefunc_string(&s); - data = malloc(strlen(DC_LOGIN_FORMAT)+strlen(c->email)+strlen(c->password)+1); - CURLcode res; + data = malloc(snprintf(data, 0, DC_LOGIN_FORMAT, c->email, c->password)+1); sprintf(data, DC_LOGIN_FORMAT, c->email, c->password); + CURLcode res; curl_slist_free_all(c->curl_headers); c->curl_headers = curl_slist_append(c->curl_headers, "Content-Type: application/json"); c->curl_headers = curl_slist_append(c->curl_headers, "User-Agent: " DC_USER_AGENT); @@ -301,7 +350,7 @@ int dc_login (struct dc_client * c) { /* noui */ json = cJSON_Parse(s.ptr); if (!json) { const char *error_ptr = cJSON_GetErrorPtr(); - if (!error_ptr) { + if (error_ptr) { DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr); } else { DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED); @@ -318,14 +367,15 @@ int dc_login (struct dc_client * c) { /* noui */ if (DC_CWLE(c, c->authorization_lock)) {rs = -7; goto rc;} c->authorization = realloc(c->authorization, strlen(token->valuestring)+1); strcpy(c->authorization, token->valuestring); - data = realloc(data, strlen(c->authorization)+strlen("Authorization: ")+1); - strcpy(data, "Authorization: "); + data = realloc(data, strlen(c->authorization)+strlen("Authorization: ")+1+1/* last one because valgrind was complaining */); + strcpy(data, "Authorization: "); strcat(data, c->authorization); if (DC_CUE(c, c->authorization_lock)) {rs = -8; goto rc;} free(s.ptr); s.ptr = NULL; init_writefunc_string(&s); curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "users/@me"); curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(c->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s); c->curl_headers = curl_slist_append(c->curl_headers, data); res = curl_easy_perform(c->curl); @@ -338,7 +388,7 @@ int dc_login (struct dc_client * c) { /* noui */ json = cJSON_Parse(s.ptr); if (!json) { const char *error_ptr = cJSON_GetErrorPtr(); - if (!error_ptr) { + if (error_ptr) { DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr); } else { DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED); @@ -368,10 +418,8 @@ int dc_fetch_guilds (struct dc_client * c) { if (!c) return -5; int rs = 1; - struct writefunc_string s; char * value = NULL; char * value2 = NULL; - CURLcode res; cJSON * json = NULL; if (c->discriminator < 0) { if ((rs = dc_login(c)) < 0) { @@ -379,24 +427,9 @@ int dc_fetch_guilds (struct dc_client * c) { return -1; } else rs = 1; } - curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "users/@me/guilds"); - curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s); - init_writefunc_string(&s); - res = curl_easy_perform(c->curl); - if (res != CURLE_OK) { - DC_CLIENT_ERROR(c, "curl_easy_perform(c->curl) " DC_I18N_FAILED ": %s", curl_easy_strerror(res)); - rs = -2; - goto rc; - } - json = cJSON_Parse(s.ptr); + json = DC_CAPI(c, NULL, DC_API_PREFIX "users/@me/guilds"); if (!json) { - const char *error_ptr = cJSON_GetErrorPtr(); - if (!error_ptr) { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr); - } else { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED); - } + DC_CLIENT_ERROR(c, "DC_CAPI users/@me/guilds " DC_I18N_FAILED); rs = -3; goto rc; } @@ -408,10 +441,12 @@ int dc_fetch_guilds (struct dc_client * c) { if(DC_CWLE(c, c->guilds_lock)) {rs = -5; goto rc;} cJSON * guild = NULL; cJSON_ArrayForEach(guild, json) { - value = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id")); - value2 = cJSON_GetStringValue(cJSON_GetObjectItem(json, "name")); + value = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "name")); + value2 = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "id")); if (!value || !value2) { - DC_CLIENT_ERROR(c, "!cJSON_GetStringValue(cJSON_GetObjectItem(json, \"id\" || \"name\"))"); + char * jsonstr = cJSON_Print(json); + DC_CLIENT_ERROR(c, "!cJSON_GetStringValue(cJSON_GetObjectItem(json, \"id\" || \"name\")) json = %s", jsonstr); + free(jsonstr); continue; } unsigned long long int idull = strtoull(value2, NULL, 10); @@ -419,6 +454,7 @@ int dc_fetch_guilds (struct dc_client * c) { if (idull == c->guilds[i]->id) continue; /* remove duplicates */ c->guilds = realloc(c->guilds, sizeof(struct dc_guild *)*++c->guilds_sizeof); + c->guilds[c->guilds_sizeof-1] = malloc(sizeof(struct dc_guild)); c->guilds[c->guilds_sizeof-1]->name = malloc(strlen(value)+1); strcpy(c->guilds[c->guilds_sizeof-1]->name, value); c->guilds[c->guilds_sizeof-1]->id = idull; @@ -428,7 +464,6 @@ int dc_fetch_guilds (struct dc_client * c) { } if(DC_CUE(c, c->guilds_lock)) {rs = -6; goto rc;} rc: - free(s.ptr); s.ptr = NULL; cJSON_Delete(json); json = NULL; return rs; } @@ -446,34 +481,17 @@ int dc_fetch_channels (struct dc_guild * g) { return -3; } else rs = 1; } - CURLcode res; - struct writefunc_string s; - init_writefunc_string(&s); cJSON * json = NULL; - char * url = malloc(strlen(DC_API_PREFIX "guilds/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/channels")+1); - sprintf(url, DC_API_PREFIX "guilds/%llu/channels", g->id); /* only api thread writes ids, so this is safe without locks */ - curl_easy_setopt(c->curl, CURLOPT_URL, url); - curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); - curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, &s); - res = curl_easy_perform(c->curl); - if (res != CURLE_OK) { - DC_CLIENT_ERROR(c, "curl_easy_perform(c->curl) " DC_I18N_FAILED ": %s", curl_easy_strerror(res)); - rs = -4; - goto rc; - } - json = cJSON_Parse(s.ptr); + json = DC_CAPI(c, NULL, DC_API_PREFIX "guilds/%llu/channels", g->id); /* ids are only written by api thread, nolock */ if (!json) { - const char *error_ptr = cJSON_GetErrorPtr(); - if (!error_ptr) { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr); - } else { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED); - } + DC_CLIENT_ERROR(c, "DC_CAPI guilds/%llu/channels " DC_I18N_FAILED, g->id); rs = -5; goto rc; } if (!cJSON_IsArray(json)) { - DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), s.ptr = %s", s.ptr); + char * jsonstr = cJSON_Print(json); + DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), g->id = %llu, json = %s", g->id, jsonstr); + free(jsonstr); rs = -6; goto rc; } @@ -481,10 +499,10 @@ int dc_fetch_channels (struct dc_guild * g) { /* we lock all client guilds when doing stuff with channels */ if (DC_CWLE(c, c->guilds_lock)) {rs = -7; goto rc;} cJSON_ArrayForEach(channel, json) { - char * topic = cJSON_GetStringValue(cJSON_GetObjectItem(json, "topic")); - char * name = cJSON_GetStringValue(cJSON_GetObjectItem(json, "name")); - char * id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id")); - double type = cJSON_GetNumberValue(cJSON_GetObjectItem(json, "type")); + char * topic = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "topic")); + char * name = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "name")); + char * id = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "id")); + double type = cJSON_GetNumberValue(cJSON_GetObjectItem(channel, "type")); if (!id || !name || type == NAN) { DC_CLIENT_ERROR(c, "!id || !name || type == NAN"); continue; @@ -498,6 +516,7 @@ int dc_fetch_channels (struct dc_guild * g) { if (idull == g->channels[i]->id) continue; /* remove duplicates */ g->channels = realloc(g->channels, sizeof(struct dc_channel *)*++g->channels_sizeof); + g->channels[g->channels_sizeof-1] = malloc(sizeof(struct dc_channel)); g->channels[g->channels_sizeof-1]->name = malloc(strlen(name)+1); strcpy(g->channels[g->channels_sizeof-1]->name, name); g->channels[g->channels_sizeof-1]->topic = malloc(strlen(topic)+1); @@ -509,8 +528,6 @@ int dc_fetch_channels (struct dc_guild * g) { } if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;} rc: - free(s.ptr); s.ptr = NULL; - free(url); url = NULL; cJSON_Delete(json); json = NULL; return rs; } @@ -542,14 +559,9 @@ int dc_send_message (struct dc_message * m) { /* nolock - once message is append } cJSON_Delete(json); json = NULL; /* {content: "yeet", nonce: "820762917392613376", tts: false} */ - json = DC_API(c->curl, body, DC_API_PREFIX "channels/%llu/messages", m->channel->id); /* ids are only written by api thread, nolock */ + json = DC_CAPI(c, body, DC_API_PREFIX "channels/%llu/messages", m->channel->id); /* ids are only written by api thread, nolock */ if (!json) { - const char *error_ptr = cJSON_GetErrorPtr(); - if (!error_ptr) { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr); - } else { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED); - } + DC_CLIENT_ERROR(c, "DC_CAPI channels/%llu/messages " DC_I18N_FAILED, m->channel->id); rs = -5; goto rc; } @@ -590,14 +602,9 @@ int dc_fetch_messages (struct dc_channel * ch) { return -4; } else rs = 1; } - cJSON * json = DC_API(c->curl, NULL, DC_API_PREFIX "channels/%llu/messages?limit=100&_=%d", ch->id, rand()); + cJSON * json = DC_CAPI(c, NULL, DC_API_PREFIX "channels/%llu/messages?limit=100&_=%d", ch->id, rand()); if (!json) { - const char *error_ptr = cJSON_GetErrorPtr(); - if (!error_ptr) { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED ": " DC_I18N_JSON_ERROR_BEFORE ": %s", error_ptr); - } else { - DC_CLIENT_ERROR(c, "cJSON_Parse " DC_I18N_FAILED); - } + DC_CLIENT_ERROR(c, "DC_CAPI channels/%llu/messages?limit=100&_=***rand()*** " DC_I18N_FAILED, ch->id); rs = -5; goto rc; } @@ -609,11 +616,11 @@ int dc_fetch_messages (struct dc_channel * ch) { if (DC_CWLE(c, c->guilds_lock)) {rs = -7; goto rc;} /* we lock all guilds of a client when writing messages */ cJSON * message = NULL; cJSON_ArrayForEach(message, json) { - char * timestamp = cJSON_GetStringValue(cJSON_GetObjectItem(json, "timestamp")); - char * content = cJSON_GetStringValue(cJSON_GetObjectItem(json, "content")); - char * id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id")); - char * discriminator = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "discriminator")); - char * username = cJSON_GetStringValue(cJSON_GetObjectItem2(json, "author", "username")); + char * timestamp = cJSON_GetStringValue(cJSON_GetObjectItem(message, "timestamp")); + char * content = cJSON_GetStringValue(cJSON_GetObjectItem(message, "content")); + char * id = cJSON_GetStringValue(cJSON_GetObjectItem(message, "id")); + char * discriminator = cJSON_GetStringValue(cJSON_GetObjectItem2(message, "author", "discriminator")); + char * username = cJSON_GetStringValue(cJSON_GetObjectItem2(message, "author", "username")); if (!id || !timestamp || !content || !username || !discriminator) { DC_CLIENT_ERROR(c, "!id || !timestamp || !content || !username || discriminator < 0"); continue; @@ -632,8 +639,9 @@ int dc_fetch_messages (struct dc_channel * ch) { for (int i = 0; i < ch->messages_sizeof; i++) if (idull == ch->messages[i]->id) continue; /* remove duplicates */ - ch->messages = realloc(ch->messages, sizeof(struct dc_message *)**+ch->messages_sizeof); + ch->messages = realloc(ch->messages, sizeof(struct dc_message *)*++ch->messages_sizeof); #define DC_FMTM /* fetch messages this message */ ch->messages[ch->messages_sizeof-1] + DC_FMTM = malloc(sizeof(struct dc_message)); DC_FMTM->time = mktime(&tm); DC_FMTM->content = malloc(strlen(content)+1); strcpy(DC_FMTM->content, content); @@ -650,8 +658,9 @@ int dc_fetch_messages (struct dc_channel * ch) { return rs; } struct dc_thread_control { - unsigned short int power_api; /* 1 if the thread should run, set it to 0 for the thread to return at the end of the loop */ + unsigned short int power_api; /* 1 if the thread should run, set it to 2 for the thread to return at the end of the loop */ unsigned short int power_ui; /* so same struct can be used for both api and ui thread, they have individual power switches */ + /* powers have a default state of 0, in this state, the thread should be paused, before first clearing 0 the thread should not do anything! */ struct dc_client ** clients; /* "array" of pointers to clients the thread should manage, ONLY ONE dc_api_thread PER PROCESS! */ _Atomic(size_t) clients_sizeof; /* noapiw */ pthread_rwlock_t * clients_lock; /* do not lock yet. you can use safe-appending from the ui thread by never reallocing the pointers and only incrementing _sizeof - don't even init&destroy*/ @@ -660,38 +669,41 @@ struct dc_thread_control { FILE * cerr; /* file descriptor of the terminal for the ui thread to write error messages to */ }; int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends messages when they are in the outbox */ + while (!t->power_api) usleep(250000); /* so as to not make the switcher go bankrupt */ /* if (pthread_rwlock_wrlock(t->clients_lock)) return -1; */ /* clients are not locked yet */ - for (int i = 0; i < t->clients_sizeof; i++) + for (int i = 0; i < t->clients_sizeof && t->power_api != 2; i++) dc_login(t->clients[i]); - while (t->power_api == 1) { /* as there's only one api thread and only it modifies things that need a guilds_lock, it's okay to */ + while (t->power_api != 2) { /* as there's only one api thread and only it modifies things that need a guilds_lock, it's okay to */ for (int i = 0; i < t->clients_sizeof; i++) { /* perform such unsafe loops without read-locking. note that you will deadlock */ if (t->clients[i]->discriminator < -1) /* should you attempt to read-lock guilds_lock. */ continue; /* the only exception is sent_messages that is write-locked */ - if (!t->clients[i]->guilds_sizeof || !(rand() % 1000)) /* roughly every 1000 cycles we'll update guilds */ + if (!t->clients[i]->guilds_sizeof /* || !(rand() % 1000) */) /* roughly every 1000+inf cycles we'll update guilds */ dc_fetch_guilds(t->clients[i]); for (int j = 0; j < t->clients[i]->guilds_sizeof; j++) { - if (!t->clients[i]->guilds[j]->channels_sizeof || !(rand() % 100)) /* roughly every 100 cycles we'll update channels */ + if (!t->clients[i]->guilds[j]->channels_sizeof /*|| !(rand() % 100)*/) /* roughly every 100+inf cycles we'll update channels */ dc_fetch_channels(t->clients[i]->guilds[j]); for (int k = 0; k < t->clients[i]->guilds[j]->channels_sizeof; k++) if (!(rand() % 10)) /* roughly every 10 cycles we'll update messages */ dc_fetch_messages(t->clients[i]->guilds[j]->channels[k]); } if (DC_CWLE(t->clients[i], t->clients[i]->sent_messages_lock)) continue; - if (t->clients[i]->sent_messages_sizeof > 0) {/* no need to lock as only one thread does this */ + if (t->clients[i]->sent_messages_sizeof > 0) { struct dc_message * msg2send = t->clients[i]->sent_messages[0]; if (dc_send_message(msg2send) > 0) { - DC_CWLE(t->clients[i], t->clients[i]->guilds_lock); + /* DC_CWLE(t->clients[i], t->clients[i]->guilds_lock); msg2send->channel->messages = realloc(msg2send->channel->messages, sizeof(struct dc_message *)*++msg2send->channel->messages_sizeof); msg2send->channel->messages[msg2send->channel->messages_sizeof-1] = msg2send; - DC_CUE(t->clients[i], t->clients[i]->guilds_lock); + DC_CUE(t->clients[i], t->clients[i]->guilds_lock); */ /* we will no longer do this, let the thread fetch the msg */ for (int j = 0; j <= t->clients[i]->sent_messages_sizeof; j++) /* shift, we removed one from the start */ t->clients[i]->sent_messages[j] = t->clients[i]->sent_messages[j+1]; t->clients[i]->sent_messages_sizeof--; } } - while (DC_CUE(t->clients[i], t->clients[i]->sent_messages_lock)); + DC_CUE(t->clients[i], t->clients[i]->sent_messages_lock); + while (!t->power_api) usleep(250000); /* so as to not make the switcher go bankrupt */ } + usleep(250000); } /* if (pthread_rwlock_unlock(t->clients_lock)) return -2; */ diff --git a/src/i18n.h b/src/i18n.h index 81e7d70..7cf244f 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -12,3 +12,11 @@ #define DC_I18N_JSON_ERROR_BEFORE "JSON napaka pred" #define DC_I18N_LOCKING "zaklepanje" #define DC_I18N_UNLOCKING "odklepanje" +#define DC_I18N_ERROR "napaka" +#define DC_I18N_MSGTIMEF "%a, %e. %b %H:%M:%S" /* strftime(3) */ +#define DC_I18N_UI_USAGE "uporaba" +#define DC_I18N_UI_CHANNELS_USAGE "/kanali <številka skupine (0-%lu)>" +#define DC_I18N_UI_JOIN_USAGE "/pridruži <številka skupine (0-%lu)> <številka kanala v tej skupini (0-%lu)>" +#define DC_I18N_UI_LINE_BEFORE_JOIN "discord.c | uporabi ukaz /s za prikaz skupin, /k za prikaz kanalov in /p za priklop v kanal" +#define DC_I18N_HITRL "zmanjkalo dovoljenih zahtev na strežnik. API nit bo čakala " /* and then print seconds */ +#define DC_I18N_UI_CNF "nepoznan ukaz" diff --git a/src/main.c b/src/main.c index 28b1449..d9ec103 100644 --- a/src/main.c +++ b/src/main.c @@ -1,26 +1,31 @@ -#include +#define _XOPEN_SOURCE 600 +#include +FILE * netreq; +#include int main (int argc, char ** argv) { + curl_global_init(CURL_GLOBAL_ALL); srand(time(NULL)); + netreq = fopen("netreq.log", "w"); int rs = 0; int opt; pthread_t api_thread, ui_thread; - int api_ret ui_ret; + int api_ret, ui_ret; struct dc_client * c = dc_client_init(); - struct dc_thread_control = { - .power_api = 1; - .power_ui = 1; - .clients = &c; - .clients_sizeof = 1; - .cout = stdout; - .cin = stdin; - .cerr = stderr; + struct dc_thread_control t = { + .power_api = 0, + .power_ui = 1, /* so we don't start without user interface lol */ + .clients = &c, + .clients_sizeof = 1, + .cout = stdout, + .cin = stdin, + .cerr = stderr }; while ((opt = getopt(argc, argv, "e:p:h")) != -1) { switch (opt) { case 'h': - fprintf(stdout, DC_I18N_USAGE); - dc_client_free(c); - return 0; + fprintf(stdout, DC_I18N_USAGE, argv[0]); + rs = 4; + goto rc; break; case 'e': c->email = malloc(strlen(optarg)+1); @@ -33,14 +38,15 @@ int main (int argc, char ** argv) { default: fprintf(stderr, DC_I18N_UNREC_ARG "\n", opt); dc_client_free(c); - return 1; + rs = 1; + goto rc; } } if (!c->email) { if (!getenv("DC_E")) { fprintf(stderr, DC_I18N_MISSING_EP "\n"); - dc_client_free(c); - return 2; + rs = 2; + goto rc; } c->email = malloc(strlen(getenv("DC_E"))+1); strcpy(c->email, getenv("DC_E")); @@ -48,16 +54,23 @@ int main (int argc, char ** argv) { if (!c->password) { if (!getenv("DC_P")) { fprintf(stderr, DC_I18N_MISSING_EP "\n"); - dc_client_free(c); - return 3; + rs = 3; + goto rc; } c->password = malloc(strlen(getenv("DC_P"))+1); strcpy(c->password, getenv("DC_P")); } - api_ret = pthread_create(&api_thread NULL, dc_api_thread, dc_thread_control); - ui_ret = pthread_create(&ui_thread, NULL, dc_ui_thread, dc_thread_control); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + api_ret = pthread_create(&api_thread, NULL, dc_api_thread, &t); + ui_ret = pthread_create(&ui_thread, NULL, dc_ui_thread, &t); +#pragma GCC diagnostic pop pthread_join(api_thread, NULL); pthread_join(ui_thread, NULL); rc: + fclose(netreq); + dc_client_free(c); + if (api_ret || ui_ret); /* to hide warnings */ + curl_global_cleanup(); return rs; } diff --git a/src/ui.c b/src/ui.c index 3f8dbaf..2578689 100644 --- a/src/ui.c +++ b/src/ui.c @@ -1,34 +1,123 @@ -#define _XOPEN_SOURCE 600 -#define _POSIX_C_SOURCE 200809L +#pragma once #include #include #include -/* #include */ +#include #include #include #include #include +#include +#include +#define DC_SIMPLEPRINT(w, c, f, ...) do { wattron(w, COLOR_PAIR(c)); wprintw(w, f __VA_OPT__(,) __VA_ARGS__); wrefresh(w); } while (0) /* link with -lncursesw and -lformw */ -/* int dc_ui_thread (struct * dc_thread_control t) { */ -int main () { +int dc_ui_print_message (WINDOW * textwin, struct dc_message * msg2do) { + char timestring[64]; + struct tm timestruct; + localtime_r(&msg2do->time, ×truct); + strftime(timestring, 64, DC_I18N_MSGTIMEF, ×truct); /* recimo, da je 23 znakov */ + DC_SIMPLEPRINT(textwin, 3, "%023.23s %08.8s: %s\n", timestring, msg2do->username, msg2do->content); + return 1; +} +int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin) { + struct dc_client * c = t->clients[0]; + /* first we trim spaces at the end */ + int i, j, k; + for (i = strlen(l)-1; i >= 0; i--) + if (l[i] == ' ') + l[i] = '\0'; + else + break; + if (l[0] == '/') + switch (l[1]) { + case 'g': + case 'G': + case 's': + case 'S': /* servers */ + DC_CRLE(c, c->guilds_lock); + for (i = 0; i < c->guilds_sizeof; i++) + DC_SIMPLEPRINT(textwin, 4, " %02d. %s\n", i, c->guilds[i]->name); + DC_CUE(c, c->guilds_lock); + break; + case 'c': + case 'C': + case 'k': + case 'K': + DC_CRLE(c, c->guilds_lock); + if (!strchr(l, ' ') || (j = atoi(strchr(l, ' ')+1)) >= c->guilds_sizeof) { + DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_USAGE ": " DC_I18N_UI_CHANNELS_USAGE "\n", c->guilds_sizeof-1); + DC_CUE(c, c->guilds_lock); + break; + } + for (i = 0; i < c->guilds[j]->channels_sizeof; i++) + DC_SIMPLEPRINT(textwin, 4, " %02d. %s - %s\n", i, c->guilds[j]->channels[i]->name, c->guilds[j]->channels[i]->topic); + DC_CUE(c, c->guilds_lock); + break; + case 'j': + case 'J': + case 'p': + case 'P': + DC_CRLE(c, c->guilds_lock); + char * jp; + if (!(jp = strchr(l, ' ')) || (j = atoi(jp+1)) >= c->guilds_sizeof) { + DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_USAGE ": " DC_I18N_UI_JOIN_USAGE "\n", c->guilds_sizeof-1, 999); + DC_CUE(c, c->guilds_lock); + break; + } + if (!strchr(jp+1, ' ') || (k = atoi(strchr(jp+1, ' ')+1)) >= c->guilds[j]->channels_sizeof) { + DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_USAGE ": " DC_I18N_UI_JOIN_USAGE "\n", c->guilds_sizeof-1, c->guilds[j]->channels_sizeof-1); + DC_CUE(c, c->guilds_lock); + break; + } + for (i = 0; i < c->guilds[j]->channels[k]->messages_sizeof; i++) + dc_ui_print_message(textwin, c->guilds[j]->channels[k]->messages[i]); + c->joinedchannel = c->guilds[j]->channels[k]; + DC_CUE(c, c->guilds_lock); + + break; + case 'q': + case 'Q': + case 'i': + case 'I': + t->power_api = 2; /* 2 for shutdown */ + t->power_ui = 2; + break; + case 'N': /* api nit (thread) control */ + case 'n': + if (!strchr(l, ' ')) { + DC_SIMPLEPRINT(textwin, 1, "!strchr(l, ' ')\n"); + break; + } + t->power_api = atoi(strchr(l, ' ')+1); + DC_SIMPLEPRINT(textwin, 4, "t->power_api = %d\n", atoi(strchr(l, ' ')+1)); + default: + DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_CNF "\n"); + } + wrefresh(textwin); + return 1; +} +int dc_ui_thread (struct dc_thread_control * t) { + while (!t->power_ui) usleep(250000); FIELD * field[2]; /* field[0] je polje z besedilom */ field[1] = NULL; FORM * form; int ret, x, y; wint_t ch; + struct dc_client * c = t->clients[0]; initscr(); cbreak(); noecho(); nodelay(stdscr, TRUE); setlocale(LC_ALL, "sl_SI.UTF-8"); start_color(); - init_pair(0, COLOR_WHITE, COLOR_BLACK); + init_pair(2, COLOR_YELLOW, COLOR_BLACK); init_pair(1, COLOR_RED, COLOR_BLACK); - attron(COLOR_PAIR(1)); + init_pair(3, COLOR_WHITE, COLOR_BLACK); + init_pair(4, COLOR_GREEN, COLOR_BLACK); keypad(stdscr, TRUE); getmaxyx(stdscr, y, x); /* to je macro, zato y in x nista kazalca (;: */ - WINDOW * textwin = newwin(y-3, x, 0, 0); - WINDOW * formwin = newwin(2, x, y-2, 0); + WINDOW * textwin = subwin(stdscr, y-3, x, 0, 0); + WINDOW * formwin = subwin(stdscr, 2, x, y-2, 0); scrollok(textwin, TRUE); field[0] = new_field(2, x, y-2, 0, 5 /* offscreen rows */, 0); set_field_back(field[0], A_UNDERLINE); @@ -38,15 +127,43 @@ int main () { post_form(form); int i = 0; wmove(textwin, 0, 0); - time_t last_time = 0; refresh(); - while (1) { - if (last_time < time(NULL)) { - last_time = time(NULL); - attron(COLOR_PAIR(0)); - wprintw(textwin, "\nvrstica stevilka %d", i); - attron(COLOR_PAIR(1)); - mvprintw(y-3, 0, "#piš-ti-kurc-2 | discord.c | šijanec 2021"); + struct dc_channel * prev_joinedchannel = (void *)&i; /* just so we get something that isn't null without warnings (): */ + while (t->power_ui != 2) { + if (!(rand() % 100)) { /* roughly every 100 cycles we get errors and messages */ + assert(!DC_CRLE(c, c->errors_lock)); + for (int i = 0; i < c->errors_sizeof; i++) { + if (!c->errors[i]->reported) { + DC_SIMPLEPRINT(textwin, 1, "[" DC_I18N_ERROR "] %s()@%s:%lu: %s", c->errors[i]->function, c->errors[i]->file, c->errors[i]->line, c->errors[i]->message); + c->errors[i]->reported = 1; + } + } + assert(!pthread_rwlock_unlock(c->errors_lock)); /* deadlock if we unlock errors with error reporting, duh */ + if (c->joinedchannel) { + DC_CRLE(c, c->guilds_lock); + for (int i = 0; i < c->joinedchannel->messages_sizeof; i++) { + struct dc_message * msg2do = c->joinedchannel->messages[i]; + if (!msg2do->status) { + dc_ui_print_message(textwin, msg2do); + msg2do->status = 1; + } + } + DC_CUE(c, c->guilds_lock); + } + if (prev_joinedchannel != c->joinedchannel) { + curs_set(0); /* too flashy */ + attron(COLOR_PAIR(2)); + if (c->joinedchannel) { + DC_CRLE(c, c->guilds_lock); + mvprintw(y-3, 0, "#%s @ %s | %s", c->joinedchannel->name, c->joinedchannel->guild->name, c->joinedchannel->topic); + DC_CUE(c, c->guilds_lock); + } + else + mvprintw(y-3, 0, DC_I18N_UI_LINE_BEFORE_JOIN); + curs_set(1); + prev_joinedchannel = c->joinedchannel; + } + pos_form_cursor(form); } ret = get_wch(&ch); if (ret != ERR) { @@ -61,20 +178,49 @@ int main () { form_driver(form, REQ_DEL_PREV); break; case KEY_DOWN: - form_driver(form, REQ_SCR_FLINE); + form_driver(form, REQ_NEXT_LINE); break; case KEY_UP: - form_driver(form, REQ_SCR_BLINE); + form_driver(form, REQ_PREV_LINE); + break; + case KEY_DC: + form_driver(form, REQ_DEL_CHAR); + break; + case KEY_END: + form_driver(form, REQ_END_LINE); + break; + case KEY_HOME: + form_driver(form, REQ_BEG_LINE); + break; + case KEY_SLEFT: + form_driver(form, REQ_PREV_WORD); + break; + case KEY_SRIGHT: + form_driver(form, REQ_NEXT_WORD); + break; + case KEY_SDC: + form_driver(form, REQ_CLR_FIELD); + break; + case KEY_ENTER: + case 10: + form_driver(form, REQ_NEXT_FIELD); + form_driver(form, REQ_PREV_FIELD); + dc_ui_processline(t, field_buffer(field[0], 0), textwin); + form_driver(form, REQ_CLR_FIELD); + pos_form_cursor(form); break; default: form_driver_w(form, ret, ch); break; } + wrefresh(formwin); } - wnoutrefresh(stdscr); + /* wnoutrefresh(stdscr); wnoutrefresh(textwin); - doupdate(); + doupdate(); */ i++; + usleep(2500); + while (t->power_ui == 0) usleep(250000); } unpost_form(form); free_form(form); -- cgit v1.2.3