#include #include #include "internal.h" #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" struct dc_api_ { dc_refable_t ref; struct event_base *base; CURLM *curl; GHashTable *syncs; char *cookie; }; static void dc_api_free(dc_api_t ptr) { return_if_true(ptr == NULL,); if (ptr->syncs != NULL) { g_hash_table_unref(ptr->syncs); ptr->syncs = NULL; } free(ptr); } dc_api_t dc_api_new(void) { dc_api_t ptr = calloc(1, sizeof(struct dc_api_)); return_if_true(ptr == NULL, NULL); ptr->ref.cleanup = (dc_cleanup_t)dc_api_free; ptr->syncs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, dc_unref ); if (ptr->syncs == NULL) { free(ptr); return NULL; } return dc_ref(ptr); } void dc_api_set_curl_multi(dc_api_t api, CURLM *curl) { return_if_true(api == NULL,); return_if_true(curl == NULL,); api->curl = curl; } void dc_api_set_event_base(dc_api_t api, struct event_base *base) { return_if_true(api == NULL,); return_if_true(base == NULL,); api->base = base; } void dc_api_signal(dc_api_t api, CURL *easy, int code) { dc_api_sync_t sync = NULL; return_if_true(api == NULL,); return_if_true(easy == NULL,); sync = g_hash_table_lookup(api->syncs, easy); if (sync != NULL) { dc_api_sync_finish(sync, code); 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("D: %s\n", data); break; case CURLINFO_DATA_OUT: printf("cookie); api->cookie = NULL; if ((ptr = strstr(data, ":")) != NULL) { api->cookie = strdup(ptr+1); if ((ptr = strstr(api->cookie, ";")) != NULL) { *ptr = '\0'; } } } return sz * num; } static dc_api_sync_t dc_api_post(dc_api_t api, char const *url, char const *token, char const *data, int64_t len) { return_if_true(api == NULL, NULL); return_if_true(api->curl == NULL, NULL); return_if_true(url == NULL, NULL); CURL *c = NULL; bool ret = false; dc_api_sync_t sync = NULL; struct curl_slist *l = NULL; char *tmp = NULL; int ptr = 0; c = curl_easy_init(); goto_if_true(c == NULL, cleanup); sync = dc_api_sync_new(api->curl, c); goto_if_true(c == NULL, cleanup); curl_easy_setopt(c, CURLOPT_URL, url); curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(c, CURLOPT_WRITEDATA, dc_api_sync_stream(sync)); curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, header_callback); curl_easy_setopt(c, CURLOPT_HEADERDATA, api); if (api->cookie != NULL) { curl_easy_setopt(c, CURLOPT_COOKIE, api->cookie); } l = dc_api_sync_list(sync); 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 if (data != NULL) { curl_easy_setopt(c, CURLOPT_POST, 1UL); curl_easy_setopt(c, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, data); if (len >= 0) { curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE_LARGE, len); } } if (curl_multi_add_handle(api->curl, c) != CURLM_OK) { goto cleanup; } g_hash_table_insert(api->syncs, c, dc_ref(sync)); curl_multi_socket_action(api->curl, CURL_SOCKET_TIMEOUT, 0, &ptr); ret = true; cleanup: if (!ret) { dc_unref(sync); sync = NULL; } return sync; } dc_api_sync_t dc_api_call(dc_api_t api, char const *token, char const *method, json_t *j) { char *data = NULL; char *url = NULL; dc_api_sync_t s = NULL; 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); } s = dc_api_post(api, url, token, data, -1); goto_if_true(s == NULL, cleanup); cleanup: free(data); data = NULL; free(url); url = NULL; return s; } json_t *dc_api_call_sync(dc_api_t api, char const *token, char const *method, json_t *j) { dc_api_sync_t s = NULL; json_t *reply = NULL; s = dc_api_call(api, token, method, j); goto_if_true(s == NULL, cleanup); if (!dc_api_sync_wait(s)) { goto cleanup; } #ifdef DEBUG printf("api_call_sync: %d\n", dc_api_sync_code(s)); #endif reply = json_loadb(dc_api_sync_data(s), dc_api_sync_datalen(s), 0, NULL ); cleanup: dc_unref(s); s = NULL; return reply; } static bool dc_api_error(json_t *j, int *code, char const **message) { 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; } bool dc_api_authenticate(dc_api_t api, dc_account_t account) { json_t *j = json_object(), *reply = NULL, *token = NULL; bool ret = false; json_object_set_new(j, "email", json_string(dc_account_email(account)) ); json_object_set_new(j, "password", json_string(dc_account_password(account)) ); reply = dc_api_call_sync(api, NULL, DISCORD_API_AUTH, j); goto_if_true(reply == NULL, cleanup); if (dc_api_error(j, NULL, NULL)) { return false; } token = json_object_get(reply, "token"); if (token == NULL || !json_is_string(token)) { goto cleanup; } dc_account_set_token(account, json_string_value(token)); ret = true; cleanup: if (j != NULL) { json_decref(j); j = NULL; } if (reply != NULL) { json_decref(reply); reply = NULL; } return ret; } bool dc_api_userinfo(dc_api_t api, dc_account_t login, dc_account_t user) { char *url = NULL; json_t *reply = NULL, *val = NULL; bool ret = false; return_if_true(api == NULL, false); return_if_true(login == NULL, false); return_if_true(user == NULL, false); asprintf(&url, "users/%s", dc_account_id(user)); reply = dc_api_call_sync(api, dc_account_token(login), url, NULL); goto_if_true(reply == NULL, cleanup); 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)); ret = true; cleanup: if (reply != NULL) { json_decref(reply); reply = NULL; } return ret; }