diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api.c | 126 | ||||
-rw-r--r-- | src/i18n.h | 1 | ||||
-rw-r--r-- | src/ui.c | 35 |
3 files changed, 113 insertions, 49 deletions
@@ -14,6 +14,7 @@ #include <pthread.h> #include <stdatomic.h> #include <math.h> +#include <signal.h> #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" @@ -23,6 +24,7 @@ #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_SHORTTIMESTAMP_FORMAT "%Y-%m-%dT%T%z" #define DC_CWLE(c, name) (pthread_rwlock_wrlock(name) ? (DC_CLIENT_ERROR(c, DC_I18N_LOCKING " " #name " " DC_I18N_FAILED) || 1) : 0) #define DC_CRLE(c, name) (pthread_rwlock_rdlock(name) ? (DC_CLIENT_ERROR(c, DC_I18N_LOCKING " " #name " " DC_I18N_FAILED) || 1) : 0) #define DC_CUE(c, name) (pthread_rwlock_unlock(name) ?(DC_CLIENT_ERROR(c, DC_I18N_UNLOCKING " " #name " " DC_I18N_FAILED) || 1) : 0) @@ -125,6 +127,7 @@ struct dc_guild { _Atomic(size_t) channels_sizeof; /* nouiw */ struct dc_channel ** channels; /* yesfree, nouiw */ _Atomic(struct dc_client *) client; /* nofree - obviously */ + char * altmsgurl; /* yesfree - alternative messages url - used for virtual guild for DMs */ }; void dc_guild_free (struct dc_guild * g) { /* noui, noapi, nolock - only called by dc_client_free */ free(g->name); g->name = NULL; @@ -133,6 +136,7 @@ void dc_guild_free (struct dc_guild * g) { /* noui, noapi, nolock - only called dc_channel_free(g->channels[i]); free(g->channels); g->channels_sizeof = 0; + free(g->altmsgurl); free(g); } struct dc_client { @@ -160,6 +164,14 @@ 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; + c->guilds_sizeof = 1; + c->guilds = calloc(1, sizeof(struct dc_guild *)); + c->guilds[0] = calloc(1, sizeof(struct dc_guild)); + c->guilds[0]->name = malloc(strlen(DC_I18N_DMS)+1); + strcpy(c->guilds[0]->name, DC_I18N_DMS); + c->guilds[0]->altmsgurl = malloc(strlen(DC_API_PREFIX "users/@me/channels")+1); + strcpy(c->guilds[0]->altmsgurl, DC_API_PREFIX "users/@me/channels"); + c->guilds[0]->client = c; #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); @@ -289,18 +301,24 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . fprintf(netreq, "%s\n%s\n%s\n%s====================================\n", endpoint_formatted ? endpoint_formatted : endpoint, body ? body : "GET", h->ptr, s->ptr); fflush(netreq); #endif - 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 */ + 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; + } + /* note that the server does not send x-ratelimit headers for normal users and cJSON has problems with parsing floats, so caveman approach is needed */ + char * cp = cJSON_GetStringValue(cJSON_GetObjectItem(json, "retry_after")); + if (cp && (cp = strstr(cp, "\"retry_after\": "))) { /* note: make sure to always check if retry_after is in the root of the object, otherwise you can get dosed if a user somehow inserts "retry_after": into a message or something. we could check error code though, but naaaah */ + if (!(cp = strchr(cp, ' '))) + goto rc; + int retry_after = atoi(++cp)+1; 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 */ + sleep(retry_after); /* TODO: prevent hanging entire thread just for this */ free(s->ptr); s->ptr = NULL; free(h->ptr); h->ptr = NULL; free(s); s = NULL; @@ -311,17 +329,6 @@ cJSON * dc_api (struct dc_client * c, char * body, int isfmt, char * endpoint, . 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); @@ -440,12 +447,10 @@ int dc_fetch_guilds (struct dc_client * c) { } 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] = calloc(1, 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; - c->guilds[c->guilds_sizeof-1]->channels_sizeof = 0; - c->guilds[c->guilds_sizeof-1]->channels = NULL; c->guilds[c->guilds_sizeof-1]->client = c; } if(DC_CUE(c, c->guilds_lock)) {rs = -6; goto rc;} @@ -455,9 +460,8 @@ int dc_fetch_guilds (struct dc_client * c) { } int dc_fetch_channels (struct dc_guild * g) { int rs = 1; - if (!g || !g->id) { + if (!g) return -1; - } struct dc_client * c = g->client; if (!c) return -2; @@ -468,15 +472,18 @@ int dc_fetch_channels (struct dc_guild * g) { } else rs = 1; } cJSON * json = NULL; - json = DC_CAPI(c, NULL, DC_API_PREFIX "guilds/%llu/channels", g->id); /* ids are only written by api thread, nolock */ + if (g->altmsgurl) + json = DC_CAPI(c, NULL, g->altmsgurl); /* used for private dm channels */ + else + json = DC_CAPI(c, NULL, DC_API_PREFIX "guilds/%llu/channels", g->id); /* ids are only written by api thread, nolock */ if (!json) { - DC_CLIENT_ERROR(c, "DC_CAPI guilds/%llu/channels " DC_I18N_FAILED, g->id); + DC_CLIENT_ERROR(c, "DC_CAPI id = %llu, g->altmsgurl = %s" DC_I18N_FAILED, g->id, g->altmsgurl ? g->altmsgurl : "NULL" /* static string */); rs = -5; goto rc; } if (!cJSON_IsArray(json)) { char * jsonstr = cJSON_Print(json); - DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), g->id = %llu, json = %s", g->id, jsonstr); + DC_CLIENT_ERROR(c, "!cJSON_IsArray(json), g->id = %llu, g->altmsgurl = %s, json = %s", g->id, g->altmsgurl ? g->altmsgurl : "NULL", jsonstr); free(jsonstr); rs = -6; goto rc; @@ -498,12 +505,30 @@ int dc_fetch_channels (struct dc_guild * g) { char * id = cJSON_GetStringValue(cJSON_GetObjectItem(channel, "id")); cJSON * type = cJSON_GetObjectItem(channel, "type"); cJSON * jsonslowmode = cJSON_GetObjectItem(channel, "rate_limit_per_user"); - if (!cJSON_IsNumber(type) || !id || !name) { - DC_CLIENT_ERROR(c, "!cJSON_IsNumber(jsontype) || !id || !name"); + if (!cJSON_IsNumber(type) || !id) { + char * krneki = cJSON_Print(channel); + DC_CLIENT_ERROR(c, "!cJSON_IsNumber(type) || !id, channel = %s", krneki); + free(krneki); krneki = NULL; continue; } - if (type->valueint != 0) /* if it's not a text channel (z. B. voice channel, category, ...) */ + if (type->valueint != 0 && type->valueint != 3 && type->valueint != 1) continue; + if (!name) { + cJSON * recipients = cJSON_GetObjectItem(channel, "recipients"); + if (!cJSON_IsArray(recipients)) + continue; + cJSON * rec = NULL; + name = calloc(1, 1); + cJSON_ArrayForEach(rec, recipients) { + char * recipient = cJSON_GetStringValue(cJSON_GetObjectItem(rec, "username")); + if (!recipient) + continue; + name = realloc(name, strlen(name)+strlen(recipient)+3); /* +3: comma, space, null character */ + strcat(name, recipient); + strcat(name, ", "); + } + name[strlen(name)-2] = '\0'; /* odrežemo zadnja dva znaka; vejico in presledek I: */ + } int slowmode = 0; if (cJSON_IsNumber(jsonslowmode)) slowmode = jsonslowmode->valueint; @@ -524,11 +549,7 @@ int dc_fetch_channels (struct dc_guild * g) { strcpy(g->channels[g->channels_sizeof-1]->topic, topic); g->channels[g->channels_sizeof-1]->id = strtoull(id, NULL, 10); 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; - g->channels[g->channels_sizeof-1]->joined = 0; - g->channels[g->channels_sizeof-1]->focused = 0; } if (DC_CUE(c, c->guilds_lock)) {rs = -8; goto rc;} rc: @@ -632,15 +653,30 @@ int dc_fetch_messages (struct dc_channel * ch) { DC_CLIENT_ERROR(c, "!id || !timestamp || !content || !username || discriminator < 0"); continue; } + int kratekts = 0; if (strlen(timestamp) < 26) { - DC_CLIENT_ERROR(c, "strlen(timestamp) < 26"); - continue; + if (strlen(timestamp) == strlen("2021-03-21T13:54:17+00:00")) { + kratekts = 1; + } else { + char * jsonstring = cJSON_Print(message); + DC_CLIENT_ERROR(c, "strlen(timestamp) < 26, json = %s", jsonstring); + free(jsonstring); + jsonstring = NULL; + continue; + } } - for (int i = 20; i <= 25; i++) - timestamp[i] = 'X'; /* because strptime does not have wildcard support and those numbers are sub-second fractions */ - if (!strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm)) { - DC_CLIENT_ERROR(c, "strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm) " DC_I18N_FAILED); - continue; + if (!kratekts) { + for (int i = 20; i <= 25; i++) + timestamp[i] = 'X'; /* because strptime does not have wildcard support and those numbers are sub-second fractions */ + if (!strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm)) { + DC_CLIENT_ERROR(c, "!strptime(timestamp, DC_TIMESTAMP_FORMAT, &tm), timestamp = %s", timestamp); + continue; + } + } else { /* if there are no subsecond fractions */ + if (!strptime(timestamp, DC_SHORTTIMESTAMP_FORMAT, &tm)) { + DC_CLIENT_ERROR(c, "!strptime(timestamp, DC_SHORTTIMESTAMP_FORMAT, &tm), timestamp = %s", timestamp); + continue; + } } unsigned long long int idull = strtoull(id, NULL, 10); for (int i = 0; i < ch->messages_sizeof; i++) @@ -693,7 +729,7 @@ int dc_api_thread (struct dc_thread_control * t) { /* updates messages and sends 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+inf cycles we'll update guilds */ + if (t->clients[i]->guilds_sizeof <= 1 /* || !(rand() % 1000) */) /* guild 0 is a virtual ZS guild */ 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+inf cycles we'll update channels */ @@ -25,3 +25,4 @@ #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." #define DC_I18N_UI_LINE_BEFORE_NETWORK "discord.c | najprej zaženi mrežno nit z ukazom /n 1" +#define DC_I18N_DMS "zasebni pogovori (virtualna skupina kanalov)" @@ -33,7 +33,11 @@ int dc_ui_print_message (WINDOW * textwin, struct dc_message * msg2do) { if (x); /* set but not used */ return 1; } -int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin) { +struct dc_ui { + int maxy; /* max line of screen */ + int maxx; /* max column of screen */ +}; +int dc_ui_processline (struct dc_thread_control * t, char * l, WINDOW * textwin, struct dc_ui ui) { struct dc_client * c = t->clients[0]; /* first we trim spaces at the end */ int i = 0, j = 0, k = 0, m = 0, n = 0; @@ -64,8 +68,19 @@ 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_sizeof; i++) - DC_SIMPLEPRINT(textwin, 4, " %02d. %s - %s\n", i, c->guilds[j]->channels[i]->name, c->guilds[j]->channels[i]->topic); + for (i = 0; i < c->guilds[j]->channels_sizeof; i += 2) { + int y = 0; + int x = 0; + getyx(textwin, y, x); + DC_SIMPLEPRINT(textwin, 4, " %02d. %s - %s", i, c->guilds[j]->channels[i]->name, c->guilds[j]->channels[i]->topic); + if (c->guilds[j]->channels_sizeof-1 == i) { + DC_SIMPLEPRINT(textwin, 1, "\n"); + break; + } + wmove(textwin, y, ui.maxx/2); + if (x); /* prevent unused warnings */ + DC_SIMPLEPRINT(textwin, 7, " %02d. %s - %s\n", i+1, c->guilds[j]->channels[i+1]->name, c->guilds[j]->channels[i+1]->topic); + } DC_CUE(c, c->guilds_lock); break; case 'j': @@ -165,6 +180,7 @@ int dc_ui_thread (struct dc_thread_control * t) { int ret, x, y; wint_t ch; struct dc_client * c = t->clients[0]; + struct dc_ui ui; initscr(); cbreak(); noecho(); @@ -177,8 +193,11 @@ int dc_ui_thread (struct dc_thread_control * t) { init_pair(4, COLOR_GREEN, COLOR_BLACK); init_pair(5, COLOR_CYAN, COLOR_BLACK); init_pair(6, COLOR_BLACK, COLOR_CYAN); + init_pair(7, COLOR_MAGENTA, COLOR_BLACK); keypad(stdscr, TRUE); getmaxyx(stdscr, y, x); /* to je macro, zato y in x nista kazalca (;: */ + ui.maxy = y; + ui.maxx = x; WINDOW * textwin = subwin(stdscr, y-3, x, 0, 0); WINDOW * formwin = subwin(stdscr, 2, x, y-2, 0); scrollok(textwin, TRUE); @@ -291,6 +310,13 @@ int dc_ui_thread (struct dc_thread_control * t) { case KEY_SDC: form_driver(form, REQ_CLR_FIELD); break; + /* case KEY_NPAGE: + wscrl(textwin, 10); + break; + case KEY_PPAGE: + wscrl(textwin, -10); + break; + */ /* you wish! ncurses does not keep scrollback. i could use fancy features such as pads, but I'll just make a gui instd */ case 9: /* idk fucken keybd */ case KEY_STAB: /* switch to next channel for sending */ dc_null(); @@ -325,7 +351,7 @@ int dc_ui_thread (struct dc_thread_control * t) { case 10: form_driver(form, REQ_NEXT_FIELD); form_driver(form, REQ_PREV_FIELD); - if (dc_ui_processline(t, field_buffer(field[0], 0), textwin) > 0) + if (dc_ui_processline(t, field_buffer(field[0], 0), textwin, ui) > 0) form_driver(form, REQ_CLR_FIELD); pos_form_cursor(form); updinforow++; @@ -335,6 +361,7 @@ int dc_ui_thread (struct dc_thread_control * t) { break; } wrefresh(formwin); + /* wrefresh(textwin); */ } i++; usleep(2500); |