diff --git a/libdc/include/dc/account.h b/libdc/include/dc/account.h index 30a49cf..8bf3367 100644 --- a/libdc/include/dc/account.h +++ b/libdc/include/dc/account.h @@ -8,8 +8,19 @@ struct dc_account_; typedef struct dc_account_ *dc_account_t; +typedef enum { + FRIEND_STATE_NONE = 0, + /* accountt is a mutual friend + */ + FRIEND_STATE_FRIEND = 1, + /* pending account, the other side hasn't accepted yet + */ + FRIEND_STATE_PENDING = 4, +} dc_account_friend_states; + dc_account_t dc_account_new(void); dc_account_t dc_account_new2(char const *email, char const *pass); +dc_account_t dc_account_from_fullid(char const *fullid); void dc_account_set_email(dc_account_t a, char const *email); char const *dc_account_email(dc_account_t a); @@ -37,5 +48,7 @@ bool dc_account_has_token(dc_account_t a); 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); +int dc_account_friend_state(dc_account_t a); +void dc_account_set_friend_state(dc_account_t a, int state); #endif diff --git a/libdc/include/dc/api.h b/libdc/include/dc/api.h index 7ac129e..80e4dfd 100644 --- a/libdc/include/dc/api.h +++ b/libdc/include/dc/api.h @@ -65,4 +65,9 @@ bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, */ bool dc_api_get_friends(dc_api_t api, dc_account_t login); +/** + * Add a given account as a friend to the friends list + */ +bool dc_api_add_friend(dc_api_t api, dc_account_t login, dc_account_t friend); + #endif diff --git a/libdc/src/account.c b/libdc/src/account.c index 363dd66..7249de7 100644 --- a/libdc/src/account.c +++ b/libdc/src/account.c @@ -34,6 +34,9 @@ struct dc_account_ /* friends we have */ GPtrArray *friends; + /* our own friend state + */ + int friend_state; }; static void dc_account_free(dc_account_t ptr) @@ -81,6 +84,38 @@ dc_account_t dc_account_new2(char const *email, char const *pass) return ptr; } +dc_account_t dc_account_from_fullid(char const *fullid) +{ + return_if_true(fullid == NULL, NULL); + + char *name = strdup(fullid), *discriminator = NULL; + dc_account_t acc = NULL; + + return_if_true(name == NULL, NULL); + + discriminator = strchr(name, '#'); + if (discriminator == NULL || *discriminator == '\0') { + free(name); + return NULL; + } + + *discriminator = '\0'; + ++discriminator; + + acc = dc_account_new(); + if (acc == NULL) { + free(name); + return NULL; + } + + dc_account_set_username(acc, name); + dc_account_set_discriminator(acc, discriminator); + + free(name); + + return acc; +} + void dc_account_set_email(dc_account_t a, char const *email) { return_if_true(a == NULL,); @@ -219,3 +254,15 @@ size_t dc_account_friends_size(dc_account_t a) return_if_true(a == NULL || a->friends == NULL, 0); return a->friends->len; } + +int dc_account_friend_state(dc_account_t a) +{ + return_if_true(a == NULL, 0); + return a->friend_state; +} + +void dc_account_set_friend_state(dc_account_t a, int state) +{ + return_if_true(a == NULL,); + a->friend_state = state; +} diff --git a/libdc/src/api.c b/libdc/src/api.c index 3d4f4e3..c37c949 100644 --- a/libdc/src/api.c +++ b/libdc/src/api.c @@ -349,6 +349,29 @@ error: return NULL; } +static json_t *dc_api_user_to_json(dc_account_t a) +{ + json_t *j = NULL; + + return_if_true(a == NULL, NULL); + return_if_true(dc_account_username(a) == NULL || + dc_account_discriminator(a) == NULL, + NULL + ); + + j = json_object(); + return_if_true(j == NULL, NULL); + + json_object_set_new(j, "username", + json_string(dc_account_username(a)) + ); + json_object_set_new(j, "discriminator", + json_string(dc_account_discriminator(a)) + ); + + return j; +} + bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, dc_account_t user) { @@ -420,6 +443,15 @@ bool dc_api_get_friends(dc_api_t api, dc_account_t login) if (a == NULL) { continue; } + + /* read the type also known as "typ" + */ + val = json_object_get(c, "type"); + if (val != NULL && json_is_integer(val)) { + int state = json_integer_value(val); + dc_account_set_friend_state(a, state); + } + g_ptr_array_add(f, a); } @@ -440,6 +472,36 @@ cleanup: return ret; } +/** + * Add a given account as a friend to the friends list + */ +bool dc_api_add_friend(dc_api_t api, dc_account_t login, dc_account_t friend) +{ + char const *url = "users/@me/relationships"; + json_t *reply = NULL, *post = NULL; + bool ret = false; + + return_if_true(api == NULL, false); + return_if_true(login == NULL, false); + + post = dc_api_user_to_json(friend); + return_if_true(post == NULL, false); + + reply = dc_api_call_sync(api, "POST", dc_account_token(login), url, post); + /* apparently if no data comes back, then the whole thing was a success + */ + goto_if_true(reply != NULL, cleanup); + + ret = true; + +cleanup: + + json_decref(post); + json_decref(reply); + + return ret; +} + bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) { char const *url = "users/@me/guilds"; diff --git a/ncdc/src/cmds.c b/ncdc/src/cmds.c index 419849e..65d1059 100644 --- a/ncdc/src/cmds.c +++ b/ncdc/src/cmds.c @@ -1,9 +1,10 @@ #include ncdc_commands_t cmds[] = { - { L"friends", ncdc_cmd_friends }, - { L"login", ncdc_cmd_login }, - { L"quit", ncdc_cmd_quit }, + { L"/friend", ncdc_cmd_friends }, + { L"/friends", ncdc_cmd_friends }, + { L"/login", ncdc_cmd_login }, + { L"/quit", ncdc_cmd_quit }, { NULL, NULL } }; @@ -36,7 +37,6 @@ static void *async_dispatcher(void *arg) /* end of working orders */ pthread_mutex_unlock(&mtx); - printf("got exit\n"); return NULL; } else { /* call the handler @@ -92,15 +92,17 @@ bool ncdc_dispatch_deinit(void) bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s) { wchar_t **tokens = NULL; - size_t i = 0; + size_t i = 0, tokenlen = 0; ncdc_commands_t *it = NULL; queue_item *item = NULL; tokens = w_tokenise(s); return_if_true(tokens == NULL, false); + tokenlen = wcslen(tokens[0]); + for (i = 0; cmds[i].name != NULL; i++) { - if (wcscmp(cmds[i].name, tokens[0]) == 0) { + if (wcsncmp(cmds[i].name, tokens[0], tokenlen) == 0) { it = cmds+i; break; } diff --git a/ncdc/src/friends.c b/ncdc/src/friends.c index 0698e38..7357274 100644 --- a/ncdc/src/friends.c +++ b/ncdc/src/friends.c @@ -6,6 +6,7 @@ ncdc_cmd_friends_list(ncdc_mainwindow_t n, size_t ac, wchar_t **av) { bool ret = false; size_t i = 0; + wchar_t c = ' '; ret = dc_api_get_friends(api, current_account); if (!ret) { @@ -16,27 +17,67 @@ ncdc_cmd_friends_list(ncdc_mainwindow_t n, size_t ac, wchar_t **av) LOG(n, L"/FRIENDS list"); for (i = 0; i < dc_account_friends_size(current_account); i++) { dc_account_t acc = dc_account_nthfriend(current_account, i); - LOG(n, L" %s", dc_account_full_username(acc)); + switch (dc_account_friend_state(acc)) { + case FRIEND_STATE_PENDING: c = 'P'; break; + default: c = ' '; break; + } + LOG(n, L"%lc %s", c, dc_account_full_username(acc)); } LOG(n, L"End of /FRIENDS list"); return true; } -bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av) +static bool +ncdc_cmd_friends_add(ncdc_mainwindow_t n, size_t ac, wchar_t **av) { - wchar_t *subcmd = NULL; + char *name = NULL; + dc_account_t friend = NULL; + bool ret = false; if (ac <= 1) { return false; } + name = w_convert(av[1]); + return_if_true(name == NULL, false); + + friend = dc_account_from_fullid(name); + if (friend == NULL) { + LOG(n, L"friends: add: invalid username given, use the full ID"); + goto cleanup; + } + + if (!dc_api_add_friend(api, current_account, friend)) { + LOG(n, L"friends: add: failed to add friend, Vulkan would be sad"); + goto cleanup; + } + + LOG(n, L"friends: add: request for friendship sent"); + ret = true; + +cleanup: + + dc_unref(friend); + free(name); + + return ret; +} + +bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av) +{ + wchar_t *subcmd = NULL; + if (current_account == NULL || !dc_account_has_token(current_account)) { LOG(n, L"friends: not logged in"); return false; } + if (ac <= 1) { + return ncdc_cmd_friends_list(n, ac, av); + } + subcmd = av[1]; --ac; @@ -44,6 +85,8 @@ bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av) if (wcscmp(subcmd, L"list") == 0) { return ncdc_cmd_friends_list(n, ac, av); + } else if (wcscmp(subcmd, L"add") == 0) { + return ncdc_cmd_friends_add(n, ac, av); } else { return false; } diff --git a/ncdc/src/mainwindow.c b/ncdc/src/mainwindow.c index 1051bbd..4f01f42 100644 --- a/ncdc/src/mainwindow.c +++ b/ncdc/src/mainwindow.c @@ -99,12 +99,12 @@ ncdc_mainwindow_callback(ncdc_input_t i, wchar_t const *s, { ncdc_mainwindow_t mainwin = (ncdc_mainwindow_t)arg; - if (s[0] == '/') { + if (s != NULL && s[0] == '/') { if (s[1] == '\0') { return false; } - return ncdc_dispatch(mainwin, s+1); + return ncdc_dispatch(mainwin, s); } return false; diff --git a/ncdc/src/util.c b/ncdc/src/util.c index 6890fd8..773de63 100644 --- a/ncdc/src/util.c +++ b/ncdc/src/util.c @@ -33,11 +33,11 @@ int aswprintf(wchar_t **buffer, wchar_t const *fmt, ...) return sz; } -wchar_t* wcsndup(const wchar_t* string, size_t maxlen) +wchar_t* wcsndup(wchar_t const* string, size_t maxlen) { - size_t n = wcsnlen(string, maxlen) + 1; - wchar_t* r = calloc(n, sizeof(wchar_t)); - return r == NULL ? NULL : wmemcpy(r, string, n); + wchar_t* r = calloc(maxlen+1, sizeof(wchar_t)); + return_if_true(r == NULL, NULL); + return wmemcpy(r, string, maxlen); } size_t w_strlenv(wchar_t **s) @@ -61,28 +61,56 @@ void w_strfreev(wchar_t **s) free(s); } -wchar_t **w_tokenise(wchar_t const *w) +wchar_t **w_tokenise(wchar_t const *str) { + wchar_t const *p = NULL, *start_of_word = NULL; + wint_t c; GPtrArray *array = g_ptr_array_new(); - wchar_t const *item = w; - wchar_t *dup = NULL; - size_t len = 0, origlen = 0; + enum states { DULL, IN_WORD, IN_STRING } state = DULL; - while ((dup = w_next_tok(item)) != NULL) { - len = origlen = wcslen(dup); + for (p = str; *p != '\0'; p++) { + c = (wint_t) *p; + switch (state) { + case DULL: + { + if (iswspace(c)) { + continue; + } + if (c == '"') { + state = IN_STRING; + start_of_word = p + 1; + continue; + } + state = IN_WORD; + start_of_word = p; + } continue; - if (*dup == '"') { - memmove(dup, dup+1, sizeof(wchar_t)*(len-1)); - --len; + case IN_STRING: + { + if (c == '"') { + size_t len = (p - 2 - start_of_word); + wchar_t *s = wcsndup(start_of_word, len); + g_ptr_array_add(array, s); + state = DULL; + } + } continue; + + case IN_WORD: + { + if (iswspace(c)) { + size_t len = (p - start_of_word); + wchar_t *s = wcsndup(start_of_word, len); + g_ptr_array_add(array, s); + state = DULL; + } + } continue; } + } - if (len > 0 && dup[len-1] == '"') { - dup[len-1] = '\0'; - --len; - } - - g_ptr_array_add(array, dup); - item += origlen; + if (state != DULL) { + size_t len = (p - start_of_word); + wchar_t *s = wcsndup(start_of_word, len); + g_ptr_array_add(array, s); } g_ptr_array_add(array, NULL); @@ -103,40 +131,3 @@ char *w_convert(wchar_t const *w) wcstombs(ptr, w, sz); return ptr; } - -wchar_t *w_next_tok(wchar_t const *w) -{ - bool quotes = false; - wchar_t const *start = NULL; - - /* skip first white spaces if there are any - */ - for (; *w != '\0' && iswspace(*w); w++) - ; - - if (*w == '\0') { - return NULL; - } - - start = w; - quotes = (*w == '"'); - - do { - if (iswspace(*w) && !quotes) { - --w; - break; - } - - if (*w == '"' && *(w-1) != '\\' && quotes) { - break; - } - - if (*w == '\0') { - break; - } - - ++w; - } while (1); - - return wcsndup(start, (w - start)); -}