summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2021-09-16 19:40:14 +0200
committerAnton Luka Šijanec <anton@sijanec.eu>2021-09-16 19:40:14 +0200
commit82e87b0744a07613ec00779d0cf05bdbfbd2689d (patch)
tree6c645743660bad830d12b67fc883e561e7b93970 /src
parentexperimenting with lejp (diff)
downloaddiscord.c-82e87b0744a07613ec00779d0cf05bdbfbd2689d.tar
discord.c-82e87b0744a07613ec00779d0cf05bdbfbd2689d.tar.gz
discord.c-82e87b0744a07613ec00779d0cf05bdbfbd2689d.tar.bz2
discord.c-82e87b0744a07613ec00779d0cf05bdbfbd2689d.tar.lz
discord.c-82e87b0744a07613ec00779d0cf05bdbfbd2689d.tar.xz
discord.c-82e87b0744a07613ec00779d0cf05bdbfbd2689d.tar.zst
discord.c-82e87b0744a07613ec00779d0cf05bdbfbd2689d.zip
Diffstat (limited to 'src')
-rw-r--r--src/api.c148
-rw-r--r--src/h.c128
-rw-r--r--src/ui.c2
3 files changed, 241 insertions, 37 deletions
diff --git a/src/api.c b/src/api.c
index ce19d58..ead58df 100644
--- a/src/api.c
+++ b/src/api.c
@@ -1,5 +1,7 @@
#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_COMPILE_INFO __VERSION__ " " __BASE_FILE__ " " __FILE__ " " __TIMESTAMP__
+#define DC_IDENTIFY_FORMAT "{\"op\":2,\"d\":{\"token\":\"%s\",\"intents\":%d" /* ",\"presence\":{\"since\":null,\"status\":\"online\",\"afk\":false,\"activities\":{\"name\":\":pager: Connected via discord.c\",\"type\":4}}" */ ",\"properties\":{\"$os\":\"posix\",\"$browser\":\"discord.c\",\"$device\":\"" DC_COMPILE_INFO "\"}}}" /* this exposes we are discord.c, if this turns out to get people banned, change to whatever an official client sends */
+#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" /* this is so we can fool the login system */
#define DC_SERVER_ADDRESS "discord.com"
#define DC_SERVER_PORT 443
#define DC_SERVER_SSL 1
@@ -8,7 +10,7 @@
#define DC_WS_PORT DC_SERVER_PORT
#define DC_WS_SSL DC_SERVER_SSL
#define DC_WS_ORIGIN DC_SERVER_ORIGIN
-#define DC_WS_PATH "/"
+#define DC_WS_PATH "/" /* add ?v=9&encoding=json in case of problems */
#define DC_API_PREFIX "/api/v9/"
#define DC_LWS_ABLE_TO_PARSE_HEADERS 1
/* libwebsockets information: libwebsockets works with event loops and discord.c primary targets debian for building (it must work on debian), but debian does not ship libwebsockets with glib or libevent event loop support, so loops are implemented in the classic poll() style. this reduces performance probably. if you use discord.c API with support for a platform specific event loop, you may rewrite LWS to do things differently. currently calls into API (dc_api_i and dc_api_o) will both do LWS stuff. */
@@ -18,11 +20,70 @@ void dc_api_stack (struct dc_api_io i) { /* stack output struct to be delivered
*(i.program->api_ios[i.program->api_ios_length++]) = i;
return;
}
+signed char dc_json_cb (struct lejp_ctx * ctx, char /* enum lejp_callbacks */ reason) {
+ enum dc_json_paths path = ctx->path_match-1; /* we assume that the order of incoming data is */
+ struct dc_lws_pass * pass = ctx->user; /* correct. op should come first, etc. */
+ pass->json_reason = reason;
+ if (reason == LEJPCB_FAILED) {
+ DC_API_IO_GC(pass->api_io);
+ pass->packet = DC_NONE;
+ pass->parsing_status = NULL;
+ return '\0';
+ }
+ fprintf(stderr, "JSON: %s reason: %d %.*s\n", ctx->path, reason, 40, pass->cp);
+ if (ctx->path_match && reason & LEJP_FLAG_CB_IS_VALUE) {
+ switch (path) {
+ case DC_JSON_S: /* packet sequence number */
+ pass->api_io.client->last_packet = strtoull(ctx->buf, NULL, 10);
+ break;
+ case DC_JSON_OP:
+ DC_API_IO_GC(pass->api_io); /* frees all unfinished parsing objects */
+ pass->packet = atoi(ctx->buf);
+ break;
+ case DC_JSON_PING:
+ pass->api_io.client->ping_interval = atoi(ctx->buf);
+ break;
+ default: /* to prevent warning: enumeration value DC_JSON_* not handled */
+ break;
+ }
+ return '\0';
+ }
+ if (pass->packet == DC_NONE) /* useless to do anything if we haven't received packet type */
+ return '\0';
+ if (reason == LEJPCB_COMPLETE) { /* gr8, fin, call back to api */
+ if (!pass->parsing_status) { /* some stuff is fully handled in switch (pass->pkt) */
+ pass->packet = DC_NONE;
+ return '\0';
+ }
+ *pass->parsing_status &= ~DC_IN_PROGRESS;
+ pass->api_io.status |= DC_FROM_LWS; /* we callback on FAILED too, so that we don't */
+ // dc_api_io(pass->api_io); /* end up with a memory leak if we allocated a struct */
+ pass->api_io.status &= ~DC_FROM_LWS;
+ pass->parsing_status = NULL;
+ pass->packet = DC_NONE;
+ return '\0';
+ }
+ switch (pass->packet) { /* we fill in structs, set DC_INCOMPLETE and pass->parsing_status */
+ case DC_PING:
+ dc_handle_ping(pass->api_io, pass /* this is just so it's not NULL */);
+ break;
+ default:
+ break;
+ }
+ return '\0'; /* by the way, what does the return value do here? write in the comments below (; */
+}
static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us, void * in, size_t len) {
struct dc_lws_pass * pass = (struct dc_lws_pass *) us;
+ unsigned char buf[LWS_PRE+DC_LWS_BUF+1]; /* whooh, boy, this is more than a meg of stack! */
switch (rs) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: /* TODO: handle and report somehow */
- pass->api_io.status = DC_NET_ERROR;
+ if (pass) {
+ pass->api_io.status = DC_NET_ERROR;
+ pass->wsi = NULL;
+ pass->api_io.status |= DC_FROM_LWS;
+ dc_api_i(pass->api_io);
+ pass->api_io.status &= ~DC_FROM_LWS;
+ }
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *) in : "(null)");
break;
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: /* lws gives us len space after *in */
@@ -90,10 +151,9 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us,
pass->body[pass->body_length] = '\0'; /* on heap and freed on _DESTROY. */
return 0; /* don't pass to lws_callback_http_dummy */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: /* uninterpreted http content */
- fprintf(stderr, "LCRCH\n");
- char buffer[DC_LWS_BUF + LWS_PRE];
- char * ptr = buffer + LWS_PRE; /* see, BEFORE the pointer - lower addr */
- int len2 /* DC_LWS_BUF again */ = sizeof(buffer) - LWS_PRE;
+ ;
+ char * ptr = (char *) buf + LWS_PRE; /* see, BEFORE the pointer - lower addr */
+ int len2 /* DC_LWS_BUF again */ = sizeof(buf) - LWS_PRE;
if (lws_http_client_read(wsi, &ptr, &len2) < 0) {
fprintf(stderr, "lws_http_client_read -1\n");
return -1;
@@ -114,21 +174,52 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us,
fprintf(stderr, "pass before freed is %p\n", (void *) pass);
if (pass->api_io.status & DC_MUST_FREE) /* we still do this check in case */
free(pass->body); /* _READ was not called and pointer was static */
+ pass->api_io.client->pass = NULL;
free(pass); /* called at the final moment when user pointer and wsi is still */
break; /* accessible - we can free the struct that was passed in as a heap ptr */
+ case LWS_CALLBACK_WSI_CREATE: /* first - outermost - call with wsi present */
+ if (pass->api_io.client && pass->api_io.status & DC_SET_PASS) {
+ fprintf(stderr, "pass->api_io.client->pass = pass\n");
+ pass->api_io.client->pass = pass;
+ }
+ break;
case LWS_CALLBACK_CLIENT_ESTABLISHED: /* websocket established */
+ fprintf(stderr, "LWS_CALLBACK_CLIENT_ESTABLISHED\n");
pass->api_io.status = DC_OK;
+ pass->wsi = wsi;
+ pass->api_io.status |= DC_FROM_LWS;
+ dc_api_i(pass->api_io);
+ pass->api_io.status &= ~DC_FROM_LWS;
break;
case LWS_CALLBACK_CLIENT_CLOSED: /* websocket closed */
+ DC_API_IO_GC(pass->api_io); /* frees all unfinished parsing objects */
+ pass->packet = DC_NONE;
pass->api_io.status = DC_NET_ERROR;
+ pass->wsi = NULL;
+ pass->api_io.status |= DC_FROM_LWS;
+ dc_api_i(pass->api_io);
+ pass->api_io.status &= ~DC_FROM_LWS;
break;
case LWS_CALLBACK_CLIENT_RECEIVE: /* websocket receive, pass to json parser ? */
- lwsl_hexdump_notice(in, len);
- if (pass->json_reason == LEJPCB_COMPLETE || !(pass->api_io.status & DC_LEJP_CONSTRUCTED)) { /* we regenerate a new context in case we didn't do that yet or in case the previous one finished parsing */
+ fprintf(stdout, "%.*s", len, (const unsigned char *) in);
+ fflush(stdout);
+ if (pass->json_reason == LEJPCB_COMPLETE || pass->json_reason == LEJPCB_FAILED || !(pass->api_io.status & DC_LEJP_CONSTRUCTED)) { /* we regenerate a new context in case we didn't do that yet or in case the previous one finished parsing */
pass->api_io.status |= DC_LEJP_CONSTRUCTED;
- lejp_construct(pass->json_context, dc_json_cb, us /* == (void *) pass */, dc_json_paths, LWS_ARRAY_SIZE(dc_json_paths));
+ pass->packet = DC_NONE;
+ lejp_construct(&pass->json_ctx, dc_json_cb, us /* == (void *) pass */, (const char * const *) dc_json_paths, LWS_ARRAY_SIZE(dc_json_paths));
+ } /* lejp_parse does return number of excess bytes after parsing json. but I */
+ pass->len = len; /* assume that server sends in chunks that always terminate */
+ pass->cp = in; /* when json object terminates. valve pls fix */
+ lejp_parse(&pass->json_ctx, (const unsigned char *) in, len);
+ break;
+ case LWS_CALLBACK_CLIENT_WRITEABLE: /* invoke with lws_callback_on_writeable(wsi) 4 ws */
+ memcpy(buf+LWS_PRE, pass->body, pass->body_length);
+ (buf+LWS_PRE)[pass->body_length] = '\0';
+ lwsl_hexdump_notice(buf+LWS_PRE, pass->body_length);
+ if (lws_write(wsi, (unsigned char *) buf+LWS_PRE, pass->body_length, LWS_WRITE_BINARY) != (int) pass->body_length) {
+ fprintf(stderr, "ws lws_write failed!\n");
+ return -1;
}
- lejp_parse(pass->json_context, (const unsigned char) in, len);
break;
default:
break;
@@ -149,10 +240,11 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
break;
case DC_API_GUILD:
break;
- case DC_API_LOGIN: /* library currently only works for a single client */
+ case DC_API_LOGIN: /* TODO: library currently only works for a single client */
if (!i.program->clients_length) /* already attempted login */
DC_MR(i.program->clients);
i.program->clients[0] = i.client;
+ i.program->clients_length = 1;
memset(&info, 0, sizeof(info));
info.context = i.program->lws_context;
#if DC_SERVER_SSL
@@ -170,6 +262,7 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
pass->body_length = smprintf(&pass->body, DC_LOGIN_FORMAT, i.client->email, i.client->password);
fprintf(stderr, "length: %u string: %s\n", pass->body_length, pass->body);
i.type = DC_API_LOGIN_CB;
+ i.status &= ~DC_SET_PASS; /* this is a HTTP request, pass is for websockets */
memcpy(&pass->api_io, &i, sizeof(i));
pass->api_io.pass = pass;
strcpy(pass->headers[DC_LWS_CONTENT_TYPE], "application/json");
@@ -186,7 +279,7 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
case DC_API_LOGIN_CB:
#define DC_STACK_RETURN(x) do { i.client->status = x; dc_api_stack(i); return; } while (0)
i.type = DC_API_LOGIN;
- i.status &= ~(DC_OK);
+ i.status &= ~DC_OK;
if (!i.pass->body) {
i.client->status |= DC_REQUEST_FAILED;
dc_api_stack(i);
@@ -230,6 +323,7 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
info.local_protocol_name = info.protocol;
pass = calloc(1, sizeof(struct dc_lws_pass));
i.type = DC_API_WS_CB;
+ i.status |= DC_SET_PASS;
memcpy(&pass->api_io, &i, sizeof(i));
pass->api_io.pass = pass;
info.userdata = pass;
@@ -243,7 +337,19 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
}
break;
case DC_API_WS_CB:
- dc_api_stack(i); /* .status is either NET_ERROR 4 closed or error or OK 4 esta */
+ i.client->status &= ~(DC_NET_ERROR | DC_OK);
+ i.client->status |= i.status;
+ if (i.status & DC_NET_ERROR)
+ fprintf(stderr, "websocket connection was closed by the remote host\n");
+ else { /* commerce identify */
+ if (i.pass->api_io.status & DC_MUST_FREE)
+ free(i.pass->body);
+ i.pass->api_io.status |= DC_MUST_FREE;
+ i.pass->body_length = smprintf(&i.pass->body, DC_IDENTIFY_FORMAT, i.client->authorization, DC_INTENTS);
+ lws_callback_on_writable(i.pass->wsi);
+ }
+ dc_api_stack(i); /* cl.stat is either NET_ERROR 4 closed or error or OK 4 esta */
+ break;
case DC_API_REGISTER:
break;
case DC_API_STATUS:
@@ -255,9 +361,8 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
case DC_API_NONE:
break;
case DC_API_ATTACH:
- if (i.attached_function->type == DC_API_NONE)
- break;
DC_MR(i.program->attached_functions); /* makes room for at least one */
+ i.program->attached_functions[i.program->attached_functions_length++] = i.attached_function; /* is on heap */
break;
}
}
@@ -273,9 +378,16 @@ struct dc_api_io dc_api_o (struct dc_api_io i /* for ->program */) {
memcpy(&o, (i.program->api_ios[--i.program->api_ios_length]), sizeof(o));
dc_api_io_free(i.program->api_ios[i.program->api_ios_length]);
}
+ int is_broken = 0;
for (size_t j = 0; j < i.program->attached_functions_length; j++) { /* call attached functions */
- if (i.program->attached_functions[j]->type == o.type) /* wow, it's possible to attach to NONE */
- i.program->attached_functions[j]->function(o, i.program->attached_functions[j]->user_data);
+ if (i.program->attached_functions[j]->type != DC_API_TIMEOUT) {
+ if (i.program->attached_functions[j]->type == o.type && !is_broken)
+ if (i.program->attached_functions[j]->function(o, i.program->attached_functions[j]->user_data) & DC_BREAK)
+ is_broken++;
+ } else
+ if (i.program->attached_functions[j]->last + i.program->attached_functions[j]->every < time(NULL))
+ if (!(i.program->attached_functions[j]->function(o, i.program->attached_functions[j]->user_data) & DC_RETRY))
+ i.program->attached_functions[j]->last = time(NULL);
}
return o;
}
diff --git a/src/h.c b/src/h.c
index c72c710..5774cf2 100644
--- a/src/h.c
+++ b/src/h.c
@@ -10,10 +10,11 @@
#define DC_MR(n) if (n##_sizeof <= n##_length) /* make room */ \
DC_BIGGER_ARRAY(n)
#define DC_STRUCT_PREFIX void * data; /* some user data to be kind to api library users */
-#define DC_LWS_BUF (2^14) /* 16384 byte chunks are handled at a time when RXing HTTP body */
+#define DC_LWS_BUF 65535 /* 2^16 SMALL FUCKING HINT: ^ je XOR operator v C (in povsod drugje) */
#define DC_LWS_MAX_RX DC_LWS_BUF /* max bytes a websocket may handle in a single receive */
#define DC_LWS_MAX_FD 64 /* max file descriptors LWS will have open. if unset, LWS acquires all unused */
#define DC_LWS_MAX_HEADER_LENGTH 64 /* _MAX_HEADER_LENGTH*_HEADERS_LENGTH is allocated for every reque */
+#define DC_WS_PING_FORMAT "{\"op\":1,\"d\":%lld}"
/* it's strongly recommended to calloc structs during initialization. */
enum dc_status { /* theese are flags and should be and-checked */
DC_UNSET = 0, /* default value when enum is calloced */
@@ -25,14 +26,18 @@ enum dc_status { /* theese are flags and should be and-checked */
DC_BAD_USERNAME = 1 << 5, /* provided username can't be registered */
DC_BAD_EMAIL = 1 << 6, /* provided email can't be registered */
DC_NOT_FOUND = 1 << 7, /* when querying for roles/users/... received a 404 */
- DC_CONTINUE = 1 << 8, /* attached handlers return this to continue processing this output, N/I */
- DC_BREAK = 1 << 9, /* attached handlers return this to stop processing this output */
+ DC_BREAK = 1 << 8, /* attached handlers return this to stop processing this output */
+ DC_RETRY = 1 << 9, /* attached timeout handlers return this so that ->last is not updated */
DC_FROM_LWS = 1 << 10, /* LWS cb is the caller, so do not attempt to do lws_service (loop) */
- DC_MUST_FREE = 1 << 11, /* cb pass: body must be freed when request is done with user_data */
- DC_REQUEST_FAILED = 1 << 12, /* http request failed, reported to ui */
- DC_ERROR = 1 << 13, /* unknown error, non implemented non expected response */
- DC_NET_ERROR = 1 << 14, /* network failed or ws closed */
- DC_LEJP_CONSTRUCTED = 1 << 15 /* json parser was constructed */
+ DC_FROM_API = 1 << 11, /* call originates from API function */
+ DC_MUST_FREE = 1 << 12, /* cb pass: body must be freed when request is done with user_data */
+ DC_REQUEST_FAILED = 1 << 13, /* http request failed, reported to ui */
+ DC_ERROR = 1 << 14, /* unknown error, non implemented non expected response */
+ DC_NET_ERROR = 1 << 15, /* network failed or ws closed */
+ DC_LEJP_CONSTRUCTED = 1 << 16, /* json parser was constructed */
+ DC_SET_PASS = 1 << 17, /* whether _CREATE _cb shall set client->passs. api sets on _WS create */
+ DC_IN_PROGRESS = 1 << 18, /* object is in parsing (by json_cb) */
+ DC_INTERNAL = DC_FROM_LWS | DC_FROM_API /* call originates from an internal function */
}; /* note: when checking status, first check for DC_OK, if it's set then disregard errors! */
enum dc_permissions { /* other permissions exist, but are not implemented/understood */
DC_ALL_PERMISSIONS = 1 << 3, /* this is incredibly retarded, why is this SEPARATE?!? - admins */
@@ -43,10 +48,21 @@ enum dc_permissions { /* other permissions exist, but are not implemented/unders
DC_VOICE_SPEAK = 1 << 21
}; /* all enum fields here have values same as the values that the server sends */
enum dc_channel_type { /* other types exist, but are not implemented/understood */
- DC_TEXT, /* all enum fields here have values same as the values that the server sends */
- DC_DM,
- DC_VOICE,
- DC_DM2 /* retarded. server sometimes sends this... converted to DC_DM at parsing server resp. */
+ DC_CM = 0, /* channel messages */
+ DC_DM = 1, /* direct messages */
+ DC_VOICE = 2, /* all enum fields here have values same as the values that the server sends */
+ DC_GROUP_DM = 3
+};
+enum dc_ws_packet { /* op numbers of websocket packets or json objects in other words */
+ DC_PING = 1,
+ DC_NONE = 69 /* nonexisting, set pass->packet to this before lejp_construct time 4 each packet */
+};
+enum dc_ws_intent { /* types of json blobs that the server sends us whenever available */
+ DC_GUILDS = 1 << 0, /* all enum fields here have values same as the values that the serv sends */
+ DC_USERS = 1 << 1, /* other types exist, but are not implemented/understood */
+ DC_CMS = 1 << 9, /* channel messages */
+ DC_DMS = 1 << 12, /* direct messages */
+ DC_INTENTS = DC_GUILDS | DC_USERS | DC_CMS | DC_DMS /* what intents to subscribe to/are implem */
};
enum dc_api_io_type {
/* all output structs have important structs set when an output is broadcast, for example if we get a channel from server but we don't possess it's guild (by id), api will wait for the guild and hold the channel in it's cache. for messages, tags in message are also parsed and queried. roles in premissions in channels are also fetched but not users in permissions, because permissions are currently only used for checking our permissions, not others' */
@@ -81,6 +97,7 @@ enum dc_api_io_type {
DC_API_ATTACH /* i: attaches function to handle output types */
/* o: N/A */
}; /* do not confuse yourself, when for example login response is checked for errors, check client->status and not struct dc_api_io's member named status. that member is mostly only used internally. */
+#define DC_API_TIMEOUT DC_API_NONE /* attached functions with type DC_API_TIMEOUT are called ->every seconds */
/* enum dc_status (* dc_api_attached_func) (struct dc_api_io, void * data); */ /* i tried simplifying but didn't work */
struct dc_api_io { /* output struct does NOT contain void * data (user pointer) from the input struct! */
DC_STRUCT_PREFIX /* mostly useless here but it's only a couple of bytes so wth */
@@ -91,13 +108,33 @@ struct dc_api_io { /* output struct does NOT contain void * data (user pointer)
struct dc_message * message; /* for safety and to mininize casting, each type has a pointer */
struct dc_channel * channel;
struct dc_guild * guild;
- struct dc_client * client;
+ struct dc_client * client; /* must be set in pass->api_io.client when creating ws for client! */
struct dc_user * user;
struct dc_role * role;
struct dc_program * program; /* this is library handle */
- struct dc_attached_function * attached_function; /* points to allocated struct when DC_API_ATTAH */
+ struct dc_permission * permission;
+ struct dc_attached_function * attached_function; /* ptr2 heap alloc struct when DC_API_ATTAH */
struct dc_lws_pass * pass;
};
+#define DC_API_IO_MEMB_GC(m, t) if (m && m->status & DC_IN_PROGRESS) { \
+ t##_free(m); \
+ m = NULL; }
+#define DC_API_IO_GC(s) do { /* this is called if: json_cb will start parsing another before callback, */ \
+ DC_API_IO_MEMB_GC(s.message, dc_message); /* WS connection closed, meaning json_cb */ \
+ DC_API_IO_MEMB_GC(s.channel, dc_channel); /* will never be called again */ \
+ DC_API_IO_MEMB_GC(s.guild, dc_guild); \
+ DC_API_IO_MEMB_GC(s.user, dc_user); \
+ DC_API_IO_MEMB_GC(s.role, dc_role); \
+ DC_API_IO_MEMB_GC(s.permission, dc_permission); \
+ } while (0)
+#define DC_API_IO_EVERY(i, b, a) do { /* not sure if this works */ \
+ if (i.message) b i.message a \
+ if (i.channel) b i.channel a \
+ if (i.guild) b i.guild a \
+ if (i.user) b i.user a \
+ if (i.role) b i.role a \
+ if (i.permission) b i.permission a \
+ } while (0)
void dc_api_io_free (struct dc_api_io * s) {
if (!s)
return;
@@ -115,20 +152,31 @@ char * dc_lws_headers[] = {
"Content-Type:"
};
enum dc_json_paths { /* lws reduces the following char array to uint8_t, so we can match easier */
- DC_JSON_OP,
+ DC_JSON_OP, /* packet type */
+ DC_JSON_S, /* packet sequence number */
+ DC_JSON_PING, /* interval */
DC_JSON_PATHS_LENGTH
-}
+};
char * dc_json_paths[] = { /* array of paths we are interested in */
"op",
-}
+ "s",
+ "d.heartbeat_interval"
+};
struct dc_lws_pass { /* struct that is allocated for in dc_lws_cb unique per connection in void * us */
+ DC_STRUCT_PREFIX
char * body; /* this contains post body and when _CB is called, it contains response */
size_t body_length; /* body is NULL terminated or NULL in case of failure */
char headers[DC_LWS_HEADERS_LENGTH][DC_LWS_MAX_HEADER_LENGTH]; /* nofree, a static 2d array */
int status; /* HTTP response code /\ headers contain request headers, then resp. */
struct dc_api_io api_io; /* so dc_api_io can decide what shall be passed into _CB */
- struct lejp_ctx json; /* holds a context for lejp */
+ struct lejp_ctx json_ctx; /* holds a context for lejp */
enum lejp_callbacks json_reason; /* holds last reason sent to json callback */
+ struct lws * wsi; /* set on ESTABLISHED and NULLed on CLOSED or ERROR */
+ enum dc_ws_packet packet; /* what type of packet are we currently handling in lejp */
+ enum dc_status * parsing_status; /* to remove DC_IN_PROGRESS & signal if call api on COMPL */
+ /* temporary debug things */
+ char * cp;
+ int len;
};
struct dc_client {
DC_STRUCT_PREFIX
@@ -138,6 +186,10 @@ struct dc_client {
struct dc_user * user; /* nofree - logged in user */
struct dc_guild * guild; /* nofree - first guild */
enum dc_status status;
+ struct dc_lws_pass * pass; /* pass of ws. _cb sets it on CREATE and NULLs on DESTROY */
+ unsigned long long int last_packet; /* last packet sequence number for ping */
+ time_t ping_interval;
+ time_t last_ping;
};
struct dc_client * dc_client_init () {
struct dc_client * s = calloc(1, sizeof(*s));
@@ -161,6 +213,7 @@ struct dc_guild {
struct dc_channel * channel; /* nofree - first channel */
struct dc_role * role; /* nofree - first role. NOTE: first role is always role with role ID that is same as guild ID and it is the @everyone role */
enum dc_permissions permissions;
+ enum dc_status status;
#ifdef DC_UI_GTK
GtkTreeIter iter; /* NOTE: only works when GtkTreeModel has a flag GTK_TREE_MODEL_ITERS_PERSIST; see paragraph 8 of description of file:///usr/share/doc/libgtk-3-doc/gtk3/GtkTreeModel.html */
gboolean is_iter;
@@ -188,6 +241,7 @@ struct dc_channel {
struct dc_channel * next; /* nofree - next channel (linked list of all guilds of dc_guild) */
struct dc_message * message; /* nofree - first message (ordered by time) */
struct dc_permission * permission; /* nofree - first permission */
+ enum dc_status status;
#ifdef DC_UI_GTK
GtkTreeIter iter; /* see notes of struct dc_guild */
gboolean is_iter; /* XXX use this with caution: only if it's possible for treemodels to contains orphans, this works. otherwise, parents will be removed first and then channels will not het their is_iters invalidated. guild's is_iter will always get invalidated. file:///usr/share/doc/libgtk-3-doc/gtk3/GtkTreeStore.html#gtk-tree-store-iter-is-valid could be a solution but it's said to be slow. */
@@ -214,6 +268,7 @@ struct dc_message {
unsigned long long int id;
struct dc_message * next; /* next message (linked list of all messages of dc_channel) */
struct dc_message * reply; /* nofree - this message replies to another message or NULL */
+ enum dc_status status;
};
struct dc_message * dc_message_init () {
struct dc_message * s = calloc(1, sizeof(*s));
@@ -288,6 +343,7 @@ struct dc_permission { /* permissions can be individual on a per-channel basis *
struct dc_user * user; /* nofree - non-null if permission applies to a user */
struct dc_role * role; /* nofree - non-null if it applies to a role */
struct dc_permission * next; /* nexrt permission in ll in channel */
+ enum dc_status status;
}; /* permissions are only useful for checking OUR permissions, not others'. keep that in mind. */
struct dc_permission * dc_permission_init () {
struct dc_permission * s = calloc(1, sizeof(*s));
@@ -303,6 +359,8 @@ struct dc_attached_function {
enum dc_api_io_type type;
void * user_data; /* I hope I don't confuse this with void * data in DC_STRUCT_PREFIX */
enum dc_status (* function) (struct dc_api_io, void *);
+ time_t every; /* no meaning if type != DC_API_TIMEOUT */
+ time_t last; /* no meaning if type != DC_API_TIMEOUT */
};
struct dc_attached_function * dc_attached_function_init () {
struct dc_attached_function * s = calloc(1, sizeof(*s));
@@ -333,6 +391,31 @@ struct dc_program { /* data storage and token used for communication with the li
DC_ISASQ(api_io); /* yesfree */
struct lws_context * lws_context; /* yesfree */
}; /* ui functions MUST check if !(->status & DC_INCOMPLETE) before using array members, incompletes are yet to be filled by the api */
+enum dc_status dc_handle_ping (struct dc_api_io io, void * userdata) { /* tries to ping ws of every cl */
+ if (io.status & DC_INTERNAL)
+ return DC_RETRY;
+ for (size_t i = 0; i < io.program->clients_length; i++) {
+ struct dc_client * client = io.program->clients[i];
+ struct dc_lws_pass * pass = client->pass;
+ if (!pass)
+ continue;
+ if (!pass->wsi)
+ continue;
+ if (!userdata) { /* so packet parser iow server may request an immediate ping */
+ if (client->ping_interval && client->last_ping + client->ping_interval < time(NULL))
+ client->last_ping = time(NULL);
+ else
+ continue;
+ }
+ if (pass->api_io.status & DC_MUST_FREE)
+ free(pass->body);
+ pass->api_io.status |= DC_MUST_FREE;
+ pass->body_length = smprintf(&pass->body, DC_WS_PING_FORMAT, client->last_packet);
+ fprintf(stderr, "dc_handle_ping: handling ping for client %d\n", i);
+ lws_callback_on_writable(pass->wsi);
+ }
+ return DC_OK;
+}
#define DC_ISAS_INIT(type/* w/o struct */, name) do { name##_sizeof = DC_ALLOC_CHUNK; /* structs ISA */ \
name = calloc(name##_sizeof, sizeof(struct type *)); } while (0) /* prep arr, NO INIT membrs */
#define DC_ISASIQ(shortname) DC_ISAS_INIT(dc_##shortname, s->shortname##s) /* ISAS init quick */
@@ -359,6 +442,15 @@ struct dc_program * dc_program_init () {
lwsl_err("lws init failed\n");
return NULL;
}
+ struct dc_api_io io; /* attach a function for pinging wss of every client */
+ memset(&io, 0, sizeof(io));
+ io.type = DC_API_ATTACH;
+ io.attached_function = calloc(1, sizeof(struct dc_attached_function));
+ io.attached_function->type = DC_API_TIMEOUT;
+ io.attached_function->every = 0; /* repediately call, handle_ping handles timing */
+ io.attached_function->function = dc_handle_ping;
+ io.program = s;
+ dc_api_i(io);
return s;
}
#define DC_ISAF(shortname) for (size_t i = 0; i < s->shortname##s_sizeof; i++) /* ISA free */ \
diff --git a/src/ui.c b/src/ui.c
index 89ee076..02375f2 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -251,7 +251,7 @@ int dc_ui (int argc, char ** argv) {
/* pre app run setup */
d.b = gtk_builder_new_from_string(dc_ui_def, -1);
#define dc_uacf "%s/%sdiscord.c", getenv("XDG_CONFIG_HOME") ? getenv("XDG_CONFIG_HOME") : getenv("HOME") ? getenv("HOME") : ".", getenv("XDG_CONFIG_HOME") ? "" : ".config/" /* as per XDG */
- gchar fn[snprintf(NULL, 0, dc_uacf)];
+ gchar fn[snprintf(NULL, 0, dc_uacf)]; /* glib has g_get_user_config_dir but naaaaaaaaaaah */
sprintf(fn, dc_uacf);
s = strrchr(fn, '/');
s[0] = '\0';