diff options
author | Anton Luka Šijanec <anton@sijanec.eu> | 2021-09-20 21:12:28 +0200 |
---|---|---|
committer | Anton Luka Šijanec <anton@sijanec.eu> | 2021-09-20 21:12:28 +0200 |
commit | 70cf778548fc47d99fef83b892191902178d1242 (patch) | |
tree | 2e621cd1fb1e7a2c030f6391fab2037e29fb8458 | |
parent | this will NEVER work (no, it will, i'm just tired) (diff) | |
download | discord.c-70cf778548fc47d99fef83b892191902178d1242.tar discord.c-70cf778548fc47d99fef83b892191902178d1242.tar.gz discord.c-70cf778548fc47d99fef83b892191902178d1242.tar.bz2 discord.c-70cf778548fc47d99fef83b892191902178d1242.tar.lz discord.c-70cf778548fc47d99fef83b892191902178d1242.tar.xz discord.c-70cf778548fc47d99fef83b892191902178d1242.tar.zst discord.c-70cf778548fc47d99fef83b892191902178d1242.zip |
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | src/api.c | 217 | ||||
-rw-r--r-- | src/h.c | 270 | ||||
-rw-r--r-- | src/identify.json | 1 | ||||
-rw-r--r-- | src/lib.c | 10 | ||||
-rw-r--r-- | src/main.c | 15 |
7 files changed, 378 insertions, 151 deletions
@@ -13,6 +13,8 @@ default: mkdir tmp -p xxd -i < src/ui.glade > tmp/ui.xxd echo ', 0' >> tmp/ui.xxd + xxd -i < src/identify.json > tmp/identify.xxd + echo ', 0' >> tmp/identify.xxd $(CC) $(CFLAGS) $(SRCFILE) $(LIBS) install: @@ -2,11 +2,11 @@ an alternative client for a messaging platform, written in the C programming language. -code rewrite in 0.0.4 brings a lot of new features and marks the 0.0.3 release obsolete, though it still kind of works for terminal users, although it is very bad. rewritten version is better because it does not reach ratelimits due to using a socket connection and not polling for messages via HTTP. it also has voice support and a GUI frontend. +code rewrite in 0.0.4 brings a lot of new features and marks the 0.0.3 release obsolete, though it still kind of works for terminal users, although it is very bad. rewritten version is better because it does not reach ratelimits due to using a socket connection and not polling for messages via HTTP. it also has ~~voice support and~~ a GUI frontend. ## screenshots -![screenshot in fullscreen](https://cdn.sijanec.eu/img/2021/03/discord.c_prtsc.png) +![screenshot in fullscreen](https://cdn.sijanec.eu/img/2021/09/discord.c_prtsc.png) ## instructions @@ -35,11 +35,13 @@ make #### building requirements * a POSIX build system - app is cross platform and should also be able to be compiled for all major OSes -* `gcc` or `clang` -* GNU `make` +* a C compiler, `gcc` 10 (faster) and `clang` (better warnings) tested working +* `make`, with support for `.NOTPARALLEL:` or without executing in parallel, GNU `make` tested working * `libwebsockets-dev` for http and ws client and json parser -* `libgtk-3-dev` for the GUI -* `libsoundio-dev` for crossplatform sound output +* GTK+, `libgtk-3-dev` tested working +* ~~`libsoundio-dev` for crossplatform sound io~~ +* ~~`libopus-dev` for sound encoding~~ +* ~~`libsodium-dev` for encrypting UDP packets to server~~ * `xxd` for embedding XML GtkBuilder UI definitions directly in the binary ## automatic building @@ -1,7 +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_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_STR(x) x +#define DC_USER_AGENT "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 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 @@ -10,9 +10,14 @@ #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 "/" /* add ?v=9&encoding=json in case of problems */ +#define DC_WS_PATH "/?encoding=json&v=9" #define DC_API_PREFIX "/api/v9/" #define DC_LWS_ABLE_TO_PARSE_HEADERS 1 +#define DC_RECONNECT_DELAY 10 +unsigned char dc_api_identify_u[] = { +#include <identify.xxd> +}; +char * dc_api_identify = (char *) dc_api_identify_u; /* 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. */ void dc_api_stack (struct dc_api_io i) { /* stack output struct to be delivered via dc_api_o 2usr */ DC_MR(i.program->api_ios); @@ -22,12 +27,13 @@ void dc_api_stack (struct dc_api_io i) { /* stack output struct to be delivered } 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. */ - struct dc_client * client = pass->api_io.client; + struct dc_lws_pass * pass = ctx->user; /* correct. op and t should come first, etc. */ + struct dc_client * client = pass->api_io.client; struct dc_program * program = pass->api_io.program; - char * cp; pass->json_reason = reason; - if (reason == LEJPCB_FAILED) { + if (reason == LEJPCB_FAILED || reason == LEJPCB_COMPLETE || reason == LEJPCB_START) { + if (pass->parsing_status) + *pass->parsing_status &= ~DC_IN_PROGRESS; DC_API_IO_GC(pass->api_io); pass->packet = DC_NONE; pass->parsing_status = NULL; @@ -40,20 +46,34 @@ signed char dc_json_cb (struct lejp_ctx * ctx, char /* enum lejp_callbacks */ re 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); + switch (atoi(ctx->buf)) { + case DC_PING: + client->last_ping = 0; + dc_handle_ping(pass->api_io, NULL); + break; + default: + break; + } break; case DC_JSON_PING: - fprintf(stderr, "found ping interval %d\n", atoi(ctx->buf)/1000-5); - pass->api_io.client->ping_interval = atoi(ctx->buf)/1000-5; + pass->api_io.client->ping_interval = atoi(ctx->buf)/1000-1; + pass->api_io.client->last_ping = time(NULL); /* we don't ping b4 IDENT */ + break; + case DC_JSON_T: + for (size_t i = 0; i < sizeof(dc_ws_packet)/sizeof(dc_ws_packet[0]); i++) + if (strcmp(dc_ws_packet[i], ctx->buf)) { + pass->packet = DC_STRPKTOFF+i; + break; + } break; default: /* to prevent warning: enumeration value DC_JSON_* not handled */ break; } return '\0'; } - if (startswith(ctx->path, dc_json_paths[DC_JSON_ME]) && reason & LEJP_FLAG_CB_IS_VALUE) { - if (!client->user) { + if (startswith(ctx->path, dc_json_paths[DC_JSON_ME]) && reason & LEJP_FLAG_CB_IS_VALUE + && (!client->user || !client->user->username || !client->user->id || client->user->status & DC_INCOMPLETE)) { /* if filled, then it's someone else */ + if (!client->user) { /* on first d.user this is our user, subsequent are someone else! */ DC_MR(program->users); /* don't need DC_IN_PROGRESS, we have ref in cl */ program->users[program->users_length++] = client->user = dc_user_init(); client->user->status |= DC_INCOMPLETE; /* when ->disc is set, it's complete */ @@ -74,68 +94,81 @@ signed char dc_json_cb (struct lejp_ctx * ctx, char /* enum lejp_callbacks */ re default: break; } + return '\0'; /* because we use same checks for parsing other users GUILD_MEMBER_UPDATE */ } - if ((cp = startswith(ctx->path, dc_json_paths[DC_JSON_FRIEND]))) { - if (!client->user) /* for the client user */ - client->user = dc_user_init(); - if (path == DC_JSON_FRIEND && reason == LEJPCB_OBJECT_START) { - dc_user_free(pass->api_io.user); /* for the newly found friend (-; */ - pass->api_io.user = dc_user_init(); + if (startswith(ctx->path, dc_json_paths[DC_JSON_FRIEND]) || startswith(ctx->path, dc_json_paths[DC_JSON_ME])) { + if ((path == DC_JSON_FRIEND || path == DC_JSON_ME) && reason == LEJPCB_OBJECT_START) { + /* dc_user_free(pass->api_io.user); */ /* parser branches MUST ALWAYS set to 0 */ + pass->api_io.user = dc_user_init(); /* and possibly free api_io members after! */ pass->api_io.user->status |= DC_IN_PROGRESS; /* if we never get here again */ } - if (path == DC_JSON_FRIEND && reason == LEJPCB_OBJECT_END) { - if (dc_find_user(client->users, client->users_length, pass->api_io.user->id) != pass->api_io.user) - dc_user_free(pass->api_io.user); /* already have */ - else { + if (path == DC_JSON_FRIEND && reason == LEJPCB_OBJECT_END) { /* if that's a friend */ + pass->api_io.user = dc_addr_user(program, DC_ISAE(program->users), pass->api_io.user, DC_MAY_FREE); + if (!dc_find_user(client->users, client->users_length, pass->api_io.user->id)) { fprintf(stderr, "got a new friend (: %s#%d %llu\n", pass->api_io.user->username ? pass->api_io.user->username : "NULL", pass->api_io.user->discriminator, pass->api_io.user->id); - pass->api_io.user = dc_addr_user(program, DC_ISAE(client->users), pass->api_io.user, DC_MAY_FREE); + pass->api_io.user = dc_add_user(DC_ISAE(client->users), pass->api_io.user, DC_UNSET); /* already reported, already inserted in program, nof */ pass->api_io.user->status &= ~(DC_IN_PROGRESS); } pass->api_io.user = NULL; /* we're done, NULL it or PROBLEMS WILL APPEAR!!! */ } - if (reason & LEJP_FLAG_CB_IS_VALUE && cp[0] && pass->api_io.user) - switch(cp[1]) { - case 'u': /* sername */ - free(pass->api_io.user->username); /* yup, we don't trust serv */ - pass->api_io.user->username = strdup(ctx->buf); + if (path == DC_JSON_ME && reason == LEJPCB_OBJECT_END) { /* if it was just a user */ + pass->api_io.user = dc_addr_user(program, DC_ISAE(program->users), pass->api_io.user, DC_MAY_FREE); + pass->api_io.user = NULL; + } + if (reason & LEJP_FLAG_CB_IS_VALUE && pass->api_io.user) + switch(path) { + case DC_JSON_FRIEND_USERNAME: + if (!pass->api_io.user->username) /* yup, we don't trust serv */ + pass->api_io.user->username = strdup(ctx->buf); break; - case 'i': /* d */ + case DC_JSON_FRIEND_ID: pass->api_io.user->id = strtoll(ctx->buf, NULL, 10); break; - case 'd': /* iscriminator */ + case DC_JSON_FRIEND_DISCRIMINATOR: pass->api_io.user->discriminator = atoi(ctx->buf); break; /* yeah, we don't care about nicknames */ + default: + break; } } - if ((cp = startswith(ctx->path, dc_json_paths[DC_JSON_DM]))) { + if (startswith(ctx->path, dc_json_paths[DC_JSON_DM])) { if (!client->guilds_length) { DC_MR(program->guilds); DC_MR(client->guilds); program->guilds[program->guilds_length++] = client->guilds[0] = dc_guild_init(); - client->guilds_length = 1; + client->guilds_length = 1; /* we don't dc_add_guild because id=0 */ client->guilds[0]->name = strdup("Direct messages"); /* TODO: gettext for i18n */ client->guilds[0]->client = client; } if (path == DC_JSON_DM && reason == LEJPCB_OBJECT_START) { fprintf(stderr, "new DM start parsing object\n"); - dc_channel_free(pass->api_io.channel); - pass->api_io.channel = dc_channel_init(); + /* dc_channel_free(pass->api_io.channel); */ /* parser branches must not leave */ + pass->api_io.channel = dc_channel_init(); /* behind any content in api_io */ pass->api_io.channel->status |= DC_IN_PROGRESS; pass->api_io.channel->guild = client->guilds[0]; } if (path == DC_JSON_DM && reason == LEJPCB_OBJECT_END) { fprintf(stderr, "new DM (:\n"); - if (dc_find_ll_channel(client->guilds[0]->channel /* checks for NULL */, pass->api_io.channel->id)) { - dc_channel_free(pass->api_io.channel); - goto already_have; - } struct dc_channel ** channel = &client->guilds[0]->channel; /* 1. guild = DMs */ while (*channel) channel = &(*channel)->next; pass->api_io.channel->status &= ~(DC_IN_PROGRESS); - pass->api_io.channel = *channel = dc_addr_channel(program, DC_ISAN, pass->api_io.channel, DC_MAY_FREE); -already_have: - pass->api_io.channel->next = NULL; + free(pass->api_io.channel->name); + pass->api_io.channel->name = strdup(""); + for (size_t i = 0; i < pass->api_io.channel->users_length; i++) { + pass->api_io.channel->name = realloc(pass->api_io.channel->name, strlen(pass->api_io.channel->name)+strlen(pass->api_io.channel->users[i]->username)+1+2+1+4); + char buf[64]; + sprintf(buf, "#%d%s", pass->api_io.channel->users[i]->discriminator, i < pass->api_io.channel->users_length ? ", " : ""); + strcat(pass->api_io.channel->name, pass->api_io.channel->users[i]->username); + strcat(pass->api_io.channel->name, buf); + } + pass->api_io.channel = *channel = dc_addr_channel(program, DC_ISAN, pass->api_io.channel, DC_MAY_FREE | DC_REPLACE); + if ((*channel = dc_find_ll_channel(client->guilds[0]->channel, pass->api_io.channel->id))) + if (*channel != pass->api_io.channel) { + dc_channel_free(pass->api_io.channel, DC_REPLACE); + memmove(*channel, pass->api_io.channel, sizeof(**channel)); + } + pass->api_io.channel->guild = client->guilds[0]; pass->api_io.channel = NULL; /* we're done, NULL it or PROBLEMS WILL APPEAR!!! */ } if (reason & LEJP_FLAG_CB_IS_VALUE && pass->api_io.channel) @@ -150,19 +183,19 @@ already_have: break; } } - if ((cp = startswith(ctx->path, dc_json_paths[DC_JSON_DM_USER]))) { - if (path == DC_JSON_DM_USER && reason == LEJPCB_OBJECT_START) { - if (!pass->api_io.user) - pass->api_io.user = dc_user_init(); + if (startswith(ctx->path, dc_json_paths[DC_JSON_DM_USER])) { /* we don't DC_REPLACE here bcoz */ + if (path == DC_JSON_DM_USER && reason == LEJPCB_OBJECT_START) { /* users never update. */ + pass->api_io.user = dc_user_init(); pass->api_io.user->status |= DC_IN_PROGRESS; } if (path == DC_JSON_DM_USER && reason == LEJPCB_OBJECT_END) { fprintf(stderr, "new DM participant (:\n"); - pass->api_io.user = dc_addr_user(program, DC_ISAE(pass->api_io.channel->users), pass->api_io.user, DC_MAY_FREE); - pass->api_io.user->status &= ~(DC_IN_PROGRESS); + pass->api_io.user = dc_addr_user(program, DC_ISAE(program->users), pass->api_io.user, DC_MAY_FREE); /* add to program ISA or find existing */ + pass->api_io.user = dc_addr_user(program, DC_ISAE(pass->api_io.channel->users), pass->api_io.user, DC_UNSET); /* do not free, because it's already in program */ + pass->api_io.user->status &= ~(DC_IN_PROGRESS); /* somehow invalid 4 byte rw? */ pass->api_io.user = NULL; /* we're done, NULL it or PROBLEMS WILL APPEAR!!! */ } - if (reason & LEJP_FLAG_CB_IS_VALUE && cp[0] && pass->api_io.channel) + if (reason & LEJP_FLAG_CB_IS_VALUE && pass->api_io.channel) switch (path) { case DC_JSON_DM_USER_ID: pass->api_io.user->id = strtoull(ctx->buf, NULL, 10); @@ -178,24 +211,20 @@ already_have: break; } } - if (pass->packet == DC_NONE) /* useless to do anything BELOW if we haven't recvd packet type */ - return '\0'; - if (reason == LEJPCB_COMPLETE) { /* NOT USED FOR ANYTHING, REMOVE */ - if (!pass->parsing_status) { /* some stuff is fully handled in switch (pass->pkt) */ - pass->packet = DC_NONE; - return '\0'; + if (startswith(ctx->path, dc_json_paths[DC_JSON_GUILD])) { + if (path == DC_JSON_GUILD && reason == LEJPCB_OBJECT_START) { + pass->api_io.guild = dc_guild_init(); + pass->api_io.guild->status |= DC_IN_PROGRESS; + } + if (path == DC_JSON_GUILD && reason == LEJPCB_OBJECT_END) { + fprintf(stderr, "new guild"); + pass->api_io.guild = dc_addr_guild(program, DC_ISAE(program->guilds), pass->api_io.guild, DC_MAY_FREE); } - *pass->parsing_status &= ~DC_IN_PROGRESS; - pass->api_io.status |= DC_FROM_LWS; - // dc_api_io(pass->api_io); /* WE'RE NOT DOING IT THIS WAY */ - pass->api_io.status &= ~DC_FROM_LWS; - pass->parsing_status = NULL; - pass->packet = DC_NONE; - return '\0'; } + if (pass->packet == DC_NONE) /* useless to do anything BELOW if we haven't recvd packet type */ + 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 */); + case DC_MESSAGE_CREATE: break; default: break; @@ -296,12 +325,16 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us, pass->api_io.status &= ~DC_FROM_LWS; } pass->api_io.client->pass = NULL; - dc_lws_pass_free(pass); /* called at the final time us ptr and wsi is still */ + dc_lws_pass_free(pass, DC_UNSET); /* called the last time us ptr & wsi r 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; + if (pass->api_io.client) { + if (pass->api_io.status & DC_SET_PASS) { + fprintf(stderr, "pass->api_io.client->pass = pass\n"); + pass->api_io.client->pass = pass; + } + if (pass->api_io.status & DC_SET_WS_ACTIVE) /* how to get if wsi is ws */ + pass->api_io.client->status |= DC_WS_ACTIVE; /* or http? IDFK. */ } break; case LWS_CALLBACK_CLIENT_ESTABLISHED: /* websocket established */ @@ -323,6 +356,7 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us, fprintf(stderr, "with additional message: %.*s\n", len-2, (char *) in+2); break; case LWS_CALLBACK_CLIENT_CLOSED: /* websocket closed */ + pass->api_io.client->disconnect_time = time(NULL); DC_API_IO_GC(pass->api_io); /* frees all unfinished parsing objects */ pass->packet = DC_NONE; pass->api_io.status = DC_NET_ERROR; @@ -344,19 +378,20 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us, 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 */ - if (!pass->api_io.client) + if (!pass->api_io.client) /* we empty all payloads from 0 to finish */ break; - if (!pass->api_io.client->payloads_length) - break; - char * body = pass->api_io.client->payloads[--pass->api_io.client->payloads_length]->body; - size_t length = pass->api_io.client->payloads[pass->api_io.client->payloads_length]->length; - fprintf(stderr, "going to write to ws: %.*s\n", length, body); - if (lws_write(wsi, (unsigned char *) body, length, LWS_WRITE_BINARY) != (int) length) { /* body already has LWS_PRE bytes allocated before it */ - fprintf(stderr, "ws lws_write failed!\n"); - return -1; + for (size_t i = 0; i < pass->api_io.client->payloads_length; i++) { + char * body = pass->api_io.client->payloads[i]->body; + size_t length = pass->api_io.client->payloads[i]->length; + fprintf(stderr, "going to write to ws: %.*s\n", length, body); + if (lws_write(wsi, (unsigned char *) body, length, LWS_WRITE_BINARY) != (int) length) { /* body already has LWS_PRE bytes allocated before it */ + fprintf(stderr, "ws lws_write failed!\n"); + return -1; + } + dc_payload_free(pass->api_io.client->payloads[i], DC_UNSET); + pass->api_io.client->payloads[i] = NULL; } - dc_payload_free(pass->api_io.client->payloads[pass->api_io.client->payloads_length]); - pass->api_io.client->payloads[pass->api_io.client->payloads_length] = NULL; /* dc_ws_pop(); */ + pass->api_io.client->payloads_length = 0; break; default: break; @@ -396,7 +431,7 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun pass = calloc(1, sizeof(struct dc_lws_pass)); /* cb frees */ fprintf(stderr, "allocated pass at %p\n", (void *) pass); i.status |= DC_DESTROY_CB; /* so that lws_cb will call api on destroy - fin rq */ - pass->body_length = smprintf(&pass->body, DC_LOGIN_FORMAT, i.client->email, i.client->password); + pass->body_length = asprintf(&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 */ @@ -443,6 +478,9 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun goto ws; /* we could call dc_api_i but that just fills stack */ break; case DC_API_WS: + if (getenv("DC_R")) + raise(SIGINT); + fprintf(stderr, "DC_API_WS called\n"); ws: memset(&info, 0, sizeof(info)); info.context = i.program->lws_context; @@ -460,7 +498,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; + i.status |= DC_SET_PASS | DC_SET_WS_ACTIVE; i.status &= ~DC_DESTROY_CB; /* this is only for http requests */ memcpy(&pass->api_io, &i, sizeof(i)); pass->api_io.pass = pass; @@ -469,7 +507,6 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun strcpy(pass->headers[DC_LWS_AUTHORIZATION], i.client->authorization); fprintf(stderr, "starting websocket session\n"); i.status = DC_UNSET; /* we clear the status */ - i.client->status |= DC_WS_ACTIVE; if (!lws_client_connect_via_info(&info)) { i.client->status = DC_NET_ERROR; dc_api_stack(i); @@ -481,10 +518,15 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun if (i.status & DC_NET_ERROR) { /* pass and pass->body were freed on _DESTROY */ fprintf(stderr, "websocket connection was closed by the remote host\n"); i.pass = NULL; /* pass was/will be freed on _DESTROY, discard it! */ + } else if (!i.client->authorization) { + fprintf(stderr, "what the fuck?! DC_API_WS_CB called without token!\n"); } else { /* commerce identify */ struct dc_payload * p = calloc(1, sizeof(struct dc_payload)); - p->length = smprintf(&p->body, DC_IDENTIFY_FORMAT, i.client->authorization, DC_INTENTS); /* body buffer to identify packet */ - dc_ws_stack(i.client, p); + p->length = asprintf(&p->body, dc_api_identify, i.client->authorization); /* body buffer to identify packet */ + dc_ws_stack(i.client, p, DC_NO_WRITE); + p = calloc(1, sizeof(struct dc_payload)); /* official client sends a */ + p->length = asprintf(&p->body, DC_WS_PING_FORMAT, (unsigned long long int) 0); + dc_ws_stack(i.client, p, DC_UNSET); /* right after identify. */ } dc_api_stack(i); /* cl.stat is either NET_ERROR 4 closed or error or OK 4 esta */ break; @@ -508,8 +550,11 @@ struct dc_api_io dc_api_o (struct dc_api_io i /* for ->program */) { if (i.program->lws_context) lws_service(i.program->lws_context, 0); for (size_t x = 0; x < i.program->clients_length; x++) { - if (i.program->clients[x]->status & DC_WS_ACTIVE && !i.program->clients[x]->pass) { + if (i.program->clients[x]->status & DC_WS_ACTIVE && !i.program->clients[x]->pass + && i.program->clients[x]->disconnect_time+DC_RECONNECT_DELAY<time(NULL)) { + fprintf(stderr, "reconnecting client %u\n", x); i.client = i.program->clients[x]; + i.client->status &= ~DC_WS_ACTIVE; /* because we create a new WSI */ i.type = DC_API_WS; dc_api_i(i); } @@ -521,7 +566,7 @@ struct dc_api_io dc_api_o (struct dc_api_io i /* for ->program */) { /* dc_api_stack pop: */ if (i.program->api_ios_length) { 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]); + dc_api_io_free(i.program->api_ios[i.program->api_ios_length], DC_UNSET); i.program->api_ios[i.program->api_ios_length] = NULL; } @@ -40,7 +40,10 @@ enum dc_status { /* theese are flags and should be and-checked */ DC_IN_PROGRESS = 1 << 17, /* object is in parsing (by json_cb) */ DC_DESTROY_CB = 1 << 18, /* wether cb shall call api on DESTROY (used for http responses) */ DC_MAY_FREE = 1 << 19, /* whether dc_*_add() shall free passed in pointer if found one in ISA */ - DC_WS_ACTIVE = 1 << 20, /* set at API_WS so that api_o will reconnect WS if connection dropps */ + DC_WS_ACTIVE = 1 << 20, /* set at WSI_CREATE so api_o will reconnect WS if connection dropps */ + DC_NO_WRITE = 1 << 21, /* signaling dc_ws_stack not to call lws_callback_on_writeable */ + DC_SET_WS_ACTIVE = 1 << 22, /* whether _CREATE _cb shall set client->status =| DC_WS_ACTIVE */ + DC_REPLACE = 1 << 23, /* dc_add_x replace old with new on found, _free: only free members */ 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 */ @@ -52,22 +55,28 @@ 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_CM = 0, /* channel messages */ - DC_DM = 1, /* direct messages */ + DC_GC = 0, /* guild channel */ + DC_DM = 1, /* direct messages channel */ DC_VOICE = 2, /* all enum fields here have values same as the values that the server sends */ DC_GROUP_DM = 3 }; +#define DC_CHANNEL_SUPPORTED(x) (x == DC_GC || x == DC_DM || x == DC_VOICE || x == DC_GROUP_DM) 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 */ + DC_STRPKTOFF = 100, /* here follow string types (t) */ + DC_NONE = DC_STRPKTOFF + 0, /* unknown packet or packet type not yet defermined */ + DC_MESSAGE_CREATE = DC_STRPKTOFF + 0, +}; /* intents enum was removed - intents're for bots, clients have capabilities - are not understood */ +char * dc_ws_packet[] = { + "", + "MESSAGE_CREATE", + "" }; -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_message_type { /* other types exist, but are not implemented/understood, same values as server */ + DC_MESSAGE = 0, + DC_REPLY = 19, }; +#define DC_MESSAGE_SUPPORTED(x) (x == DC_MESSAGE || x == DC_REPLY) 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' */ /* none of the outputs transfer memory ownership. tr0 for inputs means transfer none, trp means pointer to struct owned by library is to be passed, tr1 means transfer of ownership to library */ @@ -121,7 +130,7 @@ struct dc_api_io { /* output struct does NOT contain void * data (user pointer) struct dc_lws_pass * pass; }; #define DC_API_IO_MEMB_GC(m, t) if (m && m->status & DC_IN_PROGRESS) { \ - t##_free(m); \ + t##_free(m, DC_UNSET); \ 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 */ \ @@ -139,10 +148,11 @@ struct dc_api_io { /* output struct does NOT contain void * data (user pointer) 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) { +void dc_api_io_free (struct dc_api_io * s, enum dc_status t) { if (!s) return; - free(s); + if (!(t & DC_REPLACE)) + free(s); return; } void dc_api_i (struct dc_api_io); @@ -158,37 +168,167 @@ char * dc_lws_headers[] = { enum dc_json_paths { /* lws reduces the following char array to uint8_t, so we can match easier */ DC_JSON_OP, /* packet type */ DC_JSON_S, /* packet sequence number */ + DC_JSON_T, /* type of packet for op=0 */ DC_JSON_PING, /* interval */ - DC_JSON_ME, /* never useful in path parser, but startswith() checks if we're in object */ - DC_JSON_ME_USERNAME, - DC_JSON_ME_ID, + DC_JSON_ME, + DC_JSON_ME_USERNAME, /* note: _ME is client->user ONLY for first d.user object. subsequent */ + DC_JSON_ME_ID, /* packets may contain user objects as well, they are other users */ DC_JSON_ME_DISCRIMINATOR, - DC_JSON_FRIEND, /* never useful in path parser, but startswith() checks if we're in object */ - DC_JSON_DM, /* never useful in path parser, but startswith() checks if we're in object */ + DC_JSON_FRIEND, + DC_JSON_FRIEND_ID, + DC_JSON_FRIEND_USERNAME, + DC_JSON_FRIEND_DISCRIMINATOR, + DC_JSON_DM, DC_JSON_DM_TYPE, DC_JSON_DM_ID, - DC_JSON_DM_USER, /* never useful in path parser, but startswith() checks if we're in object */ + DC_JSON_DM_USER, DC_JSON_DM_USER_NAME, DC_JSON_DM_USER_ID, DC_JSON_DM_USER_DISCRIMINATOR, - DC_JSON_PATHS_LENGTH + DC_JSON_GUILD, + DC_JSON_GUILD_NAME, + DC_JSON_GUILD_ID, + DC_JSON_GUILD_CHANNEL, + DC_JSON_GUILD_CHANNEL_TYPE, + DC_JSON_GUILD_CHANNEL_NAME, + DC_JSON_GUILD_CHANNEL_ID, + DC_JSON_GUILD_CHANNEL_TOPIC, + DC_JSON_GUILD_CHANNEL_PERMISSION, + DC_JSON_GUILD_CHANNEL_PERMISSION_TYPE, + DC_JSON_GUILD_CHANNEL_PERMISSION_ID, + DC_JSON_GUILD_CHANNEL_PERMISSION_DENY, + DC_JSON_GUILD_CHANNEL_PERMISSION_ALLOW, + DC_JSON_GUILD_ROLE, + DC_JSON_GUILD_ROLE_PERMISSION, + DC_JSON_GUILD_ROLE_NAME, + DC_JSON_GUILD_ROLE_ID, + DC_JSON_MESSAGE, + DC_JSON_MESSAGE_ATTACHMENTS, + DC_JSON_MESSAGE_ID, + DC_JSON_MESSAGE_TYPE, + DC_JSON_MESSAGE_CHANNEL, /* id */ + DC_JSON_MESSAGE_CONTENT, + DC_JSON_MESSAGE_AUTHOR, + DC_JSON_MESSAGE_AUTHOR_USERNAME, + DC_JSON_MESSAGE_AUTHOR_DISCRIMINATOR, + DC_JSON_MESSAGE_AUTHOR_ID, + DC_JSON_MESSAGE_AUTHOR_ROLES, + DC_JSON_MESSAGE_REFERENCE, /* id */ + DC_JSON_MESSAGE_MENTION_CHANNEL, /* TODO: implement role and user mentions fetching */ + DC_JSON_MESSAGE_MENTION_CHANNEL_ID, + DC_JSON_MESSAGE_MENTION_CHANNEL_GUILD, /* id */ + DC_JSON_MESSAGE_MENTION_CHANNEL_NAME, + DC_JSON_MESSAGE_MENTION_CHANNEL_TYPE, + DC_JSON_MESSAGE_MENTION_USER, + DC_JSON_MESSAGE_MENTION_USER_NAME, + DC_JSON_MESSAGE_MENTION_USER_ROLE, + DC_JSON_MESSAGE_MENTION_USER_ID, + DC_JSON_MESSAGE_MENTION_USER_DISCRIMINATOR, + DC_JSON_MESSAGE_REFOBJ, /* server only sends reference object one level deep */ + DC_JSON_MESSAGE_REFOBJ_ATTACHMENTS, + DC_JSON_MESSAGE_REFOBJ_ID, + DC_JSON_MESSAGE_REFOBJ_TYPE, + DC_JSON_MESSAGE_REFOBJ_CHANNEL, /* id */ + DC_JSON_MESSAGE_REFOBJ_CONTENT, + DC_JSON_MESSAGE_REFOBJ_AUTHOR, + DC_JSON_MESSAGE_REFOBJ_AUTHOR_USERNAME, + DC_JSON_MESSAGE_REFOBJ_AUTHOR_DISCRIMINATOR, + DC_JSON_MESSAGE_REFOBJ_AUTHOR_ID, + DC_JSON_MESSAGE_REFOBJ_AUTHOR_ROLES, + DC_JSON_MESSAGE_REFOBJ_REFERENCE, /* id, this _is_ sent for second nesting level */ + DC_JSON_MESSAGE_REFOBJ_MENTION_CHANNEL, /* TODO: implement role and user mentions fetching */ + DC_JSON_MESSAGE_REFOBJ_MENTION_CHANNEL_ID, + DC_JSON_MESSAGE_REFOBJ_MENTION_CHANNEL_GUILD, /* id */ + DC_JSON_MESSAGE_REFOBJ_MENTION_CHANNEL_NAME, + DC_JSON_MESSAGE_REFOBJ_MENTION_CHANNEL_TYPE, + DC_JSON_MESSAGE_REFOBJ_MENTION_USER, + DC_JSON_MESSAGE_REFOBJ_MENTION_USER_NAME, + DC_JSON_MESSAGE_REFOBJ_MENTION_USER_ROLE, + DC_JSON_MESSAGE_REFOBJ_MENTION_USER_ID, + DC_JSON_MESSAGE_REFOBJ_MENTION_USER_DISCRIMINATOR, + DC_JSON_PATHS_LENGTH /* we have 256 max length, because that's what library supports */ }; char * dc_json_paths[] = { /* array of paths we are interested in */ "op", "s", + "t", "d.heartbeat_interval", "d.user", /* NOTE: presence updates have same format, so only set own user once */ "d.user.username", "d.user.id", "d.user.discriminator", "d.relationships[].user", + "d.relationships[].user.id", + "d.relationships[].user.username", + "d.relationships[].user.discriminator", "d.private_channels[]", "d.private_channels[].type", "d.private_channels[].id", "d.private_channels[].recipients[]", "d.private_channels[].recipients[].username", "d.private_channels[].recipients[].id", - "d.private_channels[].recipients[].discriminator" + "d.private_channels[].recipients[].discriminator", + "d.guilds[]", + "d.guilds[].name", + "d.guilds[].id", + "d.guilds[].channels[]", + "d.guilds[].channels[].type", + "d.guilds[].channels[].name", + "d.guilds[].channels[].id", + "d.guilds[].channels[].topic", + "d.guilds[].channels[].permission_overwrites", + "d.guilds[].channels[].permission_overwrites.type", + "d.guilds[].channels[].permission_overwrites.id", + "d.guilds[].channels[].permission_overwrites.deny", + "d.guilds[].channels[].permission_overwrites.allow", + "d.guilds[].roles[]", + "d.guilds[].roles[].permissions", + "d.guilds[].roles[].name", + "d.guilds[].roles[].id", + "d", + "d.attachments[]", + "d.id", + "d.type", + "d.channel_id", + "d.content", + "d.author", + "d.author.username", + "d.author.discriminator", + "d.author.id", + "d.member.roles[]", + "d.message_reference", /* this is ID */ + "d.mention_channels[]", + "d.mention_channels[].id", + "d.mention_channels[].guild_id", + "d.mention_channels[].name", + "d.mention_channels[].type", + "d.mentions[]", + "d.mentions[].username", + "d.mentions[].member.roles[]", + "d.mentions[].id", + "d.mentions[].discriminator", + "d.referenced_message", + "d.referenced_message.attachments[]", + "d.referenced_message.id", + "d.referenced_message.type", + "d.referenced_message.channel_id", + "d.referenced_message.content", + "d.referenced_message.author", + "d.referenced_message.author.username", + "d.referenced_message.author.discriminator", + "d.referenced_message.author.id", + "d.referenced_message.member.roles[]", + "d.referenced_message.message_reference", /* this is ID */ + "d.referenced_message.mention_channels[]", + "d.referenced_message.mention_channels[].id", + "d.referenced_message.mention_channels[].guild_id", + "d.referenced_message.mention_channels[].name", + "d.referenced_message.mention_channels[].type", + "d.referenced_message.mentions[]", + "d.referenced_message.mentions[].username", + "d.referenced_message.mentions[].member.roles[]", + "d.referenced_message.mentions[].id", + "d.referenced_message.mentions[].discriminator", }; struct dc_lws_pass { /* struct that is allocated for in dc_lws_cb unique per connection in void * us */ DC_STRUCT_PREFIX @@ -216,9 +356,10 @@ struct dc_payload * dc_payload_init () { struct dc_payload * s = calloc(1, sizeof(struct dc_payload)); return s; } -void dc_payload_free (struct dc_payload * s) { +void dc_payload_free (struct dc_payload * s, enum dc_status t) { free(s->body-LWS_PRE); - free(s); + if (!(t & DC_REPLACE)) + free(s); return; } #define DC_ISASQ(shortname) DC_ISA(struct dc_##shortname, shortname##s) /* in struct array of structs quick */ @@ -226,7 +367,7 @@ void dc_payload_free (struct dc_payload * s) { 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 */ #define DC_ISAF(shortname) for (size_t i = 0; i < s->shortname##s_sizeof; i++) /* ISA free */ \ - dc_##shortname##_free(s->shortname##s[i]); \ + dc_##shortname##_free(s->shortname##s[i], DC_UNSET); \ free(s->shortname##s); /* no problem if we free past _lenght, as uninited are NULL */ struct dc_client { DC_STRUCT_PREFIX @@ -239,6 +380,7 @@ struct dc_client { unsigned long long int last_packet; /* last packet sequence number for ping */ time_t ping_interval; time_t last_ping; + time_t disconnect_time; /* set at disconnect, so that reconnection is delayed for some seconds */ DC_ISASQ(payload); /* yesfree - array of payloads we must send over ws */ DC_ISASQ(guild); /* yesfree array of pointers only - guilds of this user */ DC_ISASQ(user); /* yesfree array of pointers only - friends of this user */ @@ -250,7 +392,7 @@ struct dc_client * dc_client_init () { DC_ISASIQ(user); return s; } -void dc_client_free (struct dc_client * s) { +void dc_client_free (struct dc_client * s, enum dc_status t) { if (!s) return; free(s->authorization); @@ -259,15 +401,17 @@ void dc_client_free (struct dc_client * s) { DC_ISAF(payload); free(s->guilds); free(s->users); - free(s); + if (!(t & DC_REPLACE)) + free(s); } -void dc_ws_stack(struct dc_client * c, struct dc_payload * p) { /* we could just use pass->body and */ - DC_MR(c->payloads); /* just append to it, but the server closes if we send two objects in one */ +void dc_ws_stack(struct dc_client * c, struct dc_payload * p, enum dc_status s) { + fprintf(stderr, "dc_ws_stack: stacking for write: %.*s\n", p->length, p->body); + DC_MR(c->payloads); /* the server closes if we send two objects in one */ c->payloads[c->payloads_length++] = p; /* packet, like so: {"packet":1}{"packet":2}, so this. */ p->body = realloc(p->body, p->length+LWS_PRE+1); /* when freeing, free p->body-LWS_PRE */ memmove(p->body+LWS_PRE, p->body, p->length+1); /* body is no longer pointer we have to free */ p->body += LWS_PRE; - if (c->pass && c->pass->wsi) + if (c->pass && c->pass->wsi && !(s & DC_NO_WRITE)) lws_callback_on_writable(c->pass->wsi); return; } @@ -277,7 +421,7 @@ struct dc_guild { unsigned long long int id; /* 0 for virtual DMs guild */ struct dc_client * client; /* nofree */ struct dc_channel * channel; /* nofree - first channel */ - struct dc_role * role; /* nofree - first role. NOTE: 1. role->id == guild->id (@everyone role) */ + struct dc_role * role; /* nofree - first role. NOTE: role->id == guild->id => @everyone role */ enum dc_permissions permissions; enum dc_status status; #ifdef DC_UI_GTK @@ -290,11 +434,12 @@ struct dc_guild * dc_guild_init () { struct dc_guild * s = calloc(1, sizeof(*s)); return s; } -void dc_guild_free (struct dc_guild * s) { +void dc_guild_free (struct dc_guild * s, enum dc_status t) { if (!s) return; free(s->name); - free(s); + if (!(t & DC_REPLACE)) /* we do this because we want to keep the pointer intact sometimes and */ + free(s); /* reused; for example when replacing/updating structs */ } struct dc_channel { DC_STRUCT_PREFIX @@ -318,13 +463,14 @@ struct dc_channel * dc_channel_init () { DC_ISASIQ(user); return s; } -void dc_channel_free (struct dc_channel * s) { +void dc_channel_free (struct dc_channel * s, enum dc_status t) { if (!s) return; free(s->name); free(s->topic); free(s->users); - free(s); + if (!(t & DC_REPLACE)) + free(s); } struct dc_message { DC_STRUCT_PREFIX @@ -342,12 +488,13 @@ struct dc_message * dc_message_init () { struct dc_message * s = calloc(1, sizeof(*s)); return s; } -void dc_message_free (struct dc_message * s) { +void dc_message_free (struct dc_message * s, enum dc_status t) { if (!s) return; free(s->message); free(s->attachment); - free(s); + if (!(t & DC_REPLACE)) + free(s); } struct dc_role { DC_STRUCT_PREFIX @@ -363,11 +510,12 @@ struct dc_role * dc_role_init () { struct dc_role * s = calloc(1, sizeof(*s)); return s; } -void dc_role_free (struct dc_role * s) { +void dc_role_free (struct dc_role * s, enum dc_status t) { if (!s) return; free(s->name); - free(s); + if (!(t & DC_REPLACE)) + free(s); } struct dc_role_membership { DC_STRUCT_PREFIX @@ -380,10 +528,11 @@ struct dc_role_membership * dc_role_membership_init () { struct dc_role_membership * s = calloc(1, sizeof(*s)); return s; } -void dc_role_membership_free (struct dc_role_membership * s) { +void dc_role_membership_free (struct dc_role_membership * s, enum dc_status t) { if (!s) return; - free(s); + if (!(t & DC_REPLACE)) + free(s); } struct dc_user { DC_STRUCT_PREFIX @@ -396,11 +545,12 @@ struct dc_user * dc_user_init () { struct dc_user * s = calloc(1, sizeof(*s)); return s; } -void dc_user_free (struct dc_user * s) { +void dc_user_free (struct dc_user * s, enum dc_status t) { if (!s) return; free(s->username); - free(s); + if (!(t & DC_REPLACE)) + free(s); } struct dc_permission { /* permissions can be individual on a per-channel basis */ DC_STRUCT_PREFIX @@ -417,10 +567,11 @@ struct dc_permission * dc_permission_init () { struct dc_permission * s = calloc(1, sizeof(*s)); return s; } -void dc_permission_free (struct dc_permission * s) { +void dc_permission_free (struct dc_permission * s, enum dc_status t) { if (!s) return; - free(s); + if (!(t & DC_REPLACE)) + free(s); } struct dc_attached_function { DC_STRUCT_PREFIX @@ -434,17 +585,19 @@ struct dc_attached_function * dc_attached_function_init () { struct dc_attached_function * s = calloc(1, sizeof(*s)); return s; } -void dc_attached_function_free (struct dc_attached_function * s) { +void dc_attached_function_free (struct dc_attached_function * s, enum dc_status t) { if (!s) return; - free(s); + if (!(t & DC_REPLACE)) + free(s); } -void dc_lws_pass_free (struct dc_lws_pass * s) { +void dc_lws_pass_free (struct dc_lws_pass * s, enum dc_status t) { if (!s) return; free(s->body); DC_API_IO_GC(s->api_io); - free(s); + if (!(t & DC_REPLACE)) + free(s); } static int dc_lws_cb (struct lws *, enum lws_callback_reasons, void *, void *, size_t); static const struct lws_protocols dc_lws_protocols[] = { @@ -483,8 +636,8 @@ enum dc_status dc_handle_ping (struct dc_api_io io, void * userdata) { /* tries } fprintf(stderr, "dc_handle_ping: handling ping for client %d\n", i); struct dc_payload * payload = calloc(1, sizeof(struct dc_payload)); - payload->length = smprintf(&payload->body, DC_WS_PING_FORMAT, client->last_packet); - dc_ws_stack(client, payload); /* send a payload */ + payload->length = asprintf(&payload->body, DC_WS_PING_FORMAT, client->last_packet); + dc_ws_stack(client, payload, DC_UNSET); /* send a payload */ } return DC_OK; } @@ -522,7 +675,7 @@ struct dc_program * dc_program_init () { dc_api_i(io); return s; } -void dc_program_free (struct dc_program * s) { +void dc_program_free (struct dc_program * s, enum dc_status t) { if (!s) return; DC_ISAF(client); @@ -536,7 +689,8 @@ void dc_program_free (struct dc_program * s) { DC_ISAF(attached_function); DC_ISAF(api_io); lws_context_destroy(s->lws_context); /* closes all connections and destroys all wsis */ - free(s); + if (!(t & DC_REPLACE)) + free(s); } void dc_api_stack (struct dc_api_io); #define DC_FIND_X(x) struct dc_##x * dc_find_##x(struct dc_##x ** p, size_t l, unsigned long long int id) { \ @@ -561,8 +715,17 @@ void dc_api_stack (struct dc_api_io); #define DC_ADD_X(x) struct dc_##x * dc_add_##x (struct dc_##x *** p, size_t * so, size_t * l, struct dc_##x * u, enum dc_status s) { \ struct dc_##x * us; \ if ((us = dc_find_##x(*p, *l, u->id))) { \ + if (us == u) \ + return us; \ + if (s & DC_REPLACE) { \ + dc_##x##_free(us, s); \ + memmove(us, u, sizeof(*us)); \ + if (s & DC_MAY_FREE) \ + free(u); /* we don't free members, they've been copied */ \ + return us; \ + } \ if (s & DC_MAY_FREE) { \ - dc_##x##_free(u); \ + dc_##x##_free(u, DC_UNSET); \ fprintf(stderr, "debug: %s freed already existing member\n", __func__); \ } \ return us; \ @@ -595,5 +758,6 @@ void dc_api_stack (struct dc_api_io); DC_GEN_X(user, USER) DC_GEN_X(channel, CHANNEL) DC_FIND_LL_X(channel) +DC_GEN_X(guild, GUILD) #define DC_ISAE(a) &a, &a##_sizeof, &a##_length /* ISA Expand */ #define DC_ISAN NULL, NULL, NULL /* ISA NULL */ diff --git a/src/identify.json b/src/identify.json new file mode 100644 index 0000000..571abf1 --- /dev/null +++ b/src/identify.json @@ -0,0 +1 @@ +{"op":2,"d":{"token":"%s","capabilities":125,"properties":{"os":"Linux","browser":"Chrome","device":"","system_locale":"en-US","browser_user_agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36","browser_version":"90.0.4430.212","os_version":"","referrer":"http://of.sijanec.eu:7327/","referring_domain":"of.sijanec.eu:7327","referrer_current":"","referring_domain_current":"","release_channel":"stable","client_build_number":97662,"client_event_source":null},"presence":{"status":"online","since":0,"activities":[{"name":"Custom Status","type":4,"state":"mrt5hg","emoji":null}],"afk":false},"compress":false,"client_state":{"guild_hashes":{},"highest_last_message_id":"0","read_state_version":0,"user_guild_settings_version":-1}}} @@ -1,16 +1,18 @@ -int smprintf (char ** str, const char * format, ...) { /* allocates automaticalls (: */ - va_list ap, aq; +#ifndef _GNU_SOURCE /* glibc already provides asprintf, but some cucks tend not to use glibc */ +int asprintf (char ** str, const char * format, ...) { /* allocates automaticalls (: */ + va_list ap, aq; /* */ va_start(ap, format); va_copy(aq, ap); int len = vsnprintf(NULL, 0, format, ap); if (!(*str = realloc(*str, len+1))) fprintf(stderr, "[BUG] !!! realloc failed\n"); - if (len != vsprintf(*str, format, ap)) - fprintf(stderr, "[BUG] !!! len1 != len2\n"); + if (len != vsprintf(*str, format, aq /* we need to use a different va handler, because */)) + fprintf(stderr, "[BUG] !!! len1 != len2\n"); /* you can't rewind an existing one you can't rewind an existing one.. */ va_end(ap); va_end(aq); return len; } +#endif char * startswith (char * testiranec, char * začetek) { if (!strncmp(testiranec, začetek, strlen(začetek))) return testiranec+strlen(začetek); @@ -3,10 +3,20 @@ #include <math.h> #include <libwebsockets.h> #include <assert.h> +#include <signal.h> #include <lib.c> #include <ui.c> #include <api.c> +int dc_interrupted; +void dc_signal (int i) { + dc_interrupted++; + return; +} int main (int argc, char * argv[]) { + if (DC_JSON_PATHS_LENGTH != sizeof(dc_json_paths)/sizeof(dc_json_paths[0]) || DC_JSON_PATHS_LENGTH > 255) { + fprintf(stderr, "json paths enum: %d, array: %d (255 is max, must be same)\n", DC_JSON_PATHS_LENGTH, sizeof(dc_json_paths)/sizeof(dc_json_paths[0])); + return 1; + } struct dc_program * p = dc_program_init(); struct dc_client * client = dc_client_init(); lws_set_log_level(0xFF /* all message types */, NULL /* not change output location - cerr */); @@ -18,9 +28,10 @@ int main (int argc, char * argv[]) { .client = client }; dc_api_i(i); - while (1) + signal(SIGINT, dc_signal); + while (!dc_interrupted) i = dc_api_o(i); /* dc_ui(argc, argv); */ - dc_program_free(p); + dc_program_free(p, DC_UNSET); return 0; } |