summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/api.c126
-rw-r--r--src/i18n.h1
-rw-r--r--src/ui.c35
3 files changed, 113 insertions, 49 deletions
diff --git a/src/api.c b/src/api.c
index b74367e..cf24448 100644
--- a/src/api.c
+++ b/src/api.c
@@ -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 */
diff --git a/src/i18n.h b/src/i18n.h
index 1aa7a88..1468e83 100644
--- a/src/i18n.h
+++ b/src/i18n.h
@@ -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)"
diff --git a/src/ui.c b/src/ui.c
index df52ac2..e741335 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -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);