diff --git a/libdc/include/dc/api.h b/libdc/include/dc/api.h index 5a826e2..2a5a129 100644 --- a/libdc/include/dc/api.h +++ b/libdc/include/dc/api.h @@ -96,6 +96,13 @@ bool dc_api_get_messages(dc_api_t api, dc_account_t login, dc_channel_t c); bool dc_api_post_message(dc_api_t api, dc_account_t login, dc_channel_t c, dc_message_t m); +/** + * "ack" a channel, meaning that you have read it its contents. You must provide + * a message, so that discord knows which message was the last you read. + */ +bool dc_api_channel_ack(dc_api_t api, dc_account_t login, + dc_channel_t c, dc_message_t msg); + /** * 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 a65d157..7a7598d 100644 --- a/libdc/include/dc/channel.h +++ b/libdc/include/dc/channel.h @@ -67,4 +67,7 @@ void dc_channel_add_messages(dc_channel_t c, dc_message_t *m, size_t s); bool dc_channel_compare(dc_channel_t a, dc_channel_t b); +bool dc_channel_has_new_messages(dc_channel_t c); +void dc_channel_mark_read(dc_channel_t c); + #endif diff --git a/libdc/src/api-channel.c b/libdc/src/api-channel.c index d84c40b..6075429 100644 --- a/libdc/src/api-channel.c +++ b/libdc/src/api-channel.c @@ -2,6 +2,40 @@ #include "internal.h" +bool dc_api_channel_ack(dc_api_t api, dc_account_t login, + dc_channel_t c, dc_message_t m) +{ + bool ret = false; + char *url = NULL; + json_t *reply = NULL, *j = NULL; + + return_if_true(api == NULL || login == NULL || + c == NULL || m == NULL, false); + + asprintf(&url, "channels/%s/messages/%s/ack", + dc_channel_id(c), + dc_message_id(m) + ); + goto_if_true(url == NULL, cleanup); + + j = json_object(); + goto_if_true(j == NULL, cleanup); + json_object_set_new(j, "token", json_string(TOKEN(login))); + + reply = dc_api_call_sync(api, "POST", TOKEN(login), url, j); + goto_if_true(reply != NULL, cleanup); + + ret = true; + +cleanup: + + free(url); + json_decref(reply); + json_decref(j); + + return ret; +} + bool dc_api_post_message(dc_api_t api, dc_account_t login, dc_channel_t c, dc_message_t m) { diff --git a/libdc/src/channel.c b/libdc/src/channel.c index 1eac160..afb1427 100644 --- a/libdc/src/channel.c +++ b/libdc/src/channel.c @@ -46,6 +46,7 @@ struct dc_channel_ GHashTable *messages_byid; GPtrArray *messages; + bool new_messages; }; static void dc_channel_free(dc_channel_t c) @@ -318,6 +319,8 @@ void dc_channel_add_messages(dc_channel_t c, dc_message_t *m, size_t s) g_hash_table_insert(c->messages_byid, strdup(id), dc_ref(m[i])); g_ptr_array_add(c->messages, dc_ref(m[i])); + + c->new_messages = true; } g_ptr_array_sort(c->messages, (GCompareFunc)dc_message_compare); @@ -329,3 +332,15 @@ bool dc_channel_compare(dc_channel_t a, dc_channel_t b) return_if_true(a->id == NULL || b->id == NULL, false); return (strcmp(a->id, b->id) == 0); } + +bool dc_channel_has_new_messages(dc_channel_t c) +{ + return_if_true(c == NULL, false); + return c->new_messages; +} + +void dc_channel_mark_read(dc_channel_t c) +{ + return_if_true(c == NULL,); + c->new_messages = false; +} diff --git a/ncdc/CMakeLists.txt b/ncdc/CMakeLists.txt index 5ccfd6d..4b2490d 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" + "src/ack.c" "src/cmds.c" "src/config.c" "src/friends.c" diff --git a/ncdc/include/ncdc/cmds.h b/ncdc/include/ncdc/cmds.h index 6a1f5fe..01b265f 100644 --- a/ncdc/include/ncdc/cmds.h +++ b/ncdc/include/ncdc/cmds.h @@ -30,6 +30,7 @@ bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s); */ ncdc_commands_t *ncdc_find_cmd(ncdc_commands_t *cmds, wchar_t const *name); +bool ncdc_cmd_ack(ncdc_mainwindow_t n, size_t ac, wchar_t **av, wchar_t const *f); bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av, wchar_t const *f); bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av, wchar_t const *f); bool ncdc_cmd_logout(ncdc_mainwindow_t n, size_t ac, wchar_t **av, wchar_t const *f); diff --git a/ncdc/src/ack.c b/ncdc/src/ack.c new file mode 100644 index 0000000..4ccc7c0 --- /dev/null +++ b/ncdc/src/ack.c @@ -0,0 +1,33 @@ +#include +#include + +bool ncdc_cmd_ack(ncdc_mainwindow_t n, size_t ac, wchar_t **av, wchar_t const *f) +{ + dc_channel_t c = NULL; + dc_message_t m = NULL; + bool ret = false; + + if (!is_logged_in()) { + return false; + } + + c = ncdc_mainwindow_current_channel(n); + return_if_true(c == NULL, false); + return_if_true(dc_channel_messages(c) == 0, false); + m = dc_channel_nth_message(c, dc_channel_messages(c)-1); + + + ret = dc_api_channel_ack(dc_session_api(current_session), + dc_session_me(current_session), + c, m + ); + + if (!ret) { + LOG(n, L"ack: failed to ack the given channel"); + return false; + } + + dc_channel_mark_read(c); + + return true; +} diff --git a/ncdc/src/cmds.c b/ncdc/src/cmds.c index 7628824..0354c11 100644 --- a/ncdc/src/cmds.c +++ b/ncdc/src/cmds.c @@ -1,13 +1,15 @@ #include ncdc_commands_t cmds[] = { - { L"/friend", ncdc_cmd_friends }, - { L"/friends", ncdc_cmd_friends }, - { L"/login", ncdc_cmd_login }, - { L"/logout", ncdc_cmd_logout }, - { L"/msg", ncdc_cmd_msg }, - { L"/post", ncdc_cmd_post }, - { L"/quit", ncdc_cmd_quit }, + { L"/ack", ncdc_cmd_ack }, + { L"/friend", ncdc_cmd_friends }, + { L"/friends", ncdc_cmd_friends }, + { L"/login", ncdc_cmd_login }, + { L"/logout", ncdc_cmd_logout }, + { L"/markread", ncdc_cmd_ack }, + { L"/msg", ncdc_cmd_msg }, + { L"/post", ncdc_cmd_post }, + { L"/quit", ncdc_cmd_quit }, { NULL, NULL } }; diff --git a/ncdc/src/mainwindow.c b/ncdc/src/mainwindow.c index afa5f0e..cfee626 100644 --- a/ncdc/src/mainwindow.c +++ b/ncdc/src/mainwindow.c @@ -119,7 +119,7 @@ ncdc_mainwindow_callback(ncdc_input_t i, wchar_t const *s, if (s[0] == '/') { ret = ncdc_dispatch(mainwin, s); } else { - wchar_t *post = calloc(wcslen(s)+6, sizeof(wchar_t)); + wchar_t *post = calloc(wcslen(s)+7, sizeof(wchar_t)); wcscat(post, L"/post "); wcscat(post, s); @@ -202,6 +202,8 @@ static void ncdc_mainwindow_render_status(ncdc_mainwindow_t n) FILE *f = open_wmemstream(&status, &statuslen); wchar_t const *wintitle = NULL; ncdc_textview_t view = NULL; + size_t i = 0; + dc_channel_t channel = NULL; werase(n->sep1); return_if_true(f == NULL,); @@ -221,8 +223,18 @@ static void ncdc_mainwindow_render_status(ncdc_mainwindow_t n) fwprintf(f, L" [%d: %ls]", n->curview, (wintitle != NULL ? wintitle : L"n/a") ); - fclose(f); + fwprintf(f, L" [Act:"); + for (i = 0; i < n->views->len; i++) { + view = g_ptr_array_index(n->views, i); + channel = ncdc_textview_channel(view); + if (channel != NULL && dc_channel_has_new_messages(channel)) { + fwprintf(f, L" %d", i); + } + } + fwprintf(f, L"]"); + + fclose(f); mvwaddwstr(n->sep1, 0, 0, status); free(status); } @@ -287,6 +299,7 @@ void ncdc_mainwindow_switchview(ncdc_mainwindow_t n, int idx) { return_if_true(n == NULL || n->views == NULL,); return_if_true(idx >= n->views->len,); + n->curview = idx; } @@ -329,16 +342,33 @@ void ncdc_mainwindow_log(ncdc_mainwindow_t w, wchar_t const *fmt, ...) ncdc_textview_append(w->log, buf); } +static void ncdc_mainwindow_ack_view(ncdc_mainwindow_t n) +{ +#if 0 + dc_channel_t c = ncdc_mainwindow_current_channel(n); + return_if_true(c == NULL,); + return_if_true(dc_channel_messages(c) == 0,); + + dc_message_t m = dc_channel_nth_message(c, dc_channel_messages(c)-1); + dc_api_channel_ack(dc_session_api(current_session), + dc_session_me(current_session), + c, m + ); +#endif +} + void ncdc_mainwindow_rightview(ncdc_mainwindow_t n) { return_if_true(n == NULL,); n->curview = (n->curview + 1) % n->views->len; + ncdc_mainwindow_ack_view(n); } void ncdc_mainwindow_leftview(ncdc_mainwindow_t n) { return_if_true(n == NULL,); n->curview = (n->curview - 1) % n->views->len; + ncdc_mainwindow_ack_view(n); } dc_channel_t ncdc_mainwindow_current_channel(ncdc_mainwindow_t n) diff --git a/todo.org b/todo.org index 014ed18..35416ba 100644 --- a/todo.org +++ b/todo.org @@ -1,6 +1,8 @@ -* Channels [1/4] +* General +** TODO move key handling away from the thread the event_loop thread +* Channels [2/4] ** TODO Automatically open window for 1:1 and 1:N -** TODO send v6/api/channels/$ID/ack for "I read that shit" +** DONE send v6/api/channels/$ID/ack for "I read that shit" ** TODO add timestamps from messages ** DONE add message sending * Guilds [0/3]