diff --git a/libdc/include/dc/account.h b/libdc/include/dc/account.h index d48b56a..1428a9e 100644 --- a/libdc/include/dc/account.h +++ b/libdc/include/dc/account.h @@ -69,11 +69,16 @@ void dc_account_set_token(dc_account_t a, char const *token); char const *dc_account_token(dc_account_t a); bool dc_account_has_token(dc_account_t a); +/* compare + */ +bool dc_account_equal(dc_account_t a, dc_account_t b); + /* relationships */ void dc_account_set_friends(dc_account_t a, dc_account_t *ptr, size_t len); dc_account_t dc_account_nthfriend(dc_account_t a, size_t i); size_t dc_account_friends_size(dc_account_t a); +dc_account_t dc_account_findfriend(dc_account_t a, char const *fullname); int dc_account_friend_state(dc_account_t a); void dc_account_set_friend_state(dc_account_t a, int state); diff --git a/libdc/include/dc/channel.h b/libdc/include/dc/channel.h index 3dbd890..d89495a 100644 --- a/libdc/include/dc/channel.h +++ b/libdc/include/dc/channel.h @@ -4,6 +4,8 @@ #include #include +#include + /** * A discord channel. Exactly what it says on the tin. A place where one * or more guardsmen can exchange Slaaneshi heresy without their commissars @@ -50,4 +52,7 @@ dc_channel_t dc_channel_from_json(json_t *j); 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); + #endif diff --git a/libdc/src/account.c b/libdc/src/account.c index 3293c36..4fac0aa 100644 --- a/libdc/src/account.c +++ b/libdc/src/account.c @@ -282,6 +282,13 @@ char const *dc_account_fullname(dc_account_t a) return a->full; } +bool dc_account_equal(dc_account_t a, dc_account_t b) +{ + return_if_true(a == NULL && b == NULL, true); + return_if_true(a == NULL || b == NULL, false); + return (strcmp(a->full, b->full) == 0); +} + void dc_account_set_friends(dc_account_t a, dc_account_t *friends, size_t len) { size_t i = 0; @@ -293,6 +300,21 @@ void dc_account_set_friends(dc_account_t a, dc_account_t *friends, size_t len) } } +dc_account_t dc_account_findfriend(dc_account_t a, char const *fullname) +{ + size_t i = 0; + return_if_true(a == NULL || fullname == NULL, NULL); + + for (i = 0; i < a->friends->len; i++) { + dc_account_t f = g_ptr_array_index(a->friends, i); + if (strcmp(f->full, fullname) == 0) { + return f; + } + } + + return NULL; +} + dc_account_t dc_account_nthfriend(dc_account_t a, size_t i) { return_if_true(a == NULL || a->friends == NULL, NULL); diff --git a/libdc/src/api-channel.c b/libdc/src/api-channel.c index 103cd3e..d0d46ab 100644 --- a/libdc/src/api-channel.c +++ b/libdc/src/api-channel.c @@ -18,7 +18,7 @@ bool dc_api_create_channel(dc_api_t api, dc_account_t login, goto_if_true(url == NULL, cleanup); /* build a JSON object that contains one array called "recipients": - * {"recipients": ["snowflake#1", "snowflake#N"]} + * {"recipients": ["snowflake#1", ..., "snowflake#N"]} */ data = json_object(); array = json_array(); @@ -42,6 +42,7 @@ bool dc_api_create_channel(dc_api_t api, dc_account_t login, goto_if_true(c == NULL, cleanup); *channel = c; + ret = true; cleanup: diff --git a/libdc/src/channel.c b/libdc/src/channel.c index 946dbf1..060396b 100644 --- a/libdc/src/channel.c +++ b/libdc/src/channel.c @@ -225,3 +225,16 @@ void dc_channel_set_type(dc_channel_t c, dc_channel_type_t t) return_if_true(c == NULL,); c->type = t; } + +size_t dc_channel_recipients(dc_channel_t c) +{ + return_if_true(c == NULL || c->recipients == NULL, 0); + return c->recipients->len; +} + +dc_account_t dc_channel_nthrecipient(dc_channel_t c, size_t i) +{ + return_if_true(c == NULL || c->recipients == NULL, NULL); + return_if_true(i >= c->recipients->len, NULL); + return g_ptr_array_index(c->recipients, i); +} diff --git a/ncdc/CMakeLists.txt b/ncdc/CMakeLists.txt index 83ee8df..de4393e 100644 --- a/ncdc/CMakeLists.txt +++ b/ncdc/CMakeLists.txt @@ -20,6 +20,7 @@ SET(SOURCES "src/input.c" "src/login.c" "src/mainwindow.c" + "src/msg.c" "src/ncdc.c" "src/textview.c" "src/util.c" diff --git a/ncdc/include/ncdc/cmds.h b/ncdc/include/ncdc/cmds.h index d1a5ef1..1b9967c 100644 --- a/ncdc/include/ncdc/cmds.h +++ b/ncdc/include/ncdc/cmds.h @@ -27,5 +27,6 @@ ncdc_commands_t *ncdc_find_cmd(ncdc_commands_t *cmds, wchar_t const *name); bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av); bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av); bool ncdc_cmd_quit(ncdc_mainwindow_t n, size_t ac, wchar_t **av); +bool ncdc_cmd_msg(ncdc_mainwindow_t n, size_t ac, wchar_t **av); #endif diff --git a/ncdc/include/ncdc/mainwindow.h b/ncdc/include/ncdc/mainwindow.h index e165394..48689d9 100644 --- a/ncdc/include/ncdc/mainwindow.h +++ b/ncdc/include/ncdc/mainwindow.h @@ -14,6 +14,9 @@ ncdc_mainwindow_t ncdc_mainwindow_new(void); #define LOG(n, ...) ncdc_mainwindow_log(n, __VA_ARGS__) void ncdc_mainwindow_log(ncdc_mainwindow_t w, wchar_t const *fmt, ...); +GPtrArray *ncdc_mainwindow_views(ncdc_mainwindow_t n); +void ncdc_mainwindow_switchview(ncdc_mainwindow_t n, int idx); + void ncdc_mainwindow_refresh(ncdc_mainwindow_t n); void ncdc_mainwindow_input_ready(ncdc_mainwindow_t n); diff --git a/ncdc/include/ncdc/ncdc.h b/ncdc/include/ncdc/ncdc.h index a51d0ac..4921563 100644 --- a/ncdc/include/ncdc/ncdc.h +++ b/ncdc/include/ncdc/ncdc.h @@ -62,6 +62,7 @@ char *w_convert(wchar_t const *w); wchar_t* wcsndup(const wchar_t* string, size_t maxlen); size_t w_strlenv(wchar_t **s); void w_strfreev(wchar_t **s); +wchar_t *w_joinv(wchar_t const **v, size_t len); wchar_t **w_tokenise(wchar_t const *w); wchar_t *w_next_tok(wchar_t const *w); wchar_t const *w_next_word(wchar_t const *w, ssize_t len); diff --git a/ncdc/include/ncdc/textview.h b/ncdc/include/ncdc/textview.h index 30376d0..933cfb4 100644 --- a/ncdc/include/ncdc/textview.h +++ b/ncdc/include/ncdc/textview.h @@ -8,6 +8,12 @@ typedef struct ncdc_textview_ *ncdc_textview_t; ncdc_textview_t ncdc_textview_new(void); +dc_account_t ncdc_textview_account(ncdc_textview_t v); +void ncdc_textview_set_account(ncdc_textview_t v, dc_account_t a); + +dc_channel_t ncdc_textview_channel(ncdc_textview_t v); +void ncdc_textview_set_channel(ncdc_textview_t v, dc_channel_t a); + void ncdc_textview_append(ncdc_textview_t v, wchar_t const *w); wchar_t const *ncdc_textview_nthline(ncdc_textview_t v, size_t i); void ncdc_textview_render(ncdc_textview_t v, WINDOW *win, int lines, int cols); diff --git a/ncdc/src/cmds.c b/ncdc/src/cmds.c index 2396db3..0ab83d3 100644 --- a/ncdc/src/cmds.c +++ b/ncdc/src/cmds.c @@ -4,6 +4,7 @@ ncdc_commands_t cmds[] = { { L"/friend", ncdc_cmd_friends }, { L"/friends", ncdc_cmd_friends }, { L"/login", ncdc_cmd_login }, + { L"/msg", ncdc_cmd_msg }, { L"/quit", ncdc_cmd_quit }, { NULL, NULL } }; diff --git a/ncdc/src/login.c b/ncdc/src/login.c index e84dee8..debcdc4 100644 --- a/ncdc/src/login.c +++ b/ncdc/src/login.c @@ -34,6 +34,11 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) goto cleanup; } + if (!dc_api_get_userinfo(api, acc, acc)) { + LOG(n, L"login: %ls: failed to get basic user information", av[1]); + goto cleanup; + } + current_account = acc; LOG(n, L"login: %ls: authentication successful", av[1]); ret = true; diff --git a/ncdc/src/mainwindow.c b/ncdc/src/mainwindow.c index 4f01f42..f44ff24 100644 --- a/ncdc/src/mainwindow.c +++ b/ncdc/src/mainwindow.c @@ -37,6 +37,9 @@ struct ncdc_mainwindow_ WINDOW *sep2; ncdc_input_t in; + + GPtrArray *views; + int curview; ncdc_textview_t log; int focus; @@ -59,7 +62,11 @@ static void ncdc_mainwindow_free(ncdc_mainwindow_t n) delwin(n->sep2); dc_unref(n->in); - dc_unref(n->log); + + if (n->views != NULL) { + g_ptr_array_unref(n->views); + n->views = NULL; + } free(n); } @@ -74,7 +81,11 @@ ncdc_mainwindow_t ncdc_mainwindow_new(void) ptr->in = ncdc_input_new(); ncdc_input_set_callback(ptr->in, ncdc_mainwindow_callback, ptr); + ptr->views = g_ptr_array_new_with_free_func( + (GDestroyNotify)dc_unref + ); ptr->log = ncdc_textview_new(); + g_ptr_array_add(ptr->views, ptr->log); ptr->guilds = newwin(5, 5, 1, 1); ptr->chat = newwin(5, 5, 4, 4); @@ -192,11 +203,28 @@ void ncdc_mainwindow_input_ready(ncdc_mainwindow_t n) } } +GPtrArray *ncdc_mainwindow_views(ncdc_mainwindow_t n) +{ + return n->views; +} + +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; +} + void ncdc_mainwindow_refresh(ncdc_mainwindow_t n) { + ncdc_textview_t v = 0; + wnoutrefresh(n->guilds); - ncdc_textview_render(n->log, n->chat, n->chat_h, n->chat_w); + /* render active text view + */ + v = g_ptr_array_index(n->views, n->curview); + ncdc_textview_render(v, n->chat, n->chat_h, n->chat_w); wnoutrefresh(n->chat); ncdc_input_draw(n->in, n->input); diff --git a/ncdc/src/msg.c b/ncdc/src/msg.c new file mode 100644 index 0000000..d1e0b90 --- /dev/null +++ b/ncdc/src/msg.c @@ -0,0 +1,79 @@ +#include +#include +#include + +bool ncdc_cmd_msg(ncdc_mainwindow_t n, size_t ac, wchar_t **av) +{ + return_if_true(ac <= 1, false); + + char * target = NULL; + wchar_t *full_message = NULL; + char * message = NULL; + bool ret = false; + dc_channel_t c = NULL; + ncdc_textview_t v = NULL; + size_t i = 0; + + if (current_account == NULL || !dc_account_has_token(current_account)) { + LOG(n, L"msg: not logged in"); + return false; + } + + target = w_convert(av[1]); + goto_if_true(target == NULL, cleanup); + + /* find out if the target is a friend we can contact + */ + dc_account_t f = dc_account_findfriend(current_account, target); + if (f == NULL) { + LOG(n, L"msg: no such friend found: \"%s\"", target); + goto cleanup; + } + + /* see if we have a channel already, that services that user + */ + for (i = 0; i < ncdc_mainwindow_views(n)->len; i++) { + v = g_ptr_array_index(ncdc_mainwindow_views(n), i); + dc_channel_t chan = ncdc_textview_channel(v); + + if (chan != NULL && + dc_channel_type(chan) == CHANNEL_TYPE_DM_TEXT && + dc_account_equal(dc_channel_nthrecipient(chan, 1), f)) { + c = dc_ref(chan); + ncdc_mainwindow_switchview(n, i); + break; + } + } + + 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; + } + + v = ncdc_textview_new(); + goto_if_true(v == NULL, cleanup); + + ncdc_textview_set_account(v, dc_ref(current_account)); + ncdc_textview_set_channel(v, dc_ref(c)); + + g_ptr_array_add(ncdc_mainwindow_views(n), dc_ref(v)); + ncdc_mainwindow_switchview(n, ncdc_mainwindow_views(n)->len-1); + } + + ret = true; + +cleanup: + + dc_unref(c); + dc_unref(v); + + free(target); + free(full_message); + free(message); + + return ret; +} diff --git a/ncdc/src/textview.c b/ncdc/src/textview.c index 9a7163e..4b5202b 100644 --- a/ncdc/src/textview.c +++ b/ncdc/src/textview.c @@ -4,14 +4,24 @@ struct ncdc_textview_ { dc_refable_t ref; + GPtrArray *par; + + dc_account_t account; + dc_channel_t channel; }; static void ncdc_textview_free(ncdc_textview_t v) { return_if_true(v == NULL,); - g_ptr_array_unref(v->par); + dc_unref(v->account); + dc_unref(v->channel); + + if (v->par != NULL) { + g_ptr_array_unref(v->par); + } + free(v); } @@ -31,6 +41,32 @@ ncdc_textview_t ncdc_textview_new(void) return p; } +dc_account_t ncdc_textview_account(ncdc_textview_t v) +{ + return_if_true(v == NULL, NULL); + return v->account; +} + +void ncdc_textview_set_account(ncdc_textview_t v, dc_account_t a) +{ + return_if_true(v == NULL || a == NULL,); + dc_unref(v->account); + v->account = dc_ref(a); +} + +dc_channel_t ncdc_textview_channel(ncdc_textview_t v) +{ + return_if_true(v == NULL, NULL); + return v->channel; +} + +void ncdc_textview_set_channel(ncdc_textview_t v, dc_channel_t a) +{ + return_if_true(v == NULL || a == NULL,); + dc_unref(v->channel); + v->channel = dc_ref(a); +} + void ncdc_textview_append(ncdc_textview_t v, wchar_t const *w) { return_if_true(v == NULL || w == NULL,); diff --git a/ncdc/src/util.c b/ncdc/src/util.c index 773de63..d1d6ef6 100644 --- a/ncdc/src/util.c +++ b/ncdc/src/util.c @@ -118,6 +118,26 @@ wchar_t **w_tokenise(wchar_t const *str) return (wchar_t**)g_ptr_array_free(array, FALSE); } +wchar_t *w_joinv(wchar_t const **v, size_t len) +{ + wchar_t *buf = NULL; + size_t buflen = 0; + FILE *f = open_wmemstream(&buf, &buflen); + size_t i = 0; + + return_if_true(f == NULL, NULL); + + for (i = 0; i < len; i++) { + fwprintf(f, L"%ls", v[i]); + if (i < (len-1)) { + fputwc(' ', f); + } + } + + fclose(f); + return buf; +} + char *w_convert(wchar_t const *w) { size_t sz = 0;