summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2021-09-18 21:33:34 +0200
committerAnton Luka Šijanec <anton@sijanec.eu>2021-09-18 21:33:34 +0200
commit93a241fc7d93aad6ad26857472d80ec58adae3d8 (patch)
treea499822c015bfe82bfd9cc18ca6a8ed6cc0bc624
parentwow, this is really going somewhere (diff)
downloaddiscord.c-93a241fc7d93aad6ad26857472d80ec58adae3d8.tar
discord.c-93a241fc7d93aad6ad26857472d80ec58adae3d8.tar.gz
discord.c-93a241fc7d93aad6ad26857472d80ec58adae3d8.tar.bz2
discord.c-93a241fc7d93aad6ad26857472d80ec58adae3d8.tar.lz
discord.c-93a241fc7d93aad6ad26857472d80ec58adae3d8.tar.xz
discord.c-93a241fc7d93aad6ad26857472d80ec58adae3d8.tar.zst
discord.c-93a241fc7d93aad6ad26857472d80ec58adae3d8.zip
-rw-r--r--Makefile2
-rw-r--r--README.md2
-rw-r--r--src/api.c151
-rw-r--r--src/h.c108
-rw-r--r--src/main.c1
-rw-r--r--src/ui.c14
6 files changed, 221 insertions, 57 deletions
diff --git a/Makefile b/Makefile
index 403fb2f..ee8209f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
DESTDIR=/
SRCFILE=src/main.c
-CFLAGS += -Wextra -Wall -pedantic -g -Og -Itmp -Isrc -I. -odiscord.c -Wno-unused-parameter -rdynamic -finput-charset=UTF-8 -fextended-identifiers
+CFLAGS += -Wextra -Wall -pedantic -g -O0 -Itmp -Isrc -I. -odiscord.c -Wno-unused-parameter -rdynamic -finput-charset=UTF-8 -fextended-identifiers
LIBS += -lm
CFLAGS += $(shell pkg-config --cflags libwebsockets) $(shell pkg-config --cflags gtk+-3.0) $(shell pkg-config --cflags gmodule-export-2.0)
LIBS += $(shell pkg-config --libs libwebsockets) $(shell pkg-config --libs gtk+-3.0) $(shell pkg-config --libs gmodule-export-2.0)
diff --git a/README.md b/README.md
index 975286d..6928498 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ make
#### building requirements
* a POSIX build system - app is cross platform and should also be able to be compiled for all major OSes
-* `gcc`
+* `gcc` or `clang`
* GNU `make`
* `libwebsockets-dev` for http and ws client and json parser
* `libgtk-3-dev` for the GUI
diff --git a/src/api.c b/src/api.c
index 1107d53..c0dfc48 100644
--- a/src/api.c
+++ b/src/api.c
@@ -16,7 +16,7 @@
/* 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);
- i.program->api_ios[i.program->api_ios_length] = malloc(sizeof(i));
+ assert((i.program->api_ios[i.program->api_ios_length] = malloc(sizeof(i))));
*(i.program->api_ios[i.program->api_ios_length++]) = i;
return;
}
@@ -52,49 +52,48 @@ signed char dc_json_cb (struct lejp_ctx * ctx, char /* enum lejp_callbacks */ re
}
return '\0';
}
- if ((cp = startswith(ctx->path, dc_json_paths[DC_JSON_ME]))) {
+ if (startswith(ctx->path, dc_json_paths[DC_JSON_ME]) && reason & LEJP_FLAG_CB_IS_VALUE) {
if (!client->user) {
- DC_MR(program->users); /* we don't need DC_IN_PROGRESS cause we have ref in cl */
- program->users[program->users_length] = client->user = dc_user_init();
- }
- if (reason & LEJPCB_OBJECT_END) {
- pass->api_io.type = DC_API_USER;
- dc_api_stack(pass->api_io); /* inform api user */
+ 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 */
+ } /* we do not check on the object end */
+ switch (path) { /* email is already set from login */
+ case DC_JSON_ME_USERNAME:
+ if (!client->user->username) client->user->username = strdup(ctx->buf);
+ break;
+ case DC_JSON_ME_ID:
+ if (!client->user->id) client->user->id = strtoull(ctx->buf, NULL, 10);
+ break;
+ case DC_JSON_ME_DISCRIMINATOR:
+ if (client->user->status & DC_INCOMPLETE) { /* we can't just check if */
+ client->user->discriminator = atoi(ctx->buf); /* !discriminat, */
+ client->user->status &= ~DC_INCOMPLETE; /* because 0 is allowd */
+ }
+ break;
+ default:
+ break;
}
- if (reason & LEJP_FLAG_CB_IS_VALUE && cp[0])
- switch (cp[1]) { /* email is already set from login */
- case 'u': /* sername */
- if (!client->user->username) client->user->username = strdup(ctx->buf);
- break;
- case 'i': /* d */
- if (!client->user->id) client->user->id = strtoull(ctx->buf, NULL, 10);
- break;
- case 'd': /* iscriminator */ /* check is for id, discriminator may b 0 */
- if (!client->user->id) client->user->discriminator = atoi(ctx->buf);
- break;
- }
}
if ((cp = startswith(ctx->path, dc_json_paths[DC_JSON_FRIEND]))) {
if (!client->user) /* for the client user */
client->user = dc_user_init();
- if (reason == LEJPCB_OBJECT_START) { /* DC_JSON_FRIEND may not be an object*/
- if (!pass->api_io.user) /* for the newly found friend (-; */
- pass->api_io.user = dc_user_init();
- pass->api_io.user->status &= ~(DC_IN_PROGRESS); /* if we never get here again */
+ 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();
+ pass->api_io.user->status |= DC_IN_PROGRESS; /* if we never get here again */
}
- if (reason == LEJPCB_OBJECT_END) {
- fprintf(stderr, "got a new friend (: %s#%d %llu", pass->api_io.user->username ? pass->api_io.user->username : "NULL", pass->api_io.user->discriminator, pass->api_io.user->id);
- struct dc_user * user = client->user; /* client->user->next is 1. friend */
- while (user->next) /* traverse to last friend */
- user = user->next;
- DC_MR(program->users);
- program->users[program->users_length++] = user->next = pass->api_io.user;
- pass->api_io.user->status &= ~(DC_IN_PROGRESS);
- pass->api_io.type = DC_API_USER;
- dc_api_stack(pass->api_io); /* inform api user */
+ 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 {
+ 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->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])
+ 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 */
@@ -108,6 +107,77 @@ signed char dc_json_cb (struct lejp_ctx * ctx, char /* enum lejp_callbacks */ re
break; /* yeah, we don't care about nicknames */
}
}
+ if ((cp = 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[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();
+ 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;
+ 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)
+ switch (path) {
+ case DC_JSON_DM_TYPE:
+ pass->api_io.channel->type = atoi(ctx->buf);
+ break;
+ case DC_JSON_DM_ID:
+ pass->api_io.channel->id = strtoull(ctx->buf, NULL, 10);
+ break;
+ default:
+ 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();
+ 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 = NULL; /* we're done, NULL it or PROBLEMS WILL APPEAR!!! */
+ }
+ if (reason & LEJP_FLAG_CB_IS_VALUE && cp[0] && pass->api_io.channel)
+ switch (path) {
+ case DC_JSON_DM_USER_ID:
+ pass->api_io.user->id = strtoull(ctx->buf, NULL, 10);
+ break;
+ case DC_JSON_DM_USER_DISCRIMINATOR:
+ pass->api_io.user->discriminator = atoi(ctx->buf);
+ break;
+ case DC_JSON_DM_USER_NAME:
+ if (!pass->api_io.user->username)
+ pass->api_io.user->username = strdup(ctx->buf);
+ break;
+ default:
+ 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 */
@@ -225,9 +295,8 @@ static int dc_lws_cb (struct lws * wsi, enum lws_callback_reasons rs, void * us,
dc_api_i(pass->api_io);
pass->api_io.status &= ~DC_FROM_LWS;
}
- free(pass->body); /* body is always allocated on heap! */
pass->api_io.client->pass = NULL;
- free(pass); /* called at the final moment when user pointer and wsi is still */
+ dc_lws_pass_free(pass); /* called at the final time us ptr 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) {
@@ -400,6 +469,7 @@ 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);
@@ -437,6 +507,13 @@ void dc_api_i (struct dc_api_io i) { /* this function does not call attached fun
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) {
+ i.client = i.program->clients[x];
+ i.type = DC_API_WS;
+ dc_api_i(i);
+ }
+ }
struct dc_api_io o = {
.type = DC_API_NONE,
.program = i.program
diff --git a/src/h.c b/src/h.c
index 8e6a56c..dd1ba21 100644
--- a/src/h.c
+++ b/src/h.c
@@ -39,6 +39,8 @@ enum dc_status { /* theese are flags and should be and-checked */
DC_SET_PASS = 1 << 16, /* whether _CREATE _cb shall set client->passs. api sets on _WS create */
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_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 */
@@ -158,8 +160,17 @@ enum dc_json_paths { /* lws reduces the following char array to uint8_t, so we c
DC_JSON_S, /* packet sequence number */
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_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_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_NAME,
+ DC_JSON_DM_USER_ID,
+ DC_JSON_DM_USER_DISCRIMINATOR,
DC_JSON_PATHS_LENGTH
};
char * dc_json_paths[] = { /* array of paths we are interested in */
@@ -167,8 +178,17 @@ char * dc_json_paths[] = { /* array of paths we are interested in */
"s",
"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.private_channels[]"
+ "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"
};
struct dc_lws_pass { /* struct that is allocated for in dc_lws_cb unique per connection in void * us */
DC_STRUCT_PREFIX
@@ -214,17 +234,20 @@ struct dc_client {
char * email; /* yesfree */
char * password; /* yesfree */
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;
- DC_ISASQ(payload); /* array of payloads we must send over ws */
+ 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 */
};
struct dc_client * dc_client_init () {
struct dc_client * s = calloc(1, sizeof(*s));
DC_ISASIQ(payload);
+ DC_ISASIQ(guild);
+ DC_ISASIQ(user);
return s;
}
void dc_client_free (struct dc_client * s) {
@@ -234,6 +257,8 @@ void dc_client_free (struct dc_client * s) {
free(s->email);
free(s->password);
DC_ISAF(payload);
+ free(s->guilds);
+ free(s->users);
free(s);
}
void dc_ws_stack(struct dc_client * c, struct dc_payload * p) { /* we could just use pass->body and */
@@ -249,12 +274,10 @@ void dc_ws_stack(struct dc_client * c, struct dc_payload * p) { /* we could just
struct dc_guild {
DC_STRUCT_PREFIX
char * name; /* yesfree */
- char * alternative_messages_url; /* yesfree, internal - for virtual DMs guild */
unsigned long long int id; /* 0 for virtual DMs guild */
struct dc_client * client; /* nofree */
- struct dc_guild * next; /* nofree - next guild (linked list of all guilds of dc_client) */
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 */
+ struct dc_role * role; /* nofree - first role. NOTE: 1. role->id == guild->id (@everyone role) */
enum dc_permissions permissions;
enum dc_status status;
#ifdef DC_UI_GTK
@@ -271,7 +294,6 @@ void dc_guild_free (struct dc_guild * s) {
if (!s)
return;
free(s->name);
- free(s->alternative_messages_url);
free(s);
}
struct dc_channel {
@@ -285,6 +307,7 @@ struct dc_channel {
struct dc_message * message; /* nofree - first message (ordered by time) */
struct dc_permission * permission; /* nofree - first permission */
enum dc_status status;
+ DC_ISASQ(user); /* yesfree array only - participants in DM channels */
#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. */
@@ -292,6 +315,7 @@ struct dc_channel {
};
struct dc_channel * dc_channel_init () {
struct dc_channel * s = calloc(1, sizeof(*s));
+ DC_ISASIQ(user);
return s;
}
void dc_channel_free (struct dc_channel * s) {
@@ -299,6 +323,7 @@ void dc_channel_free (struct dc_channel * s) {
return;
free(s->name);
free(s->topic);
+ free(s->users);
free(s);
}
struct dc_message {
@@ -366,8 +391,6 @@ struct dc_user {
unsigned long long int id;
short int discriminator;
enum dc_status status;
- char * path; /* yesfree, internal, so parser knows what object this user is from */
- struct dc_user * next; /* nofree, next friend in ll of friends, first is client->user->next */
};
struct dc_user * dc_user_init () {
struct dc_user * s = calloc(1, sizeof(*s));
@@ -377,7 +400,6 @@ void dc_user_free (struct dc_user * s) {
if (!s)
return;
free(s->username);
- free(s->path);
free(s);
}
struct dc_permission { /* permissions can be individual on a per-channel basis */
@@ -417,6 +439,13 @@ void dc_attached_function_free (struct dc_attached_function * s) {
return;
free(s);
}
+void dc_lws_pass_free (struct dc_lws_pass * s) {
+ if (!s)
+ return;
+ free(s->body);
+ DC_API_IO_GC(s->api_io);
+ 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[] = {
{"dc", dc_lws_cb, /* sizeof(struct dc_lws_pass) */ 0 /* lws naj NE ALOCIRA */, DC_LWS_MAX_RX, 0, NULL, 0},
@@ -509,3 +538,62 @@ void dc_program_free (struct dc_program * s) {
lws_context_destroy(s->lws_context); /* closes all connections and destroys all wsis */
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) { \
+ for (size_t i = 0; i < l; i++) \
+ if (p[i]->id == id) { \
+ fprintf(stderr, "id %llu was found!\n", id); \
+ return p[i]; \
+ } \
+ return NULL; \
+}
+#define DC_FIND_LL_X(x) struct dc_##x * dc_find_ll_##x(struct dc_##x * u, unsigned long long int id) { \
+ struct dc_##x ** z = &u; \
+ if (!u) \
+ return NULL; \
+ while (*z) { \
+ if ((*z)->id == id) \
+ return *z; \
+ z = &(*z)->next; \
+ } \
+ return NULL; \
+} /* untested, not sure if works!!! */
+#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 (s & DC_MAY_FREE) { \
+ dc_##x##_free(u); \
+ fprintf(stderr, "debug: %s freed already existing member\n", __func__); \
+ } \
+ return us; \
+ } \
+ if (*so <= *l) { \
+ *so = ceil(*so*DC_REALLOC_K); \
+ *p = realloc(*p, sizeof(**p) * *so); \
+ fprintf(stderr, "debug: %s resized ISA\n", __func__); \
+ } \
+ (*p)[(*l)++] = u; \
+ return u; \
+}
+#define DC_ADDR_X(x, y) struct dc_##x * dc_addr_##x (struct dc_program * p, struct dc_##x *** a, size_t * so, size_t * l, struct dc_##x * u, enum dc_status s) { \
+ struct dc_##x * us; \
+ if (!a) { \
+ if (u == (us = dc_add_##x(&p->x##s, &p->x##s_sizeof, &p->x##s_length, u, s))) \
+ return us; \
+ } else \
+ if (u == (us = dc_add_##x(a, so, l, u, s))) \
+ return us; \
+ struct dc_api_io io; \
+ memset(&io, 0, sizeof(io)); \
+ io.type = DC_API_##y; \
+ io.program = p; \
+ io.x = u; \
+ dc_api_stack(io); \
+ return u; \
+} /* add with report */
+#define DC_GEN_X(x, y) DC_FIND_X(x) DC_ADD_X(x) DC_ADDR_X(x, y)
+DC_GEN_X(user, USER)
+DC_GEN_X(channel, CHANNEL)
+DC_FIND_LL_X(channel)
+#define DC_ISAE(a) &a, &a##_sizeof, &a##_length /* ISA Expand */
+#define DC_ISAN NULL, NULL, NULL /* ISA NULL */
diff --git a/src/main.c b/src/main.c
index 4b4121e..401fb2d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2,6 +2,7 @@
#include <stdlib.h>
#include <math.h>
#include <libwebsockets.h>
+#include <assert.h>
#include <lib.c>
#include <ui.c>
#include <api.c>
diff --git a/src/ui.c b/src/ui.c
index 02375f2..f2dbe7a 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -75,15 +75,13 @@ void dc_ui_spawn_channel (struct dc_channel * c /* needs a functional guild or s
GtkTreeIter i;
if (!c) {
gtk_tree_store_clear(l);
- struct dc_guild * g = c->guild->client->guild;
- while (g) {
- g->is_iter = FALSE;
- struct dc_channel * c = g->channel;
- while (c) {
- c->is_iter = FALSE;
- c = c->next;
+ for (size_t i = 0; i < c->guild->client->guilds_length; i++) {
+ c->guild->client->guilds[i]->is_iter = FALSE;
+ struct dc_channel * ch = c->guild->client->guilds[i]->channel;
+ while (ch) {
+ ch->is_iter = FALSE;
+ ch = ch->next;
}
- g = g->next;
}
}
if (!c->guild->is_iter) { /* we don't have this guild already rendered */