full friends list support, add remove, and accept

This commit is contained in:
Florian Stinglmayr 2019-07-04 19:25:38 +02:00
parent efce6a6543
commit 7f5ceff688
11 changed files with 380 additions and 165 deletions

View File

@ -17,6 +17,8 @@ SET(SOURCES
"include/dc/util.h"
"src/account.c"
"src/api.c"
"src/api-friends.c"
"src/api-util.c"
"src/apisync.c"
"src/guild.c"
"src/loop.c"

View File

@ -13,14 +13,35 @@ typedef enum {
/* accountt is a mutual friend
*/
FRIEND_STATE_FRIEND = 1,
/* pending account, the other side hasn't accepted yet
/* pending friend request, the other side hasn't accepted yet
*/
FRIEND_STATE_PENDING = 4,
FRIEND_STATE_PENDING = 3,
} dc_account_friend_states;
/**
* Represents a given account within the discord system. To start your work,
* you will have to create an account object, give it email and password, and
* call the dc_api_authenticate() with it. This gives you a login token that
* you can use to call other API methods (such as dc_api_get_friends()) for
* that account.
*
* Accounts have a few important attributes that we store:
* * ID (or snowflake), a 64 bit ID of the user, that we store as a string.
* * username, a string that represents the accounts user name
* * discriminator, a number that differentiates users with the same name
* * email, for login accounts only
* * password, for login accounts only
* * friend_state, if the account is someone login account's friend, we store
* the relationship in this flag. See the dc_account_friend_state enum for
* details.
* And one compound attribute:
* * fullname, a combination of username and discriminator separated by the
* pound sign, e.g. nola#2457
*/
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);
dc_account_t dc_account_from_fullname(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,7 +58,7 @@ char const *dc_account_username(dc_account_t a);
void dc_account_set_discriminator(dc_account_t a, char const *id);
char const *dc_account_discriminator(dc_account_t a);
char const *dc_account_full_username(dc_account_t a);
char const *dc_account_fullname(dc_account_t a);
void dc_account_set_token(dc_account_t a, char const *token);
char const *dc_account_token(dc_account_t a);
@ -48,6 +69,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);

View File

@ -70,4 +70,21 @@ bool dc_api_get_friends(dc_api_t api, dc_account_t login);
*/
bool dc_api_add_friend(dc_api_t api, dc_account_t login, dc_account_t friend);
/**
* Remove a given account as a friend to the friends list. Warning: The
* account ID (aka account snowflake) is required to perform this operation,
* so you cannot just do dc_account_from_fullname(). Suggestion: use an object
* from the actual friends list of the account login.
*/
bool dc_api_remove_friend(dc_api_t api, dc_account_t login,
dc_account_t friend);
/**
* Accepts someone who has sent a friend request to you, as a friend. Warning:
* The object "friend" requires an account ID (aka snowflake) for this method
* to work. You should take this object perhaps from the "login"'s friend list.
*/
bool dc_api_accept_friend(dc_api_t api, dc_account_t login,
dc_account_t friend);
#endif

View File

@ -84,7 +84,7 @@ dc_account_t dc_account_new2(char const *email, char const *pass)
return ptr;
}
dc_account_t dc_account_from_fullid(char const *fullid)
dc_account_t dc_account_from_fullname(char const *fullid)
{
return_if_true(fullid == NULL, NULL);
@ -226,7 +226,7 @@ char const *dc_account_discriminator(dc_account_t a)
return a->discriminator;
}
char const *dc_account_full_username(dc_account_t a)
char const *dc_account_fullname(dc_account_t a)
{
return_if_true(a == NULL, NULL);
return a->full;

150
libdc/src/api-friends.c Normal file
View File

@ -0,0 +1,150 @@
#include <dc/api.h>
#include "internal.h"
bool dc_api_get_friends(dc_api_t api, dc_account_t login)
{
char const *url = "users/@me/relationships";
json_t *reply = NULL, *c = NULL, *val = NULL;
bool ret = false;
size_t i = 0;
GPtrArray *f = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref);
return_if_true(api == NULL, false);
return_if_true(login == NULL, false);
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
goto_if_true(reply == NULL, cleanup);
goto_if_true(!json_is_array(reply), cleanup);
json_array_foreach(reply, i, c) {
/* the return is an array of objects, with a "user" member
* type 1 is probably a friend
*/
val = json_object_get(c, "user");
if (val == NULL) {
continue;
}
dc_account_t a = dc_api_account_from_json(val);
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);
}
if (f->len == 0) {
/* me_irl :-(
*/
}
dc_account_set_friends(login, (dc_account_t*)f->pdata, f->len);
ret = true;
cleanup:
g_ptr_array_free(f, FALSE);
json_decref(reply);
reply = NULL;
return ret;
}
bool dc_api_remove_friend(dc_api_t api, dc_account_t login, dc_account_t friend)
{
char *url = NULL;
json_t *reply = NULL, *post = NULL;
bool ret = false;
return_if_true(api == NULL, false);
return_if_true(login == NULL || friend == NULL, false);
return_if_true(dc_account_id(friend) == NULL, false);
asprintf(&url, "users/@me/relationships/%s", dc_account_id(friend));
post = dc_api_account_to_json(friend);
return_if_true(post == NULL, false);
reply = dc_api_call_sync(api, "DELETE", dc_account_token(login), url, post);
/* if no data comes back, then the whole thing was a success
*/
goto_if_true(reply != NULL, cleanup);
ret = true;
cleanup:
free(url);
json_decref(post);
json_decref(reply);
return ret;
}
bool dc_api_accept_friend(dc_api_t api, dc_account_t login, dc_account_t friend)
{
char *url = NULL;
json_t *reply = NULL, *post = NULL;
bool ret = false;
return_if_true(api == NULL, false);
return_if_true(login == NULL || friend == NULL, false);
return_if_true(dc_account_id(friend) == NULL, false);
asprintf(&url, "users/@me/relationships/%s", dc_account_id(friend));
post = dc_api_account_to_json(friend);
return_if_true(post == NULL, false);
reply = dc_api_call_sync(api, "PUT", dc_account_token(login), url, post);
/* no data = successful
*/
goto_if_true(reply != NULL, cleanup);
ret = true;
cleanup:
free(url);
json_decref(post);
json_decref(reply);
return ret;
}
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_account_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;
}

53
libdc/src/api-util.c Normal file
View File

@ -0,0 +1,53 @@
#include <dc/api.h>
#include "internal.h"
dc_account_t dc_api_account_from_json(json_t *j)
{
dc_account_t user = dc_account_new();
json_t *val = NULL;
goto_if_true(!json_is_object(j), error);
val = json_object_get(j, "username");
goto_if_true(val == NULL || !json_is_string(val), error);
dc_account_set_username(user, json_string_value(val));
val = json_object_get(j, "discriminator");
goto_if_true(val == NULL || !json_is_string(val), error);
dc_account_set_discriminator(user, json_string_value(val));
val = json_object_get(j, "id");
goto_if_true(val == NULL || !json_is_string(val), error);
dc_account_set_id(user, json_string_value(val));
return user;
error:
dc_unref(user);
return NULL;
}
json_t *dc_api_account_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;
}

View File

@ -322,56 +322,6 @@ cleanup:
return ret;
}
static dc_account_t dc_api_account_from_json(json_t *j)
{
dc_account_t user = dc_account_new();
json_t *val = NULL;
goto_if_true(!json_is_object(j), error);
val = json_object_get(j, "username");
goto_if_true(val == NULL || !json_is_string(val), error);
dc_account_set_username(user, json_string_value(val));
val = json_object_get(j, "discriminator");
goto_if_true(val == NULL || !json_is_string(val), error);
dc_account_set_discriminator(user, json_string_value(val));
val = json_object_get(j, "id");
goto_if_true(val == NULL || !json_is_string(val), error);
dc_account_set_id(user, json_string_value(val));
return user;
error:
dc_unref(user);
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)
{
@ -414,94 +364,6 @@ cleanup:
return ret;
}
bool dc_api_get_friends(dc_api_t api, dc_account_t login)
{
char const *url = "users/@me/relationships";
json_t *reply = NULL, *c = NULL, *val = NULL;
bool ret = false;
size_t i = 0;
GPtrArray *f = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref);
return_if_true(api == NULL, false);
return_if_true(login == NULL, false);
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
goto_if_true(reply == NULL, cleanup);
goto_if_true(!json_is_array(reply), cleanup);
json_array_foreach(reply, i, c) {
/* the return is an array of objects, with a "user" member
* type 1 is probably a friend
*/
val = json_object_get(c, "user");
if (val == NULL) {
continue;
}
dc_account_t a = dc_api_account_from_json(val);
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);
}
if (f->len == 0) {
/* me_irl :-(
*/
}
dc_account_set_friends(login, (dc_account_t*)f->pdata, f->len);
ret = true;
cleanup:
g_ptr_array_free(f, FALSE);
json_decref(reply);
reply = NULL;
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";

View File

@ -22,10 +22,18 @@
#include <event2/thread.h>
#include <dc/util.h>
#include <dc/refable.h>
#include <dc/account.h>
//#define DEBUG
#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)
/* These are internal helper methods, their ABI, and API stability
* is not garuanteed. So please beware
*/
json_t *dc_api_account_to_json(dc_account_t a);
dc_account_t dc_api_account_from_json(json_t *j);
#endif

View File

@ -19,6 +19,11 @@ bool ncdc_dispatch_deinit(void);
bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s);
/* find a given command in a list of commands, helpful if your command has
* sub commands. for example usage see the friends command
*/
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);

View File

@ -89,26 +89,29 @@ bool ncdc_dispatch_deinit(void)
return true;
}
ncdc_commands_t *ncdc_find_cmd(ncdc_commands_t *cmds, wchar_t const *name)
{
ncdc_commands_t *it = NULL;
for (it = cmds; it->name != NULL; it++) {
if (wcscmp(it->name, name) == 0) {
return it;
}
}
return NULL;
}
bool ncdc_dispatch(ncdc_mainwindow_t n, wchar_t const *s)
{
wchar_t **tokens = NULL;
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 (wcsncmp(cmds[i].name, tokens[0], tokenlen) == 0) {
it = cmds+i;
break;
}
}
if (it == NULL) {
if ((it = ncdc_find_cmd(cmds, tokens[0])) == NULL) {
/* no such command
*/
LOG(n, L"error: no such command \"%ls\"", tokens[0]);

View File

@ -6,7 +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 = ' ';
char c = ' ';
ret = dc_api_get_friends(api, current_account);
if (!ret) {
@ -21,7 +21,7 @@ ncdc_cmd_friends_list(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
case FRIEND_STATE_PENDING: c = 'P'; break;
default: c = ' '; break;
}
LOG(n, L"%lc %s", c, dc_account_full_username(acc));
LOG(n, L" %c %s", c, dc_account_fullname(acc));
}
LOG(n, L"End of /FRIENDS list");
@ -42,7 +42,7 @@ ncdc_cmd_friends_add(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
name = w_convert(av[1]);
return_if_true(name == NULL, false);
friend = dc_account_from_fullid(name);
friend = dc_account_from_fullname(name);
if (friend == NULL) {
LOG(n, L"friends: add: invalid username given, use the full ID");
goto cleanup;
@ -53,7 +53,7 @@ ncdc_cmd_friends_add(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
goto cleanup;
}
LOG(n, L"friends: add: request for friendship sent");
LOG(n, L"friends: add: request for friendship sent to %s", name);
ret = true;
cleanup:
@ -64,9 +64,105 @@ cleanup:
return ret;
}
static bool
ncdc_cmd_friends_remove(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
{
char *name = NULL;
dc_account_t friend = NULL;
bool ret = false;
size_t i = 0;
if (ac <= 1) {
return false;
}
name = w_convert(av[1]);
return_if_true(name == NULL, false);
for (i = 0; i < dc_account_friends_size(current_account); i++) {
dc_account_t cur = dc_account_nthfriend(current_account, i);
if (strcmp(dc_account_fullname(cur), name) == 0) {
friend = cur;
break;
}
}
if (friend == NULL) {
LOG(n, L"friends: remove: no such friend in friend's list");
goto cleanup;
}
if (!dc_api_remove_friend(api, current_account, friend)) {
LOG(n, L"friends: remove: failed to remove friend");
goto cleanup;
}
LOG(n, L"friends: remove: friend %s removed", name);
ret = true;
cleanup:
free(name);
return ret;
}
static bool
ncdc_cmd_friends_accept(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
{
char *name = NULL;
dc_account_t friend = NULL;
bool ret = false;
size_t i = 0;
if (ac <= 1) {
return false;
}
name = w_convert(av[1]);
return_if_true(name == NULL, false);
for (i = 0; i < dc_account_friends_size(current_account); i++) {
dc_account_t cur = dc_account_nthfriend(current_account, i);
if (strcmp(dc_account_fullname(cur), name) == 0 &&
dc_account_friend_state(cur) == FRIEND_STATE_PENDING) {
friend = cur;
break;
}
}
if (friend == NULL) {
LOG(n, L"friends: accept: no such pending friend on the list");
goto cleanup;
}
if (!dc_api_accept_friend(api, current_account, friend)) {
LOG(n, L"friends: accept: failed to accept friend");
goto cleanup;
}
LOG(n, L"friends: accept: friend %s accepted", name);
ret = true;
cleanup:
free(name);
return ret;
}
static ncdc_commands_t subcmds[] = {
{ L"accept", ncdc_cmd_friends_accept },
{ L"add", ncdc_cmd_friends_add },
{ L"list", ncdc_cmd_friends_list },
{ L"remove", ncdc_cmd_friends_remove },
{ NULL, NULL }
};
bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
{
wchar_t *subcmd = NULL;
ncdc_commands_t *it = NULL;
if (current_account == NULL ||
!dc_account_has_token(current_account)) {
@ -83,13 +179,10 @@ bool ncdc_cmd_friends(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
--ac;
++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 {
if ((it = ncdc_find_cmd(subcmds, subcmd)) == NULL) {
LOG(n, L"friends: no such subcommand \"%ls\"", subcmd);
return false;
}
return true;
return it->handler(n, ac, av);
}