ncdc/libdc/src/api.c

366 lines
8.9 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
2019-07-08 23:29:08 +02:00
#define DISCORD_URL "https://discordapp.com/api/v6"
#define DISCORD_GATEWAY "https://gateway.discord.gg/?encoding=json&v=6"
2019-06-15 21:33:46 +02:00
#define DISCORD_USERAGENT "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
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);
}
}
2019-07-09 14:54:25 +02:00
int debug_callback(CURL *handle, curl_infotype type,
char *data, size_t size,
void *userptr)
2019-06-15 21:33:46 +02:00
{
2019-07-09 14:54:25 +02:00
FILE *f = fopen("debug.log", "a+");
2019-06-15 21:33:46 +02:00
switch (type) {
2019-07-09 14:54:25 +02:00
case CURLINFO_TEXT: fprintf(f, "+T: %s", data); break;
case CURLINFO_HEADER_IN: fprintf(f, ">H: %s", data); break;
case CURLINFO_HEADER_OUT: fprintf(f, "<H: %s", data); break;
case CURLINFO_DATA_IN: fprintf(f, ">D: %s\n", data); break;
case CURLINFO_DATA_OUT: fprintf(f, "<D: %s\n", data); break;
case CURLINFO_SSL_DATA_IN: fprintf(f, "<S: SSL_DATA_IN\n"); break;
case CURLINFO_SSL_DATA_OUT: fprintf(f, "<S: SSL_DATA_OUT\n"); break;
2019-06-15 21:33:46 +02:00
default: break;
}
2019-07-09 14:54:25 +02:00
fclose(f);
2019-06-15 21:33:46 +02:00
return 0;
}
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;
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
if (len >= 0) {
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE_LARGE, len);
}
2019-07-08 14:08:25 +02:00
curl_easy_setopt(c, CURLOPT_COPYPOSTFIELDS, data);
2019-06-15 21:33:46 +02:00
}
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
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-07-08 13:04:53 +02:00
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-07-08 23:29:08 +02:00
static size_t stall_connection(char *buffer, size_t size, size_t nitems,
void *userdata)
2019-06-15 21:33:46 +02:00
{
2019-07-08 23:29:08 +02:00
CURL *easy = (CURL *)userdata;
2019-06-15 21:33:46 +02:00
2019-07-08 23:29:08 +02:00
if (strncmp(buffer, "\r\n", size) == 0) {
curl_easy_setopt(easy, CURLOPT_CONNECT_ONLY, 1);
//curl_easy_pause(easy, CURLPAUSE_ALL);
curl_easy_setopt(easy, CURLOPT_FORBID_REUSE, 1);
2019-07-03 21:14:23 +02:00
}
2019-06-15 21:33:46 +02:00
2019-07-08 23:29:08 +02:00
return size * nitems;
}
2019-06-15 21:33:46 +02:00
2019-07-08 23:29:08 +02:00
dc_gateway_t dc_api_establish_gateway(dc_api_t api, dc_account_t login)
{
return_if_true(api == NULL, NULL);
return_if_true(api->curl == NULL, NULL);
return_if_true(login == NULL || !dc_account_has_token(login), NULL);
2019-06-25 17:00:25 +02:00
2019-07-08 23:29:08 +02:00
CURL *c = NULL;
struct curl_slist *list = NULL;
/* BE THE BROKEN OR THE BREAKER
*/
dc_gateway_t gw = NULL;
dc_gateway_t ret = NULL;
2019-06-25 17:00:25 +02:00
2019-07-08 23:29:08 +02:00
c = curl_easy_init();
goto_if_true(c == NULL, cleanup);
2019-06-15 21:33:46 +02:00
2019-07-08 23:29:08 +02:00
gw = dc_gateway_new();
goto_if_true(gw == NULL, cleanup);
2019-06-15 21:33:46 +02:00
2019-07-08 23:29:08 +02:00
curl_easy_setopt(c, CURLOPT_URL, DISCORD_GATEWAY);
2019-06-15 21:33:46 +02:00
2019-07-08 23:29:08 +02:00
list = dc_gateway_slist(gw);
curl_slist_append(list, "Content-Type: application/json");
curl_slist_append(list, "Accept: application/json");
curl_slist_append(list, "User-Agent: " DISCORD_USERAGENT);
curl_slist_append(list, "Pragma: no-cache");
curl_slist_append(list, "Cache-Control: no-cache");
curl_slist_append(list, "Sec-WebSocket-Key: cbYK1Jm6cpk3Rua");
curl_slist_append(list, "Sec-WebSocket-Version: 13");
curl_slist_append(list, "Upgrade: websocket");
2019-06-15 21:33:46 +02:00
2019-07-08 23:29:08 +02:00
curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, stall_connection);
curl_easy_setopt(c, CURLOPT_HEADERDATA, c);
2019-06-25 18:20:27 +02:00
2019-07-08 23:29:08 +02:00
curl_easy_setopt(c, CURLOPT_HTTPHEADER, list);
2019-06-25 18:20:27 +02:00
2019-07-08 23:29:08 +02:00
curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_setopt(c, CURLOPT_TCP_KEEPIDLE, 120L);
curl_easy_setopt(c, CURLOPT_TCP_KEEPINTVL, 60L);
2019-06-25 18:20:27 +02:00
2019-07-08 23:29:08 +02:00
curl_easy_setopt(c, CURLOPT_FORBID_REUSE, 1L);
curl_easy_setopt(c, CURLOPT_FRESH_CONNECT, 1L);
2019-06-26 10:48:55 +02:00
2019-07-08 23:29:08 +02:00
curl_easy_setopt(c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
2019-06-26 10:48:55 +02:00
2019-07-08 23:29:08 +02:00
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, dc_gateway_writefunc);
curl_easy_setopt(c, CURLOPT_WRITEDATA, gw);
2019-06-26 10:48:55 +02:00
2019-07-08 23:29:08 +02:00
dc_gateway_set_login(gw, login);
dc_gateway_set_curl(gw, api->curl, c);
2019-06-26 10:48:55 +02:00
2019-07-08 23:29:08 +02:00
if (curl_multi_add_handle(api->curl, c) != CURLM_OK) {
goto cleanup;
2019-06-26 10:48:55 +02:00
}
2019-07-08 23:29:08 +02:00
c = NULL;
2019-06-26 10:48:55 +02:00
2019-07-08 23:29:08 +02:00
ret = gw;
gw = NULL;
2019-06-26 10:48:55 +02:00
2019-06-25 18:20:27 +02:00
cleanup:
2019-07-08 23:29:08 +02:00
if (c != NULL) {
curl_easy_cleanup(c);
2019-06-26 10:48:55 +02:00
}
2019-07-08 23:29:08 +02:00
dc_unref(gw);
2019-06-25 18:20:27 +02:00
return ret;
}