summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2021-09-13 23:10:12 +0200
committerAnton Luka Šijanec <anton@sijanec.eu>2021-09-13 23:10:12 +0200
commit32ee8fb67c0b513d253f07759d133340691d6239 (patch)
treea3e2b9e2d3b098d93100c54e63b4673129bbdd36
parentlogin now works, yay! (diff)
downloaddiscord.c-32ee8fb67c0b513d253f07759d133340691d6239.tar
discord.c-32ee8fb67c0b513d253f07759d133340691d6239.tar.gz
discord.c-32ee8fb67c0b513d253f07759d133340691d6239.tar.bz2
discord.c-32ee8fb67c0b513d253f07759d133340691d6239.tar.lz
discord.c-32ee8fb67c0b513d253f07759d133340691d6239.tar.xz
discord.c-32ee8fb67c0b513d253f07759d133340691d6239.tar.zst
discord.c-32ee8fb67c0b513d253f07759d133340691d6239.zip
-rw-r--r--README.md19
-rw-r--r--src/api.c82
-rw-r--r--src/h.c27
-rw-r--r--src/lib.c2
-rw-r--r--src/main.c4
5 files changed, 107 insertions, 27 deletions
diff --git a/README.md b/README.md
index ac78ab5..975286d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# discord.c
-an alternative client for the discord messaging platform, written in the C programming language.
+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.
@@ -34,12 +34,11 @@ make
#### building requirements
-* a POSIX system - app is cross platform and should also be able to be compiled for all major OSes
+* a POSIX build system - app is cross platform and should also be able to be compiled for all major OSes
* `gcc`
* GNU `make`
-* `libwebsockets-dev` for faster bidirectional connections to the server
+* `libwebsockets-dev` for http and ws client and json parser
* `libgtk-3-dev` for the GUI
-* `libcjson-dev` for parsing server responses
* `libsoundio-dev` for crossplatform sound output
* `xxd` for embedding XML GtkBuilder UI definitions directly in the binary
@@ -62,11 +61,19 @@ discord.c is programmed in two separate sections, the ui section, that was previ
to your surprise, you can use the ui section, as of the code rewrite, as a library as well and integrate other platforms into it. No docs yet here either.
-The package for Debian comes with include header files and compiled shared objects so it can be used as a dependency, but beware, this dependency will depend on GTK+3.0, which would be, in case you are doing a GUI with a different widget toolkit, useless.
+~~The package for Debian comes with include header files and compiled shared objects so it can be used as a dependency, but beware, this dependency will depend on GTK+3.0, which would be, in case you are doing a GUI with a different widget toolkit, useless.~~
the program is, as of the code rewrite, single threaded, for increased stability and performance. it used to have separated network and ui threads, but now the threading model is abstracted with an event-based programming.
-## android port?
+## todo
+
+### discord.c interface for the blind
+
+a must-have feature is an alternative `ui.c` that would simply print messages to the console and be controllable fully from the keyboard, similar to the way pre-0.0.4 `discord.c` was created, but without `ncurses`. blind people can't interact with the messaging platform's official client at the time of writing, and I assume that users of `espeakup` and similar services mostly use their computer from the console and advanced GUI features are only a burden to them. I got an idea that if a piece of software is usable via the old-style printer terminals, only then it's usable for the blind.
+
+completing this task would make `discord.c` the first and the only alternative client with an easy to use interface for the blind.
+
+### android port
it would be useful to have an android port, and luckily this is possible with little effort due to the GTK broadway backend that interfaces with a HTML renderer, WebView for example.
diff --git a/src/api.c b/src/api.c
index 70bae66..ce19d58 100644
--- a/src/api.c
+++ b/src/api.c
@@ -4,6 +4,11 @@
#define DC_SERVER_PORT 443
#define DC_SERVER_SSL 1
#define DC_SERVER_ORIGIN "https://discord.com"
+#define DC_WS_ADDRESS "gateway.discord.gg"
+#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_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. */
@@ -17,6 +22,7 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us,
struct dc_lws_pass * pass = (struct dc_lws_pass *) us;
switch (rs) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: /* TODO: handle and report somehow */
+ pass->api_io.status = DC_NET_ERROR;
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 */
@@ -110,12 +116,28 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us,
free(pass->body); /* _READ was not called and pointer was static */
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_CLIENT_ESTABLISHED: /* websocket established */
+ pass->api_io.status = DC_OK;
+ break;
+ case LWS_CALLBACK_CLIENT_CLOSED: /* websocket closed */
+ pass->api_io.status = DC_NET_ERROR;
+ 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 */
+ 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));
+ }
+ lejp_parse(pass->json_context, (const unsigned char) in, len);
+ break;
default:
break;
}
return lws_callback_http_dummy(wsi, rs, us, in, len);
}
void dc_api_i (struct dc_api_io i) { /* this function does not call attached functions, only output does that */
+ struct lws_client_connect_info info;
+ struct dc_lws_pass * pass;
if (!i.program)
return;
if (i.program->lws_context && !(i.status & DC_FROM_LWS))
@@ -131,7 +153,6 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
if (!i.program->clients_length) /* already attempted login */
DC_MR(i.program->clients);
i.program->clients[0] = i.client;
- struct lws_client_connect_info info;
memset(&info, 0, sizeof(info));
info.context = i.program->lws_context;
#if DC_SERVER_SSL
@@ -143,7 +164,7 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
info.host = info.address;
/* info.origin = DC_SERVER_ORIGIN; */ /* just don't send it */
info.method = "POST";
- struct dc_lws_pass * pass = calloc(1, sizeof(struct dc_lws_pass)); /* cb frees */
+ pass = calloc(1, sizeof(struct dc_lws_pass)); /* cb frees */
fprintf(stderr, "allocated pass at %p\n", (void *) pass);
i.status |= DC_MUST_FREE;
pass->body_length = smprintf(&pass->body, DC_LOGIN_FORMAT, i.client->email, i.client->password);
@@ -159,33 +180,70 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
info.protocol = dc_lws_protocols[0].name;
if (i.client->authorization) /* attempt cookie login without user/pass */
strcpy(pass->headers[DC_LWS_AUTHORIZATION], i.client->authorization);
+ i.status = DC_UNSET /* we clear the status */;
lws_client_connect_via_info(&info);
break;
case DC_API_LOGIN_CB:
-#define DC_STACK_RETURN(x) do { i.status = x; dc_api_stack(i); return; } while (0)
+#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);
if (!i.pass->body) {
- i.status |= DC_REQUEST_FAILED;
- i.status &= ~(DC_OK);
+ i.client->status |= DC_REQUEST_FAILED;
dc_api_stack(i);
break;
}
fprintf(stderr, "DEBUG: %s\n", i.pass->body);
if (strstr(i.pass->body, "INVALID_LOGIN"))
- DC_STACK_ERROR(DC_BAD_LOGIN);
+ DC_STACK_RETURN(DC_BAD_LOGIN);
if (strstr(i.pass->body, "captcha-required"))
- DC_STACK_ERROR(DC_CAPTCHA_NEEDED);
+ DC_STACK_RETURN(DC_CAPTCHA_NEEDED);
if (strstr(i.pass->body, "ACCOUNT_LOGIN_VERIFICATION_EMAIL"))
- DC_STACK_ERROR(DC_VERIFICATION_NEEDED);
- char * cp, c2;
- if (!(cp = strstr(i.pass->body, "\"token\": \"")) || !(c2 = strchr(cp+strlen("\"token\": \""), '"')))
- DC_STACK_ERROR(DC_ERROR);
+ DC_STACK_RETURN(DC_VERIFICATION_NEEDED);
+ char * cp, * c2;
+#define DC_LTS "\"token\": \""
+ if (!(cp = strstr(i.pass->body, DC_LTS)+strlen(DC_LTS)) || !(c2 = strchr(cp+strlen(DC_LTS), '"')))
+ DC_STACK_RETURN(DC_ERROR);
c2[0] = '\0'; /* body is on heap, we can edit it */
free(i.client->authorization); /* in case we set it previously */
i.client->authorization = strdup(cp);
fprintf(stderr, "got token %s\n", i.client->authorization);
- DC_STACK_RETURN(DC_OK | DC_INCOMPLETE /* we now have to request properties */);
+ i.client->status |= DC_OK | DC_INCOMPLETE;
+ dc_api_stack(i);
+ /* we now have to connect to the websocket */
+ goto ws; /* we could call dc_api_i but that just fills stack */
+ break;
+ case DC_API_WS:
+ ws:
+ memset(&info, 0, sizeof(info));
+ info.context = i.program->lws_context;
+ if (!info.context)
+ fprintf(stderr, "[BUG] !!! !info.context\n");
+ info.port = DC_WS_PORT;
+ info.address = DC_WS_ADDRESS;
+ info.path = DC_WS_PATH;
+ info.host = info.address;
+ info.origin = DC_WS_ORIGIN;
+#if DC_WS_SSL
+ info.ssl_connection = LCCSCF_USE_SSL;
+#endif
+ info.protocol = dc_lws_protocols[0].name;
+ info.local_protocol_name = info.protocol;
+ pass = calloc(1, sizeof(struct dc_lws_pass));
+ i.type = DC_API_WS_CB;
+ memcpy(&pass->api_io, &i, sizeof(i));
+ pass->api_io.pass = pass;
+ info.userdata = pass;
+ if (i.client->authorization) /* attempt cookie login without user/pass */
+ strcpy(pass->headers[DC_LWS_AUTHORIZATION], i.client->authorization);
+ fprintf(stderr, "starting websocket session\n");
+ i.status = DC_UNSET; /* we clear the status */
+ if (!lws_client_connect_via_info(&info)) {
+ i.client->status = DC_NET_ERROR;
+ dc_api_stack(i);
+ }
break;
+ case DC_API_WS_CB:
+ dc_api_stack(i); /* .status is either NET_ERROR 4 closed or error or OK 4 esta */
case DC_API_REGISTER:
break;
case DC_API_STATUS:
diff --git a/src/h.c b/src/h.c
index 19e52fb..c72c710 100644
--- a/src/h.c
+++ b/src/h.c
@@ -18,7 +18,7 @@
enum dc_status { /* theese are flags and should be and-checked */
DC_UNSET = 0, /* default value when enum is calloced */
DC_INCOMPLETE = 1 << 0, /* struct SHALL NOT be used by the ui, it is yet to be filled by api */
- DC_OK = 1 << 1, /* success status, api usually sets this after completion/filling of the strct */
+ DC_OK = 1 << 1, /* success status and also ws established*/
DC_BAD_LOGIN = 1 << 2, /* login failed because of wrong credentials */
DC_VERIFICATION_NEEDED = 1 << 3, /* login: check email, click link/reg: tough luck ur IP flagd */
DC_CAPTCHA_NEEDED = 1 << 4, /* must solve captcha, tough luck, not impl, use browser login */
@@ -30,8 +30,10 @@ enum dc_status { /* theese are flags and should be and-checked */
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_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 */
+}; /* 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 */
DC_CHANNEL_VIEW = 1 << 10, /* all enum fields here have values same as the server values */
@@ -63,7 +65,11 @@ enum dc_api_io_type {
DC_API_LOGIN, /* i: pass a dc_client-tr1, to relogin FIX prev retd cl not create new */
/* o: the previously passed dc_client with set status, do not use yet! \/ */
DC_API_LOGIN_CB,/* i: used internally for passing response from http client to api, see source */
- /* o: to tell user that client is now fully filled and ready for use */
+ /* o: to tell user that ->client is now fully filled and ready for use */
+ DC_API_WS, /* i: internal, LOGIN_CB calls it to start websocket setup */
+ /* o: ->status of websocket setup (DC_OK indicates success) */
+ DC_API_WS_CB, /* i: internal, for passing response from ws lib to api, see source */
+ /* o: N/A */
DC_API_REGISTER,/* i: pass a dc_client, to relogin FIX pr rt cl&cl->user not creat new */
/* o: the previously passed dc_client with set status */
DC_API_STATUS, /* i: N/A */
@@ -74,7 +80,7 @@ enum dc_api_io_type {
/* o: prev passed dc_role but filled (or not: ->status may indicate error) */
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. */
/* 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 */
@@ -108,12 +114,21 @@ char * dc_lws_headers[] = {
"Authorization:",
"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_PATHS_LENGTH
+}
+char * dc_json_paths[] = { /* array of paths we are interested in */
+ "op",
+}
struct dc_lws_pass { /* struct that is allocated for in dc_lws_cb unique per connection in void * us */
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 */
+ enum lejp_callbacks json_reason; /* holds last reason sent to json callback */
};
struct dc_client {
DC_STRUCT_PREFIX
@@ -300,7 +315,7 @@ void dc_attached_function_free (struct dc_attached_function * s) {
}
static int dc_lws_cb (struct lws *, enum lws_callback_reasons, void *, void *, size_t);
static const struct lws_protocols dc_lws_protocols[] = {
- {"dc", dc_lws_cb, sizeof(struct dc_lws_pass), DC_LWS_MAX_RX, 0, NULL, 0},
+ {"dc", dc_lws_cb, /* sizeof(struct dc_lws_pass) */ 0 /* lws naj NE ALOCIRA */, DC_LWS_MAX_RX, 0, NULL, 0},
{NULL, NULL, 0, 0, 0, NULL, 0}
};
#define DC_ISASQ(shortname) DC_ISA(struct dc_##shortname, shortname##s) /* in struct array of structs quick */
diff --git a/src/lib.c b/src/lib.c
index 9916264..e8d54b2 100644
--- a/src/lib.c
+++ b/src/lib.c
@@ -1,4 +1,4 @@
-int smprintf(char ** str, const char * format, ...) { /* allocates automaticalls (: */
+int smprintf (char ** str, const char * format, ...) { /* allocates automaticalls (: */
va_list ap, aq;
va_start(ap, format);
va_copy(aq, ap);
diff --git a/src/main.c b/src/main.c
index cd35da6..4b4121e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -9,8 +9,8 @@ int main (int argc, char * argv[]) {
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 */);
- client->email = strdup(argv[1]);
- client->password = strdup(argv[2]);
+ client->email = strdup(getenv("DC_E"));
+ client->password = strdup(getenv("DC_P"));
struct dc_api_io i = {
.program = p,
.type = DC_API_LOGIN,