aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorturret <turret@duck.com>2025-06-22 19:15:47 -0500
committerturret <turret@duck.com>2025-06-22 19:15:47 -0500
commitabb1c579d29b3fb61e79601518799beca46609c3 (patch)
tree8585a73b78efb5b0b2c52fb0e4b6f46ff134a1e5
parent35d7228fb15bc7b262745fef179f79d1ac0b4045 (diff)
downloaddiscord-bot-skeleton-abb1c579d29b3fb61e79601518799beca46609c3.tar.gz
discord-bot-skeleton-abb1c579d29b3fb61e79601518799beca46609c3.tar.bz2
discord-bot-skeleton-abb1c579d29b3fb61e79601518799beca46609c3.zip
net: api changes, data export, ident, an more
Move HELLO IDENT functionality into util/net Add ready event in example/hello, grab app id and print info Change http_request family of functions - Remove bufsiz for response code - Remove post input pipe in exchange for simple writebuf Add heartbeat latency ack tracking Add websocket handle close function (dummy function, to be extended upon) Add message for invalid token
-rw-r--r--example/hello.c43
-rw-r--r--include/dbs/api.h8
-rw-r--r--util/net.c134
3 files changed, 139 insertions, 46 deletions
diff --git a/example/hello.c b/example/hello.c
index fe208d5..41ef753 100644
--- a/example/hello.c
+++ b/example/hello.c
@@ -7,35 +7,32 @@
#include <dbs/event.h>
#include <dbs/log.h>
+#include <dbs/util.h>
extern CURL *ws_handle;
+char *app_id;
int hello(cJSON *data)
{
- cJSON *ev_payload = cJSON_CreateObject();
- cJSON *ev_data = cJSON_CreateObject();
- cJSON_AddNumberToObject(ev_payload, "op", 2);
- cJSON_AddItemToObject(ev_payload, "d", ev_data);
-
- cJSON_AddStringToObject(ev_data, "token", getenv("TOKEN"));
- cJSON_AddNumberToObject(ev_data, "intents", 0);
-
- cJSON *properties = cJSON_CreateObject();
- cJSON_AddItemToObject(ev_data, "properties", properties);
- cJSON_AddStringToObject(properties, "browser", "DBS");
- cJSON_AddStringToObject(properties, "device", "DBS");
-
- struct utsname unamed;
- uname(&unamed);
- cJSON_AddStringToObject(properties, "os", unamed.sysname);
-
- char *msg = cJSON_PrintUnformatted(ev_payload);
- size_t sent;
- curl_ws_send(ws_handle, msg, strlen(msg), &sent, 0, CURLWS_TEXT);
- free(msg);
- cJSON_Delete(ev_payload);
- print("hello: sent IDENT");
+ print("hello: hello from userland!");
return 0;
}
declare_event(HELLO, hello);
+
+int ready(cJSON *data)
+{
+ print("hello: received ready event!");
+
+ cJSON *app = cJSON_GetObjectItemCaseSensitive(data, "application");
+ char *id = js_getStr(app, "id");
+ app_id = malloc(strlen(id) + 1);
+ strcpy(app_id, id);
+
+ cJSON *user = cJSON_GetObjectItemCaseSensitive(data, "user");
+ char *username = js_getStr(user, "username");
+ print("hello: logged in! my name is %s (id %s)", username, app_id);
+
+ return 0;
+}
+declare_event(READY, ready);
diff --git a/include/dbs/api.h b/include/dbs/api.h
index d421f08..5d4d4fc 100644
--- a/include/dbs/api.h
+++ b/include/dbs/api.h
@@ -10,10 +10,12 @@ typedef enum {
} HTTPMethod;
int http_request(HTTPMethod method, char *url,
- struct curl_slist *headers, char *writebuf, size_t bufsiz);
+ struct curl_slist *headers, char *writebuf,
+ long *response_code);
-int api_request(HTTPMethod method, char * url,
- struct curl_slist *headers, char *writebuf, size_t bufsiz);
+int api_request(HTTPMethod method, char *url,
+ struct curl_slist *headers, char *writebuf,
+ long *response_code);
#define http_get(...) http_request(HTTP_GET, __VA_ARGS__)
#define http_post(...) http_request(HTTP_POST, __VA_ARGS__)
diff --git a/util/net.c b/util/net.c
index 77e944f..f9e4a13 100644
--- a/util/net.c
+++ b/util/net.c
@@ -6,6 +6,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/signalfd.h>
+#include <sys/utsname.h>
#include <unistd.h>
#include <cJSON.h>
@@ -19,9 +20,11 @@
/* functions */
int http_request(HTTPMethod method, char *url,
- struct curl_slist *headers, char *writebuf, size_t bufsiz);
+ struct curl_slist *headers, char *writebuf,
+ long *response_code);
int api_request(HTTPMethod method, char *url,
- struct curl_slist *headers, char *writebuf, size_t bufsiz);
+ struct curl_slist *headers, char *writebuf,
+ long *response_code);
static void setup_token_header();
static int (**ev_get_handler(enum Event event)) (cJSON *);
@@ -44,25 +47,22 @@ CURL *ws_handle;
static char *gateway_url;
static char *token_header;
+static struct timeval last_heartbeat_sent;
+double api_latency;
+
static long last_sequence = -1;
static struct timeval heartbeat_time;
int http_request(HTTPMethod method, char *url,
- struct curl_slist *headers, char *writebuf, size_t bufsiz)
+ struct curl_slist *headers, char *writebuf,
+ long *response_code)
{
- int inputpipe[2];
+ /* TODO: async */
int outputpipe[2];
- if(pipe(inputpipe) < 0)
- return -(errno << 8);
if(pipe(outputpipe) < 0)
return -(errno << 8);
- if(writebuf && bufsiz > 0)
- write(inputpipe[1], writebuf, bufsiz);
- close(inputpipe[1]);
-
- FILE *input_read = fdopen(inputpipe[0], "r");
FILE *output_write = fdopen(outputpipe[1], "w");
int ret = outputpipe[0];
@@ -72,7 +72,6 @@ int http_request(HTTPMethod method, char *url,
panic("api: curl_easy_init failed");
curl_easy_setopt(job, CURLOPT_URL, url);
- curl_easy_setopt(job, CURLOPT_READDATA, input_read);
curl_easy_setopt(job, CURLOPT_WRITEDATA, output_write);
char *requestmethod = "GET";
switch(method) {
@@ -93,6 +92,9 @@ int http_request(HTTPMethod method, char *url,
break;
}
curl_easy_setopt(job, CURLOPT_CUSTOMREQUEST, requestmethod);
+ if(method != HTTP_GET && writebuf != NULL) {
+ curl_easy_setopt(job, CURLOPT_POSTFIELDS, writebuf);
+ }
if(headers)
curl_easy_setopt(job, CURLOPT_HTTPHEADER, headers);
CURLcode res = curl_easy_perform(job);
@@ -102,8 +104,11 @@ int http_request(HTTPMethod method, char *url,
ret = -res;
}
+ if(response_code != NULL) {
+ curl_easy_getinfo(job, CURLINFO_RESPONSE_CODE, response_code);
+ }
+
curl_easy_cleanup(job);
- fclose(input_read);
fclose(output_write);
return ret;
}
@@ -122,7 +127,8 @@ static void setup_token_header()
l1_initcall(setup_token_header);
int api_request(HTTPMethod method, char *url,
- struct curl_slist *headers, char *writebuf, size_t bufsiz)
+ struct curl_slist *headers, char *writebuf,
+ long *response_code)
{
char *new_url = calloc((strlen("https://discord.com/api") + strlen(url) + 1),
sizeof(char));
@@ -131,7 +137,7 @@ int api_request(HTTPMethod method, char *url,
if(token_header == NULL)
setup_token_header();
struct curl_slist *headers_auth = curl_slist_append(headers, token_header);
- int ret = http_request(method, new_url, headers_auth, writebuf, bufsiz);
+ int ret = http_request(method, new_url, headers_auth, writebuf, response_code);
free(new_url);
curl_slist_free_all(headers_auth);
return ret;
@@ -176,11 +182,14 @@ static void ws_send_heartbeat()
itimer.it_value = heartbeat_time;
setitimer(ITIMER_REAL, &itimer, NULL);
}
+
+ gettimeofday(&last_heartbeat_sent, NULL);
}
static void ws_handle_event(cJSON *event)
{
int op = cJSON_GetObjectItem(event, "op")->valueint;
+ char *msg;
cJSON *data = cJSON_GetObjectItem(event, "d");
switch(op) {
case 0: ; /* Event dispatch */
@@ -201,6 +210,7 @@ static void ws_handle_event(cJSON *event)
{ ev = EVENT_INVALID; }
int (*ev_handler)(cJSON *) = *ev_get_handler(ev);
+ /* TODO: intercept ready event for session information */
if(ev_handler != NULL) {
ev_handler(data);
} else {
@@ -223,11 +233,12 @@ static void ws_handle_event(cJSON *event)
/* FALLTHROUGH */
case 7: ; /* Reconnect */
/* TODO */
- char *msg = cJSON_Print(data);
+ msg = cJSON_Print(data);
panic("ws: cannot reconnect to ws after failure (Not supported)\n%s", msg);
free(msg); /* at least the effort is there (not a memory leak) */
break;
case 10: ; /* Hello */
+ /* Establish heartbeat interval*/
int heartbeat_wait = cJSON_GetObjectItem(data,
"heartbeat_interval")->valueint;
float jitter = (float)rand() / (RAND_MAX * 1.0f);
@@ -244,14 +255,47 @@ static void ws_handle_event(cJSON *event)
};
setitimer(ITIMER_REAL, &new_itimer, NULL);
+ /* Send IDENT */
+ /* TODO: resume functionality */
+ cJSON *ev_payload = cJSON_CreateObject();
+ cJSON *ev_data = cJSON_CreateObject();
+ cJSON_AddNumberToObject(ev_payload, "op", 2);
+ cJSON_AddItemToObject(ev_payload, "d", ev_data);
+
+ cJSON_AddStringToObject(ev_data, "token", getenv("TOKEN"));
+ cJSON_AddNumberToObject(ev_data, "intents", 0);
+
+ cJSON *properties = cJSON_CreateObject();
+ cJSON_AddItemToObject(ev_data, "properties", properties);
+ cJSON_AddStringToObject(properties, "browser", "DBS");
+ cJSON_AddStringToObject(properties, "device", "DBS");
+
+ struct utsname unamed;
+ uname(&unamed);
+ cJSON_AddStringToObject(properties, "os", unamed.sysname);
+
+ msg = cJSON_PrintUnformatted(ev_payload);
+ size_t sent;
+ curl_ws_send(ws_handle, msg, strlen(msg), &sent, 0, CURLWS_TEXT);
+ free(msg);
+ cJSON_Delete(ev_payload);
+ print(LOG_DEBUG "hello: sent IDENT");
+
+ /* Call user hello handler */
int (*hello_handler)(cJSON *) = *ev_get_handler(HELLO);
if(hello_handler) {
(hello_handler)(data);
}
break;
- case 11: /* Heartbeat ACK */
- print(LOG_DEBUG "ws: heartbeat ACK");
+ case 11: ; /* Heartbeat ACK */
+ // last_heartbeat_sent
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ api_latency = (now.tv_sec - last_heartbeat_sent.tv_sec) * 1000.0f
+ + (now.tv_usec - last_heartbeat_sent.tv_usec) / 1000.0f;
+ print(LOG_DEBUG "ws: heartbeat ACK (latency %.4f)", api_latency);
+
break;
default:
print(LOG_ERR "ws: received unknown WS opcode %d", op);
@@ -259,6 +303,37 @@ static void ws_handle_event(cJSON *event)
}
}
+static void ws_handle_close(short code, char *msg) {
+ switch(code){
+ case 4003: /* Not authenticated */
+ case 4007: /* Invalid seq */
+ case 4009: /* Session timed out */
+ /* Reconnect is allowed, however our previous session has been invalidated,
+ so we need to create a new session */
+ panic("ws code %d: %s", code, msg);
+ break;
+ /* TODO: handle new session reconnect */
+
+ case 4004: /* Authentication failed */
+ case 4010: /* Invalid shard */
+ case 4011: /* Sharding required */
+ case 4012: /* Invalid API version */
+ case 4013: /* Invalid intent(s) */
+ case 4014: /* Disallowed intent(s) */
+ /* We should not try to reconnect! These are issues either
+ with DBS or with configuration and need to be resolved manually. */
+ panic("ws code %d: %s", code, msg);
+ break;
+
+ default:
+ /* All other codes have us reconnect, reusing our established session so
+ data is not lost */
+ panic("ws code %d: %s", code, msg);
+ /* TODO: handle session reuse reconnect */
+ break;
+ }
+}
+
int net_subsystem(void)
{
if(!gateway_url)
@@ -283,6 +358,7 @@ int net_subsystem(void)
"%s", curl_easy_strerror(ret));
+ /* TODO: catch sigterm & terminate session */
/* Block ALRM */
sigset_t *set = malloc(sizeof(sigset_t));
sigemptyset(set);
@@ -338,13 +414,19 @@ int net_subsystem(void)
case(CURLWS_PING):
curl_ws_send(ws_handle, NULL, 0, NULL, 0, CURLWS_PONG);
goto sockpoll_continue;
- case(CURLWS_CLOSE):
+ case(CURLWS_CLOSE):;
+ short code = (uint8_t)inbuf[1] | (uint8_t)inbuf[0] << 8;
+ char *msg = (char*)((void*)inbuf + 2);
+ inbuf[rlen] = '\0';
+ ws_handle_close(code, msg);
+ goto sockpoll_continue;
default:
break;
}
cJSON *event = cJSON_ParseWithLength(inbuf, rlen);
if(!event) {
+ fwrite(inbuf, rlen, sizeof(char), stdout);
print(LOG_ERR "net: dropped malformed frame");
goto sockpoll_continue;
}
@@ -395,12 +477,24 @@ void net_get_gateway_url()
panic("net: wss not supported by libcurl");
/* fetch preferred url from discord */
- int fd = api_get("/gateway/bot", NULL, NULL, 0);
+ long response_code;
+ int fd = api_get("/gateway/bot", NULL, NULL, &response_code);
if(fd < 0) {
print(LOG_ERR "net: cannot get gateway url: %s", curl_easy_strerror(-fd));
goto assume;
}
+ if(response_code != 200) {
+ switch(response_code){
+ case 401:
+ panic("net: token is invalid, please use a valid token");
+ break;
+ default:
+ panic("net: received non-OK status while asking for url (code %d)", response_code);
+ break;
+ }
+ }
+
char buf[512];
int buf_length = read(fd, buf, 512);
close(fd);