From c3f4f5a18070338355a74712abb017821fd0bfa6 Mon Sep 17 00:00:00 2001 From: Florian Stinglmayr Date: Mon, 8 Jul 2019 18:38:24 +0200 Subject: [PATCH] fetch messages for channels and display them --- libdc/CMakeLists.txt | 2 + libdc/include/dc/api.h | 5 ++ libdc/include/dc/channel.h | 7 ++ libdc/include/dc/message.h | 24 ++++++ libdc/src/api-channel.c | 41 ++++++++++ libdc/src/channel.c | 44 +++++++++++ libdc/src/internal.h | 2 + libdc/src/message.c | 155 +++++++++++++++++++++++++++++++++++++ ncdc/include/ncdc/ncdc.h | 2 + ncdc/src/login.c | 5 ++ ncdc/src/msg.c | 6 +- ncdc/src/textview.c | 98 +++++++++++++++++++++-- ncdc/src/util.c | 14 ++++ 13 files changed, 397 insertions(+), 8 deletions(-) create mode 100644 libdc/include/dc/message.h create mode 100644 libdc/src/message.c diff --git a/libdc/CMakeLists.txt b/libdc/CMakeLists.txt index 5f29e65..aff9c81 100644 --- a/libdc/CMakeLists.txt +++ b/libdc/CMakeLists.txt @@ -14,6 +14,7 @@ SET(SOURCES "include/dc/channel.h" "include/dc/guild.h" "include/dc/loop.h" + "include/dc/message.h" "include/dc/refable.h" "include/dc/util.h" "src/account.c" @@ -25,6 +26,7 @@ SET(SOURCES "src/channel.c" "src/guild.c" "src/loop.c" + "src/message.c" "src/refable.c" "src/util.c" ) diff --git a/libdc/include/dc/api.h b/libdc/include/dc/api.h index 3beb91b..e2c7e21 100644 --- a/libdc/include/dc/api.h +++ b/libdc/include/dc/api.h @@ -77,6 +77,11 @@ bool dc_api_create_channel(dc_api_t api, dc_account_t login, dc_account_t *recipients, size_t nrecp, dc_channel_t *channel); +/** + * Fetch 50 messages for the given channel. + */ +bool dc_api_get_messages(dc_api_t api, dc_account_t login, dc_channel_t c); + /** * Fetch a list of friends of the login account "login". The friends are stored * within the login object. diff --git a/libdc/include/dc/channel.h b/libdc/include/dc/channel.h index d89495a..ce0b1f5 100644 --- a/libdc/include/dc/channel.h +++ b/libdc/include/dc/channel.h @@ -5,6 +5,7 @@ #include #include +#include /** * A discord channel. Exactly what it says on the tin. A place where one @@ -49,10 +50,16 @@ typedef struct dc_channel_ *dc_channel_t; 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); + dc_channel_type_t dc_channel_type(dc_channel_t c); void dc_channel_set_type(dc_channel_t c, dc_channel_type_t t); size_t dc_channel_recipients(dc_channel_t c); dc_account_t dc_channel_nthrecipient(dc_channel_t c, size_t i); +size_t dc_channel_messages(dc_channel_t c); +dc_message_t dc_channel_nthmessage(dc_channel_t c, size_t i); +void dc_channel_addmessages(dc_channel_t c, dc_message_t *m, size_t s); + #endif diff --git a/libdc/include/dc/message.h b/libdc/include/dc/message.h new file mode 100644 index 0000000..2555ee0 --- /dev/null +++ b/libdc/include/dc/message.h @@ -0,0 +1,24 @@ +#ifndef DC_MESSAGE_H +#define DC_MESSAGE_H + +#include +#include + +#include + +struct dc_message_; +typedef struct dc_message_ *dc_message_t; + +dc_message_t dc_message_new(void); +dc_message_t dc_message_from_json(json_t *j); +json_t *dc_message_to_json(dc_message_t m); + +char const *dc_message_id(dc_message_t m); +char const *dc_message_channel_id(dc_message_t m); +char const *dc_message_timestamp(dc_message_t m); +char const *dc_message_content(dc_message_t m); +dc_account_t dc_message_author(dc_message_t m); + +int dc_message_compare(dc_message_t *a, dc_message_t *b); + +#endif diff --git a/libdc/src/api-channel.c b/libdc/src/api-channel.c index d0d46ab..5d3957f 100644 --- a/libdc/src/api-channel.c +++ b/libdc/src/api-channel.c @@ -2,6 +2,47 @@ #include "internal.h" +bool dc_api_get_messages(dc_api_t api, dc_account_t login, dc_channel_t c) +{ + bool ret = false; + char *url = NULL; + json_t *reply = NULL, *i = NULL; + GPtrArray *msgs = NULL; + size_t idx = 0; + + return_if_true(api == NULL || login == NULL || c == NULL, false); + + msgs = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); + goto_if_true(msgs == NULL, cleanup); + + asprintf(&url, "channels/%s/messages", dc_channel_id(c)); + goto_if_true(url == NULL, cleanup); + + reply = dc_api_call_sync(api, "GET", TOKEN(login), url, NULL); + goto_if_true(reply == NULL, cleanup); + goto_if_true(!json_is_array(reply), cleanup); + + json_array_foreach(reply, idx, i) { + dc_message_t m = dc_message_from_json(i); + g_ptr_array_add(msgs, m); + } + + dc_channel_addmessages(c, (dc_message_t*)msgs->pdata, msgs->len); + ret = true; + +cleanup: + + if (msgs != NULL) { + g_ptr_array_unref(msgs); + msgs = NULL; + } + + json_decref(reply); + free(url); + + return ret; +} + bool dc_api_create_channel(dc_api_t api, dc_account_t login, dc_account_t *recipients, size_t nrecp, dc_channel_t *channel) diff --git a/libdc/src/channel.c b/libdc/src/channel.c index 060396b..8f370d0 100644 --- a/libdc/src/channel.c +++ b/libdc/src/channel.c @@ -43,6 +43,8 @@ struct dc_channel_ /* application ID of the group DM creator if it is bot-created */ char *application_id; + + GPtrArray *messages; }; static void dc_channel_free(dc_channel_t c) @@ -62,6 +64,11 @@ static void dc_channel_free(dc_channel_t c) c->recipients = NULL; } + if (c->messages != NULL) { + g_ptr_array_unref(c->messages); + c->messages = NULL; + } + free(c); } @@ -76,6 +83,10 @@ dc_channel_t dc_channel_new(void) (GDestroyNotify)dc_unref ); + c->messages = g_ptr_array_new_with_free_func( + (GDestroyNotify)dc_unref + ); + return dc_ref(c); } @@ -214,6 +225,12 @@ json_t *dc_channel_to_json(dc_channel_t c) return j; } +char const *dc_channel_id(dc_channel_t c) +{ + return_if_true(c == NULL, NULL); + return c->id; +} + dc_channel_type_t dc_channel_type(dc_channel_t c) { return_if_true(c == NULL, -1); @@ -238,3 +255,30 @@ dc_account_t dc_channel_nthrecipient(dc_channel_t c, size_t i) return_if_true(i >= c->recipients->len, NULL); return g_ptr_array_index(c->recipients, i); } + +size_t dc_channel_messages(dc_channel_t c) +{ + return_if_true(c == NULL || c->messages == NULL, 0); + return c->messages->len; +} + +dc_message_t dc_channel_nthmessage(dc_channel_t c, size_t i) +{ + return_if_true(c == NULL || c->messages == NULL, NULL); + return_if_true(i >= c->messages->len, NULL); + return g_ptr_array_index(c->messages, i); +} + +void dc_channel_addmessages(dc_channel_t c, dc_message_t *m, size_t s) +{ + return_if_true(c == NULL || c->messages == NULL,); + return_if_true(m == NULL || s == 0,); + + size_t i = 0; + + for (i = 0; i < s; i++) { + g_ptr_array_add(c->messages, dc_ref(m[i])); + } + + g_ptr_array_sort(c->messages, (GCompareFunc)dc_message_compare); +} diff --git a/libdc/src/internal.h b/libdc/src/internal.h index 8ad02e4..8cc5e7e 100644 --- a/libdc/src/internal.h +++ b/libdc/src/internal.h @@ -30,4 +30,6 @@ #define return_if_true(v,r) do { if (v) return r; } while(0) #define goto_if_true(v,l) do { if (v) goto l; } while(0) +#define TOKEN(l) (dc_account_token(l)) + #endif diff --git a/libdc/src/message.c b/libdc/src/message.c new file mode 100644 index 0000000..b9d41bc --- /dev/null +++ b/libdc/src/message.c @@ -0,0 +1,155 @@ +#include +#include "internal.h" + +struct dc_message_ +{ + dc_refable_t ref; + + char *id; + char *timestamp; + char *content; + char *channel_id; + + time_t ts; + + dc_account_t author; +}; + +static void dc_message_parse_timestamp(dc_message_t m); + +static void dc_message_free(dc_message_t m) +{ + return_if_true(m == NULL,); + + free(m->id); + free(m->timestamp); + free(m->content); + free(m->channel_id); + + dc_unref(m->author); + + free(m); +} + +dc_message_t dc_message_new(void) +{ + dc_message_t m = calloc(1, sizeof(struct dc_message_)); + return_if_true(m == NULL, NULL); + + m->ref.cleanup = (dc_cleanup_t)dc_message_free; + return dc_ref(m); +} + +dc_message_t dc_message_from_json(json_t *j) +{ + dc_message_t m = NULL; + json_t *val = NULL; + return_if_true(j == NULL || !json_is_object(j), NULL); + + m = dc_message_new(); + return_if_true(m == NULL, NULL); + + val = json_object_get(j, "id"); + goto_if_true(val == NULL || !json_is_string(val), error); + m->id = strdup(json_string_value(val)); + + val = json_object_get(j, "timestamp"); + goto_if_true(val == NULL || !json_is_string(val), error); + m->timestamp = strdup(json_string_value(val)); + + dc_message_parse_timestamp(m); + + val = json_object_get(j, "content"); + goto_if_true(val == NULL || !json_is_string(val), error); + m->content = strdup(json_string_value(val)); + + val = json_object_get(j, "channel_id"); + goto_if_true(val == NULL || !json_is_string(val), error); + m->channel_id = strdup(json_string_value(val)); + + val = json_object_get(j, "author"); + goto_if_true(val == NULL || !json_is_object(val), error); + m->author = dc_account_from_json(val); + + return m; + +error: + + dc_unref(m); + return NULL; +} + +json_t *dc_message_to_json(dc_message_t m) +{ + return_if_true(m == NULL, NULL); + + json_t *j = json_object(); + return_if_true(j == NULL, NULL); + + if (m->id != NULL) { + json_object_set_new(j, "id", json_string(m->id)); + } + + if (m->timestamp != NULL) { + json_object_set_new(j, "timestamp", json_string(m->timestamp)); + } + + if (m->channel_id != NULL) { + json_object_set_new(j, "channel_id", json_string(m->channel_id)); + } + + if (m->author != NULL) { + json_t *a = dc_account_to_json(m->author); + json_object_set_new(j, "author", a); + } + + json_object_set_new(j, "content", json_string(m->content)); + + return j; +} + +static void dc_message_parse_timestamp(dc_message_t m) +{ + return_if_true(m == NULL || m->timestamp == NULL,); + struct tm t = {0}; + + strptime(m->timestamp, "%Y-%m-%dT%H:%M:%S", &t); + m->ts = timegm(&t); +} + +int dc_message_compare(dc_message_t *a, dc_message_t *b) +{ + return_if_true(a == NULL || *a == NULL || + b == NULL || *b == NULL, 0); + return (*a)->ts - (*b)->ts; +} + +char const *dc_message_id(dc_message_t m) +{ + return_if_true(m == NULL, NULL); + return m->id; +} + +char const *dc_message_channel_id(dc_message_t m) +{ + return_if_true(m == NULL, NULL); + return m->channel_id; +} + +char const *dc_message_timestamp(dc_message_t m) +{ + return_if_true(m == NULL, NULL); + return m->timestamp; +} + +char const *dc_message_content(dc_message_t m) +{ + return_if_true(m == NULL, NULL); + return m->content; +} + +dc_account_t dc_message_author(dc_message_t m) +{ + return_if_true(m == NULL, NULL); + return m->author; +} diff --git a/ncdc/include/ncdc/ncdc.h b/ncdc/include/ncdc/ncdc.h index 4eea444..4484063 100644 --- a/ncdc/include/ncdc/ncdc.h +++ b/ncdc/include/ncdc/ncdc.h @@ -58,6 +58,8 @@ wchar_t *util_readkey(int esc, WINDOW *win); void exit_main(void); +wchar_t *s_convert(char const *s); + int strwidth(char const *string); char *read_char(FILE *stream); diff --git a/ncdc/src/login.c b/ncdc/src/login.c index b6b8b61..68d25f2 100644 --- a/ncdc/src/login.c +++ b/ncdc/src/login.c @@ -39,6 +39,11 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) goto cleanup; } + if (!dc_api_get_friends(api, acc)) { + LOG(n, L"login: %ls: failed to load friends", av[1]); + goto cleanup; + } + current_account = dc_ref(acc); LOG(n, L"login: %ls: authentication successful", av[1]); ret = true; diff --git a/ncdc/src/msg.c b/ncdc/src/msg.c index f69f200..c131e7c 100644 --- a/ncdc/src/msg.c +++ b/ncdc/src/msg.c @@ -48,12 +48,16 @@ bool ncdc_cmd_msg(ncdc_mainwindow_t n, size_t ac, wchar_t **av) if (c == NULL) { /* no? create a new window and switch to it */ - if (!dc_api_create_channel(api, current_account, &f, 1, &c)) { LOG(n, L"msg: failed to create channel"); goto cleanup; } + if (!dc_api_get_messages(api, current_account, c)) { + LOG(n, L"msg: failed to fetch messages in channel"); + goto cleanup; + } + v = ncdc_textview_new(); goto_if_true(v == NULL, cleanup); diff --git a/ncdc/src/textview.c b/ncdc/src/textview.c index b110f5c..72507bd 100644 --- a/ncdc/src/textview.c +++ b/ncdc/src/textview.c @@ -133,16 +133,11 @@ wchar_t const *ncdc_textview_nthline(ncdc_textview_t v, size_t idx) return g_ptr_array_index(v->par, idx); } -void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols) +static void +ncdc_textview_render_par(ncdc_textview_t v, WINDOW *win, int lines, int cols) { ssize_t i = 0, needed_lines = 0, atline = 0; - werase(win); - - if (v->par == NULL || v->par->len == 0) { - return; - } - for (i = v->par->len-1; i >= 0; i--) { wchar_t const *w = ncdc_textview_nthline(v, i); size_t sz = wcslen(w); @@ -159,3 +154,92 @@ void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols) } } } + +static wchar_t *ncdc_textview_format(dc_message_t m) +{ + wchar_t *c = NULL, *author = NULL, *message = NULL; + size_t clen = 0; + FILE *f = open_wmemstream(&c, &clen); + wchar_t *ret = NULL; + dc_account_t a = dc_message_author(m); + + return_if_true(f == NULL, NULL); + + author = s_convert(dc_account_fullname(a)); + goto_if_true(author == NULL, cleanup); + + message = s_convert(dc_message_content(m)); + goto_if_true(message == NULL, cleanup); + + fwprintf(f, L"< %ls> %ls", author, message); + + fclose(f); + f = NULL; + + ret = c; + c = NULL; + +cleanup: + + if (f != NULL) { + fclose(f); + } + + free(author); + free(message); + free(c); + + return ret; +} + +static void +ncdc_textview_render_msgs(ncdc_textview_t v, WINDOW *win, int lines, int cols) +{ + ssize_t i = 0, atline = 0, msgs = 0; + + msgs = dc_channel_messages(v->channel); + atline = lines; + + for (i = msgs-1; i >= 0; i--) { + dc_message_t m = dc_channel_nthmessage(v->channel, i); + wchar_t *s = ncdc_textview_format(m); + wchar_t const *end = s, *last = NULL; + size_t len = 0; + size_t needed_lines = 0; + + /* count each line, and, see if it is longer than COLS + */ + while ((end = wcschr(end, '\n')) != NULL) { + ++needed_lines; + + len = wcswidth(last, (end - last)); + needed_lines += (len % cols); + last = end; + } + + if (last == NULL) { + last = s; + } + + len = wcswidth(last, wcslen(last)); + needed_lines += (len / cols) + 1; + + if ((atline - needed_lines) >= 0) { + atline -= needed_lines; + mvwaddwstr(win, atline, 0, s); + } + + free(s); + } +} + +void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols) +{ + werase(win); + + if (v->par != NULL && v->par->len > 0) { + ncdc_textview_render_par(v, win, lines, cols); + } else if (v->channel != NULL) { + ncdc_textview_render_msgs(v, win, lines, cols); + } +} diff --git a/ncdc/src/util.c b/ncdc/src/util.c index 9fc2cb1..1e84441 100644 --- a/ncdc/src/util.c +++ b/ncdc/src/util.c @@ -168,3 +168,17 @@ char *w_convert(wchar_t const *w) wcstombs(ptr, w, sz); return ptr; } + +wchar_t *s_convert(char const *w) +{ + size_t sz = 0; + wchar_t *ptr = NULL; + + sz = mbstowcs(NULL, w, 0); + + ptr = calloc(sz+1, sizeof(wchar_t)); + return_if_true(ptr == NULL, NULL); + + mbstowcs(ptr, w, sz); + return ptr; +}