ncdc/libdc/src/api.c

439 lines
10 KiB
C
Raw Normal View History

2019-06-25 14:52:38 +02:00
#include <dc/api.h>
#include <dc/refable.h>
#include "internal.h"
2019-06-15 21:33:46 +02:00
#define DISCORD_URL "https://discordapp.com/api/v6"
#define DISCORD_USERAGENT "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
#define DISCORD_API_AUTH "auth/login"
2019-06-25 14:52:38 +02:00
struct dc_api_
2019-06-15 21:33:46 +02:00
{
2019-06-25 14:52:38 +02:00
dc_refable_t ref;
2019-06-15 21:33:46 +02:00
struct event_base *base;
CURLM *curl;
GHashTable *syncs;
char *cookie;
};
2019-06-25 14:52:38 +02:00
static void dc_api_free(dc_api_t ptr)
2019-06-15 21:33:46 +02:00
{
return_if_true(ptr == NULL,);
if (ptr->syncs != NULL) {
g_hash_table_unref(ptr->syncs);
ptr->syncs = NULL;
}
free(ptr);
}
2019-06-25 14:52:38 +02:00
dc_api_t dc_api_new(void)
2019-06-15 21:33:46 +02:00
{
2019-06-25 14:52:38 +02:00
dc_api_t ptr = calloc(1, sizeof(struct dc_api_));
2019-06-15 21:33:46 +02:00
return_if_true(ptr == NULL, NULL);
2019-06-25 14:52:38 +02:00
ptr->ref.cleanup = (dc_cleanup_t)dc_api_free;
2019-06-15 21:33:46 +02:00
ptr->syncs = g_hash_table_new_full(g_direct_hash, g_direct_equal,
2019-06-25 14:52:38 +02:00
NULL, dc_unref
2019-06-15 21:33:46 +02:00
);
if (ptr->syncs == NULL) {
free(ptr);
return NULL;
}
2019-06-25 14:52:38 +02:00
return dc_ref(ptr);
2019-06-15 21:33:46 +02:00
}
2019-06-25 14:52:38 +02:00
void dc_api_set_curl_multi(dc_api_t api, CURLM *curl)
2019-06-15 21:33:46 +02:00
{
return_if_true(api == NULL,);
return_if_true(curl == NULL,);
api->curl = curl;
}
2019-06-25 14:52:38 +02:00
void dc_api_set_event_base(dc_api_t api, struct event_base *base)
2019-06-15 21:33:46 +02:00
{
return_if_true(api == NULL,);
return_if_true(base == NULL,);
api->base = base;
}
2019-06-25 14:52:38 +02:00
void dc_api_signal(dc_api_t api, CURL *easy, int code)
2019-06-15 21:33:46 +02:00
{
2019-06-25 14:52:38 +02:00
dc_api_sync_t sync = NULL;
2019-06-15 21:33:46 +02:00
return_if_true(api == NULL,);
return_if_true(easy == NULL,);
sync = g_hash_table_lookup(api->syncs, easy);
if (sync != NULL) {
2019-06-25 14:52:38 +02:00
dc_api_sync_finish(sync, code);
2019-06-15 21:33:46 +02:00
g_hash_table_remove(api->syncs, easy);
}
}
#ifdef DEBUG
static int debug_callback(CURL *handle, curl_infotype type,
char *data, size_t size,
void *userptr
)
{
switch (type) {
case CURLINFO_TEXT: printf("+T: %s", data); break;
case CURLINFO_HEADER_IN: printf(">H: %s", data); break;
case CURLINFO_HEADER_OUT: printf("<H: %s", data); break;
case CURLINFO_DATA_IN: printf(">D: %s\n", data); break;
case CURLINFO_DATA_OUT: printf("<D: %s\n", data); break;
case CURLINFO_SSL_DATA_IN:
case CURLINFO_SSL_DATA_OUT:
default: break;
}
return 0;
}
#endif
2019-06-25 18:20:27 +02:00
static dc_api_sync_t
dc_api_do(dc_api_t api, char const *verb,
char const *url, char const *token,
char const *data, int64_t len)
2019-06-15 21:33:46 +02:00
{
return_if_true(api == NULL, NULL);
return_if_true(api->curl == NULL, NULL);
return_if_true(url == NULL, NULL);
2019-06-25 18:20:27 +02:00
return_if_true(verb == NULL, NULL);
2019-06-15 21:33:46 +02:00
CURL *c = NULL;
bool ret = false;
2019-06-25 14:52:38 +02:00
dc_api_sync_t sync = NULL;
2019-06-15 21:33:46 +02:00
struct curl_slist *l = NULL;
char *tmp = NULL;
int ptr = 0;
c = curl_easy_init();
goto_if_true(c == NULL, cleanup);
2019-06-25 14:52:38 +02:00
sync = dc_api_sync_new(api->curl, c);
2019-06-15 21:33:46 +02:00
goto_if_true(c == NULL, cleanup);
curl_easy_setopt(c, CURLOPT_URL, url);
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, fwrite);
2019-06-25 14:52:38 +02:00
curl_easy_setopt(c, CURLOPT_WRITEDATA, dc_api_sync_stream(sync));
2019-06-15 21:33:46 +02:00
if (api->cookie != NULL) {
curl_easy_setopt(c, CURLOPT_COOKIE, api->cookie);
}
2019-06-25 14:52:38 +02:00
l = dc_api_sync_list(sync);
2019-06-15 21:33:46 +02:00
if (data != NULL) {
curl_slist_append(l, "Content-Type: application/json");
}
curl_slist_append(l, "Accept: application/json");
curl_slist_append(l, "User-Agent: " DISCORD_USERAGENT);
curl_slist_append(l, "Pragma: no-cache");
curl_slist_append(l, "Cache-Control: no-cache");
if (token != NULL) {
asprintf(&tmp, "Authorization: %s", token);
curl_slist_append(l, tmp);
free(tmp);
tmp = NULL;
}
curl_easy_setopt(c, CURLOPT_HTTPHEADER, l);
curl_easy_setopt(c, CURLOPT_FORBID_REUSE, 1L);
//curl_easy_setopt(c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
#ifdef DEBUG
curl_easy_setopt(c, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(c, CURLOPT_DEBUGFUNCTION, debug_callback);
#endif
2019-06-25 18:20:27 +02:00
if (strcmp(verb, "POST") == 0) {
2019-06-15 21:33:46 +02:00
curl_easy_setopt(c, CURLOPT_POST, 1UL);
curl_easy_setopt(c, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
2019-06-25 18:20:27 +02:00
}
if (data != NULL) {
2019-06-15 21:33:46 +02:00
curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, data);
if (len >= 0) {
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE_LARGE, len);
}
}
2019-06-25 18:20:27 +02:00
if (strcmp(verb, "PUT") == 0 ||
strcmp(verb, "DELETE") == 0) {
curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, verb);
}
2019-06-15 21:33:46 +02:00
if (curl_multi_add_handle(api->curl, c) != CURLM_OK) {
goto cleanup;
}
2019-06-25 14:52:38 +02:00
g_hash_table_insert(api->syncs, c, dc_ref(sync));
2019-06-15 21:33:46 +02:00
curl_multi_socket_action(api->curl, CURL_SOCKET_TIMEOUT, 0, &ptr);
ret = true;
cleanup:
if (!ret) {
2019-06-25 14:52:38 +02:00
dc_unref(sync);
2019-06-15 21:33:46 +02:00
sync = NULL;
}
return sync;
}
2019-06-25 14:52:38 +02:00
dc_api_sync_t dc_api_call(dc_api_t api, char const *token,
2019-06-25 18:20:27 +02:00
char const *verb, char const *method,
json_t *j)
2019-06-15 21:33:46 +02:00
{
char *data = NULL;
char *url = NULL;
2019-06-25 14:52:38 +02:00
dc_api_sync_t s = NULL;
2019-06-15 21:33:46 +02:00
asprintf(&url, "%s/%s", DISCORD_URL, method);
goto_if_true(url == NULL, cleanup);
if (j != NULL) {
data = json_dumps(j, JSON_COMPACT);
goto_if_true(data == NULL, cleanup);
}
2019-06-25 18:20:27 +02:00
s = dc_api_do(api, verb, url, token, data, -1);
2019-06-15 21:33:46 +02:00
goto_if_true(s == NULL, cleanup);
cleanup:
free(data);
data = NULL;
free(url);
url = NULL;
return s;
}
2019-06-25 14:52:38 +02:00
json_t *dc_api_call_sync(dc_api_t api, char const *token,
2019-06-25 18:20:27 +02:00
char const *verb, char const *method,
json_t *j)
2019-06-15 21:33:46 +02:00
{
2019-06-25 14:52:38 +02:00
dc_api_sync_t s = NULL;
2019-06-15 21:33:46 +02:00
json_t *reply = NULL;
2019-06-25 18:20:27 +02:00
s = dc_api_call(api, verb, token, method, j);
2019-06-15 21:33:46 +02:00
goto_if_true(s == NULL, cleanup);
2019-06-25 14:52:38 +02:00
if (!dc_api_sync_wait(s)) {
2019-06-15 21:33:46 +02:00
goto cleanup;
}
#ifdef DEBUG
2019-06-25 14:52:38 +02:00
printf("api_call_sync: %d\n", dc_api_sync_code(s));
2019-06-15 21:33:46 +02:00
#endif
2019-06-25 14:52:38 +02:00
reply = json_loadb(dc_api_sync_data(s),
dc_api_sync_datalen(s),
2019-06-15 21:33:46 +02:00
0, NULL
);
cleanup:
2019-06-25 14:52:38 +02:00
dc_unref(s);
2019-06-15 21:33:46 +02:00
s = NULL;
return reply;
}
2019-06-25 14:52:38 +02:00
static bool dc_api_error(json_t *j, int *code, char const **message)
2019-06-15 21:33:46 +02:00
{
return_if_true(j == NULL, false);
bool error = false;
json_t *c = NULL, *m = NULL;
c = json_object_get(j, "code");
if (c != NULL) {
error = true;
if (code != NULL) {
*code = json_integer_value(c);
}
}
m = json_object_get(j, "message");
if (m != NULL) {
error = true;
if (message != NULL) {
*message = json_string_value(m);
}
}
return error;
}
2019-06-25 14:52:38 +02:00
bool dc_api_authenticate(dc_api_t api, dc_account_t account)
2019-06-15 21:33:46 +02:00
{
json_t *j = json_object(), *reply = NULL, *token = NULL;
bool ret = false;
json_object_set_new(j, "email",
2019-06-25 14:52:38 +02:00
json_string(dc_account_email(account))
2019-06-15 21:33:46 +02:00
);
json_object_set_new(j, "password",
2019-06-25 14:52:38 +02:00
json_string(dc_account_password(account))
2019-06-15 21:33:46 +02:00
);
2019-06-25 18:20:27 +02:00
reply = dc_api_call_sync(api, "POST", NULL, DISCORD_API_AUTH, j);
2019-06-15 21:33:46 +02:00
goto_if_true(reply == NULL, cleanup);
2019-06-25 14:52:38 +02:00
if (dc_api_error(j, NULL, NULL)) {
2019-06-15 21:33:46 +02:00
return false;
}
token = json_object_get(reply, "token");
if (token == NULL || !json_is_string(token)) {
goto cleanup;
}
2019-06-25 14:52:38 +02:00
dc_account_set_token(account, json_string_value(token));
2019-06-15 21:33:46 +02:00
ret = true;
cleanup:
if (j != NULL) {
json_decref(j);
j = NULL;
}
if (reply != NULL) {
json_decref(reply);
reply = NULL;
}
return ret;
}
2019-06-25 18:20:27 +02:00
bool dc_api_get_userinfo(dc_api_t api, dc_account_t login,
dc_account_t user)
2019-06-15 21:33:46 +02:00
{
char *url = NULL;
2019-06-25 17:00:25 +02:00
json_t *reply = NULL, *val = NULL;
2019-06-15 21:33:46 +02:00
bool ret = false;
return_if_true(api == NULL, false);
return_if_true(login == NULL, false);
return_if_true(user == NULL, false);
2019-06-25 14:52:38 +02:00
asprintf(&url, "users/%s", dc_account_id(user));
2019-06-15 21:33:46 +02:00
2019-06-25 18:20:27 +02:00
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
2019-06-15 21:33:46 +02:00
goto_if_true(reply == NULL, cleanup);
2019-06-25 17:00:25 +02:00
val = json_object_get(reply, "username");
goto_if_true(val == NULL || !json_is_string(val), cleanup);
dc_account_set_username(user, json_string_value(val));
val = json_object_get(reply, "discriminator");
goto_if_true(val == NULL || !json_is_string(val), cleanup);
dc_account_set_discriminator(user, json_string_value(val));
val = json_object_get(reply, "id");
goto_if_true(val == NULL || !json_is_string(val), cleanup);
dc_account_set_id(user, json_string_value(val));
2019-06-15 21:33:46 +02:00
ret = true;
cleanup:
2019-06-26 10:48:55 +02:00
free(url);
json_decref(reply);
2019-06-15 21:33:46 +02:00
return ret;
}
2019-06-25 18:20:27 +02:00
2019-07-02 21:53:29 +02:00
bool dc_api_get_friends(dc_api_t api, dc_account_t login, GPtrArray **friends)
{
char *url = NULL;
json_t *reply = NULL;
bool ret = false;
return_if_true(api == NULL, false);
return_if_true(login == NULL, false);
asprintf(&url, "users/%s/guilds", dc_account_id(login));
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
goto_if_true(reply == NULL, cleanup);
dc_util_dump_json(reply);
ret = true;
cleanup:
json_decref(reply);
reply = NULL;
return ret;
}
2019-06-26 10:48:55 +02:00
bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out)
2019-06-25 18:20:27 +02:00
{
char *url = NULL;
2019-06-26 10:48:55 +02:00
json_t *reply = NULL, *c = NULL, *val = NULL;
size_t i = 0;
2019-06-25 18:20:27 +02:00
bool ret = false;
2019-07-02 21:53:29 +02:00
GPtrArray *guilds = g_ptr_array_new_with_free_func(
(GDestroyNotify)dc_unref
);
2019-06-25 18:20:27 +02:00
return_if_true(api == NULL, false);
return_if_true(login == NULL, false);
2019-06-26 10:48:55 +02:00
asprintf(&url, "users/%s/guilds", dc_account_id(login));
2019-06-25 18:20:27 +02:00
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
goto_if_true(reply == NULL, cleanup);
2019-06-26 10:48:55 +02:00
goto_if_true(!json_is_array(reply), cleanup);
json_array_foreach(reply, i, c) {
dc_guild_t g = dc_guild_new();
val = json_object_get(c, "id");
goto_if_true(val == NULL || !json_is_string(val), cleanup);
dc_guild_set_id(g, json_string_value(val));
val = json_object_get(c, "name");
goto_if_true(val == NULL || !json_is_string(val), cleanup);
dc_guild_set_name(g, json_string_value(val));
g_ptr_array_add(guilds, g);
}
*out = guilds;
guilds = NULL;
ret = true;
2019-06-25 18:20:27 +02:00
cleanup:
2019-06-26 10:48:55 +02:00
free(url);
2019-06-25 18:20:27 +02:00
json_decref(reply);
2019-06-26 10:48:55 +02:00
if (guilds) {
g_ptr_array_unref(guilds);
guilds = NULL;
}
2019-06-25 18:20:27 +02:00
return ret;
}