From 59004bab38044f694853188d537b2fc545abeb0b Mon Sep 17 00:00:00 2001 From: sijanec Date: Thu, 18 Mar 2021 14:18:29 +0100 Subject: finished first PoC code --- .gitignore | 4 ++ Makefile | 2 +- src/api.c | 189 +++++++++++++++++++++++++++++++------------------------------ src/i18n.h | 6 +- src/lib.c | 4 +- src/main.c | 2 - src/ui.c | 58 ++++++++++++++----- 7 files changed, 152 insertions(+), 113 deletions(-) diff --git a/.gitignore b/.gitignore index 6097dbc..74a1ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ lib/cJSON.h lib/cJSON.c # the following is the binary discord.c +# debugging log files +netreq.log +stderr.log +valgrind-out.txt diff --git a/Makefile b/Makefile index 5d5f623..1ea322b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ default: - gcc -Wall -pedantic -g -Ilib -Isrc -I. -pthread src/main.c -lcurl -lncursesw -lformw -lm -odiscord.c + gcc -Wall -pedantic -g -Ilib -Isrc -I. -pthread $$(ncursesw6-config --cflags --libs) src/main.c -lcurl -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 diff --git a/src/api.c b/src/api.c index f798aff..e456e53 100644 --- a/src/api.c +++ b/src/api.c @@ -18,9 +18,9 @@ #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, ...) (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_ERROR(c, m, ...) (dc_push_error(c, __func__, __FILE__, __LINE__, 0##__VA_OPT__(1), m __VA_OPT__(,) __VA_ARGS__)) +/* #define DC_ERROR(c, m, ...) (fprintf(stderr, "%s()@" __FILE__ ":%d: " m "\n", __func__, __LINE__ __VA_OPT__(,) __VA_ARGS__)) */ +#define DC_CLIENT_ERROR(c, m, ...) DC_ERROR(c, m __VA_OPT__(,) __VA_ARGS__) /* yeah, that m is not a typo */ #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" @@ -105,6 +105,7 @@ struct dc_channel { _Atomic(struct dc_guild *) guild; /* nofree, nouiw */ struct dc_message ** messages; /* yesfree, nouiw */ _Atomic(size_t) messages_sizeof; /* nouiw */ + int slowmode; /* number of seconds to wait in case of slowmode, HANDLED BY THE USER INTERFACE!! */ }; void dc_channel_free (struct dc_channel * ch) { /* noui, noapi, nolock - only called by dc_guild_free */ free(ch->name); ch->name = NULL; @@ -153,6 +154,7 @@ struct dc_client { struct dc_message ** sent_messages; /* yesfree - ui appends, api pops and moves to messages */ _Atomic(size_t) sent_messages_sizeof; pthread_rwlock_t * sent_messages_lock; + _Atomic(time_t) last_sent_message; /* for slowmode implementations */ }; struct dc_client * dc_client_init () { /* gives you a prepared dc_client */ struct dc_client * c = calloc(1, sizeof(struct dc_client)); @@ -196,11 +198,17 @@ void dc_client_free (struct dc_client * c) { /* noui, noapi, nolock - only calle 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, ...) { - if (lock && pthread_rwlock_wrlock(lock)) +int dc_push_error (struct dc_client * c, const char * caller, char * f, size_t l, unsigned short int isfmt, char * m, ...) { +#define DC_PEE /* dc scalnia oziroma push error error */ c->errors[c->errors_sizeof-1] + if (!c) + return -2; + pthread_rwlock_t * lock = c->errors_lock; + if (!lock) + return -3; + if (pthread_rwlock_wrlock(lock)) return -1; /* does not report an error as that may make things even worse. I could try writing to stderr here but meh */ - e = realloc(e, sizeof(struct dc_error *)*++*s); /* note: format arguments are evaluated twice */ - e[*s-1] = malloc(sizeof(struct dc_error)); + c->errors = realloc(c->errors, sizeof(struct dc_error *)*++c->errors_sizeof); /* note: format arguments are evaluated twice */ + DC_PEE = malloc(sizeof(struct dc_error)); size_t strlenm = strlen(m); size_t va_count = parse_printf_format(m, 0, NULL); if (isfmt && va_count > 0) { @@ -208,19 +216,19 @@ int dc_push_error (struct dc_error ** e, _Atomic size_t * s, pthread_rwlock_t * va_start(ap, m); va_copy(ap2, ap); strlenm = vsnprintf(NULL, 0, m, ap); - e[*s-1]->message = malloc(sizeof(char)*strlenm+1); - vsnprintf(e[*s-1]->message, strlenm+1, m, ap2); + DC_PEE->message = malloc(sizeof(char)*strlenm+1); + vsnprintf(DC_PEE->message, strlenm+1, m, ap2); va_end(ap); va_end(ap2); } else { - e[*s-1]->message = malloc(sizeof(char)*strlenm+1); - strcpy(e[*s-1]->message, m); - } - e[*s-1]->file = f; - e[*s-1]->line = l; - e[*s-1]->function = c /* Caller */; - e[*s-1]->time = time(NULL); - e[*s-1]->reported = 0; + DC_PEE->message = malloc(sizeof(char)*strlenm+1); + strcpy(DC_PEE->message, m); + } + DC_PEE->file = f; + DC_PEE->line = l; + DC_PEE->function = caller /* Caller */; + DC_PEE->time = time(NULL); + DC_PEE->reported = 0; if (lock && pthread_rwlock_unlock(lock)) return -2; return 1; @@ -240,14 +248,15 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . return NULL; } cJSON * json = NULL; - struct writefunc_string s, h; + struct writefunc_string * s = malloc(sizeof(struct writefunc_string)); + struct writefunc_string * h = malloc(sizeof(struct writefunc_string)); size_t va_count = parse_printf_format(endpoint, 0, NULL); char * endpoint_formatted = NULL; int retried = 0; long response_code = 0; retry: - init_writefunc_string(&s); - init_writefunc_string(&h); + init_writefunc_string(s); + init_writefunc_string(h); if (isfmt && va_count > 0 && endpoint_formatted == NULL) { va_list ap, ap2; va_start(ap, endpoint); @@ -259,34 +268,41 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . va_end(ap2); } curl_easy_setopt(c->curl, CURLOPT_URL, endpoint_formatted ? endpoint_formatted : endpoint); - if (!body) - 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); + if (!body) + curl_easy_setopt(c->curl, CURLOPT_HTTPGET, 1L); /* this must be done after postfields */ + curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, s); + curl_easy_setopt(c->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(c->curl, CURLOPT_HEADERDATA, h); + curl_easy_setopt(c->curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(c->curl, CURLOPT_HTTPHEADER, c->curl_headers); + curl_easy_setopt(c->curl, CURLOPT_TIMEOUT, 20L); /* 20 second timeout */ curl_easy_setopt(c->curl, CURLOPT_HEADERFUNCTION, writefunc); + curl_easy_setopt(c->curl, CURLOPT_WRITEFUNCTION, 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); + 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: "); + 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: "); + 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; + free(s->ptr); s->ptr = NULL; + free(h->ptr); h->ptr = NULL; + free(s); s = NULL; + free(h); h = NULL; cJSON_Delete(json); json = NULL; if (retried++) @@ -294,26 +310,27 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . goto retry; } norlheaders: - json = cJSON_Parse(s.ptr); + 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); + 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"); + 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; + free(s->ptr); s->ptr = NULL; + free(h->ptr); h->ptr = NULL; + free(s); s = NULL; + free(h); h = NULL; return json; } int dc_login (struct dc_client * c) { /* noui */ int rs = 1; - struct writefunc_string s; char * data = NULL; cJSON * json = NULL; if (!c) @@ -328,34 +345,15 @@ int dc_login (struct dc_client * c) { /* noui */ DC_CLIENT_ERROR(c, "curl_easy_init() " DC_I18N_FAILED); return -3; } - init_writefunc_string(&s); - data = malloc(snprintf(data, 0, DC_LOGIN_FORMAT, c->email, c->password)+1); + data = malloc(snprintf(NULL, 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); + /* 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); - curl_easy_setopt(c->curl, CURLOPT_URL, DC_API_PREFIX "auth/login"); - curl_easy_setopt(c->curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(c->curl, CURLOPT_POSTFIELDS, data); - curl_easy_setopt(c->curl, CURLOPT_WRITEFUNCTION, writefunc); - curl_easy_setopt(c->curl, CURLOPT_HTTPHEADER, c->curl_headers); - 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() " DC_I18N_FAILED ": %s", curl_easy_strerror(res)); /* yeah, format strings are supported */ - rs = -4; - goto rc; - } - json = cJSON_Parse(s.ptr); + json = DC_CAPI(c, data, DC_API_PREFIX "auth/login"); 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); - } - rs = -5; + DC_CLIENT_ERROR(c, "!DC_CAPI auth/login, data = %s", data); + rs = -4; goto rc; } cJSON * token = cJSON_GetObjectItem(json, "token"); @@ -371,29 +369,12 @@ int dc_login (struct dc_client * c) { /* noui */ 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); - if (res != CURLE_OK) { - DC_CLIENT_ERROR(c, "curl_easy_perform() " DC_I18N_FAILED ": %s", curl_easy_strerror(res)); - rs = -9; - goto rc; - } cJSON_Delete(json); - json = cJSON_Parse(s.ptr); + json = DC_CAPI(c, NULL, DC_API_PREFIX "users/@me"); 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); - } - rs = -10; + DC_CLIENT_ERROR(c, "!DC_CAPI users/@me"); + rs = -7; goto rc; } token = cJSON_GetObjectItem(json, "username"); @@ -409,7 +390,6 @@ int dc_login (struct dc_client * c) { /* noui */ if (DC_CUE(c, c->username_lock)) {rs = -12; goto rc;} c->discriminator = strtol(token2->valuestring, NULL, 10); rc: - free(s.ptr); s.ptr = NULL; free(data); data = NULL; cJSON_Delete(json); return rs; @@ -441,6 +421,7 @@ 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) { + int skip = 0; value = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "name")); value2 = cJSON_GetStringValue(cJSON_GetObjectItem(guild, "id")); if (!value || !value2) { @@ -451,8 +432,11 @@ int dc_fetch_guilds (struct dc_client * c) { } unsigned long long int idull = strtoull(value2, NULL, 10); for (int i = 0; i < c->guilds_sizeof; i++) - if (idull == c->guilds[i]->id) - continue; /* remove duplicates */ + if (idull == c->guilds[i]->id) { + skip = 1; + break; /* remove duplicates */ + } + if (skip) continue; 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); @@ -496,13 +480,22 @@ int dc_fetch_channels (struct dc_guild * g) { goto rc; } cJSON * channel = NULL; + cJSON * perms = NULL; + cJSON * perm = NULL; /* 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) { + int skip = 0; + if (cJSON_IsArray(perms = cJSON_GetObjectItem(channel, "permission_overwrites"))); /* not supported */ + cJSON_ArrayForEach(perm, perms) { + skip++; + } + if (skip) continue; 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")); + double slowmode = cJSON_GetNumberValue(cJSON_GetObjectItem(channel, "rate_limit_per_user")); if (!id || !name || type == NAN) { DC_CLIENT_ERROR(c, "!id || !name || type == NAN"); continue; @@ -513,8 +506,11 @@ int dc_fetch_channels (struct dc_guild * g) { topic = ""; unsigned long long int idull = strtoull(id, NULL, 10); for (int i = 0; i < g->channels_sizeof; i++) - if (idull == g->channels[i]->id) - continue; /* remove duplicates */ + if (idull == g->channels[i]->id) { + skip++; + break; + } + if (skip) continue; 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); @@ -525,6 +521,7 @@ int dc_fetch_channels (struct dc_guild * g) { g->channels[g->channels_sizeof-1]->guild = g; g->channels[g->channels_sizeof-1]->messages = NULL; g->channels[g->channels_sizeof-1]->messages_sizeof = 0; + g->channels[g->channels_sizeof-1]->slowmode = slowmode != NAN ? slowmode : 0; } if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;} rc: @@ -583,6 +580,7 @@ int dc_send_message (struct dc_message * m) { /* nolock - once message is append rc: free(body); body = NULL; cJSON_Delete(json); json = NULL; + c->last_sent_message = time(NULL); return rs; } int dc_fetch_messages (struct dc_channel * ch) { @@ -616,6 +614,7 @@ 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) { + int skip = 0; char * timestamp = cJSON_GetStringValue(cJSON_GetObjectItem(message, "timestamp")); char * content = cJSON_GetStringValue(cJSON_GetObjectItem(message, "content")); char * id = cJSON_GetStringValue(cJSON_GetObjectItem(message, "id")); @@ -637,11 +636,15 @@ int dc_fetch_messages (struct dc_channel * ch) { } unsigned long long int idull = strtoull(id, NULL, 10); for (int i = 0; i < ch->messages_sizeof; i++) - if (idull == ch->messages[i]->id) - continue; /* remove duplicates */ + if (idull == ch->messages[i]->id) { + skip++; + break; /* remove duplicates */ + } + if (skip) continue; 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_CLIENT_ERROR(c, "recvd msg %llu", idull); */ /* remember: continue in a nested forloop is not useful in some cases (: */ + DC_FMTM = calloc(1, sizeof(struct dc_message)); DC_FMTM->time = mktime(&tm); DC_FMTM->content = malloc(strlen(content)+1); strcpy(DC_FMTM->content, content); @@ -651,7 +654,7 @@ int dc_fetch_messages (struct dc_channel * ch) { DC_FMTM->discriminator = strtol(discriminator, NULL, 10); DC_FMTM->channel = ch; } - qsort(ch->messages, ch->messages_sizeof, sizeof(struct dc_message), dc_message_compare); /* we sort so that present messages are in the start of the array and old messages are to the end of the array */ + qsort(ch->messages, ch->messages_sizeof, sizeof(struct dc_message *), dc_message_compare); /* we sort so that present messages are in the start of the array and old messages are to the end of the array */ if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;} rc: cJSON_Delete(json); json = NULL; @@ -670,6 +673,7 @@ struct dc_thread_control { }; 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 */ + curl_global_init(CURL_GLOBAL_ALL); /* if (pthread_rwlock_wrlock(t->clients_lock)) return -1; */ /* clients are not locked yet */ for (int i = 0; i < t->clients_sizeof && t->power_api != 2; i++) @@ -683,9 +687,9 @@ int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends for (int j = 0; j < t->clients[i]->guilds_sizeof; j++) { 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]); + /* for (int k = 0; k < t->clients[i]->guilds[j]->channels_sizeof; k++) */ + if (!(rand() % 10) && t->clients[i]->joinedchannel) /* roughly every 10 cycles we'll update messages in the joined ch */ + dc_fetch_messages(t->clients[i]->joinedchannel); } if (DC_CWLE(t->clients[i], t->clients[i]->sent_messages_lock)) continue; if (t->clients[i]->sent_messages_sizeof > 0) { @@ -695,7 +699,7 @@ int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends 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); */ /* 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 */ + for (int j = 0; j < t->clients[i]->sent_messages_sizeof-1; 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--; } @@ -705,6 +709,7 @@ int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends } usleep(250000); } + curl_global_cleanup(); /* if (pthread_rwlock_unlock(t->clients_lock)) return -2; */ return 1; diff --git a/src/i18n.h b/src/i18n.h index 7cf244f..955d810 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -13,10 +13,14 @@ #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_MSGTIMEF "%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" +#define DC_I18N_UI_NOT_JOINED "niste pridruženi v kanal. uporabite ukaz /pridruži" +#define DC_I18N_CHANNEL_WILL_BE_REMOVED "kanal bo odstranjen iz lokalnega seznama kanalov" +#define DC_I18N_UI_EMPTYMSG "ne moreš poslati praznega sporočila." +#define DC_I18N_UI_SLOWMODE "na tem kanalu po poslanem sporočilu novega ne smeš poslati %ds. počakaj še %ds in poskusi znova." diff --git a/src/lib.c b/src/lib.c index b6a5f3a..5a611a3 100644 --- a/src/lib.c +++ b/src/lib.c @@ -4,7 +4,7 @@ struct writefunc_string { }; void init_writefunc_string(struct writefunc_string *s) { s->len = 0; - s->ptr = malloc(s->len+1); + s->ptr = malloc(s->len+1+250); if (s->ptr == NULL) { fprintf(stderr, "malloc() " DC_I18N_FAILED "\n"); exit(EXIT_FAILURE); @@ -13,7 +13,7 @@ void init_writefunc_string(struct writefunc_string *s) { } size_t writefunc(void *ptr, size_t size, size_t nmemb, struct writefunc_string *s) { size_t new_len = s->len + size*nmemb; - s->ptr = realloc(s->ptr, new_len+1); + s->ptr = realloc(s->ptr, new_len+1+250); if (s->ptr == NULL) { fprintf(stderr, "realloc() " DC_I18N_FAILED "\n"); exit(EXIT_FAILURE); diff --git a/src/main.c b/src/main.c index d9ec103..340af42 100644 --- a/src/main.c +++ b/src/main.c @@ -3,7 +3,6 @@ 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; @@ -71,6 +70,5 @@ int main (int argc, char ** argv) { 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 2578689..7bcd070 100644 --- a/src/ui.c +++ b/src/ui.c @@ -9,6 +9,7 @@ #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_print_message (WINDOW * textwin, struct dc_message * msg2do) { @@ -16,7 +17,8 @@ int dc_ui_print_message (WINDOW * textwin, struct dc_message * msg2do) { 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); + DC_SIMPLEPRINT(textwin, 3, "%018.18s %08.8s: %s\n", timestring, msg2do->username, msg2do->content); + msg2do->status = 1; return 1; } int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin) { @@ -69,11 +71,10 @@ int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin) DC_CUE(c, c->guilds_lock); break; } - for (i = 0; i < c->guilds[j]->channels[k]->messages_sizeof; i++) + for (i = c->guilds[j]->channels[k]->messages_sizeof-1; i >= 0; 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': @@ -90,9 +91,35 @@ int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin) } t->power_api = atoi(strchr(l, ' ')+1); DC_SIMPLEPRINT(textwin, 4, "t->power_api = %d\n", atoi(strchr(l, ' ')+1)); + break; default: DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_CNF "\n"); } + else { /* send the message, it's not a command */ + if (!c->joinedchannel) { + DC_SIMPLEPRINT(textwin, 1, "!c->joinedchannel - %s\n", DC_I18N_UI_NOT_JOINED); + return -1; + } + if (!strlen(l)) { + DC_SIMPLEPRINT(textwin, 1, "!strlen(l) - %s\n", DC_I18N_UI_EMPTYMSG); + return -2; + } + if (time(NULL) - c->last_sent_message <= c->joinedchannel->slowmode) { + DC_SIMPLEPRINT(textwin, 1, DC_I18N_UI_SLOWMODE "\n", c->joinedchannel->slowmode, c->joinedchannel->slowmode-(time(NULL)-c->last_sent_message)); + return -3; + } + c->last_sent_message = time(NULL); /* because the other thread may not update counter before the next message is sent */ + /* raise(SIGINT); */ /* To continue from here in GDB: "signal 0". */ + DC_CWLE(c, c->sent_messages_lock); + c->sent_messages = realloc(c->sent_messages, sizeof(struct dc_message *)*++c->sent_messages_sizeof); +#define DC_UISM c->sent_messages[c->sent_messages_sizeof-1] /* ui send messaeg */ + DC_UISM = calloc(1, sizeof(struct dc_message)); + DC_UISM->content = malloc(strlen(l)+1); + strcpy(DC_UISM->content, l); + DC_UISM->channel = c->joinedchannel; + DC_CUE(c, c->sent_messages_lock); + /* DO NOT free it */ + } wrefresh(textwin); return 1; } @@ -108,12 +135,13 @@ int dc_ui_thread (struct dc_thread_control * t) { cbreak(); noecho(); nodelay(stdscr, TRUE); - setlocale(LC_ALL, "sl_SI.UTF-8"); + setlocale(LC_ALL, ""); start_color(); init_pair(2, COLOR_YELLOW, COLOR_BLACK); init_pair(1, COLOR_RED, COLOR_BLACK); init_pair(3, COLOR_WHITE, COLOR_BLACK); init_pair(4, COLOR_GREEN, COLOR_BLACK); + init_pair(5, COLOR_CYAN, COLOR_BLACK); keypad(stdscr, TRUE); getmaxyx(stdscr, y, x); /* to je macro, zato y in x nista kazalca (;: */ WINDOW * textwin = subwin(stdscr, y-3, x, 0, 0); @@ -130,7 +158,7 @@ int dc_ui_thread (struct dc_thread_control * t) { refresh(); 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 */ + if (!(rand() % 10)) { /* roughly every 10 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) { @@ -141,25 +169,25 @@ int dc_ui_thread (struct dc_thread_control * t) { 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++) { + for (int i = c->joinedchannel->messages_sizeof-1; i >= 0; i--) { struct dc_message * msg2do = c->joinedchannel->messages[i]; - if (!msg2do->status) { + 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)); + attron(COLOR_PAIR(5)); 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); + mvprintw(y-3, 0, "#%s @ %s%s%s ", c->joinedchannel->name, c->joinedchannel->guild->name, + strlen(c->joinedchannel->topic) ? " - " : "", c->joinedchannel->topic); DC_CUE(c, c->guilds_lock); - } - else + } else { + attron(COLOR_PAIR(2)); mvprintw(y-3, 0, DC_I18N_UI_LINE_BEFORE_JOIN); + } curs_set(1); prev_joinedchannel = c->joinedchannel; } @@ -205,8 +233,8 @@ int dc_ui_thread (struct dc_thread_control * t) { 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); + if (dc_ui_processline(t, field_buffer(field[0], 0), textwin) > 0) + form_driver(form, REQ_CLR_FIELD); pos_form_cursor(form); break; default: -- cgit v1.2.3