diff --git a/libdc/include/dc/channel.h b/libdc/include/dc/channel.h index 7a7598d..d174a71 100644 --- a/libdc/include/dc/channel.h +++ b/libdc/include/dc/channel.h @@ -21,27 +21,27 @@ typedef enum { /* A direct message channel for 1:1 communication */ - CHANNEL_TYPE_DM, + CHANNEL_TYPE_DM = 1, /* A guild voice channel */ - CHANNEL_TYPE_GUILD_VOICE, + CHANNEL_TYPE_GUILD_VOICE = 2, /* Group direct message channel 1:N communication */ - CHANNEL_TYPE_GROUP_DM, + CHANNEL_TYPE_GROUP_DM = 3, /* Category within a GUILD */ - CHANNEL_TYPE_GUILD_CATEGORY, + CHANNEL_TYPE_GUILD_CATEGORY = 4, /* News channel */ - CHANNEL_TYPE_GUILD_NEWS, + CHANNEL_TYPE_GUILD_NEWS = 5, /* Guild store, no idea what this is */ - CHANNEL_TYPE_GUILD_STORE, + CHANNEL_TYPE_GUILD_STORE = 6, } dc_channel_type_t; struct dc_channel_; @@ -51,6 +51,7 @@ dc_channel_t dc_channel_new(void); dc_channel_t dc_channel_from_json(json_t *j); char const *dc_channel_id(dc_channel_t c); +char const *dc_channel_name(dc_channel_t c); dc_channel_type_t dc_channel_type(dc_channel_t c); bool dc_channel_is_dm(dc_channel_t c); diff --git a/libdc/include/dc/guild.h b/libdc/include/dc/guild.h index 97cdb04..ab3b13a 100644 --- a/libdc/include/dc/guild.h +++ b/libdc/include/dc/guild.h @@ -1,12 +1,21 @@ #ifndef DC_GUILD_H #define DC_GUILD_H +#include + +#include +#include + /* Discords version of groups or chat servers */ struct dc_guild_; typedef struct dc_guild_ *dc_guild_t; dc_guild_t dc_guild_new(void); +dc_guild_t dc_guild_from_json(json_t *j); + +size_t dc_guild_channels(dc_guild_t d); +dc_channel_t dc_guild_nth_channel(dc_guild_t d, size_t idx); char const *dc_guild_name(dc_guild_t d); void dc_guild_set_name(dc_guild_t d, char const *val); diff --git a/libdc/include/dc/session.h b/libdc/include/dc/session.h index 76f78c5..943f045 100644 --- a/libdc/include/dc/session.h +++ b/libdc/include/dc/session.h @@ -8,6 +8,7 @@ #include #include #include +#include /** * A session object will contain all information gathered after a user @@ -86,6 +87,12 @@ dc_channel_t dc_session_make_channel(dc_session_t s, dc_account_t *r, dc_channel_t dc_session_channel_recipients(dc_session_t s, dc_account_t *r, size_t sz); +/** + * Add a guild to be managed by this session. + */ +void dc_session_add_guild(dc_session_t s, dc_guild_t g); +GHashTable *dc_session_guilds(dc_session_t s); + /** * comparision functions for sorting, and finding */ diff --git a/libdc/src/channel.c b/libdc/src/channel.c index 20fd937..a01c784 100644 --- a/libdc/src/channel.c +++ b/libdc/src/channel.c @@ -124,7 +124,7 @@ dc_channel_t dc_channel_from_json(json_t *j) } v = json_object_get(j, "name"); - if (v == NULL && json_is_string(v)) { + if (v != NULL && json_is_string(v)) { c->name = strdup(json_string_value(v)); } @@ -237,6 +237,12 @@ json_t *dc_channel_to_json(dc_channel_t c) return j; } +char const *dc_channel_name(dc_channel_t c) +{ + return_if_true(c == NULL, NULL); + return c->name; +} + char const *dc_channel_id(dc_channel_t c) { return_if_true(c == NULL, NULL); diff --git a/libdc/src/guild.c b/libdc/src/guild.c index 966f1bd..f6fd739 100644 --- a/libdc/src/guild.c +++ b/libdc/src/guild.c @@ -9,6 +9,8 @@ struct dc_guild_ char *name; char *id; + + GPtrArray *channels; }; static void dc_guild_free(dc_guild_t ptr) @@ -16,6 +18,11 @@ static void dc_guild_free(dc_guild_t ptr) free(ptr->name); free(ptr->id); + if (ptr->channels != NULL) { + g_ptr_array_unref(ptr->channels); + ptr->channels = NULL; + } + free(ptr); } @@ -26,9 +33,64 @@ dc_guild_t dc_guild_new(void) p->ref.cleanup = (dc_cleanup_t)dc_guild_free; + p->channels = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); + if (p->channels == NULL) { + free(p); + return NULL; + } + return dc_ref(p); } +dc_guild_t dc_guild_from_json(json_t *j) +{ + dc_guild_t g = dc_guild_new(); + json_t *val = NULL; + json_t *c = NULL; + size_t idx = 0; + + val = json_object_get(j, "name"); + goto_if_true(val == NULL || !json_is_string(val), error); + g->name = strdup(json_string_value(val)); + + val = json_object_get(j, "id"); + goto_if_true(val == NULL || !json_is_string(val), error); + g->id = strdup(json_string_value(val)); + + /* there is a ton of more information here, that we should look + * add, including "member_count", "owner_id", but for channels + * will do nicely + */ + val = json_object_get(j, "channels"); + goto_if_true(val == NULL || !json_is_array(val), error); + + json_array_foreach(val, idx, c) { + dc_channel_t chan = dc_channel_from_json(c); + continue_if_true(chan == NULL); + g_ptr_array_add(g->channels, chan); + } + + return g; + +error: + + dc_unref(g); + return NULL; +} + +size_t dc_guild_channels(dc_guild_t d) +{ + return_if_true(d == NULL || d->channels == NULL, 0); + return d->channels->len; +} + +dc_channel_t dc_guild_nth_channel(dc_guild_t d, size_t idx) +{ + return_if_true(d == NULL || d->channels == NULL, NULL); + return_if_true(idx >= d->channels->len, NULL); + return g_ptr_array_index(d->channels, idx); +} + char const *dc_guild_name(dc_guild_t d) { return_if_true(d == NULL, NULL); diff --git a/libdc/src/session.c b/libdc/src/session.c index 75a2552..ebd556b 100644 --- a/libdc/src/session.c +++ b/libdc/src/session.c @@ -12,6 +12,7 @@ struct dc_session_ GHashTable *accounts; GHashTable *channels; + GHashTable *guilds; }; /* event handlers @@ -40,6 +41,11 @@ static void dc_session_free(dc_session_t s) s->channels = NULL; } + if (s->guilds != NULL) { + g_hash_table_unref(s->guilds); + s->guilds = NULL; + } + dc_session_logout(s); dc_unref(s->api); @@ -79,6 +85,7 @@ static void dc_session_handle_ready(dc_session_t s, dc_event_t e) size_t idx = 0; json_t *c = NULL; json_t *channels = NULL; + json_t *guilds = NULL; /* retrieve user information about ourselves, including snowflake, * discriminator, and other things @@ -127,6 +134,17 @@ static void dc_session_handle_ready(dc_session_t s, dc_event_t e) } } + /* load guilds + */ + guilds = json_object_get(r, "guilds"); + if (guilds != NULL && json_is_array(guilds)) { + json_array_foreach(guilds, idx, c) { + dc_guild_t guild = dc_guild_from_json(c); + continue_if_true(guild == NULL); + dc_session_add_guild(s, guild); + } + } + /* load channels */ channels = json_object_get(r, "private_channels"); @@ -181,6 +199,11 @@ dc_session_t dc_session_new(dc_loop_t loop) ); goto_if_true(s->channels == NULL, error); + s->guilds = g_hash_table_new_full(g_str_hash, g_str_equal, + free, dc_unref + ); + goto_if_true(s->channels == NULL, error); + s->loop = dc_ref(loop); s->api = dc_api_new(); @@ -394,3 +417,23 @@ dc_channel_t dc_session_channel_recipients(dc_session_t s, return NULL; } + +GHashTable *dc_session_guilds(dc_session_t s) +{ + return_if_true(s == NULL, NULL); + return s->guilds; +} + +void dc_session_add_guild(dc_session_t s, dc_guild_t g) +{ + return_if_true(s == NULL || g == NULL,); + return_if_true(dc_guild_id(g) == NULL,); + + char const *id = dc_guild_id(g); + + if (!g_hash_table_contains(s->guilds, id)) { + g_hash_table_insert(s->guilds, strdup(id), dc_ref(g)); + /* TODO: dedup for saving storage + */ + } +} diff --git a/ncdc/CMakeLists.txt b/ncdc/CMakeLists.txt index 4b2490d..7b01352 100644 --- a/ncdc/CMakeLists.txt +++ b/ncdc/CMakeLists.txt @@ -13,6 +13,7 @@ SET(SOURCES "include/ncdc/mainwindow.h" "include/ncdc/ncdc.h" "include/ncdc/textview.h" + "include/ncdc/treeview.h" "src/ack.c" "src/cmds.c" "src/config.c" @@ -26,6 +27,7 @@ SET(SOURCES "src/ncdc.c" "src/post.c" "src/textview.c" + "src/treeview.c" "src/util.c" ) diff --git a/ncdc/include/ncdc/mainwindow.h b/ncdc/include/ncdc/mainwindow.h index d92b8de..d5f4639 100644 --- a/ncdc/include/ncdc/mainwindow.h +++ b/ncdc/include/ncdc/mainwindow.h @@ -24,4 +24,6 @@ void ncdc_mainwindow_input_ready(ncdc_mainwindow_t n); void ncdc_mainwindow_rightview(ncdc_mainwindow_t n); void ncdc_mainwindow_leftview(ncdc_mainwindow_t n); +void ncdc_mainwindow_update_guilds(ncdc_mainwindow_t n); + #endif diff --git a/ncdc/include/ncdc/treeview.h b/ncdc/include/ncdc/treeview.h new file mode 100644 index 0000000..116098c --- /dev/null +++ b/ncdc/include/ncdc/treeview.h @@ -0,0 +1,31 @@ +#ifndef NCDC_TREEVIEW_H +#define NCDC_TREEVIEW_H + +#include +#include +#include + +struct ncdc_treeitem_; +typedef struct ncdc_treeitem_ *ncdc_treeitem_t; + +ncdc_treeitem_t ncdc_treeitem_new(void); +ncdc_treeitem_t ncdc_treeitem_new_string(wchar_t const *s); + +wchar_t const *ncdc_treeitem_label(ncdc_treeitem_t i); +void ncdc_treeitem_set_label(ncdc_treeitem_t i, wchar_t const *s); + +void *ncdc_treeitem_tag(ncdc_treeitem_t i); +void ncdc_treeitem_set_tag(ncdc_treeitem_t i, void *t); + +void ncdc_treeitem_clear(ncdc_treeitem_t i); +void ncdc_treeitem_add(ncdc_treeitem_t i, ncdc_treeitem_t c); +void ncdc_treeitem_remove(ncdc_treeitem_t i, ncdc_treeitem_t c); + +struct ncdc_treeview_; +typedef struct ncdc_treeview_ *ncdc_treeview_t; + +ncdc_treeview_t ncdc_treeview_new(void); +ncdc_treeitem_t ncdc_treeview_root(ncdc_treeview_t t); +void ncdc_treeview_render(ncdc_treeview_t t, WINDOW *w, int lines, int cols); + +#endif diff --git a/ncdc/src/mainwindow.c b/ncdc/src/mainwindow.c index b113307..cb27156 100644 --- a/ncdc/src/mainwindow.c +++ b/ncdc/src/mainwindow.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,8 @@ struct ncdc_mainwindow_ WINDOW *sep2; ncdc_input_t in; + ncdc_treeview_t guildview; + ncdc_treeitem_t root; GPtrArray *views; int curview; @@ -62,6 +65,7 @@ static void ncdc_mainwindow_free(ncdc_mainwindow_t n) delwin(n->sep2); dc_unref(n->in); + dc_unref(n->guildview); if (n->views != NULL) { g_ptr_array_unref(n->views); @@ -81,6 +85,9 @@ ncdc_mainwindow_t ncdc_mainwindow_new(void) ptr->in = ncdc_input_new(); ncdc_input_set_callback(ptr->in, ncdc_mainwindow_callback, ptr); + ptr->guildview = ncdc_treeview_new(); + ptr->root = ncdc_treeview_root(ptr->guildview); + ptr->views = g_ptr_array_new_with_free_func( (GDestroyNotify)dc_unref ); @@ -134,7 +141,7 @@ ncdc_mainwindow_callback(ncdc_input_t i, wchar_t const *s, static void ncdc_mainwindow_resize(ncdc_mainwindow_t n) { n->guilds_h = LINES - 2; - n->guilds_w = (COLS / 5); + n->guilds_w = (COLS / 4); n->guilds_y = 0; n->guilds_x = 0; @@ -239,6 +246,85 @@ static void ncdc_mainwindow_render_status(ncdc_mainwindow_t n) free(status); } +void ncdc_mainwindow_update_guilds(ncdc_mainwindow_t n) +{ + GHashTableIter iter; + gpointer key = NULL, value = NULL; + size_t idx = 0; + + ncdc_treeitem_clear(n->root); + + if (!is_logged_in()) { + return; + } + + g_hash_table_iter_init(&iter, dc_session_guilds(current_session)); + while (g_hash_table_iter_next(&iter, &key, &value)) { + dc_guild_t g = (dc_guild_t)value; + ncdc_treeitem_t i = ncdc_treeitem_new(); + wchar_t *name = NULL; + + if (i == NULL) { + continue; + } + + name = s_convert(dc_guild_name(g)); + if (name == NULL) { + continue; + } + + ncdc_treeitem_set_label(i, name); + + free(name); + name = NULL; + + ncdc_treeitem_set_tag(i, g); + + /* add subchannels + */ + for (idx = 0; idx < dc_guild_channels(g); idx++) { + dc_channel_t c = dc_guild_nth_channel(g, idx); + ncdc_treeitem_t ci = NULL; + + if (dc_channel_name(c) == NULL || + dc_channel_id(c) == NULL) { + continue; + } + + /* skip categories, and shop channels + */ + if (dc_channel_type(c) == CHANNEL_TYPE_GUILD_CATEGORY || + dc_channel_type(c) == CHANNEL_TYPE_GUILD_NEWS || + dc_channel_type(c) == CHANNEL_TYPE_GUILD_STORE) { + continue; + } + + ci = ncdc_treeitem_new(); + if (ci == NULL) { + continue; + } + + aswprintf(&name, L"[%s] %s", + (dc_channel_type(c) == CHANNEL_TYPE_GUILD_VOICE ? + "<" : "#"), + dc_channel_name(c) + ); + if (name == NULL) { + continue; + } + + ncdc_treeitem_set_label(ci, name); + free(name); + name = NULL; + + ncdc_treeitem_set_tag(ci, c); + ncdc_treeitem_add(i, ci); + } + + ncdc_treeitem_add(n->root, i); + } +} + void ncdc_mainwindow_input_ready(ncdc_mainwindow_t n) { wint_t i = 0; @@ -311,6 +397,8 @@ void ncdc_mainwindow_refresh(ncdc_mainwindow_t n) { ncdc_textview_t v = 0; + ncdc_mainwindow_update_guilds(n); + ncdc_treeview_render(n->guildview, n->guilds, n->guilds_h, n->guilds_w); wnoutrefresh(n->guilds); /* render active text view diff --git a/ncdc/src/treeview.c b/ncdc/src/treeview.c new file mode 100644 index 0000000..22002a2 --- /dev/null +++ b/ncdc/src/treeview.c @@ -0,0 +1,181 @@ +#include +#include + +struct ncdc_treeitem_ +{ + dc_refable_t ref; + + /* content + */ + wchar_t *content; + + /* collapsed? + */ + bool collapsed; + + /* children + */ + GPtrArray *children; + + /* user defined data + */ + void *tag; +}; + +static void ncdc_treeitem_free(ncdc_treeitem_t t) +{ + return_if_true(t == NULL,); + + free(t->content); + t->content = NULL; + + if (t->children != NULL) { + g_ptr_array_unref(t->children); + t->children = NULL; + } + + free(t); +} + +ncdc_treeitem_t ncdc_treeitem_new(void) +{ + ncdc_treeitem_t t = calloc(1, sizeof(struct ncdc_treeitem_)); + return_if_true(t == NULL, NULL); + + t->ref.cleanup = (dc_cleanup_t)ncdc_treeitem_free; + + t->children = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); + if (t->children == NULL) { + free(t); + return NULL; + } + + return dc_ref(t); +} + +ncdc_treeitem_t ncdc_treeitem_new_string(wchar_t const *s) +{ + ncdc_treeitem_t t = ncdc_treeitem_new(); + return_if_true(t == NULL, NULL); + + t->content = wcsdup(s); + + return t; +} + +void ncdc_treeitem_add(ncdc_treeitem_t i, ncdc_treeitem_t c) +{ + return_if_true(i == NULL || c == NULL,); + g_ptr_array_add(i->children, dc_ref(c)); +} + +void ncdc_treeitem_remove(ncdc_treeitem_t i, ncdc_treeitem_t c) +{ + return_if_true(i == NULL || c == NULL,); + g_ptr_array_remove(i->children, c); +} + +void ncdc_treeitem_clear(ncdc_treeitem_t i) +{ + return_if_true(i == NULL || i->children == NULL,); + return_if_true(i->children->len == 0,); + g_ptr_array_remove_range(i->children, 0, i->children->len); +} + +void *ncdc_treeitem_tag(ncdc_treeitem_t i) +{ + return_if_true(i == NULL, NULL); + return i->tag; +} + +void ncdc_treeitem_set_tag(ncdc_treeitem_t i, void *t) +{ + return_if_true(i == NULL,); + i->tag = t; +} + +wchar_t const *ncdc_treeitem_label(ncdc_treeitem_t i) +{ + return_if_true(i == NULL, NULL); + return i->content; +} + +void ncdc_treeitem_set_label(ncdc_treeitem_t i, wchar_t const *s) +{ + return_if_true(i == NULL || s == NULL,); + free(i->content); + i->content = wcsdup(s); +} + +static int +ncdc_treeitem_render(ncdc_treeitem_t t, WINDOW *win, + int lines, int cols, int l, int c) +{ + size_t i = 0, off = 0; + int ret = 0; + + if (t->content != NULL) { + size_t len = wcslen(t->content); + + mvwaddwstr(win, l, c, t->content); + off = ((len + c) / cols) + 1; + } + + for (i = 0; i < t->children->len; i++) { + ncdc_treeitem_t child = g_ptr_array_index(t->children, i); + ret = ncdc_treeitem_render(child, win, lines, cols, + l + off, c + 1 + ); + off += ret; + } + + return off; +} + +struct ncdc_treeview_ +{ + dc_refable_t ref; + + ncdc_treeitem_t root; +}; + +static void ncdc_treeview_free(ncdc_treeview_t t) +{ + return_if_true(t == NULL,); + + dc_unref(t->root); + t->root = NULL; + + free(t); +} + +ncdc_treeview_t ncdc_treeview_new(void) +{ + ncdc_treeview_t t = calloc(1, sizeof(struct ncdc_treeview_)); + return_if_true(t == NULL, NULL); + + t->ref.cleanup = (dc_cleanup_t)ncdc_treeview_free; + + t->root = ncdc_treeitem_new(); + goto_if_true(t->root == NULL, error); + + return dc_ref(t); + +error: + + dc_unref(t); + return NULL; +} + +void ncdc_treeview_render(ncdc_treeview_t t, WINDOW *w, int lines, int cols) +{ + werase(w); + wmove(w, 0, 0); + ncdc_treeitem_render(t->root, w, lines, cols, 0, 0); +} + +ncdc_treeitem_t ncdc_treeview_root(ncdc_treeview_t t) +{ + return_if_true(t == NULL, NULL); + return t->root; +}