diff --git a/.github/workflows/test_build.yml b/.github/workflows/test_build.yml index fb5134ccf..96f1e1744 100644 --- a/.github/workflows/test_build.yml +++ b/.github/workflows/test_build.yml @@ -28,6 +28,8 @@ jobs: make examples echo "Building tests" make test + echo "Building voice" + make voice - name: Run Makefile with parallelism run: | @@ -39,3 +41,5 @@ jobs: make examples -j$(nproc) echo "Building tests with parallelism" make test -j$(nproc) + echo "Building voice with parallelism" + make voice -j$(nproc) diff --git a/Makefile b/Makefile index 6f63e645d..f3ba8f845 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,7 @@ COGUTILS_OBJS = $(COGUTILS_DIR)/cog-utils.o \ $(COGUTILS_DIR)/logconf.o \ $(COGUTILS_DIR)/json-build.o \ $(COGUTILS_DIR)/jsmn-find.o -CORE_OBJS = $(CORE_DIR)/work.o \ - $(CORE_DIR)/user-agent.o \ +CORE_OBJS = $(CORE_DIR)/user-agent.o \ $(CORE_DIR)/websockets.o \ $(CORE_DIR)/io_poller.o THIRDP_OBJS = $(THIRDP_DIR)/sha1.o \ @@ -31,14 +30,18 @@ THIRDP_OBJS = $(THIRDP_DIR)/sha1.o \ $(THIRDP_DIR)/threadpool.o \ $(THIRDP_DIR)/priority_queue.o DISCORD_OBJS = $(SRC_DIR)/concord-once.o \ - $(SRC_DIR)/discord-adapter.o \ - $(SRC_DIR)/discord-adapter_ratelimit.o \ - $(SRC_DIR)/discord-adapter_refcount.o \ + $(SRC_DIR)/discord-refcount.o \ + $(SRC_DIR)/discord-rest.o \ + $(SRC_DIR)/discord-rest_request.o \ + $(SRC_DIR)/discord-rest_ratelimit.o \ $(SRC_DIR)/discord-client.o \ - $(SRC_DIR)/discord-loop.o \ + $(SRC_DIR)/discord-loop.o \ $(SRC_DIR)/discord-gateway.o \ + $(SRC_DIR)/discord-gateway_dispatch.o \ + $(SRC_DIR)/discord-messagecommands.o \ $(SRC_DIR)/discord-timer.o \ $(SRC_DIR)/discord-misc.o \ + $(SRC_DIR)/discord-worker.o \ $(SRC_DIR)/application_command.o \ $(SRC_DIR)/interaction.o \ $(SRC_DIR)/audit_log.o \ @@ -50,31 +53,39 @@ DISCORD_OBJS = $(SRC_DIR)/concord-once.o \ $(SRC_DIR)/invite.o \ $(SRC_DIR)/user.o \ $(SRC_DIR)/voice.o \ - $(SRC_DIR)/webhook.o \ - $(XOBJ) + $(SRC_DIR)/webhook.o -OBJS := $(COGUTILS_OBJS) $(CORE_OBJS) $(THIRDP_OBJS) $(DISCORD_OBJS) \ - $(GENCODECS_OBJ) +OBJS = $(COGUTILS_OBJS) $(CORE_OBJS) $(THIRDP_OBJS) $(DISCORD_OBJS) \ + $(GENCODECS_OBJ) -LIB := $(LIBDIR)/libdiscord.a +ARLIB = $(LIBDIR)/libdiscord.a +ARFLAGS = -cqsv +SOLIB = $(LIBDIR)/libdiscord.so +SOFLAGS = -fPIC +LDFLAGS = -lcurl + +WFLAGS += -Wall -Wextra -Wshadow -Wdouble-promotion -Wconversion -Wpedantic CFLAGS += -std=c99 -O0 -g -pthread -D_XOPEN_SOURCE=600 \ -I$(INCLUDE_DIR) -I$(COGUTILS_DIR) -I$(CORE_DIR) -I$(THIRDP_DIR) \ -I$(GENCODECS_DIR) -I$(PREFIX)/include -DLOG_USE_COLOR -WFLAGS += -Wall -Wextra -Wshadow -Wdouble-promotion -Wconversion -Wpedantic $(SRC_DIR)/%.o: $(SRC_DIR)/%.c - $(CC) $(CFLAGS) $(WFLAGS) $(XFLAGS) -c -o $@ $< -%.o: %.c - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CFLAGS) $(WFLAGS) -c -o $@ $< -all: $(LIB) +all: $(ARLIB) + +shared: + @ $(MAKE) clean + @ $(MAKE) CFLAGS="$(SOFLAGS) $(CFLAGS)" $(SOLIB) voice: - @ $(MAKE) XFLAGS=-DCCORD_VOICE XOBJ=$(SRC_DIR)/discord-voice.o all + @ $(MAKE) CFLAGS="$(CFLAGS) -DCCORD_VOICE" \ + OBJS="$(OBJS) $(SRC_DIR)/discord-voice.o" all debug: - @ $(MAKE) XFLAGS="-DCCORD_DEBUG_WEBSOCKETS -DCCORD_DEBUG_ADAPTER" all + @ $(MAKE) CFLAGS="$(CFLAGS) -DCCORD_DEBUG_WEBSOCKETS -DCCORD_DEBUG_HTTP" \ + all test: all @ $(MAKE) -C $(TEST_DIR) @@ -85,8 +96,10 @@ examples: all gencodecs: @ $(MAKE) -C $(GENCODECS_DIR) -$(LIB): $(OBJS) | $(LIBDIR) - $(AR) -cqsv $@ $? +$(ARLIB): $(OBJS) | $(LIBDIR) + $(AR) $(ARFLAGS) $@ $? +$(SOLIB): $(OBJS) | $(LIBDIR) + $(CC) -shared $(LDFLAGS) -o $@ $< $(LIBDIR): @ mkdir -p $@ @@ -96,16 +109,16 @@ $(OBJS): $(GENCODECS_HDR) | $(OBJDIR) $(GENCODECS_HDR): gencodecs $(OBJDIR): - @ mkdir -p $@/$(THIRDP_DIR) \ - $@/$(COGUTILS_DIR) \ - $@/$(SRC_DIR) \ + @ mkdir -p $@/$(THIRDP_DIR) $@/$(COGUTILS_DIR) $@/$(SRC_DIR) \ $@/$(GENCODECS_DIR) +.IGNORE: install: @ mkdir -p $(PREFIX)/lib/ @ mkdir -p $(PREFIX)/include/concord install -d $(PREFIX)/lib/ - install -m 644 $(LIB) $(PREFIX)/lib/ + install -m 644 $(ARLIB) $(PREFIX)/lib/ + install -m 644 $(SOLIB) $(PREFIX)/lib/ install -d $(PREFIX)/include/concord/ install -m 644 $(INCLUDE_DIR)/*.h $(COGUTILS_DIR)/*.h $(CORE_DIR)/*.h \ $(THIRDP_DIR)/*.h $(GENCODECS_DIR)/*.h $(PREFIX)/include/concord/ @@ -124,11 +137,11 @@ echo: clean: @ $(RM) $(GENCODECS_OBJS) $(COGUTILS_OBJS) $(CORE_OBJS) $(THIRDP_OBJS) $(DISCORD_OBJS) + @ $(RM) -r $(LIBDIR) @ $(MAKE) -C $(TEST_DIR) clean @ $(MAKE) -C $(EXAMPLES_DIR) clean purge: clean - @ $(RM) -r $(LIBDIR) @ $(MAKE) -C $(GENCODECS_DIR) clean .PHONY: test examples install echo clean purge docs gencodecs diff --git a/Makefile.dynamic b/Makefile.dynamic deleted file mode 100644 index 7b9601d70..000000000 --- a/Makefile.dynamic +++ /dev/null @@ -1,135 +0,0 @@ -PREFIX = /usr/local -CC = gcc - -SRC_DIR = src -INCLUDE_DIR = include -OBJDIR = obj -LIBDIR = lib -DOCS_DIR = docs -COGUTILS_DIR = cog-utils -GENCODECS_DIR = gencodecs -CORE_DIR = core -THIRDP_DIR = $(CORE_DIR)/third-party -EXAMPLES_DIR = examples -TEST_DIR = test -CCORDDOCS_DIR = concord-docs - -GENCODECS_HDR = $(GENCODECS_DIR)/discord_codecs.h -GENCODECS_OBJ = $(GENCODECS_DIR)/discord_codecs.o - -COGUTILS_OBJS = $(COGUTILS_DIR)/cog-utils.o \ - $(COGUTILS_DIR)/log.o \ - $(COGUTILS_DIR)/logconf.o \ - $(COGUTILS_DIR)/json-build.o \ - $(COGUTILS_DIR)/jsmn-find.o -CORE_OBJS = $(CORE_DIR)/work.o \ - $(CORE_DIR)/user-agent.o \ - $(CORE_DIR)/websockets.o \ - $(CORE_DIR)/io_poller.o -THIRDP_OBJS = $(THIRDP_DIR)/sha1.o \ - $(THIRDP_DIR)/curl-websocket.o \ - $(THIRDP_DIR)/threadpool.o \ - $(THIRDP_DIR)/priority_queue.o -DISCORD_OBJS = $(SRC_DIR)/concord-once.o \ - $(SRC_DIR)/discord-adapter.o \ - $(SRC_DIR)/discord-adapter_ratelimit.o \ - $(SRC_DIR)/discord-adapter_refcount.o \ - $(SRC_DIR)/discord-client.o \ - $(SRC_DIR)/discord-loop.o \ - $(SRC_DIR)/discord-gateway.o \ - $(SRC_DIR)/discord-timer.o \ - $(SRC_DIR)/discord-misc.o \ - $(SRC_DIR)/application_command.o \ - $(SRC_DIR)/interaction.o \ - $(SRC_DIR)/audit_log.o \ - $(SRC_DIR)/channel.o \ - $(SRC_DIR)/emoji.o \ - $(SRC_DIR)/gateway.o \ - $(SRC_DIR)/guild.o \ - $(SRC_DIR)/guild_template.o \ - $(SRC_DIR)/invite.o \ - $(SRC_DIR)/user.o \ - $(SRC_DIR)/voice.o \ - $(SRC_DIR)/webhook.o \ - $(XOBJ) - -OBJS = $(COGUTILS_OBJS) $(CORE_OBJS) $(THIRDP_OBJS) $(DISCORD_OBJS) \ - $(GENCODECS_OBJ) - -LIB = $(LIBDIR)/libdiscord.so -DYNLIB_DEPS = -lcurl - -CFLAGS += -std=c99 -O0 -g -pthread -D_XOPEN_SOURCE=600 \ - -I$(INCLUDE_DIR) -I$(COGUTILS_DIR) -I$(CORE_DIR) -I$(THIRDP_DIR) \ - -I$(GENCODECS_DIR) -I$(PREFIX)/include -DLOG_USE_COLOR -WFLAGS += -Wall -Wextra -Wshadow -Wdouble-promotion -Wconversion -Wpedantic - -$(SRC_DIR)/%.o: $(SRC_DIR)/%.c - $(CC) $(CFLAGS) $(WFLAGS) $(XFLAGS) -c -o $@ $< -%.o: %.c - $(CC) $(CFLAGS) -c -o $@ $< - -all: $(LIB) - -voice: - @ $(MAKE) XFLAGS=-DCCORD_VOICE XOBJ=$(SRC_DIR)/discord-voice.o all - -debug: - @ $(MAKE) XFLAGS="-DCCORD_DEBUG_WEBSOCKETS -DCCORD_DEBUG_ADAPTER" all - -test: all - @ $(MAKE) -C $(TEST_DIR) - -examples: all - @ $(MAKE) -C $(EXAMPLES_DIR) - -gencodecs: - @ $(MAKE) -C $(GENCODECS_DIR) - -$(LIB): $(OBJS) - $(CC) -shared $(DYNLIB_DEPS) -o $@ $(OBJS) - -$(LIBDIR): - @ mkdir -p $@ - -$(OBJS): $(GENCODECS_HDR) | $(OBJDIR) - -$(GENCODECS_HDR): gencodecs - -$(OBJDIR): - @ mkdir -p $@/$(THIRDP_DIR) \ - $@/$(COGUTILS_DIR) \ - $@/$(SRC_DIR) \ - $@/$(GENCODECS_DIR) - -install: - @ mkdir -p $(PREFIX)/lib/ - @ mkdir -p $(PREFIX)/include/concord - install -d $(PREFIX)/lib/ - install -m 644 $(LIB) $(PREFIX)/lib/ - install -d $(PREFIX)/include/concord/ - install -m 644 $(INCLUDE_DIR)/*.h $(COGUTILS_DIR)/*.h $(CORE_DIR)/*.h \ - $(THIRDP_DIR)/*.h $(GENCODECS_DIR)/*.h $(PREFIX)/include/concord/ - -docs: - @ $(MAKE) -C $(GENCODECS_DIR) docs - -echo: - @ echo -e 'CC: $(CC)\n' - @ echo -e 'PREFIX: $(PREFIX)\n' - @ echo -e 'CFLAGS: $(CFLAGS)\n' - @ echo -e 'COGUTILS_OBJS: $(COGUTILS_OBJS)\n' - @ echo -e 'CORE_OBJS: $(CORE_OBJS)\n' - @ echo -e 'DISCORD_OBJS: $(DISCORD_OBJS)\n' - @ echo -e 'OBJS: $(OBJS)\n' - -clean: - @ $(RM) $(GENCODECS_OBJS) $(COGUTILS_OBJS) $(CORE_OBJS) $(THIRDP_OBJS) $(DISCORD_OBJS) - @ $(MAKE) -C $(TEST_DIR) clean - @ $(MAKE) -C $(EXAMPLES_DIR) clean - -purge: clean - @ $(RM) -r $(LIBDIR) - @ $(MAKE) -C $(GENCODECS_DIR) clean - -.PHONY: test examples install echo clean purge docs gencodecs diff --git a/README.md b/README.md index 4a46e6215..cb5f98cea 100644 --- a/README.md +++ b/README.md @@ -15,33 +15,77 @@ Concord is an asynchronous C99 Discord API library. It has minimal external dependencies, and a low-level translation of the Discord official documentation to C code. -### Minimal example +### Examples + +*The following are minimalistic examples, refer to [`examples/`](examples/) for a better overview.* + +#### Slash Commands (new method) ```c #include #include -void on_ready(struct discord *client) { - const struct discord_user *bot = discord_get_self(client); - log_info("Logged in as %s!", bot->username); +void on_ready(struct discord *client, const struct discord_ready *event) { + struct discord_create_guild_application_command params = { + .name = "ping", + .description = "Ping command!" + }; + discord_create_guild_application_command(client, event->application->id, + GUILD_ID, ¶ms, NULL); +} + +void on_interaction(struct discord *client, const struct discord_interaction *event) { + if (event->type != DISCORD_INTERACTION_APPLICATION_COMMAND) + return; /* return if interaction isn't a slash command */ + + for (int i = 0; i < event->data->options->size; ++i) { + char *command_name = event->data->options->array[i].name; + + if (strcmp(command_name, "ping") == 0) { + struct discord_interaction_response params = { + .type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE, + .data = &(struct discord_interaction_callback_data){ + .content = "pong" + } + }; + discord_create_interaction_response(client, event->id, + event->token, ¶ms, NULL); + } + } } -void on_message(struct discord *client, const struct discord_message *msg) { - if (strcmp(msg->content, "ping") != 0) - return; /* ignore messages that aren't 'ping' */ +int main(void) { + struct discord *client = discord_init(BOT_TOKEN); + discord_set_on_ready(client, &on_ready); + discord_set_on_interaction_create(client, &on_interaction); + discord_run(client); +} +``` + +#### Message Commands (old method) + +```c +#include +#include + +void on_ready(struct discord *client, const struct discord_ready *event) { + log_info("Logged in as %s!", event->user->username); +} - struct discord_create_message params = { .content = "pong" }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); +void on_message(struct discord *client, const struct discord_message *event) { + if (strcmp(event->content, "ping") == 0) { + struct discord_create_message params = { .content = "pong" }; + discord_create_message(client, event->channel_id, ¶ms, NULL); + } } int main(void) { - struct discord *client = discord_init(BOT_TOKEN); - discord_set_on_ready(client, &on_ready); - discord_set_on_message_create(client, &on_message); - discord_run(client); + struct discord *client = discord_init(BOT_TOKEN); + discord_set_on_ready(client, &on_ready); + discord_set_on_message_create(client, &on_message); + discord_run(client); } ``` -*This is a minimalistic example, refer to [`examples/`](examples/) for a better overview.* ## Supported operating systems (minimum requirements) * GNU/Linux 4.x @@ -134,28 +178,6 @@ On Windows with Cygwin, you might need to pass both arguments to use POSIX threa $ CFLAGS="-pthread -lpthread" make ``` -#### Special compilation flags - -The following section outlines flags that can be attached to the Makefile if you wish to override the default compilation behavior with additional functionalities. Example: - -```console -$ CFLAGS="-DCCORD_SIGINTCATCH -DCCORD_VOICE" make -``` - -* `-DCCORD_SIGINTCATCH` - * By default Concord will not shutdown gracefully when a SIGINT is received (i.e. Ctrl+c), enable this flag if you wish it to be handled for you. -* `-DCCORD_VOICE` - * Enable experimental Voice Connection handling. -* `-DCCORD_DEBUG_WEBSOCKETS` - * Enable verbose debugging for WebSockets communication. -* `-DCCORD_DEBUG_ADAPTER` - * Enable verbose debugging for REST communication. - -#### Dynamic Linking Support -If you wish to produce a dynamically-linked version of Concord, use -`make -f Makefile.dynamic`. Note that this Makefile is intended only for -GNU-style compilers, like `gcc` or `clang`. - ### Configuring Concord The following outlines the default fields of `config.json` @@ -207,6 +229,34 @@ Type a message in any channel the bot is part of and the bot should send an exac With Ctrl+c or with Ctrl+| +### Configure your build + +The following outlines special flags and targets to override the default Makefile build with additional functionalities. + +#### Special compilation flags + +* `-DCCORD_SIGINTCATCH` + * By default Concord will not shutdown gracefully when a SIGINT is received (i.e. Ctrl+c), enable this flag if you wish it to be handled for you. +* `-DCCORD_DEBUG_WEBSOCKETS` + * Enable verbose debugging for WebSockets communication. +* `-DCCORD_DEBUG_HTTP` + * Enable verbose debugging for HTTP communication. + +*Example:* +```console +$ CFLAGS="-DCCORD_SIGINTCATCH -DCCORD_DEBUG_HTTP" make +``` + +#### Special targets + +* `make shared` + * Produce a dynamically-linked version of Concord. This Makefile is intented for GNU-style compilers, such as `gcc` or `clang`. + +* `make voice` + * Enable experimental Voice Connection handling - not production ready. +* `make debug` + * Same as enabling `-DCCORD_DEBUG_WEBSOCKETS` and `-DCCORD_DEBUG_HTTP` + ## Installing Concord *(note -- `#` means that you should be running as root)* diff --git a/cog-utils/chash.h b/cog-utils/chash.h index d66ec801d..a3fc90f12 100644 --- a/cog-utils/chash.h +++ b/cog-utils/chash.h @@ -1,7 +1,11 @@ +/* Modified by Lucas Müller (muller.lucas@hotmail.com), 16 May 2022 + * - add __chash_init() and __chash_free() as a non-malloc option + */ + #ifndef CWARE_LIBCHASH_H #define CWARE_LIBCHASH_H -#define CWARE_LIBCHASH_VERSION "2.0.0" +#define CWARE_LIBCHASH_VERSION "x.0.0" /* How big heap-allocated hashtables are by default */ #ifndef CHASH_INITIAL_SIZE @@ -222,12 +226,8 @@ do { \ - /* operations */ -#define chash_init(hashtable, namespace) \ - NULL; \ - \ - (hashtable) = malloc(sizeof((*(hashtable)))); \ +#define __chash_init(hashtable, namespace) \ (hashtable)->CHASH_LENGTH_FIELD = 0; \ (hashtable)->CHASH_CAPACITY_FIELD = CHASH_INITIAL_SIZE; \ (hashtable)->CHASH_BUCKETS_FIELD = malloc(CHASH_INITIAL_SIZE \ @@ -235,6 +235,12 @@ do { \ memset((hashtable)->CHASH_BUCKETS_FIELD, 0, \ sizeof(*((hashtable)->CHASH_BUCKETS_FIELD)) * CHASH_INITIAL_SIZE) +#define chash_init(hashtable, namespace) \ + NULL; \ + \ + (hashtable) = malloc(sizeof((*(hashtable)))); \ + __chash_init(hashtable, namespace) + #define chash_init_stack(hashtable, buffer, _length, namespace) \ (*(hashtable)); \ \ @@ -378,6 +384,34 @@ do { \ storage = ((hashtable)->CHASH_BUCKETS_FIELD + __CHASH_HASH); \ } while(0) +#define __chash_free(hashtable, namespace) \ +do { \ + __chash_assert_nonnull(__chash_free, hashtable); \ + __chash_assert_nonnull(__chash_free, (hashtable)->CHASH_BUCKETS_FIELD); \ + (hashtable)->CHASH_CAPACITY_FIELD--; \ + \ + while((hashtable)->CHASH_CAPACITY_FIELD != -1) { \ + if((hashtable)->CHASH_BUCKETS_FIELD[(hashtable)->CHASH_CAPACITY_FIELD] \ + .CHASH_STATE_FIELD != CHASH_FILLED) { \ + (hashtable)->CHASH_CAPACITY_FIELD--; \ + continue; \ + } \ + \ + namespace ##_FREE_KEY( \ + (hashtable)->CHASH_BUCKETS_FIELD[(hashtable)->CHASH_CAPACITY_FIELD] \ + .CHASH_KEY_FIELD); \ + namespace ##_FREE_VALUE( \ + (hashtable)->CHASH_BUCKETS_FIELD[(hashtable)->CHASH_CAPACITY_FIELD] \ + .CHASH_VALUE_FIELD); \ + (hashtable)->CHASH_CAPACITY_FIELD--; \ + (hashtable)->CHASH_LENGTH_FIELD--; \ + } \ + \ + if((namespace ## _HEAP) == 1) { \ + free((hashtable)->CHASH_BUCKETS_FIELD); \ + } \ +} while(0) + #define chash_free(hashtable, namespace) \ do { \ __chash_assert_nonnull(chash_free, hashtable); \ @@ -405,7 +439,7 @@ do { \ free((hashtable)->CHASH_BUCKETS_FIELD); \ free((hashtable)); \ } \ -} while(0); +} while(0) #define chash_is_full(hashtable, namespace) \ (((hashtable)->CHASH_LENGTH_FIELD) == ((hashtable)->CHASH_CAPACITY_FIELD)) diff --git a/cog-utils/cog-utils.c b/cog-utils/cog-utils.c index 7c8db3bfe..0d4da144f 100644 --- a/cog-utils/cog-utils.c +++ b/cog-utils/cog-utils.c @@ -45,12 +45,6 @@ cog_load_whole_file(const char filename[], size_t *len) return str; } -size_t -cog_sized_buffer_from_json(const char str[], size_t len, struct sized_buffer *buf) -{ - return buf->size = cog_strndup(str, len, &buf->start); -} - long cog_timezone(void) { diff --git a/cog-utils/cog-utils.h b/cog-utils/cog-utils.h index 5ddc84c0a..7027707a7 100644 --- a/cog-utils/cog-utils.h +++ b/cog-utils/cog-utils.h @@ -8,20 +8,6 @@ extern "C" { #endif /* __cplusplus */ -/** - * @brief Sized buffer - * - * A very important data structure that is used - * pervasively in the conversion between JSON strings and C structs, - * http request/response body - */ -struct sized_buffer { - /** the buffer's start */ - char *start; - /** the buffer's size in bytes */ - size_t size; -}; - /** * @brief Load file contents into a string * @@ -41,33 +27,6 @@ char *cog_load_whole_file_fp(FILE *fp, size_t *len); */ char *cog_load_whole_file(const char filename[], size_t *len); -/** - * @brief Fill a structure from a JSON file - * - * @param filename the name of the JSON file to be read - * @param p_data a pointer to the structure to be filled - * @param from_json_cb the callback that will receive the JSON data - * and then fill the structure - * @return 1 on success, 0 on failure - */ -int cog_dati_from_fjson(char filename[], - void *p_data, - void(from_json_cb)(char *str, - size_t len, - void *p_data)); - -/** - * @brief Create a copy of JSON string to a `struct sized_buffer` - * - * @param str the JSON string - * @param len the JSON string length - * @param buf the sized buffer - * @return amount of bytes written to buf - */ -size_t cog_sized_buffer_from_json(const char str[], - size_t len, - struct sized_buffer *buf); - /** * @brief Get the difference between UTC and the latest local standard time, in * seconds. diff --git a/cog-utils/logconf.c b/cog-utils/logconf.c index ae9d1c36f..9f51999d9 100644 --- a/cog-utils/logconf.c +++ b/cog-utils/logconf.c @@ -7,6 +7,7 @@ #include /* getpid() */ #include "logconf.h" +#include "cog-utils.h" #define JSMN_STRICT #define JSMN_HEADER @@ -83,8 +84,8 @@ void logconf_http(struct logconf *conf, struct loginfo *p_info, char url[], - struct sized_buffer header, - struct sized_buffer body, + struct logconf_szbuf header, + struct logconf_szbuf body, char label_fmt[], ...) { @@ -319,10 +320,10 @@ logconf_cleanup(struct logconf *conf) memset(conf, 0, sizeof *conf); } -struct sized_buffer +struct logconf_field logconf_get_field(struct logconf *conf, char *const path[], unsigned depth) { - struct sized_buffer field = { 0 }; + struct logconf_field field = { 0 }; jsmn_parser parser; jsmntok_t tokens[256]; diff --git a/cog-utils/logconf.h b/cog-utils/logconf.h index 548634c6b..1a1d55143 100644 --- a/cog-utils/logconf.h +++ b/cog-utils/logconf.h @@ -8,7 +8,6 @@ extern "C" { #include /* uint64_t */ #include "log.h" -#include "cog-utils.h" #define __ERR(fmt, ...) log_fatal(fmt "%s", __VA_ARGS__) @@ -187,6 +186,25 @@ extern "C" { /** Maximum length for module id */ #define LOGCONF_ID_LEN 64 + 1 +/** + * @brief The read-only `config.json` field + * @see logconf_get_field() + */ +struct logconf_field { + /** the buffer's start */ + const char *start; + /** the buffer's size in bytes */ + size_t size; +}; + +/** @brief Generic sized-buffer */ +struct logconf_szbuf { + /** the buffer's start */ + char *start; + /** the buffer's size in bytes */ + size_t size; +}; + /** * @brief A stackful and modularized wrapper over the popular 'log.c' * facilities @@ -205,7 +223,7 @@ struct logconf { /** if true then logging will be ignored for this module */ _Bool is_disabled; /** config file contents */ - struct sized_buffer file; + struct logconf_szbuf file; /** http logging counter */ int *counter; @@ -274,12 +292,12 @@ void logconf_cleanup(struct logconf *conf); * @param conf the `struct logconf` module * @param path the JSON key path * @param depth the path depth - * @return a read-only sized buffer containing the field's value + * @return a read-only sized buffer containing the field's contents * @see logconf_setup() for initializing `conf` with a config file */ -struct sized_buffer logconf_get_field(struct logconf *conf, - char *const path[], - unsigned depth); +struct logconf_field logconf_get_field(struct logconf *conf, + char *const path[], + unsigned depth); /** * @brief Log HTTP transfers @@ -297,8 +315,8 @@ struct sized_buffer logconf_get_field(struct logconf *conf, void logconf_http(struct logconf *conf, struct loginfo *info, char url[], - struct sized_buffer header, - struct sized_buffer body, + struct logconf_szbuf header, + struct logconf_szbuf body, char label_fmt[], ...); diff --git a/core/error.h b/core/error.h index 77e108742..d6e16e9ff 100644 --- a/core/error.h +++ b/core/error.h @@ -9,7 +9,8 @@ /** the error code datatype */ typedef int CCORDcode; -/** request was a success */ + +/** action was a success */ #define CCORD_OK 0 /** request wasn't succesful */ #define CCORD_HTTP_CODE -1 @@ -27,6 +28,12 @@ typedef int CCORDcode; #define CCORD_CURLM_INTERNAL -7 /** attempt to initialize globals more than once */ #define CCORD_GLOBAL_INIT -8 +/** couldn't perform action because of resource's ownership issues */ +#define CCORD_OWNERSHIP -9 +/** couldn't perform action because resource is unavailable */ +#define CCORD_UNAVAILABLE -10 +/** couldn't enqueue worker thread (queue is full) */ +#define CCORD_FULL_WORKER -11 /** @} ConcordError */ diff --git a/core/io_poller.c b/core/io_poller.c index 6ad6e1fdc..17f870696 100644 --- a/core/io_poller.c +++ b/core/io_poller.c @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -38,8 +40,19 @@ struct io_poller { struct io_curlm **curlm; int curlm_cap; int curlm_cnt; + + int wakeup_fds[2]; }; +static void +on_io_poller_wakeup(struct io_poller *io, + enum io_poller_events events, + void *user_data) +{ + char buf[0x1000]; + read(io->wakeup_fds[0], buf, sizeof buf); +} + struct io_poller * io_poller_create(void) { @@ -48,8 +61,16 @@ io_poller_create(void) io->cap = 0x10; io->elements = calloc(io->cap, sizeof *io->elements); io->pollfds = calloc(io->cap, sizeof *io->pollfds); - if (io->elements && io->pollfds) - return io; + if (io->elements && io->pollfds) { + if (0 == pipe(io->wakeup_fds)) { + int flags = fcntl(io->wakeup_fds[0], F_GETFL); + fcntl(io->wakeup_fds[0], F_SETFL, flags | O_NONBLOCK); + + io_poller_socket_add(io, io->wakeup_fds[0], IO_POLLER_IN, + on_io_poller_wakeup, NULL); + return io; + } + } free(io->elements); free(io->pollfds); free(io); @@ -60,6 +81,8 @@ io_poller_create(void) void io_poller_destroy(struct io_poller *io) { + close(io->wakeup_fds[0]); + close(io->wakeup_fds[1]); for (int i = 0; i < io->curlm_cnt; i++) { free(io->curlm[i]->fds); free(io->curlm[i]); @@ -70,6 +93,13 @@ io_poller_destroy(struct io_poller *io) free(io); } +void +io_poller_wakeup(struct io_poller *io) +{ + char buf = 0; + write(io->wakeup_fds[1], &buf, sizeof buf); +} + int io_poller_poll(struct io_poller *io, int milliseconds) { @@ -96,10 +126,8 @@ io_poller_perform(struct io_poller *io) for (int i = 0; i < io->cnt; i++) { if (io->pollfds[i].revents) { int events = 0; - if (io->pollfds[i].revents & POLLIN) - events |= IO_POLLER_IN; - if (io->pollfds[i].revents & POLLOUT) - events |= IO_POLLER_OUT; + if (io->pollfds[i].revents & POLLIN) events |= IO_POLLER_IN; + if (io->pollfds[i].revents & POLLOUT) events |= IO_POLLER_OUT; io->pollfds[i].revents = 0; struct io_poller_element *element = &io->elements[i]; element->cb(io, events, element->user_data); @@ -107,15 +135,15 @@ io_poller_perform(struct io_poller *io) } for (int i = 0; i < io->curlm_cnt; i++) { struct io_curlm *curlm = io->curlm[i]; - if (curlm->should_perform || - (-1 != curlm->timeout && now >= curlm->timeout)) { + if (curlm->should_perform + || (-1 != curlm->timeout && now >= curlm->timeout)) { curlm->should_perform = false; - int result = curlm->cb ? - curlm->cb(io, curlm->multi, curlm->user_data) : - curl_multi_socket_all(curlm->multi, &curlm->running); - - if (result != 0) - return result; + int result = + curlm->cb + ? curlm->cb(io, curlm->multi, curlm->user_data) + : curl_multi_socket_all(curlm->multi, &curlm->running); + + if (result != 0) return result; } } return 0; @@ -156,10 +184,8 @@ io_poller_socket_add(struct io_poller *io, modify: io->pollfds[index].events = 0; - if (events & IO_POLLER_IN) - io->pollfds[index].events |= POLLIN; - if (events & IO_POLLER_OUT) - io->pollfds[index].events |= POLLOUT; + if (events & IO_POLLER_IN) io->pollfds[index].events |= POLLIN; + if (events & IO_POLLER_OUT) io->pollfds[index].events |= POLLOUT; io->elements[index].cb = cb; io->elements[index].user_data = user_data; return true; @@ -241,7 +267,8 @@ curl_socket_cb( } io_curlm->fds[io_curlm->fds_cnt++] = fd; } - io_poller_socket_add(io_curlm->io_poller, fd, events, io_curl_cb, io_curlm); + io_poller_socket_add(io_curlm->io_poller, fd, events, io_curl_cb, + io_curlm); return CURLM_OK; } @@ -264,7 +291,7 @@ io_poller_curlm_add(struct io_poller *io, CURLM *multi, io_poller_curl_cb cb, void *user_data) -{ +{ struct io_curlm *io_curlm = NULL; size_t index = 0; for (; index < io->curlm_cnt; index++) { @@ -282,13 +309,12 @@ io_poller_curlm_add(struct io_poller *io, io->curlm_cap = cap; } - if (!(io_curlm = calloc(1, sizeof *io_curlm))) - return false; + if (!(io_curlm = calloc(1, sizeof *io_curlm))) return false; io->curlm[io->curlm_cnt++] = io_curlm; io_curlm->io_poller = io; io_curlm->multi = multi; io_curlm->timeout = -1; - io_curlm->should_perform = true; + io_curlm->should_perform = true; curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, curl_timer_cb); curl_multi_setopt(multi, CURLMOPT_TIMERDATA, io_curlm); curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, curl_socket_cb); @@ -323,7 +349,6 @@ io_poller_curlm_del(struct io_poller *io, CURLM *multi) return false; } - bool io_poller_curlm_enable_perform(struct io_poller *io, CURLM *multi) { diff --git a/core/io_poller.h b/core/io_poller.h index c7f717e79..bc3ba5cb2 100644 --- a/core/io_poller.h +++ b/core/io_poller.h @@ -32,6 +32,14 @@ typedef void (*io_poller_cb)(struct io_poller *io, struct io_poller *io_poller_create(void); void io_poller_destroy(struct io_poller *io); +/** + * @brief wakeup the thread listening to this io_poller + * + * @param io the io_poller to wake up + */ +void +io_poller_wakeup(struct io_poller *io); + /** * @brief wait for events to be triggered * @param io the io_poller to poll on diff --git a/core/types.h b/core/types.h index 90ff4b9e7..c8ce14eeb 100644 --- a/core/types.h +++ b/core/types.h @@ -40,6 +40,32 @@ typedef uint64_t u64bitmask; */ typedef char json_char; +/** @brief Generic sized buffer */ +struct ccord_szbuf { + /** the buffer's start */ + char *start; + /** the buffer's size in bytes */ + size_t size; +}; + +/** @brief Read-only generic sized buffer */ +struct ccord_szbuf_readonly { + /** the buffer's start */ + const char *start; + /** the buffer's size in bytes */ + size_t size; +}; + +/** @brief Reusable generic sized buffer */ +struct ccord_szbuf_reusable { + /** the buffer's start */ + char *start; + /** the buffer's relative size in bytes */ + size_t size; + /** the buffer's real size in bytes */ + size_t realsize; +}; + /** @} ConcordTypes */ #endif /* CONCORD_TYPES_H */ diff --git a/core/user-agent.c b/core/user-agent.c index 4de2a6f74..4530e07e3 100644 --- a/core/user-agent.c +++ b/core/user-agent.c @@ -15,6 +15,14 @@ logconf_fatal(&conn->ua->conf, "(CURLE code: %d) %s", ecode, \ !*conn->errbuf ? curl_easy_strerror(ecode) : conn->errbuf) +/** @brief Generic sized buffer */ +struct _ua_szbuf { + /** the buffer's start */ + char *start; + /** the buffer's size in bytes */ + size_t size; +}; + struct user_agent { /** * queue of connection nodes for easy reuse @@ -23,7 +31,7 @@ struct user_agent { */ struct ua_conn_queue *connq; /** the base_url for every conn */ - struct sized_buffer base_url; + struct _ua_szbuf base_url; /** the user agent logging module */ struct logconf conf; @@ -55,7 +63,7 @@ struct ua_conn { struct ua_info info; /** request URL */ - struct sized_buffer url; + struct _ua_szbuf url; /** the conn request header */ struct curl_slist *header; @@ -444,24 +452,25 @@ _ua_info_reset(struct ua_info *info) static void _ua_info_populate(struct ua_info *info, struct ua_conn *conn) { - struct sized_buffer header = { conn->info.header.buf, - conn->info.header.len }; - struct sized_buffer body = { conn->info.body.buf, conn->info.body.len }; + struct logconf_szbuf logheader = { conn->info.header.buf, + conn->info.header.len }; + struct logconf_szbuf logbody = { conn->info.body.buf, + conn->info.body.len }; char *resp_url = NULL; memcpy(info, &conn->info, sizeof(struct ua_info)); - info->body.len = cog_strndup(body.start, body.size, &info->body.buf); + info->body.len = cog_strndup(logbody.start, logbody.size, &info->body.buf); info->header.len = - cog_strndup(header.start, header.size, &info->header.buf); + cog_strndup(logheader.start, logheader.size, &info->header.buf); /* get response's code */ curl_easy_getinfo(conn->ehandle, CURLINFO_RESPONSE_CODE, &info->httpcode); /* get response's url */ curl_easy_getinfo(conn->ehandle, CURLINFO_EFFECTIVE_URL, &resp_url); - logconf_http(&conn->ua->conf, &conn->info.loginfo, resp_url, header, body, - "HTTP_RCV_%s(%d)", http_code_print(info->httpcode), + logconf_http(&conn->ua->conf, &conn->info.loginfo, resp_url, logheader, + logbody, "HTTP_RCV_%s(%d)", http_code_print(info->httpcode), info->httpcode); } @@ -513,12 +522,10 @@ ua_init(struct ua_attr *attr) void ua_cleanup(struct user_agent *ua) { - QUEUE(struct ua_conn) - * ua_queues[] = { &ua->connq->idle, &ua->connq->busy }; - size_t i; + QUEUE *const ua_queues[] = { &ua->connq->idle, &ua->connq->busy }; /* cleanup connection queues */ - for (i = 0; i < sizeof(ua_queues) / sizeof(QUEUE *); ++i) { + for (size_t i = 0; i < sizeof(ua_queues) / sizeof *ua_queues; ++i) { QUEUE(struct ua_conn) queue, *qelem; struct ua_conn *conn; @@ -562,22 +569,22 @@ ua_set_url(struct user_agent *ua, const char base_url[]) static void _ua_conn_set_method(struct ua_conn *conn, enum http_method method, - struct sized_buffer *body) + char *body, + size_t body_size) { - static struct sized_buffer blank_body = { "", 0 }; - char logbuf[1024] = ""; - struct sized_buffer logheader = { logbuf, sizeof(logbuf) }; + struct logconf_szbuf logheader = { logbuf, sizeof(logbuf) }; + struct logconf_szbuf logbody = { body, body_size }; const char *method_str = http_method_print(method); struct logconf *conf = &conn->ua->conf; ua_conn_print_header(conn, logbuf, sizeof(logbuf)); /* make sure body points to something */ - if (!body) body = &blank_body; + if (!body) body = ""; - logconf_http(conf, &conn->info.loginfo, conn->url.start, logheader, *body, - "HTTP_SEND_%s", method_str); + logconf_http(conf, &conn->info.loginfo, conn->url.start, logheader, + logbody, "HTTP_SEND_%s", method_str); logconf_trace(conf, ANSICOLOR("SEND", ANSI_FG_GREEN) " %s [@@@_%zu_@@@]", method_str, conn->info.loginfo.counter); @@ -619,8 +626,8 @@ _ua_conn_set_method(struct ua_conn *conn, } /* set ptr to payload that will be sent via POST/PUT/PATCH */ - curl_easy_setopt(conn->ehandle, CURLOPT_POSTFIELDSIZE, body->size); - curl_easy_setopt(conn->ehandle, CURLOPT_POSTFIELDS, body->start); + curl_easy_setopt(conn->ehandle, CURLOPT_POSTFIELDSIZE, body_size); + curl_easy_setopt(conn->ehandle, CURLOPT_POSTFIELDS, body); } /* combine base url with endpoint and assign it to 'conn' */ @@ -669,7 +676,7 @@ void ua_conn_setup(struct ua_conn *conn, struct ua_conn_attr *attr) { _ua_conn_set_url(conn, attr->base_url, attr->endpoint); - _ua_conn_set_method(conn, attr->method, attr->body); + _ua_conn_set_method(conn, attr->method, attr->body, attr->body_size); } /* get request results */ @@ -813,15 +820,15 @@ ua_info_cleanup(struct ua_info *info) } /** attempt to get value from matching response header field */ -struct sized_buffer +struct ua_szbuf_readonly ua_info_get_header(struct ua_info *info, char field[]) { size_t len = strlen(field); - struct sized_buffer value; + struct ua_szbuf_readonly value; int i; for (i = 0; i < info->header.n_pairs; ++i) { - struct sized_buffer header = { + struct ua_szbuf_readonly header = { info->header.buf + info->header.pairs[i].field.idx, info->header.pairs[i].field.size, }; @@ -842,10 +849,10 @@ ua_info_get_header(struct ua_info *info, char field[]) return value; } -struct sized_buffer +struct ua_szbuf_readonly ua_info_get_body(struct ua_info *info) { - struct sized_buffer body = { info->body.buf, info->body.len }; + struct ua_szbuf_readonly body = { info->body.buf, info->body.len }; return body; } diff --git a/core/user-agent.h b/core/user-agent.h index ef256a59d..a9d155c77 100644 --- a/core/user-agent.h +++ b/core/user-agent.h @@ -96,12 +96,22 @@ struct ua_attr { struct logconf *conf; }; +/** @brief Read-only generic sized buffer */ +struct ua_szbuf_readonly { + /** the buffer's start */ + const char *start; + /** the buffer's size in bytes */ + size_t size; +}; + /** @brief Connection attributes */ struct ua_conn_attr { /** the HTTP method of this transfer (GET, POST, ...) */ enum http_method method; /** the optional request body, can be NULL */ - struct sized_buffer *body; + char *body; + /** the request body size */ + size_t body_size; /** the endpoint to be appended to the base URL */ char *endpoint; /** optional base_url to override ua_set_url(), can be NULL */ @@ -337,17 +347,18 @@ void ua_info_cleanup(struct ua_info *info); * * @param info handle containing information on previous request * @param field the header field to fetch the value - * @return a sized_buffer containing the field's value + * @return a @ref ua_szbuf_readonly containing the field's value */ -struct sized_buffer ua_info_get_header(struct ua_info *info, char field[]); +struct ua_szbuf_readonly ua_info_get_header(struct ua_info *info, + char field[]); /** * @brief Get the response body * * @param info handle containing information on previous request - * @return a sized_buffer containing the response body + * @return a @ref ua_szbuf_readonly containing the response body */ -struct sized_buffer ua_info_get_body(struct ua_info *info); +struct ua_szbuf_readonly ua_info_get_body(struct ua_info *info); #ifdef __cplusplus } diff --git a/core/websockets.c b/core/websockets.c index a5b7f1785..982400fbd 100644 --- a/core/websockets.c +++ b/core/websockets.c @@ -196,8 +196,8 @@ cws_on_connect_cb(void *p_ws, CURL *ehandle, const char *ws_protocols) logconf_http( &ws->conf, &ws->info.loginfo, ws->base_url, - (struct sized_buffer){ "", 0 }, - (struct sized_buffer){ (char *)ws_protocols, strlen(ws_protocols) }, + (struct logconf_szbuf){ "", 0 }, + (struct logconf_szbuf){ (char *)ws_protocols, strlen(ws_protocols) }, "WS_RCV_CONNECT"); logconf_trace( @@ -219,8 +219,8 @@ cws_on_close_cb(void *p_ws, size_t len) { struct websockets *ws = p_ws; - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)reason, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)reason, len }; (void)ehandle; _ws_set_status(ws, WS_DISCONNECTING); @@ -247,8 +247,8 @@ static void cws_on_text_cb(void *p_ws, CURL *ehandle, const char *text, size_t len) { struct websockets *ws = p_ws; - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)text, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)text, len }; (void)ehandle; logconf_http(&ws->conf, &ws->info.loginfo, ws->base_url, logheader, @@ -267,8 +267,8 @@ static void cws_on_binary_cb(void *p_ws, CURL *ehandle, const void *mem, size_t len) { struct websockets *ws = p_ws; - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)mem, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)mem, len }; (void)ehandle; logconf_http(&ws->conf, &ws->info.loginfo, ws->base_url, logheader, @@ -289,8 +289,8 @@ cws_on_ping_cb(void *p_ws, CURL *ehandle, const char *reason, size_t len) struct websockets *ws = p_ws; (void)ehandle; #if 0 - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)reason, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)reason, len }; logconf_http(&ws->conf, &ws->info.loginfo, ws->base_url, logheader, logbody, "WS_RCV_PING"); @@ -311,8 +311,8 @@ cws_on_pong_cb(void *p_ws, CURL *ehandle, const char *reason, size_t len) struct websockets *ws = p_ws; (void)ehandle; #if 0 - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)reason, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)reason, len }; logconf_http(&ws->conf, &ws->info.loginfo, ws->base_url, logheader, logbody, "WS_RCV_PONG"); @@ -405,8 +405,8 @@ _ws_close(struct websockets *ws, enum ws_close_reason code, const char reason[]) { - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)reason, strlen(reason) }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)reason, strlen(reason) }; logconf_http(&ws->conf, &ws->info.loginfo, ws->base_url, logheader, logbody, "WS_SEND_CLOSE(%d)", code); @@ -547,8 +547,8 @@ ws_send_binary(struct websockets *ws, const char msg[], size_t msglen) { - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)msg, msglen }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)msg, msglen }; logconf_http(&ws->conf, NULL, ws->base_url, logheader, logbody, "WS_SEND_BINARY"); @@ -585,8 +585,8 @@ ws_send_text(struct websockets *ws, const char text[], size_t len) { - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)text, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)text, len }; logconf_http(&ws->conf, NULL, ws->base_url, logheader, logbody, "WS_SEND_TEXT"); @@ -628,8 +628,8 @@ ws_ping(struct websockets *ws, { (void)info; #if 0 - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)reason, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)reason, len }; logconf_http(&ws->conf, &ws->info.loginfo, ws->base_url, logheader, logbody, "WS_SEND_PING"); @@ -667,8 +667,8 @@ ws_pong(struct websockets *ws, { (void)info; #if 0 - struct sized_buffer logheader = { "", 0 }; - struct sized_buffer logbody = { (char *)reason, len }; + struct logconf_szbuf logheader = { "", 0 }; + struct logconf_szbuf logbody = { (char *)reason, len }; logconf_http(&ws->conf, &ws->info.loginfo, ws->base_url, logheader, logbody, "WS_SEND_PONG"); diff --git a/core/work.c b/core/work.c deleted file mode 100644 index 24a155fd2..000000000 --- a/core/work.c +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#include - -#include "work.h" -#include "threadpool.h" - -/** true after threadpool initialization */ -static _Bool once; - -/** request thread and optional callback execution thread */ -static threadpool_t *tpool; - -int -work_global_init(void) -{ - static int nthreads = 0; - static int queue_size = 0; - const char *val; - char *p_end; - - if (once) return 1; - - /* get threadpool thread amount */ - val = getenv("CCORD_THREADPOOL_SIZE"); - if (val != NULL) { - nthreads = (int)strtol(val, &p_end, 10); - } - if (nthreads < 2 || ERANGE == errno || p_end == val) { - nthreads = 2; - } - /* get threadpool queue size */ - val = getenv("CCORD_THREADPOOL_QUEUE_SIZE"); - if (val != NULL) { - queue_size = (int)strtol(val, &p_end, 10); - } - if (queue_size < 8 || ERANGE == errno || p_end == val) { - queue_size = 8; - } - - /* initialize threadpool */ - tpool = threadpool_create(nthreads, queue_size, 0); - - once = 1; - - return 0; -} - -int -work_run(void (*callback)(void *data), void *data) -{ - return threadpool_add(tpool, callback, data, 0); -} - -void -work_global_cleanup(void) -{ - /* cleanup thread-pool manager */ - threadpool_destroy(tpool, threadpool_graceful); - once = 0; -} diff --git a/core/work.h b/core/work.h deleted file mode 100644 index b06645c9f..000000000 --- a/core/work.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @file work.h - */ - -#ifndef WORK_H -#define WORK_H - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -/** - * @brief Initialize global threadpool and priority queue - * @return `0` on success, `1` if it has already been initialized - */ -int work_global_init(void); - -/** - * @brief Cleanup global threadpool and priority queue - */ -void work_global_cleanup(void); - -/** - * @brief Run a callback from a worker thread - * - * @param callback user callback to be executed - * @param data user data to be passed to callback - * @return 0 if all goes well, negative values in case of error (see - * threadpool.h for codes) - */ -int work_run(void (*callback)(void *data), void *data); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* WORK_H */ diff --git a/docs/INTERNALS.md b/docs/INTERNALS.md index c1f514146..4548be6a6 100644 --- a/docs/INTERNALS.md +++ b/docs/INTERNALS.md @@ -49,177 +49,4 @@ The `src/` folder is where we place all of our Discord API wrapping logic. The `core/` folder is where we place all of Concord core's logic, such as handling of the WebSockets and REST protocols, threadpool management, etc. - - If you have any questions, feel free to join our [Discord server](https://discord.gg/Y7Xa6MA82v). diff --git a/examples/8ball.c b/examples/8ball.c index 92ec1f39d..d231e6779 100644 --- a/examples/8ball.c +++ b/examples/8ball.c @@ -1,89 +1,96 @@ #include #include +#include #include -#include -void on_ready(struct discord *client) -{ - const struct discord_user *bot = discord_get_self(client); +#include "discord.h" - log_info("8ball-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); +void +print_usage(void) +{ + printf("\n\nThis is a bot to demonstrate an easy to make 8ball response " + "to a question.\n" + "1. type '8ball (question)' in chat\n" + "\nTYPE ANY KEY TO START BOT\n"); } -void eight_ball(struct discord *client, const struct discord_message *msg) +void +on_ready(struct discord *client, const struct discord_ready *event) { - if(msg->author->bot) return; - - srand(time(0));//generate seed for randomizer - - char *phrases[20] = { //List of 8ball phrases/responses - ":green_circle: It is certain.", - ":green_circle: It is decidedly so.", - ":green_circle: Without a doubt.", - ":green_circle: Yes definitely.", - ":green_circle: You may rely on it.", - ":green_circle: As I see it, yes.", - ":green_circle: Most likely.", - ":green_circle: Outlook good.", - ":green_circle: Yes.", - ":green_circle: Signs Point to Yes.", - ":yellow_circle: Reply hazy, try again.", - ":yellow_circle: Ask again later.", - ":yellow_circle: Better not tell you now.", - ":yellow_circle: Cannot predict now.", - ":yellow_circle: Concentrate and ask again.", - ":red_circle: Don't count on it.", - ":red_circle: My reply is no.", - ":red_circle: My sources say no.", - ":red_circle: Outlook not so good.", - ":red_circle: Very doubtful." - }; - - int answer = rand() % (sizeof(phrases) / sizeof(*phrases)); // random index to phrases array - - struct discord_embed embeds[] = { // simple embed message - { - .title = ":8ball: 8-Ball", - .description = phrases[answer] - } - }; - - struct discord_create_message params = { - .embeds = &(struct discord_embeds) { - .size = sizeof(embeds) / sizeof *embeds, - .array = embeds, - } - }; - - discord_create_message(client, msg->channel_id, ¶ms, NULL); - + log_info("8ball-Bot succesfully connected to Discord as %s#%s!", + event->user->username, event->user->discriminator); } -int main(int argc, char *argv[]) +void +eight_ball(struct discord *client, const struct discord_message *event) { - const char *config_file; - if (argc > 1) - config_file = argv[1]; - else - config_file = "../config.json"; + if (event->author->bot) return; + + /* List of 8ball phrases/responses */ + char *phrases[] = { + ":green_circle: It is certain.", + ":green_circle: It is decidedly so.", + ":green_circle: Without a doubt.", + ":green_circle: Yes definitely.", + ":green_circle: You may rely on it.", + ":green_circle: As I see it, yes.", + ":green_circle: Most likely.", + ":green_circle: Outlook good.", + ":green_circle: Yes.", + ":green_circle: Signs Point to Yes.", + ":yellow_circle: Reply hazy, try again.", + ":yellow_circle: Ask again later.", + ":yellow_circle: Better not tell you now.", + ":yellow_circle: Cannot predict now.", + ":yellow_circle: Concentrate and ask again.", + ":red_circle: Don't count on it.", + ":red_circle: My reply is no.", + ":red_circle: My sources say no.", + ":red_circle: Outlook not so good.", + ":red_circle: Very doubtful.", + }; + /* random index to phrases array */ + int answer = rand() % (sizeof(phrases) / sizeof(*phrases)); + + struct discord_embed embeds[] = { { + .title = ":8ball: 8-Ball", + .description = phrases[answer], + } }; + + struct discord_create_message params = { + .embeds = + &(struct discord_embeds){ + .size = sizeof(embeds) / sizeof *embeds, + .array = embeds, + }, + }; + discord_create_message(client, event->channel_id, ¶ms, NULL); +} - ccord_global_init(); - struct discord *client = discord_config_init(config_file); +int +main(int argc, char *argv[]) +{ + const char *config_file; + if (argc > 1) + config_file = argv[1]; + else + config_file = "../config.json"; - discord_set_on_ready(client, &on_ready); + srand(time(0)); - discord_set_on_command(client, "8ball", &eight_ball); + ccord_global_init(); + struct discord *client = discord_config_init(config_file); + assert(NULL != client && "Couldn't initialize client"); - printf("\n\nThis is a bot to demonstrate an easy to make 8ball response to a question.\n" - "1. type '8ball (question)' in chat\n" - "\nTYPE ANY KEY TO START BOT\n"); + discord_set_on_ready(client, &on_ready); - fgetc(stdin); // wait for input + discord_set_on_command(client, "8ball", &eight_ball); - discord_run(client); + print_usage(); + fgetc(stdin); // wait for input - discord_cleanup(client); - ccord_global_cleanup(); + discord_run(client); + discord_cleanup(client); + ccord_global_cleanup(); } diff --git a/examples/Makefile b/examples/Makefile index bf094328f..ae2f5ed8b 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -2,54 +2,52 @@ TOP = .. CC ?= gcc -COGUTILS_DIR := $(TOP)/cog-utils -CORE_DIR := $(TOP)/core -INCLUDE_DIR := $(TOP)/include -GENCODECS_DIR := $(TOP)/gencodecs - -BOTS := audit-log \ - ban \ - channel \ - components \ - copycat \ - embed \ - emoji \ - fetch-messages \ - guild-template \ - guild \ - invite \ - manual-dm \ - pin \ - ping-pong \ - presence \ - reaction \ - shell \ - slash-commands \ - slash-commands2 \ - spam \ - webhook \ - timers \ - $(XSRC) +COGUTILS_DIR = $(TOP)/cog-utils +CORE_DIR = $(TOP)/core +INCLUDE_DIR = $(TOP)/include +GENCODECS_DIR = $(TOP)/gencodecs + +VOICE_BOTS = voice-join +BOTS = 8ball \ + audit-log \ + ban \ + channel \ + components \ + copycat \ + embed \ + emoji \ + fetch-messages \ + guild-template \ + guild \ + invite \ + manual-dm \ + pin \ + ping-pong \ + presence \ + reaction \ + shell \ + slash-commands \ + slash-commands2 \ + spam \ + webhook \ + timers CFLAGS = -I$(INCLUDE_DIR) -I$(COGUTILS_DIR) -I$(CORE_DIR) \ -I$(CORE_DIR)/third-party -I$(GENCODECS_DIR) \ - -O0 -g -pthread -Wall $(XFLAGS) -LDFLAGS = -ldiscord -L$(TOP)/lib -lcurl + -O0 -g -pthread -Wall +LDFLAGS = -L$(TOP)/lib +LDLIBS = -ldiscord -lcurl all: $(BOTS) voice: - $(MAKE) XFLAGS=-DCCORD_VOICE XSRC=voice all - -.SUFFIXES: -.DEFAULT: - $(CC) $(CFLAGS) -o $@ $@.c $(LDFLAGS) + @ $(MAKE) CFLAGS="$(CFLAGS) -DCCORD_VOICE" BOTS="$(BOTS) $(VOICE_BOTS)" all echo: @ echo -e 'CC: $(CC)\n' @ echo -e 'BOTS: $(BOTS)\n' clean: - @ $(RM) $(BOTS) voice + @ $(RM) $(BOTS) $(VOICE_BOTS) .PHONY: all echo clean diff --git a/examples/audit-log.c b/examples/audit-log.c index 05ebeaf2d..1a8dac7f6 100644 --- a/examples/audit-log.c +++ b/examples/audit-log.c @@ -21,97 +21,89 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Audit-Log-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void log_on_guild_member_add(struct discord *client, - u64snowflake guild_id, - const struct discord_guild_member *member) + const struct discord_guild_member *event) { - log_info("%s#%s joined guild %" PRIu64, member->user->username, - member->user->discriminator, guild_id); + log_info("%s#%s joined guild %" PRIu64, event->user->username, + event->user->discriminator, event->guild_id); } void log_on_guild_member_update(struct discord *client, - u64snowflake guild_id, - const struct discord_guild_member *member) + const struct discord_guild_member_update *event) { char nick[128] = ""; - if (member->nick && *member->nick) - snprintf(nick, sizeof(nick), " (%s)", member->nick); + if (event->nick && *event->nick) + snprintf(nick, sizeof(nick), " (%s)", event->nick); - log_info("%s#%s%s updated (guild %" PRIu64 ")", member->user->username, - member->user->discriminator, nick, guild_id); + log_info("%s#%s%s updated (guild %" PRIu64 ")", event->user->username, + event->user->discriminator, nick, event->guild_id); } void log_on_guild_member_remove(struct discord *client, - u64snowflake guild_id, - const struct discord_user *user) + const struct discord_guild_member_remove *event) { - log_info("%s#%s left guild %" PRIu64, user->username, user->discriminator, - guild_id); + log_info("%s#%s left guild %" PRIu64, event->user->username, + event->user->discriminator, event->guild_id); } void done(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_audit_log *audit_log) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; if (!audit_log->audit_log_entries || !audit_log->audit_log_entries->size) { log_warn("No audit log entries found!"); return; } - struct discord_audit_log_entry *entry = &audit_log->audit_log_entries->array[0]; + struct discord_audit_log_entry *entry = + &audit_log->audit_log_entries->array[0]; char text[1028]; snprintf(text, sizeof(text), "<@!%" PRIu64 "> has created <#%" PRIu64 ">!", entry->user_id, entry->target_id); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail(struct discord *client, CCORDcode code, void *data) +fail(struct discord *client, struct discord_response *resp) { - (void)data; + (void)resp; log_error("Couldn't retrieve audit log: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); } void on_audit_channel_create(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_audit_log ret = { .done = &done, .fail = &fail, - .data = channel_id, - .cleanup = &free, + .keep = event, }; struct discord_get_guild_audit_log params = { - .user_id = msg->author->id, + .user_id = event->author->id, .action_type = DISCORD_AUDIT_LOG_CHANNEL_CREATE, }; - discord_get_guild_audit_log(client, msg->guild_id, ¶ms, &ret); + discord_get_guild_audit_log(client, event->guild_id, ¶ms, &ret); } int diff --git a/examples/ban.c b/examples/ban.c index ab4df40e3..6494c8f7e 100644 --- a/examples/ban.c +++ b/examples/ban.c @@ -16,52 +16,49 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Ban-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void log_on_guild_ban_add(struct discord *client, - u64snowflake guild_id, - const struct discord_user *user) + const struct discord_guild_ban_add *event) { - log_info("User `%s#%s` has been banned.", user->username, - user->discriminator); + log_info("User `%s#%s` has been banned.", event->user->username, + event->user->discriminator); } void log_on_guild_ban_remove(struct discord *client, - u64snowflake guild_id, - const struct discord_user *user) + const struct discord_guild_ban_remove *event) { - log_info("User `%s#%s` has been unbanned.", user->username, - user->discriminator); + log_info("User `%s#%s` has been unbanned.", event->user->username, + event->user->discriminator); } void -on_ban(struct discord *client, const struct discord_message *msg) +on_ban(struct discord *client, const struct discord_message *event) { u64snowflake target_id = 0ULL; - sscanf(msg->content, "%" SCNu64, &target_id); + sscanf(event->content, "%" SCNu64, &target_id); struct discord_create_guild_ban params = { .delete_message_days = 1, .reason = "Someone really dislikes you!", }; - discord_create_guild_ban(client, msg->guild_id, target_id, ¶ms, NULL); + discord_create_guild_ban(client, event->guild_id, target_id, ¶ms, + NULL); } void -on_unban(struct discord *client, const struct discord_message *msg) +on_unban(struct discord *client, const struct discord_message *event) { u64snowflake target_id = 0ULL; - sscanf(msg->content, "%" SCNu64, &target_id); + sscanf(event->content, "%" SCNu64, &target_id); - discord_remove_guild_ban(client, msg->guild_id, target_id, NULL); + discord_remove_guild_ban(client, event->guild_id, target_id, NULL); } int diff --git a/examples/channel.c b/examples/channel.c index 614910f73..20bcde79b 100644 --- a/examples/channel.c +++ b/examples/channel.c @@ -24,87 +24,85 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Channel-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void log_on_channel_create(struct discord *client, - const struct discord_channel *channel) + const struct discord_channel *event) { - log_info("Channel %s (%" PRIu64 ") created", channel->name, channel->id); + log_info("Channel %s (%" PRIu64 ") created", event->name, event->id); } void log_on_channel_update(struct discord *client, - const struct discord_channel *channel) + const struct discord_channel *event) { - log_info("Channel %s (%" PRIu64 ") updated", channel->name, channel->id); + log_info("Channel %s (%" PRIu64 ") updated", event->name, event->id); } void log_on_channel_delete(struct discord *client, - const struct discord_channel *channel) + const struct discord_channel *event) { - log_info("Channel %s (%" PRIu64 ") deleted", channel->name, channel->id); + log_info("Channel %s (%" PRIu64 ") deleted", event->name, event->id); } void log_on_thread_create(struct discord *client, - const struct discord_channel *thread) + const struct discord_channel *event) { - log_info("Thread %s (%" PRIu64 ") created", thread->name, thread->id); + log_info("Thread %s (%" PRIu64 ") created", event->name, event->id); } void log_on_thread_update(struct discord *client, - const struct discord_channel *thread) + const struct discord_channel *event) { - log_info("Thread %s (%" PRIu64 ") updated", thread->name, thread->id); + log_info("Thread %s (%" PRIu64 ") updated", event->name, event->id); } void log_on_thread_delete(struct discord *client, - const struct discord_channel *thread) + const struct discord_channel *event) { - log_info("Thread %s (%" PRIu64 ") deleted", thread->name, thread->id); + log_info("Thread %s (%" PRIu64 ") deleted", event->name, event->id); } void -on_channel_create(struct discord *client, const struct discord_message *msg) +on_channel_create(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; - struct discord_create_guild_channel params = { .name = msg->content }; - discord_create_guild_channel(client, msg->guild_id, ¶ms, NULL); + struct discord_create_guild_channel params = { .name = event->content }; + discord_create_guild_channel(client, event->guild_id, ¶ms, NULL); } void on_channel_rename_this(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; - struct discord_modify_channel params = { .name = msg->content }; - discord_modify_channel(client, msg->channel_id, ¶ms, NULL); + struct discord_modify_channel params = { .name = event->content }; + discord_modify_channel(client, event->channel_id, ¶ms, NULL); } void on_channel_delete_this(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; - discord_delete_channel(client, msg->channel_id, NULL); + discord_delete_channel(client, event->channel_id, NULL); } void done_get_channel_invites(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_invites *invites) { if (!invites->size) { @@ -112,133 +110,120 @@ done_get_channel_invites(struct discord *client, return; } - u64snowflake *channel_id = data; - + const struct discord_message *event = resp->keep; char text[DISCORD_MAX_MESSAGE_LEN]; snprintf(text, sizeof(text), "%d invite links created.", invites->size); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_get_channel_invites(struct discord *client, CCORDcode code, void *data) +fail_get_channel_invites(struct discord *client, struct discord_response *resp) { - (void)data; - - log_info("Couldn't fetch invites: %s", discord_strerror(code, client)); + log_info("Couldn't fetch invites: %s", + discord_strerror(resp->code, client)); } void on_channel_get_invites(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_invites ret = { .done = &done_get_channel_invites, .fail = &fail_get_channel_invites, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_get_channel_invites(client, msg->channel_id, &ret); + discord_get_channel_invites(client, event->channel_id, &ret); } void done_create_channel_invite(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_invite *invite) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[256]; snprintf(text, sizeof(text), "https://discord.gg/%s", invite->code); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_create_channel_invite(struct discord *client, CCORDcode code, void *data) +fail_create_channel_invite(struct discord *client, + struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; struct discord_create_message params = { .content = "Couldn't create invite", }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void on_channel_create_invite(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_invite ret = { .done = &done_create_channel_invite, .fail = &fail_create_channel_invite, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_create_channel_invite(client, msg->channel_id, NULL, &ret); + discord_create_channel_invite(client, event->channel_id, NULL, &ret); } void done_start_thread(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_channel *thread) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[1024]; - snprintf(text, sizeof(text), "Created thread <#%" PRIu64 ">", *channel_id); + snprintf(text, sizeof(text), "Created thread <#%" PRIu64 ">", thread->id); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_start_thread(struct discord *client, CCORDcode code, void *data) +fail_start_thread(struct discord *client, struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[1024]; snprintf(text, sizeof(text), "Couldn't create thread: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void on_channel_start_thread(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_channel ret = { .done = &done_start_thread, .fail = &fail_start_thread, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - if (msg->message_reference) { + if (event->message_reference) { struct discord_start_thread_with_message params = { .name = "new_thread", }; - discord_start_thread_with_message(client, msg->channel_id, - msg->message_reference->message_id, + discord_start_thread_with_message(client, event->channel_id, + event->message_reference->message_id, ¶ms, &ret); } else { @@ -246,8 +231,8 @@ on_channel_start_thread(struct discord *client, .name = "new_thread", .type = DISCORD_CHANNEL_GUILD_PUBLIC_THREAD, }; - discord_start_thread_without_message(client, msg->channel_id, ¶ms, - &ret); + discord_start_thread_without_message(client, event->channel_id, + ¶ms, &ret); } } diff --git a/examples/components.c b/examples/components.c index 5d094ddae..86f19e41e 100644 --- a/examples/components.c +++ b/examples/components.c @@ -67,18 +67,16 @@ char JSON[] = "]\n"; void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Components-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void -on_dynamic(struct discord *client, const struct discord_message *msg) +on_dynamic(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_components components = { 0 }; discord_components_from_json(JSON, sizeof(JSON), &components); @@ -88,16 +86,16 @@ on_dynamic(struct discord *client, const struct discord_message *msg) "you play?", .components = &components }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); /* must cleanup 'components' afterwards */ discord_components_cleanup(&components); } void -on_static(struct discord *client, const struct discord_message *msg) +on_static(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_select_option select_options[] = { { @@ -166,19 +164,19 @@ on_static(struct discord *client, const struct discord_message *msg) }, }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void on_interaction_create(struct discord *client, - const struct discord_interaction *interaction) + const struct discord_interaction *event) { - log_info("Interaction %" PRIu64 " received", interaction->id); + log_info("Interaction %" PRIu64 " received", event->id); - if (!interaction->data || !interaction->data->values) return; + if (!event->data || !event->data->values) return; char values[1024]; - strings_to_json(values, sizeof(values), interaction->data->values); + strings_to_json(values, sizeof(values), event->data->values); char text[DISCORD_MAX_MESSAGE_LEN]; snprintf(text, sizeof(text), @@ -196,8 +194,8 @@ on_interaction_create(struct discord *client, .flags = DISCORD_MESSAGE_EPHEMERAL // 1 << 6 } }; - discord_create_interaction_response(client, interaction->id, - interaction->token, ¶ms, NULL); + discord_create_interaction_response(client, event->id, event->token, + ¶ms, NULL); } int diff --git a/examples/copycat.c b/examples/copycat.c index 1f9bfc3ed..e1910e938 100644 --- a/examples/copycat.c +++ b/examples/copycat.c @@ -19,81 +19,70 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Copycat-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void on_reaction_add(struct discord *client, - u64snowflake user_id, - u64snowflake channel_id, - u64snowflake message_id, - u64snowflake guild_id, - const struct discord_guild_member *member, - const struct discord_emoji *emoji) + const struct discord_message_reaction_add *event) { - if (member->user->bot) return; + if (event->member->user->bot) return; - discord_create_reaction(client, channel_id, message_id, emoji->id, - emoji->name, NULL); + discord_create_reaction(client, event->channel_id, event->message_id, + event->emoji->id, event->emoji->name, NULL); } void -on_message_create(struct discord *client, const struct discord_message *msg) +on_message_create(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_create_message params = { - .content = msg->content, - .message_reference = !msg->referenced_message + .content = event->content, + .message_reference = !event->referenced_message ? NULL : &(struct discord_message_reference){ - .message_id = msg->referenced_message->id, - .channel_id = msg->channel_id, - .guild_id = msg->guild_id, + .message_id = event->referenced_message->id, + .channel_id = event->channel_id, + .guild_id = event->guild_id, }, }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_message_update(struct discord *client, const struct discord_message *msg) +on_message_update(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_create_message params = { .content = "I see what you did there." }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void on_message_delete(struct discord *client, - u64snowflake id, - u64snowflake channel_id, - u64snowflake guild_id) + const struct discord_message_delete *event) { struct discord_create_message params = { .content = "Did that message just disappear?" }; - discord_create_message(client, channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void on_message_delete_bulk(struct discord *client, - const struct snowflakes *ids, - u64snowflake channel_id, - u64snowflake guild_id) + const struct discord_message_delete_bulk *event) { char text[128]; - sprintf(text, "Where did those %d messages go?", ids->size); + sprintf(text, "Where did those %d messages go?", event->ids->size); struct discord_create_message params = { .content = text }; - discord_create_message(client, channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } int diff --git a/examples/embed.c b/examples/embed.c index 0997f9d15..fc2d71b04 100644 --- a/examples/embed.c +++ b/examples/embed.c @@ -59,18 +59,16 @@ char JSON[] = "{\n" "}"; void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Embed-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void -on_dynamic(struct discord *client, const struct discord_message *msg) +on_dynamic(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; /* load a embed from the json string */ struct discord_embed embed = { 0 }; @@ -85,16 +83,16 @@ on_dynamic(struct discord *client, const struct discord_message *msg) .array = &embed, }, }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); /* must cleanup 'embed' afterwards */ discord_embed_cleanup(&embed); } void -on_static(struct discord *client, const struct discord_message *msg) +on_static(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_embed_field fields[] = { { @@ -146,13 +144,13 @@ on_static(struct discord *client, const struct discord_message *msg) .array = embeds, }, }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_builder(struct discord *client, const struct discord_message *msg) +on_builder(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_embed embed = { .color = 0x3498DB, @@ -184,7 +182,7 @@ on_builder(struct discord *client, const struct discord_message *msg) .array = &embed, }, }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); /* must cleanup 'embed' afterwards */ discord_embed_cleanup(&embed); diff --git a/examples/emoji.c b/examples/emoji.c index afbd3f8bc..b03970e8a 100644 --- a/examples/emoji.c +++ b/examples/emoji.c @@ -17,20 +17,18 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Emoji-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void done_list_guild_emojis(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_emojis *emojis) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[2000] = ""; if (!emojis->size) { @@ -58,94 +56,87 @@ done_list_guild_emojis(struct discord *client, --i; struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); continue; } } struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_list_guild_emojis(struct discord *client, CCORDcode code, void *data) +fail_list_guild_emojis(struct discord *client, struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[256]; snprintf(text, sizeof(text), "Couldn't fetch guild emojis: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_list_guild_emojis(struct discord *client, const struct discord_message *msg) +on_list_guild_emojis(struct discord *client, + const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_emojis ret = { .done = &done_list_guild_emojis, .fail = &fail_list_guild_emojis, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_list_guild_emojis(client, msg->guild_id, &ret); + discord_list_guild_emojis(client, event->guild_id, &ret); } void done_get_guild_emoji(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_emoji *emoji) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[DISCORD_MAX_MESSAGE_LEN]; snprintf(text, sizeof(text), "Here you go: <%s:%s:%" PRIu64 ">", emoji->animated ? "a" : "", emoji->name, emoji->id); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_get_guild_emoji(struct discord *client, CCORDcode code, void *data) +fail_get_guild_emoji(struct discord *client, struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[256]; snprintf(text, sizeof(text), "Unknown emoji: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_get_guild_emoji(struct discord *client, const struct discord_message *msg) +on_get_guild_emoji(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake emoji_id = 0ULL; - sscanf(msg->content, "%" SCNu64, &emoji_id); + sscanf(event->content, "%" SCNu64, &emoji_id); if (!emoji_id) return; - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; - struct discord_ret_emoji ret = { .done = &done_get_guild_emoji, .fail = &fail_get_guild_emoji, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_get_guild_emoji(client, msg->guild_id, emoji_id, &ret); + discord_get_guild_emoji(client, event->guild_id, emoji_id, &ret); } int diff --git a/examples/guild-template.c b/examples/guild-template.c index c84debb9d..de7dd74a8 100644 --- a/examples/guild-template.c +++ b/examples/guild-template.c @@ -19,20 +19,18 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Guild-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void done(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_guild_template *template) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[DISCORD_MAX_MESSAGE_LEN]; snprintf(text, sizeof(text), @@ -41,50 +39,42 @@ done(struct discord *client, template->name, template->description, template->creator_id); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail(struct discord *client, CCORDcode code, void *data) +fail(struct discord *client, struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[DISCORD_MAX_MESSAGE_LEN]; snprintf(text, sizeof(text), "Couldn't perform operation: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void on_get_guild_template(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; - struct discord_ret_guild_template ret = { .done = &done, .fail = &fail, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_get_guild_template(client, msg->content, &ret); + discord_get_guild_template(client, event->content, &ret); } void on_create_guild_template(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; - struct discord_ret_guild_template ret = { .done = &done, .fail = &fail, - .data = channel_id, - .cleanup = &free, + .keep = event, }; struct discord_create_guild_template params = { @@ -92,24 +82,20 @@ on_create_guild_template(struct discord *client, .description = "This is a new server template created with Concord!" }; - discord_create_guild_template(client, msg->guild_id, ¶ms, &ret); + discord_create_guild_template(client, event->guild_id, ¶ms, &ret); } void on_sync_guild_template(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; - struct discord_ret_guild_template ret = { .done = &done, .fail = &fail, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_sync_guild_template(client, msg->guild_id, msg->content, &ret); + discord_sync_guild_template(client, event->guild_id, event->content, &ret); } int diff --git a/examples/guild.c b/examples/guild.c index afa08c9c6..cfac0d744 100644 --- a/examples/guild.c +++ b/examples/guild.c @@ -25,111 +25,106 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Guild-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void log_on_role_create(struct discord *client, - u64snowflake guild_id, - const struct discord_role *role) + const struct discord_guild_role_create *event) { - log_warn("Role (%" PRIu64 ") created", role->id); + log_warn("Role (%" PRIu64 ") created", event->role->id); } void log_on_role_update(struct discord *client, - u64snowflake guild_id, - const struct discord_role *role) + const struct discord_guild_role_update *event) { - log_warn("Role (%" PRIu64 ") updated", role->id); + log_warn("Role (%" PRIu64 ") updated", event->role->id); } void log_on_role_delete(struct discord *client, - u64snowflake guild_id, - u64snowflake role_id) + const struct discord_guild_role_delete *event) { - log_warn("Role (%" PRIu64 ") deleted", role_id); + log_warn("Role (%" PRIu64 ") deleted", event->role_id); } void -on_role_create(struct discord *client, const struct discord_message *msg) +on_role_create(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; char name[128] = ""; - sscanf(msg->content, "%s", name); + sscanf(event->content, "%s", name); if (!*name) { log_error("Couldn't create role `%s`", name); return; } struct discord_create_guild_role params = { .name = name }; - discord_create_guild_role(client, msg->guild_id, ¶ms, NULL); + discord_create_guild_role(client, event->guild_id, ¶ms, NULL); } void -on_role_delete(struct discord *client, const struct discord_message *msg) +on_role_delete(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake role_id = 0; - sscanf(msg->content, "%" SCNu64, &role_id); + sscanf(event->content, "%" SCNu64, &role_id); if (!role_id) { log_error("Invalid format for `guild.role_delete `"); return; } - discord_delete_guild_role(client, msg->guild_id, role_id, NULL); + discord_delete_guild_role(client, event->guild_id, role_id, NULL); } void -on_role_member_add(struct discord *client, const struct discord_message *msg) +on_role_member_add(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake user_id = 0, role_id = 0; - sscanf(msg->content, "%" SCNu64 " %" SCNu64, &user_id, &role_id); + sscanf(event->content, "%" SCNu64 " %" SCNu64, &user_id, &role_id); if (!user_id || !role_id) { log_error( "Invalid format for `guild.role_member_add `"); return; } - discord_add_guild_member_role(client, msg->guild_id, user_id, role_id, + discord_add_guild_member_role(client, event->guild_id, user_id, role_id, NULL); } void on_role_member_remove(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake user_id = 0, role_id = 0; - sscanf(msg->content, "%" SCNu64 " %" SCNu64, &user_id, &role_id); + sscanf(event->content, "%" SCNu64 " %" SCNu64, &user_id, &role_id); if (!user_id || !role_id) { log_error("Invalid format for `guild.role_member_remove " "`"); return; } - discord_remove_guild_member_role(client, msg->guild_id, user_id, role_id, + discord_remove_guild_member_role(client, event->guild_id, user_id, role_id, NULL); } void done_get_guild_roles(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_roles *roles) { char text[DISCORD_MAX_MESSAGE_LEN]; @@ -157,48 +152,49 @@ done_get_guild_roles(struct discord *client, } void -fail_get_guild_roles(struct discord *client, CCORDcode code, void *data) +fail_get_guild_roles(struct discord *client, struct discord_response *resp) { log_error("Couldn't fetch guild roles: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); } void -on_role_list(struct discord *client, const struct discord_message *msg) +on_role_list(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_ret_roles ret = { .done = &done_get_guild_roles, .fail = &fail_get_guild_roles, }; - discord_get_guild_roles(client, msg->guild_id, &ret); + discord_get_guild_roles(client, event->guild_id, &ret); } void done_get_guild_member(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_guild_member *member) { + (void)resp; log_info("Member %s (%" PRIu64 ") found!", member->user->username, member->user->id); } void -fail_get_guild_member(struct discord *client, CCORDcode code, void *data) +fail_get_guild_member(struct discord *client, struct discord_response *resp) { log_error("Couldn't fetch guild member: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); } void -on_member_get(struct discord *client, const struct discord_message *msg) +on_member_get(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake user_id = 0; - sscanf(msg->content, "%" SCNu64, &user_id); + sscanf(event->content, "%" SCNu64, &user_id); if (!user_id) { log_error("Invalid format for `guild.member_get `"); return; @@ -208,15 +204,15 @@ on_member_get(struct discord *client, const struct discord_message *msg) .done = &done_get_guild_member, .fail = &fail_get_guild_member, }; - discord_get_guild_member(client, msg->guild_id, user_id, &ret); + discord_get_guild_member(client, event->guild_id, user_id, &ret); } void done_get_guild_channels(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_channels *channels) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[DISCORD_MAX_MESSAGE_LEN]; char *cur = text; @@ -239,37 +235,33 @@ done_get_guild_channels(struct discord *client, } struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_get_guild_channels(struct discord *client, CCORDcode code, void *data) +fail_get_guild_channels(struct discord *client, struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[256]; snprintf(text, sizeof(text), "Couldn't fetch guild channels: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_channels_get(struct discord *client, const struct discord_message *msg) +on_channels_get(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_channels ret = { .done = &done_get_guild_channels, .fail = &fail_get_guild_channels, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_get_guild_channels(client, msg->guild_id, &ret); + discord_get_guild_channels(client, event->guild_id, &ret); } int diff --git a/examples/invite.c b/examples/invite.c index ee83010e6..683e8a88d 100644 --- a/examples/invite.c +++ b/examples/invite.c @@ -18,75 +18,67 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Invite-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void -done(struct discord *client, void *data, const struct discord_invite *invite) +done(struct discord *client, + struct discord_response *resp, + const struct discord_invite *invite) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[256]; snprintf(text, sizeof(text), "Success: https://discord.gg/%s", invite->code); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail(struct discord *client, CCORDcode code, void *data) +fail(struct discord *client, struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; struct discord_create_message params = { .content = "Couldn't perform operation." }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_invite_get(struct discord *client, const struct discord_message *msg) +on_invite_get(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_invite ret = { .done = &done, .fail = &fail, - .data = channel_id, - .cleanup = &free, + .keep = event, }; struct discord_get_invite params = { .with_counts = true, .with_expiration = true, }; - discord_get_invite(client, msg->content, ¶ms, &ret); + discord_get_invite(client, event->content, ¶ms, &ret); } void -on_invite_delete(struct discord *client, const struct discord_message *msg) +on_invite_delete(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_invite ret = { .done = &done, .fail = &fail, - .data = channel_id, - .cleanup = &free, + .keep = event, }; - discord_delete_invite(client, msg->content, &ret); + discord_delete_invite(client, event->content, &ret); } int diff --git a/examples/manual-dm.c b/examples/manual-dm.c index d94a71071..8414145cb 100644 --- a/examples/manual-dm.c +++ b/examples/manual-dm.c @@ -23,20 +23,18 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("ManualDM-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void -on_dm_receive(struct discord *client, const struct discord_message *msg) +on_dm_receive(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; - printf("%s:%s\n", msg->author->username, msg->content); + printf("%s:%s\n", event->author->username, event->content); } void * diff --git a/examples/pin.c b/examples/pin.c index d770d368d..ae3dbd0d8 100644 --- a/examples/pin.c +++ b/examples/pin.c @@ -20,61 +20,54 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Pin-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void -on_pin(struct discord *client, const struct discord_message *msg) +on_pin(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake msg_id = 0; - sscanf(msg->content, "%" SCNu64, &msg_id); + sscanf(event->content, "%" SCNu64, &msg_id); if (!msg_id) { - if (!msg->referenced_message) return; + if (!event->referenced_message) return; - msg_id = msg->referenced_message->id; + msg_id = event->referenced_message->id; } - discord_pin_message(client, msg->channel_id, msg_id, NULL); + discord_pin_message(client, event->channel_id, msg_id, NULL); } void -on_unpin(struct discord *client, const struct discord_message *msg) +on_unpin(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake msg_id = 0; - sscanf(msg->content, "%" SCNu64, &msg_id); + sscanf(event->content, "%" SCNu64, &msg_id); if (!msg_id) { - if (!msg->referenced_message) return; + if (!event->referenced_message) return; - msg_id = msg->referenced_message->id; + msg_id = event->referenced_message->id; } - discord_unpin_message(client, msg->channel_id, msg_id, NULL); + discord_unpin_message(client, event->channel_id, msg_id, NULL); } -struct context { - u64snowflake channel_id; - u64snowflake guild_id; -}; - void done_get_pins(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_messages *msgs) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; char text[2000] = "No pins on channel"; char *cur = text; @@ -84,46 +77,40 @@ done_get_pins(struct discord *client, cur += snprintf(cur, end - cur, "https://discord.com/channels/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "\n", - cxt->guild_id, cxt->channel_id, msgs->array[i].id); + event->guild_id, event->channel_id, msgs->array[i].id); if (cur >= end) break; } struct discord_create_message params = { .content = text }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_get_pins(struct discord *client, CCORDcode code, void *data) +fail_get_pins(struct discord *client, struct discord_response *resp) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; char text[2000] = ""; snprintf(text, sizeof(text), "Failed fetching pinned messages at <#%" PRIu64 ">", - cxt->channel_id); + event->channel_id); struct discord_create_message params = { .content = text }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_get_pins(struct discord *client, const struct discord_message *msg) +on_get_pins(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; - - struct context *cxt = malloc(sizeof(struct context)); - cxt->channel_id = msg->channel_id; - cxt->guild_id = msg->guild_id; + if (event->author->bot) return; struct discord_ret_messages ret = { .done = &done_get_pins, .fail = &fail_get_pins, - .data = cxt, - .cleanup = &free, + .keep = event, }; - - discord_get_pinned_messages(client, msg->channel_id, &ret); + discord_get_pinned_messages(client, event->channel_id, &ret); } int diff --git a/examples/ping-pong.c b/examples/ping-pong.c index 2d4a80ca9..0eb2618ff 100644 --- a/examples/ping-pong.c +++ b/examples/ping-pong.c @@ -13,30 +13,28 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("PingPong-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void -on_ping(struct discord *client, const struct discord_message *msg) +on_ping(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_create_message params = { .content = "pong" }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_pong(struct discord *client, const struct discord_message *msg) +on_pong(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_create_message params = { .content = "ping" }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } int diff --git a/examples/presence.c b/examples/presence.c index a79e64165..bf1d10a63 100644 --- a/examples/presence.c +++ b/examples/presence.c @@ -16,12 +16,10 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Presence-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); struct discord_activity activities[] = { { @@ -42,7 +40,7 @@ on_ready(struct discord *client) .since = discord_timestamp(client), }; - discord_set_presence(client, &status); + discord_update_presence(client, &status); } int diff --git a/examples/reaction.c b/examples/reaction.c index 08f19d93a..b8b425087 100644 --- a/examples/reaction.c +++ b/examples/reaction.c @@ -28,20 +28,18 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Reaction-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void done_get_users(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_users *users) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[2000]; if (!users->size) { @@ -60,93 +58,90 @@ done_get_users(struct discord *client, } struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_get_users(struct discord *client, CCORDcode code, void *data) +fail_get_users(struct discord *client, struct discord_response *resp) { - u64snowflake *channel_id = data; + const struct discord_message *event = resp->keep; char text[256]; snprintf(text, sizeof(text), "Couldn't fetch reactions: %s", - discord_strerror(code, client)); + discord_strerror(resp->code, client)); struct discord_create_message params = { .content = text }; - discord_create_message(client, *channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_get_users(struct discord *client, const struct discord_message *msg) +on_get_users(struct discord *client, const struct discord_message *event) { - if (msg->author->bot || !msg->referenced_message) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot || !event->referenced_message) return; struct discord_ret_users ret = { .done = &done_get_users, .fail = &fail_get_users, - .data = channel_id, - .cleanup = &free, + .keep = event, }; struct discord_get_reactions params = { .limit = 25 }; - discord_get_reactions(client, msg->channel_id, msg->referenced_message->id, - 0, msg->content, ¶ms, &ret); + discord_get_reactions(client, event->channel_id, + event->referenced_message->id, 0, event->content, + ¶ms, &ret); } void -on_create(struct discord *client, const struct discord_message *msg) +on_create(struct discord *client, const struct discord_message *event) { - if (msg->author->bot || !msg->referenced_message) return; + if (event->author->bot || !event->referenced_message) return; - discord_create_reaction(client, msg->referenced_message->channel_id, - msg->referenced_message->id, 0, msg->content, + discord_create_reaction(client, event->referenced_message->channel_id, + event->referenced_message->id, 0, event->content, NULL); } void -on_delete(struct discord *client, const struct discord_message *msg) +on_delete(struct discord *client, const struct discord_message *event) { - if (msg->author->bot || !msg->referenced_message) return; + if (event->author->bot || !event->referenced_message) return; discord_delete_all_reactions_for_emoji( - client, msg->referenced_message->channel_id, - msg->referenced_message->id, 0, msg->content, NULL); + client, event->referenced_message->channel_id, + event->referenced_message->id, 0, event->content, NULL); } void -on_delete_all(struct discord *client, const struct discord_message *msg) +on_delete_all(struct discord *client, const struct discord_message *event) { - if (msg->author->bot || !msg->referenced_message) return; + if (event->author->bot || !event->referenced_message) return; - discord_delete_all_reactions(client, msg->referenced_message->channel_id, - msg->referenced_message->id, NULL); + discord_delete_all_reactions(client, event->referenced_message->channel_id, + event->referenced_message->id, NULL); } void -on_delete_self(struct discord *client, const struct discord_message *msg) +on_delete_self(struct discord *client, const struct discord_message *event) { - if (msg->author->bot || !msg->referenced_message) return; + if (event->author->bot || !event->referenced_message) return; - discord_delete_own_reaction(client, msg->referenced_message->channel_id, - msg->referenced_message->id, 0, msg->content, - NULL); + discord_delete_own_reaction(client, event->referenced_message->channel_id, + event->referenced_message->id, 0, + event->content, NULL); } void -on_delete_user(struct discord *client, const struct discord_message *msg) +on_delete_user(struct discord *client, const struct discord_message *event) { - if (msg->author->bot || !msg->referenced_message) return; + if (event->author->bot || !event->referenced_message) return; u64snowflake user_id = 0; char emoji_name[256] = ""; - sscanf(msg->content, "%" SCNu64 " %s", &user_id, emoji_name); + sscanf(event->content, "%" SCNu64 " %s", &user_id, emoji_name); - discord_delete_user_reaction(client, msg->referenced_message->channel_id, - msg->referenced_message->id, user_id, 0, + discord_delete_user_reaction(client, event->referenced_message->channel_id, + event->referenced_message->id, user_id, 0, emoji_name, NULL); } diff --git a/examples/shell.c b/examples/shell.c index 9bddcfed9..590a536e3 100644 --- a/examples/shell.c +++ b/examples/shell.c @@ -22,46 +22,43 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Shell-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void -on_cd(struct discord *client, const struct discord_message *msg) +on_cd(struct discord *client, const struct discord_message *event) { - if (msg->author->id != g_sudo_id) return; + if (event->author->id != g_sudo_id) return; char path[PATH_MAX]; - chdir(*msg->content ? msg->content : "."); + chdir(*event->content ? event->content : "."); struct discord_create_message params = { .content = getcwd(path, sizeof(path)), }; - - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_less_like(struct discord *client, const struct discord_message *msg) +on_less_like(struct discord *client, const struct discord_message *event) { - if (msg->author->id != g_sudo_id) return; + if (event->author->id != g_sudo_id) return; - if (!msg->content || !*msg->content) { + if (!event->content || !*event->content) { struct discord_create_message params = { .content = "No file specified" }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } else { - struct discord_embed embed = { .title = msg->content }; - struct discord_attachment attachment = { .filename = msg->content }; + struct discord_embed embed = { .title = event->content }; + struct discord_attachment attachment = { .filename = event->content }; char text[512]; - snprintf(text, sizeof(text), "attachment://%s", msg->content); + snprintf(text, sizeof(text), "attachment://%s", event->content); struct discord_create_message params = { .content = text, @@ -77,20 +74,20 @@ on_less_like(struct discord *client, const struct discord_message *msg) }, }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } } void -on_fallback(struct discord *client, const struct discord_message *msg) +on_fallback(struct discord *client, const struct discord_message *event) { const size_t MAX_FSIZE = 5e6; // 5 mb const size_t MAX_CHARS = 2000; FILE *fp; - if (msg->author->id != g_sudo_id) return; + if (event->author->id != g_sudo_id) return; - if (NULL == (fp = popen(msg->content, "r"))) { + if (NULL == (fp = popen(event->content, "r"))) { perror("Failed to run command"); return; } @@ -105,7 +102,7 @@ on_fallback(struct discord *client, const struct discord_message *msg) if (fsize <= MAX_CHARS) { struct discord_create_message params = { .content = pathtmp }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } else { struct discord_attachment attachment = { @@ -120,7 +117,7 @@ on_fallback(struct discord *client, const struct discord_message *msg) .array = &attachment, } }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } pclose(fp); @@ -144,8 +141,10 @@ main(int argc, char *argv[]) discord_set_prefix(client, "$"); discord_set_on_command(client, NULL, &on_fallback); discord_set_on_command(client, "cd", &on_cd); - discord_set_on_commands(client, &on_less_like, "less", "cat", "hexdump", - NULL); + + char *cmds[] = { "less", "cat", "hexdump" }; + discord_set_on_commands(client, cmds, sizeof(cmds) / sizeof *cmds, + &on_less_like); print_usage(); do { diff --git a/examples/slash-commands.c b/examples/slash-commands.c index 923e82f2e..be7593dcf 100644 --- a/examples/slash-commands.c +++ b/examples/slash-commands.c @@ -21,26 +21,24 @@ print_usage(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Slash-Commands-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void log_on_app_create(struct discord *client, - const struct discord_application_command *cmd) + const struct discord_application_command *event) { - log_info("Application Command %s created", cmd->name); + log_info("Application Command %s created", event->name); } void on_slash_command_create(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; struct discord_application_command_option_choice gender_choices[] = { { @@ -94,7 +92,6 @@ on_slash_command_create(struct discord *client, }; struct discord_create_guild_application_command params = { - .type = DISCORD_APPLICATION_CHAT_INPUT, .name = "fill-form", .description = "A slash command example for form filling", .default_permission = true, @@ -106,27 +103,27 @@ on_slash_command_create(struct discord *client, }; /* Create slash command */ - discord_create_guild_application_command(client, g_app_id, msg->guild_id, + discord_create_guild_application_command(client, g_app_id, event->guild_id, ¶ms, NULL); } void on_interaction_create(struct discord *client, - const struct discord_interaction *interaction) + const struct discord_interaction *event) { /* We're only interested on slash commands */ - if (interaction->type != DISCORD_INTERACTION_APPLICATION_COMMAND) return; + if (event->type != DISCORD_INTERACTION_APPLICATION_COMMAND) return; /* Return in case user input is missing for some reason */ - if (!interaction->data || !interaction->data->options) return; + if (!event->data || !event->data->options) return; char *nick = "blank"; int pets = 0; char *gender = "blank"; u64snowflake channel_id = 0; - for (int i = 0; i < interaction->data->options->size; ++i) { - char *name = interaction->data->options->array[i].name; - char *value = interaction->data->options->array[i].value; + for (int i = 0; i < event->data->options->size; ++i) { + char *name = event->data->options->array[i].name; + char *value = event->data->options->array[i].value; if (0 == strcmp(name, "nick")) nick = value; @@ -145,15 +142,15 @@ on_interaction_create(struct discord *client, "Pets: %d\n" "Gender: %s\n" "Favorite channel: <#%" PRIu64 ">\n", - interaction->member->user->id, nick, pets, gender, channel_id); + event->member->user->id, nick, pets, gender, channel_id); struct discord_interaction_response params = { .type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE, .data = &(struct discord_interaction_callback_data){ .content = buf } }; - discord_create_interaction_response(client, interaction->id, - interaction->token, ¶ms, NULL); + discord_create_interaction_response(client, event->id, event->token, + ¶ms, NULL); } int diff --git a/examples/slash-commands2.c b/examples/slash-commands2.c index 3c96c21ae..2f798a0dc 100644 --- a/examples/slash-commands2.c +++ b/examples/slash-commands2.c @@ -35,46 +35,44 @@ print_help(void) } void -on_ready(struct discord *client) +on_ready(struct discord *client, const struct discord_ready *event) { - const struct discord_user *bot = discord_get_self(client); - log_info("Slash-Commands-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + event->user->username, event->user->discriminator); } void log_on_app_create(struct discord *client, - const struct discord_application_command *cmd) + const struct discord_application_command *event) { - log_info("Application Command %s created", cmd->name); + log_info("Application Command %s created", event->name); } void log_on_app_update(struct discord *client, - const struct discord_application_command *cmd) + const struct discord_application_command *event) { - log_info("Application Command %s updated", cmd->name); + log_info("Application Command %s updated", event->name); } void log_on_app_delete(struct discord *client, - const struct discord_application_command *cmd) + const struct discord_application_command *event) { - log_info("Application Command %s deleted", cmd->name); + log_info("Application Command %s deleted", event->name); } void -fail_interaction_create(struct discord *client, CCORDcode code, void *data) +fail_interaction_create(struct discord *client, struct discord_response *resp) { - log_error("%s", discord_strerror(code, client)); + log_error("%s", discord_strerror(resp->code, client)); } void on_interaction_create(struct discord *client, - const struct discord_interaction *interaction) + const struct discord_interaction *event) { - log_info("Interaction %" PRIu64 " received", interaction->id); + log_info("Interaction %" PRIu64 " received", event->id); struct discord_interaction_callback_data data = { .content = "Hello World!", @@ -88,8 +86,8 @@ on_interaction_create(struct discord *client, .fail = &fail_interaction_create }; - discord_create_interaction_response(client, interaction->id, - interaction->token, ¶ms, &ret); + discord_create_interaction_response(client, event->id, event->token, + ¶ms, &ret); } void * diff --git a/examples/spam.c b/examples/spam.c index aa601361c..b7228e95b 100644 --- a/examples/spam.c +++ b/examples/spam.c @@ -31,25 +31,25 @@ char *SPAM[] = { }; void -on_spam_async(struct discord *client, const struct discord_message *msg) +on_spam_async(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; for (size_t i = 0; i < 10; ++i) { struct discord_create_message params = { .content = SPAM[i] }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } } void -on_spam_sync(struct discord *client, const struct discord_message *msg) +on_spam_sync(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; for (size_t i = 0; i < 10; ++i) { struct discord_ret_message ret = { .sync = DISCORD_SYNC_FLAG }; struct discord_create_message params = { .content = SPAM[i] }; - discord_create_message(client, msg->channel_id, ¶ms, &ret); + discord_create_message(client, event->channel_id, ¶ms, &ret); } } diff --git a/examples/voice.c b/examples/voice-join.c similarity index 61% rename from examples/voice.c rename to examples/voice-join.c index 573ba64b2..884484544 100644 --- a/examples/voice.c +++ b/examples/voice-join.c @@ -6,82 +6,71 @@ #include "discord.h" -struct context { - u64snowflake channel_id; - u64snowflake guild_id; -}; - void print_usage(void) { printf( - "\n\nThis bot is a work in progress, it should demonstrate some " - "Voice related utilities\n" + "\n\nThis bot demonstrates some of the Discord Voice Connections " + "interface\n" "1. Type 'voice.list_regions' to list regions that can be used when " "creating servers\n" "2. Type 'voice.join ' to join a particular voice " "channel by its position\n" "3. Type 'voice.kick ' to kick a particular user from the " - "voice channel he's at\n" + "voice channel they are at\n" "\nTYPE ANY KEY TO START BOT\n"); } void -log_on_voice_state_update(struct discord *client, - const struct discord_voice_state *vs) +on_ready(struct discord *client, const struct discord_ready *event) { - log_info("User <@!%" PRIu64 "> has joined <#%" PRIu64 ">!", vs->user_id, - vs->channel_id); + log_info("Voice-Bot succesfully connected to Discord as %s#%s!", + event->user->username, event->user->discriminator); } void -on_ready(struct discord *client) +log_on_voice_state_update(struct discord *client, + const struct discord_voice_state *event) { - const struct discord_user *bot = discord_get_self(client); - - log_info("Voice-Bot succesfully connected to Discord as %s#%s!", - bot->username, bot->discriminator); + log_info("User <@!%" PRIu64 "> has joined <#%" PRIu64 ">!", event->user_id, + event->channel_id); } void done_list_voice_regions(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_voice_regions *regions) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; for (int i = 0; i < regions->size; ++i) { struct discord_create_message params = { .content = regions->array[i].name }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } } void -fail_list_voice_regions(struct discord *client, CCORDcode code, void *data) +fail_list_voice_regions(struct discord *client, struct discord_response *resp) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; struct discord_create_message params = { .content = "Could not fetch voice regions" }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void on_list_voice_regions(struct discord *client, - const struct discord_message *msg) + const struct discord_message *event) { - if (msg->author->bot) return; - - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - *channel_id = msg->channel_id; + if (event->author->bot) return; struct discord_ret_voice_regions ret = { .done = &done_list_voice_regions, .fail = &fail_list_voice_regions, - .data = channel_id, - .cleanup = &free, + .keep = event, }; discord_list_voice_regions(client, &ret); @@ -89,111 +78,102 @@ on_list_voice_regions(struct discord *client, void done_get_vchannel_position(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_channel *vchannel) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; char text[256]; - discord_voice_join(client, cxt->guild_id, vchannel->id, false, false); + discord_voice_join(client, event->guild_id, vchannel->id, false, false); snprintf(text, sizeof(text), "Joining <@!%" PRIu64 "> to <#%" PRIu64 ">!", discord_get_self(client)->id, vchannel->id); struct discord_create_message params = { .content = text }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -fail_get_vchannel_position(struct discord *client, CCORDcode code, void *data) +fail_get_vchannel_position(struct discord *client, + struct discord_response *resp) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; struct discord_create_message params = { .content = "Invalid channel position" }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_voice_join(struct discord *client, const struct discord_message *msg) +on_voice_join(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; int position = -1; - sscanf(msg->content, "%d", &position); - - struct context *cxt = malloc(sizeof(struct context)); - cxt->channel_id = msg->channel_id; - cxt->guild_id = msg->guild_id; + sscanf(event->content, "%d", &position); struct discord_ret_channel ret = { .done = &done_get_vchannel_position, .fail = &fail_get_vchannel_position, - .data = cxt, - .cleanup = &free, + .keep = event, }; - discord_get_channel_at_pos(client, msg->guild_id, + discord_get_channel_at_pos(client, event->guild_id, DISCORD_CHANNEL_GUILD_VOICE, position - 1, &ret); } void done_disconnect_guild_member(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_guild_member *member) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; char text[256]; snprintf(text, sizeof(text), "<@!%" PRIu64 "> has been kicked from VC", member->user->id); struct discord_create_message params = { .content = text }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void fail_disconnect_guild_member(struct discord *client, - CCORDcode code, - void *data) + struct discord_response *resp) { - struct context *cxt = data; + const struct discord_message *event = resp->keep; struct discord_create_message params = { .content = "Couldn't disconnect user from voice channel" }; - discord_create_message(client, cxt->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } void -on_voice_kick(struct discord *client, const struct discord_message *msg) +on_voice_kick(struct discord *client, const struct discord_message *event) { - if (msg->author->bot) return; + if (event->author->bot) return; u64snowflake user_id = 0; - sscanf(msg->content, "%" SCNu64, &user_id); + sscanf(event->content, "%" SCNu64, &user_id); if (!user_id) { struct discord_create_message params = { .content = "Missing user ID" }; - discord_create_message(client, msg->channel_id, ¶ms, NULL); + discord_create_message(client, event->channel_id, ¶ms, NULL); } else { - struct context *cxt = malloc(sizeof(struct context)); - cxt->channel_id = msg->channel_id; - cxt->guild_id = msg->guild_id; - struct discord_ret_guild_member ret = { .done = &done_disconnect_guild_member, .fail = &fail_disconnect_guild_member, - .data = cxt, - .cleanup = &free, + .keep = event, }; - discord_disconnect_guild_member(client, msg->guild_id, user_id, &ret); + discord_disconnect_guild_member(client, event->guild_id, user_id, + &ret); } } diff --git a/examples/webhook.c b/examples/webhook.c index e3bb621c8..3b0b7c807 100644 --- a/examples/webhook.c +++ b/examples/webhook.c @@ -47,14 +47,16 @@ main(int argc, char *argv[]) /* Get Webhook */ { struct discord_ret_webhook ret = { .sync = DISCORD_SYNC_FLAG }; - discord_get_webhook_with_token(client, webhook_id, webhook_token, &ret); + discord_get_webhook_with_token(client, webhook_id, webhook_token, + &ret); } /* Execute Webhook */ { struct discord_ret ret = { .sync = true }; struct discord_execute_webhook params = { .content = "Hello World!" }; - discord_execute_webhook(client, webhook_id, webhook_token, ¶ms, &ret); + discord_execute_webhook(client, webhook_id, webhook_token, ¶ms, + &ret); } free(webhook_token); diff --git a/gencodecs/api/application.pre.h b/gencodecs/api/application.pre.h index 7e6156c86..cfd644d47 100644 --- a/gencodecs/api/application.pre.h +++ b/gencodecs/api/application.pre.h @@ -25,7 +25,7 @@ PUB_STRUCT(discord_application) /** the description of the app */ FIELD_PTR(description, char, *) /** an array of rpc origin urls, if rpc is enabled */ - COND_WRITE(this->rpc_origins != NULL) + COND_WRITE(self->rpc_origins != NULL) FIELD_STRUCT_PTR(rpc_origins, strings, *) COND_END /** when false only app owner can join the app's bot to guilds */ @@ -38,7 +38,7 @@ PUB_STRUCT(discord_application) /** the url of the app's privacy policy */ FIELD_PTR(privacy_policy_url, char, *) /** partial user object containing info on the owner of the application */ - COND_WRITE(this->owner != NULL) + COND_WRITE(self->owner != NULL) FIELD_STRUCT_PTR(owner, discord_user, *) COND_END /** if this application is a game sold on Discord, this field will be the @@ -49,7 +49,7 @@ PUB_STRUCT(discord_application) FIELD_PTR(verify_key, char, *) /** if the application belongs to a team, this will be a list of the members of that team */ - COND_WRITE(this->team != NULL) + COND_WRITE(self->team != NULL) FIELD_STRUCT_PTR(team, discord_team, *) COND_END /** if this application is a game sold on Discord, this field will be the diff --git a/gencodecs/api/application_commands.pre.h b/gencodecs/api/application_commands.pre.h index 6d60b4bff..428d81815 100644 --- a/gencodecs/api/application_commands.pre.h +++ b/gencodecs/api/application_commands.pre.h @@ -42,12 +42,12 @@ PUB_STRUCT(discord_application_command) /** unique ID of the command */ FIELD_SNOWFLAKE(id) /** one of application command types */ - COND_WRITE(this->type != 0) + COND_WRITE(self->type != 0) FIELD_ENUM(type, discord_application_command_types) COND_END /** unique ID of the parent application */ FIELD_SNOWFLAKE(application_id) - COND_WRITE(this->guild_id != 0) + COND_WRITE(self->guild_id != 0) /** guild ID of the command, if not global */ FIELD_SNOWFLAKE(guild_id) COND_END @@ -57,12 +57,12 @@ PUB_STRUCT(discord_application_command) for `USER` and `MESSAGE` commands */ FIELD_PTR(description, char, *) /** the parameters for the command, max 25 */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_application_command_options, *) COND_END /** whether the command is enabled by default when the app is added to a guild */ - COND_WRITE(this->default_permission != true) + COND_WRITE(self->default_permission != true) FIELD(default_permission, bool, true) COND_END /** autoincrementing version identifier updated during substantial @@ -82,33 +82,33 @@ STRUCT(discord_application_command_option) /** 1-100 character description */ FIELD_PTR(description, char, *) /** if the parameter is required or optional -- default `false` */ - COND_WRITE(this->required != false) + COND_WRITE(self->required != false) FIELD(required, bool, false) COND_END /** choices for string and int types for the user to pick from */ - COND_WRITE(this->choices != NULL) + COND_WRITE(self->choices != NULL) FIELD_STRUCT_PTR(choices, discord_application_command_option_choices, *) COND_END /** if the option is a subcommand or subcommand group type, this nested options will be the parameters */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_application_command_options, *) COND_END /** if the option is a channel type, the channels shown will be restricted to these types */ - COND_WRITE(this->channel_types != NULL) + COND_WRITE(self->channel_types != NULL) FIELD_STRUCT_PTR(channel_types, integers, *) COND_END /** if the option is an INTEGER or NUMBER type, the minimum value permitted */ - COND_WRITE(this->min_value != NULL) + COND_WRITE(self->min_value != NULL) FIELD_PTR(min_value, char, *) COND_END /** if the option is an INTEGER or NUMBER type, the maximum value permitted */ - COND_WRITE(this->max_value != NULL) + COND_WRITE(self->max_value != NULL) FIELD_PTR(max_value, char, *) COND_END /** enable autocomplete interactions for this option */ - COND_WRITE(this->choices == NULL) + COND_WRITE(self->choices == NULL) FIELD(autocomplete, bool, false) COND_END STRUCT_END @@ -136,11 +136,11 @@ STRUCT(discord_application_command_interaction_data_option) FIELD_ENUM(type, discord_application_command_option_types) /** the value of the option resulting from user input @note in case of a string the value must be enclosed with escaped commands, ex: `\"hi\"` */ - COND_WRITE(this->value != NULL && *this->value != '\0') + COND_WRITE(self->value != NULL && *self->value != '\0') FIELD_PTR(value, json_char, *) COND_END /** present if this option is a group or subcommand */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_application_command_interaction_data_options, *) COND_END /** true if this option is the currently focused option for autocomplete */ @@ -189,14 +189,14 @@ PUB_STRUCT(discord_create_global_application_command) /** 1-100 character description */ FIELD_PTR(description, char, *) /** the parameters for the command */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_application_command_options, *) COND_END /** whether the command is enabled by default when the app is added to a guild */ FIELD(default_permission, bool, true) /** the type of command, default `1` if not set */ - COND_WRITE(this->type != 0) + COND_WRITE(self->type != 0) FIELD_ENUM(type, discord_application_command_types) COND_END STRUCT_END @@ -207,7 +207,7 @@ PUB_STRUCT(discord_edit_global_application_command) /** 1-100 character description */ FIELD_PTR(description, char, *) /** the parameters for the command */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_application_command_options, *) COND_END /** whether the command is enabled by default when the app is added to a @@ -221,14 +221,14 @@ PUB_STRUCT(discord_create_guild_application_command) /** 1-100 character description */ FIELD_PTR(description, char, *) /** the parameters for the command */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_application_command_options, *) COND_END /** whether the command is enabled by default when the app is added to a guild */ FIELD(default_permission, bool, true) /** the type of command, default `1` if not set */ - COND_WRITE(this->type != 0) + COND_WRITE(self->type != 0) FIELD_ENUM(type, discord_application_command_types) COND_END STRUCT_END @@ -239,7 +239,7 @@ PUB_STRUCT(discord_edit_guild_application_command) /** 1-100 character description */ FIELD_PTR(description, char, *) /** the parameters for the command */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_application_command_options, *) COND_END /** whether the command is enabled by default when the app is added to a @@ -249,7 +249,7 @@ STRUCT_END PUB_STRUCT(discord_edit_application_command_permissions) /** the permissions for the command in the guild */ - COND_WRITE(this->permissions != NULL) + COND_WRITE(self->permissions != NULL) FIELD_STRUCT_PTR(permissions, discord_application_command_permissions, *) COND_END STRUCT_END diff --git a/gencodecs/api/audit_log.pre.h b/gencodecs/api/audit_log.pre.h index 616f0ec34..a2fa9ea6c 100644 --- a/gencodecs/api/audit_log.pre.h +++ b/gencodecs/api/audit_log.pre.h @@ -55,27 +55,27 @@ ENUM_END /** @CCORD_pub_struct{discord_audit_log} */ PUB_STRUCT(discord_audit_log) /** list of audit log entries */ - COND_WRITE(this->audit_log_entries != NULL) + COND_WRITE(self->audit_log_entries != NULL) FIELD_STRUCT_PTR(audit_log_entries, discord_audit_log_entries, *) COND_END /** list of guild scheduled events found in the audit log */ - COND_WRITE(this->guild_scheduled_events != NULL) + COND_WRITE(self->guild_scheduled_events != NULL) FIELD_STRUCT_PTR(guild_scheduled_events, discord_guild_scheduled_events, *) COND_END /** list of partial integration objects */ - COND_WRITE(this->integrations != NULL) + COND_WRITE(self->integrations != NULL) FIELD_STRUCT_PTR(integrations, discord_integrations, *) COND_END /** list of threads found in the audit log */ - COND_WRITE(this->threads != NULL) + COND_WRITE(self->threads != NULL) FIELD_STRUCT_PTR(threads, discord_channels, *) COND_END /** list of users found in the audit log */ - COND_WRITE(this->users != NULL) + COND_WRITE(self->users != NULL) FIELD_STRUCT_PTR(users, discord_users, *) COND_END /** list of webhooks found in the audit log */ - COND_WRITE(this->webhooks != NULL) + COND_WRITE(self->webhooks != NULL) FIELD_STRUCT_PTR(webhooks, discord_webhooks, *) COND_END STRUCT_END @@ -84,7 +84,7 @@ STRUCT(discord_audit_log_entry) /** ID of the affected entity (webhook, user, role, etc.) */ FIELD_SNOWFLAKE(target_id) /** changes made to the target_id */ - COND_WRITE(this->changes != NULL) + COND_WRITE(self->changes != NULL) FIELD_STRUCT_PTR(changes, discord_audit_log_changes, *) COND_END /** the user who made the changes */ @@ -92,11 +92,11 @@ STRUCT(discord_audit_log_entry) /** id of the entry */ FIELD_SNOWFLAKE(id) /** type of action that occurred */ - COND_WRITE(this->action_type != 0) + COND_WRITE(self->action_type != 0) FIELD_ENUM(action_type, discord_audit_log_events) COND_END /** additional info for certain action types */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_optional_audit_entry_infos, *) COND_END /** the reason for the change (0-512) characters */ @@ -156,7 +156,7 @@ STRUCT(discord_get_guild_audit_log) /** filter the log before a certain entry ID */ FIELD_SNOWFLAKE(before) /** how many entries are returned (default 50, minimum 1, maximum 100) */ - COND_WRITE(this->limit >= 1 && this->limit <= 100) + COND_WRITE(self->limit >= 1 && self->limit <= 100) FIELD(limit, int, 50) COND_END STRUCT_END diff --git a/gencodecs/api/channel.pre.h b/gencodecs/api/channel.pre.h index 49ecdc835..1ebdd487e 100644 --- a/gencodecs/api/channel.pre.h +++ b/gencodecs/api/channel.pre.h @@ -108,7 +108,7 @@ PUB_STRUCT(discord_channel) FIELD_ENUM(type, discord_channel_types) /** the ID of the guild (may be missing for some channel objects received over gateway guild dispatches) */ - COND_WRITE(this->guild_id != 0) + COND_WRITE(self->guild_id != 0) FIELD_SNOWFLAKE(guild_id) COND_END /** sorting position of the channel */ @@ -192,7 +192,7 @@ PUB_STRUCT(discord_message) /** when this message was sent */ FIELD_TIMESTAMP(timestamp) /** when this message was edited (or null if never) */ - COND_WRITE(this->edited_timestamp != 0) + COND_WRITE(self->edited_timestamp != 0) FIELD_TIMESTAMP(edited_timestamp) COND_END /** whether this was a TTS message */ @@ -326,7 +326,8 @@ STRUCT(discord_thread_metadata) FIELD_TIMESTAMP(create_timestamp) STRUCT_END -STRUCT(discord_thread_member) +/** @CCORD_pub_struct{discord_thread_member} */ +PUB_STRUCT(discord_thread_member) /** the id of the thread */ FIELD_SNOWFLAKE(id) /** the id of the user */ @@ -335,6 +336,8 @@ STRUCT(discord_thread_member) FIELD_TIMESTAMP(join_timestamp) /** any user-thread settings, currently only used for notifications */ FIELD_BITMASK(flags) + /** the id of the guild @note used at `Thread Member Update` */ + FIELD_SNOWFLAKE(guild_id) STRUCT_END /** @CCORD_pub_list{discord_thread_members} */ @@ -350,15 +353,15 @@ STRUCT(discord_attachment) /** attachment ID */ FIELD_SNOWFLAKE(id) /** name of file attached */ - COND_WRITE(this->filename != NULL) + COND_WRITE(self->filename != NULL) FIELD_PTR(filename, char, *) COND_END /** description for the file */ - COND_WRITE(this->description != NULL) + COND_WRITE(self->description != NULL) FIELD_PTR(description, char, *) COND_END /** the attachment media type */ - COND_WRITE(this->content_type != NULL) + COND_WRITE(self->content_type != NULL) FIELD_PTR(content_type, char, *) COND_END /** size of file in bytes */ @@ -368,11 +371,11 @@ STRUCT(discord_attachment) /** proxied url of file */ FIELD_PTR(proxy_url, char, *) /** height of file (if image) */ - COND_WRITE(this->height != 0) + COND_WRITE(self->height != 0) FIELD(height, int, 0) COND_END /** width of file (if image) */ - COND_WRITE(this->width != 0) + COND_WRITE(self->width != 0) FIELD(width, int, 0) COND_END /** whether this attachment is ephemeral */ @@ -395,36 +398,36 @@ PUB_STRUCT(discord_embed) /** url of embed */ FIELD_PTR(url, char, *) /** timestamp of embed content */ - COND_WRITE(this->timestamp != 0) + COND_WRITE(self->timestamp != 0) FIELD_TIMESTAMP(timestamp) COND_END /** color code of the embed */ - COND_WRITE(this->color != 0) + COND_WRITE(self->color != 0) FIELD(color, int, 0) COND_END /** footer information */ - COND_WRITE(this->footer != NULL) + COND_WRITE(self->footer != NULL) FIELD_STRUCT_PTR(footer, discord_embed_footer, *) COND_END /** image information */ - COND_WRITE(this->image != NULL) + COND_WRITE(self->image != NULL) FIELD_STRUCT_PTR(image, discord_embed_image, *) COND_END /** thumbnail information */ - COND_WRITE(this->thumbnail != NULL) + COND_WRITE(self->thumbnail != NULL) FIELD_STRUCT_PTR(thumbnail, discord_embed_thumbnail, *) COND_END /** video information */ - COND_WRITE(this->video != NULL) + COND_WRITE(self->video != NULL) FIELD_STRUCT_PTR(video, discord_embed_video, *) COND_END - COND_WRITE(this->provider != NULL) + COND_WRITE(self->provider != NULL) FIELD_STRUCT_PTR(provider, discord_embed_provider, *) COND_END - COND_WRITE(this->author != NULL) + COND_WRITE(self->author != NULL) FIELD_STRUCT_PTR(author, discord_embed_author, *) COND_END - COND_WRITE(this->fields != NULL) + COND_WRITE(self->fields != NULL) FIELD_STRUCT_PTR(fields, discord_embed_fields, *) COND_END STRUCT_END @@ -439,15 +442,15 @@ PUB_STRUCT(discord_embed_thumbnail) /** source url of thumbnail (only supports http(s) and attachments) */ FIELD_PTR(url, char, *) /** a proxied url of the thumbnail */ - COND_WRITE(this->proxy_url != NULL) + COND_WRITE(self->proxy_url != NULL) FIELD_PTR(proxy_url, char, *) COND_END /** height of thumbnail */ - COND_WRITE(this->height != 0) + COND_WRITE(self->height != 0) FIELD(height, int, 0) COND_END /** width of thumbnail */ - COND_WRITE(this->width != 0) + COND_WRITE(self->width != 0) FIELD(width, int, 0) COND_END STRUCT_END @@ -455,19 +458,19 @@ STRUCT_END /** @CCORD_pub_struct{discord_embed_video} */ PUB_STRUCT(discord_embed_video) /** source url of video */ - COND_WRITE(this->url != NULL) + COND_WRITE(self->url != NULL) FIELD_PTR(url, char, *) COND_END /** a proxied url of the video */ - COND_WRITE(this->proxy_url != NULL) + COND_WRITE(self->proxy_url != NULL) FIELD_PTR(proxy_url, char, *) COND_END /** height of video */ - COND_WRITE(this->height != 0) + COND_WRITE(self->height != 0) FIELD(height, int, 0) COND_END /** width of video */ - COND_WRITE(this->width != 0) + COND_WRITE(self->width != 0) FIELD(width, int, 0) COND_END STRUCT_END @@ -477,15 +480,15 @@ PUB_STRUCT(discord_embed_image) /** source url of image (only supports http(s) and attachments) */ FIELD_PTR(url, char, *) /** a proxied url of the image */ - COND_WRITE(this->proxy_url != NULL) + COND_WRITE(self->proxy_url != NULL) FIELD_PTR(proxy_url, char, *) COND_END /** height of image */ - COND_WRITE(this->height != 0) + COND_WRITE(self->height != 0) FIELD(height, int, 0) COND_END /** width of image */ - COND_WRITE(this->width != 0) + COND_WRITE(self->width != 0) FIELD(width, int, 0) COND_END STRUCT_END @@ -493,11 +496,11 @@ STRUCT_END /** @CCORD_pub_struct{discord_embed_provider} */ PUB_STRUCT(discord_embed_provider) /** name of provider */ - COND_WRITE(this->name != NULL) + COND_WRITE(self->name != NULL) FIELD_PTR(name, char, *) COND_END /** url of provider */ - COND_WRITE(this->url != NULL) + COND_WRITE(self->url != NULL) FIELD_PTR(url, char, *) COND_END STRUCT_END @@ -507,15 +510,15 @@ PUB_STRUCT(discord_embed_author) /** name of author */ FIELD_PTR(name, char, *) /** url of author */ - COND_WRITE(this->url != NULL) + COND_WRITE(self->url != NULL) FIELD_PTR(url, char, *) COND_END /** url of author icon (only supports http(s) and attachments) */ - COND_WRITE(this->icon_url != NULL) + COND_WRITE(self->icon_url != NULL) FIELD_PTR(icon_url, char, *) COND_END /** a proxied url of author icon */ - COND_WRITE(this->proxy_icon_url != NULL) + COND_WRITE(self->proxy_icon_url != NULL) FIELD_PTR(proxy_icon_url, char, *) COND_END STRUCT_END @@ -525,11 +528,11 @@ PUB_STRUCT(discord_embed_footer) /** footer text */ FIELD_PTR(text, char, *) /** url of footer icon (only supports http(s) and attachments) */ - COND_WRITE(this->icon_url != NULL) + COND_WRITE(self->icon_url != NULL) FIELD_PTR(icon_url, char, *) COND_END /** a proxied url of footer icon */ - COND_WRITE(this->proxy_icon_url != NULL) + COND_WRITE(self->proxy_icon_url != NULL) FIELD_PTR(proxy_icon_url, char, *) COND_END STRUCT_END @@ -605,7 +608,7 @@ PUB_STRUCT(discord_modify_channel) supported and only in guilds with the `NEWS` feature */ FIELD_ENUM(type, discord_channel_types) /** the position of the channel in the left-hand listing */ - COND_WRITE(this->position != 0) + COND_WRITE(self->position != 0) FIELD(position, int, 0) COND_END /** 0-1024 character channel topic */ @@ -615,32 +618,32 @@ PUB_STRUCT(discord_modify_channel) /** amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission `MANAGE_MESSAGES` or `MANAGE_CHANNEL`, are unaffected */ - COND_WRITE(this->rate_limit_per_user != 0) + COND_WRITE(self->rate_limit_per_user != 0) FIELD(rate_limit_per_user, int, 0) COND_END /** the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit */ - COND_WRITE(this->user_limit != 0) + COND_WRITE(self->user_limit != 0) FIELD(user_limit, int, 0) COND_END /** channel or category-specific permissions */ - COND_WRITE(this->permission_overwrites != NULL) + COND_WRITE(self->permission_overwrites != NULL) FIELD_STRUCT_PTR(permission_overwrites, discord_overwrites, *) COND_END /** ID of the new parent category for a channel */ - COND_WRITE(this->parent_id != 0) + COND_WRITE(self->parent_id != 0) FIELD_SNOWFLAKE(parent_id) COND_END /** channel voice region id, automatic when set to NULL */ FIELD_PTR(rtc_region, char, *) /** the camera video quality mode of the voice channel */ - COND_WRITE(this->video_quality_mode != 0) + COND_WRITE(self->video_quality_mode != 0) FIELD(video_quality_mode, int, 0) COND_END /** the default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity */ - COND_WRITE(this->default_auto_archive_duration != 0) + COND_WRITE(self->default_auto_archive_duration != 0) FIELD(default_auto_archive_duration, int, 0) COND_END /* THREAD */ @@ -648,7 +651,7 @@ PUB_STRUCT(discord_modify_channel) FIELD(archived, bool, false) /** duration in minutes to automatically arhived the thread after recent activity, can be set to: 60, 1440, 4320, 10080 */ - COND_WRITE(this->auto_archive_duration != 0) + COND_WRITE(self->auto_archive_duration != 0) FIELD(auto_archive_duration, int, 0) COND_END /** whether the thread is locked; when a thread is locked, only users with @@ -662,19 +665,19 @@ STRUCT_END #if defined(GENCODECS_ON_STRUCT) PUB_STRUCT(discord_get_channel_messages) /** get messages around this message ID */ - COND_WRITE(this->around != 0) + COND_WRITE(self->around != 0) FIELD_SNOWFLAKE(around) COND_END /** get messages before this message ID */ - COND_WRITE(this->before != 0) + COND_WRITE(self->before != 0) FIELD_SNOWFLAKE(before) COND_END /** get messages after this message ID */ - COND_WRITE(this->after != 0) + COND_WRITE(self->after != 0) FIELD_SNOWFLAKE(after) COND_END /** max number of messages to return (1-100) */ - COND_WRITE(this->limit != 0) + COND_WRITE(self->limit != 0) FIELD(limit, int, 50) COND_END STRUCT_END @@ -689,28 +692,28 @@ PUB_STRUCT(discord_create_message) /** embedded `rich` content (up to 6000 characters) */ FIELD_STRUCT_PTR(embeds, discord_embeds, *) /** allowed mentions for the message */ - COND_WRITE(this->allowed_mentions != NULL) + COND_WRITE(self->allowed_mentions != NULL) FIELD_STRUCT_PTR(allowed_mentions, discord_allowed_mention, *) COND_END /** include to make your message a reply */ - COND_WRITE(this->message_reference != NULL) + COND_WRITE(self->message_reference != NULL) FIELD_STRUCT_PTR(message_reference, discord_message_reference, *) COND_END /** the components to include with the message */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** IDs of up to 3 stickers in the server to send in the message */ - COND_WRITE(this->sticker_ids != NULL) + COND_WRITE(self->sticker_ids != NULL) FIELD_STRUCT_PTR(sticker_ids, snowflakes, *) COND_END /** attachment objects with filename and description */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END /** @ref DiscordAPIChannelMessageFlags combined as a bitfield (only `SUPPRESS_EMBEDS` can be set) */ - COND_WRITE(this->flags != 0) + COND_WRITE(self->flags != 0) FIELD_BITMASK(flags) COND_END STRUCT_END @@ -718,11 +721,11 @@ STRUCT_END #if defined(GENCODECS_ON_STRUCT) PUB_STRUCT(discord_get_reactions) /** get users after this user ID */ - COND_WRITE(this->after != 0) + COND_WRITE(self->after != 0) FIELD_SNOWFLAKE(after) COND_END /** max number of users to return (1-100) */ - COND_WRITE(this->limit != 0) + COND_WRITE(self->limit != 0) FIELD(limit, int, 0) COND_END STRUCT_END @@ -736,19 +739,19 @@ PUB_STRUCT(discord_edit_message) FIELD_STRUCT_PTR(embeds, discord_embeds, *) /** @ref DiscordAPIChannelMessageFlags combined as a bitfield (only `SUPPRESS_EMBEDS` can be set) */ - COND_WRITE(this->flags != 0) + COND_WRITE(self->flags != 0) FIELD_BITMASK(flags) COND_END /** allowed mentions for the message */ - COND_WRITE(this->allowed_mentions != NULL) + COND_WRITE(self->allowed_mentions != NULL) FIELD_STRUCT_PTR(allowed_mentions, discord_allowed_mention, *) COND_END /** the components to include with the message */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** attachment objects with filename and description */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END STRUCT_END @@ -763,12 +766,12 @@ STRUCT_END PUB_STRUCT(discord_edit_channel_permissions) /** the bitwise value of all allowed permissions (default \"0\") @see @ref DiscordPermissions */ - COND_WRITE(this->allow != 0) + COND_WRITE(self->allow != 0) FIELD_BITMASK(allow) COND_END /** the bitwise value of all disallowed permissions (default \"0\") @see @ref DiscordPermissions */ - COND_WRITE(this->deny != 0) + COND_WRITE(self->deny != 0) FIELD_BITMASK(deny) COND_END /** 0 for a role or 1 for a member */ @@ -779,34 +782,34 @@ STRUCT_END PUB_STRUCT(discord_create_channel_invite) /** duration of invite in seconds before expiry, or 0 for never. between 0 and 604800 (7 days) */ - COND_WRITE(this->max_age != 0) + COND_WRITE(self->max_age != 0) FIELD(max_age, int, 86400) COND_END /** max number of uses or 0 for unlimited. betwee 0 and 100 */ - COND_WRITE(this->max_uses != 0) + COND_WRITE(self->max_uses != 0) FIELD(max_uses, int, 0) COND_END /** whether this invite only grants temporary membership */ - COND_WRITE(this->temporary != 0) + COND_WRITE(self->temporary != 0) FIELD(temporary, bool, false) COND_END /** if true, don't true to reuse a similar invite (useful for creating many unique one time use invites) */ - COND_WRITE(this->unique != 0) + COND_WRITE(self->unique != 0) FIELD(unique, bool, false) COND_END /** the type of target for this voice channel invite */ - COND_WRITE(this->target_type != 0) + COND_WRITE(self->target_type != 0) FIELD_ENUM(target_type, discord_invite_target_types) COND_END /** the id of the user whose stream to display for this invite, required if `target_type` is 1, the user must be streaming in the channel */ - COND_WRITE(this->target_user_id != 0) + COND_WRITE(self->target_user_id != 0) FIELD_SNOWFLAKE(target_user_id) COND_END /** the id of the embedded application to open for this invite, required if `target_type` is 2, the application must have the `EMBEDDED` flag */ - COND_WRITE(this->target_application_id != 0) + COND_WRITE(self->target_application_id != 0) FIELD_SNOWFLAKE(target_application_id) COND_END STRUCT_END @@ -814,7 +817,7 @@ STRUCT_END /** @CCORD_pub_struct{discord_follow_news_channel} */ PUB_STRUCT(discord_follow_news_channel) /** id of target channel */ - COND_WRITE(this->webhook_channel_id != 0) + COND_WRITE(self->webhook_channel_id != 0) FIELD_SNOWFLAKE(webhook_channel_id) COND_END STRUCT_END @@ -833,13 +836,13 @@ PUB_STRUCT(discord_start_thread_with_message) FIELD_PTR(name, char, *) /** duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 */ - COND_WRITE(this->auto_archive_duration != 0) + COND_WRITE(self->auto_archive_duration != 0) FIELD(auto_archive_duration, int, 0) COND_END /** amount of seconds a user has to wait before sending another message (0-21600) */ - COND_WRITE(this->rate_limit_per_user >= 0 - && this->rate_limit_per_user <= 21600) + COND_WRITE(self->rate_limit_per_user >= 0 + && self->rate_limit_per_user <= 21600) FIELD(rate_limit_per_user, int, 0) COND_END STRUCT_END @@ -850,7 +853,7 @@ PUB_STRUCT(discord_start_thread_without_message) FIELD_PTR(name, char, *) /** duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 */ - COND_WRITE(this->auto_archive_duration != 0) + COND_WRITE(self->auto_archive_duration != 0) FIELD(auto_archive_duration, int, 0) COND_END /** the type of thread to create */ @@ -860,8 +863,8 @@ PUB_STRUCT(discord_start_thread_without_message) FIELD(invitable, bool, false) /** amount of seconds a user has to wait before sending another message (0-21600) */ - COND_WRITE(this->rate_limit_per_user >= 0 - && this->rate_limit_per_user <= 21600) + COND_WRITE(self->rate_limit_per_user >= 0 + && self->rate_limit_per_user <= 21600) FIELD(rate_limit_per_user, int, 0) COND_END STRUCT_END @@ -869,12 +872,12 @@ STRUCT_END /** @CCORD_pub_struct{discord_list_active_threads} */ PUB_STRUCT(discord_list_active_threads) /** the active threads */ - COND_WRITE(this->threads != NULL) + COND_WRITE(self->threads != NULL) FIELD_STRUCT_PTR(threads, discord_channels, *) COND_END /** a thread member object for each returned thread the current user has joined */ - COND_WRITE(this->members != NULL) + COND_WRITE(self->members != NULL) FIELD_STRUCT_PTR(members, discord_thread_members, *) COND_END /** whether there are potentially additional threads that could be returned diff --git a/gencodecs/api/emoji.pre.h b/gencodecs/api/emoji.pre.h index 8dd156729..c5190fb00 100644 --- a/gencodecs/api/emoji.pre.h +++ b/gencodecs/api/emoji.pre.h @@ -9,11 +9,11 @@ PUB_STRUCT(discord_emoji) /** emoji name */ FIELD_PTR(name, char, *) /** roles allowed to use this emoji */ - COND_WRITE(this->roles != NULL) + COND_WRITE(self->roles != NULL) FIELD_STRUCT_PTR(roles, discord_roles, *) COND_END /** user that created this emoji */ - COND_WRITE(this->user != NULL) + COND_WRITE(self->user != NULL) FIELD_STRUCT_PTR(user, discord_user, *) COND_END /** whether this emoji must be wrapped in colons */ @@ -44,7 +44,7 @@ PUB_STRUCT(discord_create_guild_emoji) /** the 128x128 emoji image */ FIELD_PTR(image, char, *) /** roles allowed to use this emoji */ - COND_WRITE(this->roles != NULL) + COND_WRITE(self->roles != NULL) FIELD_STRUCT_PTR(roles, snowflakes, *) COND_END STRUCT_END @@ -57,7 +57,7 @@ PUB_STRUCT(discord_modify_guild_emoji) /** the 128x128 emoji image */ FIELD_PTR(image, char, *) /** roles allowed to use this emoji */ - COND_WRITE(this->roles != NULL) + COND_WRITE(self->roles != NULL) FIELD_STRUCT_PTR(roles, snowflakes, *) COND_END STRUCT_END diff --git a/gencodecs/api/gateway.pre.h b/gencodecs/api/gateway.pre.h index a7044c5e9..9904a9a2c 100644 --- a/gencodecs/api/gateway.pre.h +++ b/gencodecs/api/gateway.pre.h @@ -71,60 +71,62 @@ ENUM(discord_gateway_opcodes) ENUM_END ENUM(discord_gateway_events) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_NONE, = 0) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_READY, = 1) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_RESUMED, = 2) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_APPLICATION_COMMAND_CREATE, = 3) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_APPLICATION_COMMAND_UPDATE, = 4) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_APPLICATION_COMMAND_DELETE, = 5) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_CHANNEL_CREATE, = 6) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_CHANNEL_UPDATE, = 7) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_CHANNEL_DELETE, = 8) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_CHANNEL_PINS_UPDATE, = 9) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_THREAD_CREATE, = 10) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_THREAD_UPDATE, = 11) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_THREAD_DELETE, = 12) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_THREAD_LIST_SYNC, = 13) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_THREAD_MEMBER_UPDATE, = 14) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_THREAD_MEMBERS_UPDATE, = 15) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_CREATE, = 16) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_UPDATE, = 17) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_DELETE, = 18) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_BAN_ADD, = 19) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_BAN_REMOVE, = 20) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_EMOJIS_UPDATE, = 21) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_STICKERS_UPDATE, = 22) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_INTEGRATIONS_UPDATE, = 23) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_MEMBER_ADD, = 24) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_MEMBER_REMOVE, = 25) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_MEMBER_UPDATE, = 26) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_MEMBERS_CHUNK, = 27) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_ROLE_CREATE, = 28) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_ROLE_UPDATE, = 29) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_GUILD_ROLE_DELETE, = 30) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_INTEGRATION_CREATE, = 31) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_INTEGRATION_UPDATE, = 32) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_INTEGRATION_DELETE, = 33) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_INTERACTION_CREATE, = 34) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_INVITE_CREATE, = 35) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_INVITE_DELETE, = 36) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_CREATE, = 37) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_UPDATE, = 38) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_DELETE, = 39) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_DELETE_BULK, = 40) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_ADD, = 41) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_REMOVE, = 42) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_REMOVE_ALL, = 43) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_REMOVE_EMOJI, = 44) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_PRESENCE_UPDATE, = 45) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_STAGE_INSTANCE_CREATE, = 46) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_STAGE_INSTANCE_DELETE, = 47) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_STAGE_INSTANCE_UPDATE, = 48) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_TYPING_START, = 49) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_USER_UPDATE, = 50) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_VOICE_STATE_UPDATE, = 51) - ENUMERATOR(DISCORD_GATEWAY_EVENTS_VOICE_SERVER_UPDATE, = 52) - ENUMERATOR_LAST(DISCORD_GATEWAY_EVENTS_WEBHOOKS_UPDATE, = 53) + ENUMERATOR(DISCORD_EV_NONE, = 0) + ENUMERATOR(DISCORD_EV_READY, = 1) + ENUMERATOR(DISCORD_EV_RESUMED, = 2) + ENUMERATOR(DISCORD_EV_APPLICATION_COMMAND_CREATE, = 3) + ENUMERATOR(DISCORD_EV_APPLICATION_COMMAND_UPDATE, = 4) + ENUMERATOR(DISCORD_EV_APPLICATION_COMMAND_DELETE, = 5) + ENUMERATOR(DISCORD_EV_CHANNEL_CREATE, = 6) + ENUMERATOR(DISCORD_EV_CHANNEL_UPDATE, = 7) + ENUMERATOR(DISCORD_EV_CHANNEL_DELETE, = 8) + ENUMERATOR(DISCORD_EV_CHANNEL_PINS_UPDATE, = 9) + ENUMERATOR(DISCORD_EV_THREAD_CREATE, = 10) + ENUMERATOR(DISCORD_EV_THREAD_UPDATE, = 11) + ENUMERATOR(DISCORD_EV_THREAD_DELETE, = 12) + ENUMERATOR(DISCORD_EV_THREAD_LIST_SYNC, = 13) + ENUMERATOR(DISCORD_EV_THREAD_MEMBER_UPDATE, = 14) + ENUMERATOR(DISCORD_EV_THREAD_MEMBERS_UPDATE, = 15) + ENUMERATOR(DISCORD_EV_GUILD_CREATE, = 16) + ENUMERATOR(DISCORD_EV_GUILD_UPDATE, = 17) + ENUMERATOR(DISCORD_EV_GUILD_DELETE, = 18) + ENUMERATOR(DISCORD_EV_GUILD_BAN_ADD, = 19) + ENUMERATOR(DISCORD_EV_GUILD_BAN_REMOVE, = 20) + ENUMERATOR(DISCORD_EV_GUILD_EMOJIS_UPDATE, = 21) + ENUMERATOR(DISCORD_EV_GUILD_STICKERS_UPDATE, = 22) + ENUMERATOR(DISCORD_EV_GUILD_INTEGRATIONS_UPDATE, = 23) + ENUMERATOR(DISCORD_EV_GUILD_MEMBER_ADD, = 24) + ENUMERATOR(DISCORD_EV_GUILD_MEMBER_REMOVE, = 25) + ENUMERATOR(DISCORD_EV_GUILD_MEMBER_UPDATE, = 26) + ENUMERATOR(DISCORD_EV_GUILD_MEMBERS_CHUNK, = 27) + ENUMERATOR(DISCORD_EV_GUILD_ROLE_CREATE, = 28) + ENUMERATOR(DISCORD_EV_GUILD_ROLE_UPDATE, = 29) + ENUMERATOR(DISCORD_EV_GUILD_ROLE_DELETE, = 30) + ENUMERATOR(DISCORD_EV_INTEGRATION_CREATE, = 31) + ENUMERATOR(DISCORD_EV_INTEGRATION_UPDATE, = 32) + ENUMERATOR(DISCORD_EV_INTEGRATION_DELETE, = 33) + ENUMERATOR(DISCORD_EV_INTERACTION_CREATE, = 34) + ENUMERATOR(DISCORD_EV_INVITE_CREATE, = 35) + ENUMERATOR(DISCORD_EV_INVITE_DELETE, = 36) + ENUMERATOR(DISCORD_EV_MESSAGE_CREATE, = 37) + ENUMERATOR(DISCORD_EV_MESSAGE_UPDATE, = 38) + ENUMERATOR(DISCORD_EV_MESSAGE_DELETE, = 39) + ENUMERATOR(DISCORD_EV_MESSAGE_DELETE_BULK, = 40) + ENUMERATOR(DISCORD_EV_MESSAGE_REACTION_ADD, = 41) + ENUMERATOR(DISCORD_EV_MESSAGE_REACTION_REMOVE, = 42) + ENUMERATOR(DISCORD_EV_MESSAGE_REACTION_REMOVE_ALL, = 43) + ENUMERATOR(DISCORD_EV_MESSAGE_REACTION_REMOVE_EMOJI, = 44) + ENUMERATOR(DISCORD_EV_PRESENCE_UPDATE, = 45) + ENUMERATOR(DISCORD_EV_STAGE_INSTANCE_CREATE, = 46) + ENUMERATOR(DISCORD_EV_STAGE_INSTANCE_DELETE, = 47) + ENUMERATOR(DISCORD_EV_STAGE_INSTANCE_UPDATE, = 48) + ENUMERATOR(DISCORD_EV_TYPING_START, = 49) + ENUMERATOR(DISCORD_EV_USER_UPDATE, = 50) + ENUMERATOR(DISCORD_EV_VOICE_STATE_UPDATE, = 51) + ENUMERATOR(DISCORD_EV_VOICE_SERVER_UPDATE, = 52) + ENUMERATOR(DISCORD_EV_WEBHOOKS_UPDATE, = 53) + /** amount of enumerators */ + ENUMERATOR_LAST(DISCORD_EV_MAX, ) ENUM_END ENUM(discord_activity_types) @@ -142,129 +144,39 @@ ENUM(discord_activity_types) ENUMERATOR_LAST(DISCORD_ACTIVITY_COMPETING, = 5) ENUM_END -/** @CCORD_pub_struct{discord_identify} */ -PUB_STRUCT(discord_identify) - /** authentication token */ - FIELD_PTR(token, char, *) - /** connection properties */ - FIELD_STRUCT_PTR(properties, discord_identify_connection, *) - /** whether this connection supports compression packets */ - FIELD(compress, bool, false) - /** value between 50 and 250, total number of members where the gateway - will stop sending offline members in the guild member list */ - FIELD(large_threshold, int, 50) -#if 0 - /** array of two integers (shard_id, num_shards) */ - FIELD_STRUCT_PTR(shard, integers, *) -#endif - /** presence structure for initial presence information */ - FIELD_STRUCT_PTR(presence, discord_presence_update, *) - /** the gateway intents you wish to receive - @see @ref DiscordInternalGatewayIntents */ - FIELD_BITMASK(intents) -STRUCT_END - -STRUCT(discord_identify_connection) - /** your operating system */ - FIELD_CUSTOM(os, "$os", char, *, INIT_BLANK, CLEANUP_PTR, - GENCODECS_JSON_ENCODER_PTR_char, - GENCODECS_JSON_DECODER_PTR_char, NULL) - /** your library name */ - FIELD_CUSTOM(browser, "$browser", char, *, INIT_BLANK, CLEANUP_PTR, - GENCODECS_JSON_ENCODER_PTR_char, - GENCODECS_JSON_DECODER_PTR_char, NULL) - /** your library name */ - FIELD_CUSTOM(device, "$device", char, *, INIT_BLANK, CLEANUP_PTR, - GENCODECS_JSON_ENCODER_PTR_char, - GENCODECS_JSON_DECODER_PTR_char, NULL) -STRUCT_END - -/** @CCORD_pub_struct{discord_voice_state_status} */ -PUB_STRUCT(discord_voice_state_status) - /** ID of the guild */ - FIELD_SNOWFLAKE(guild_id) - /** ID of the voice channel client wants to join (null if disconnecting) */ - FIELD_SNOWFLAKE(channel_id) - /** is the client muted */ - FIELD(self_mute, bool, false) - /** is the client deafened */ - FIELD(self_deaf, bool, false) -STRUCT_END - -/** @CCORD_pub_struct{discord_presence_update} */ -PUB_STRUCT(discord_presence_update) - /** unix time (in milliseconds) of when the client went idle, or null if - the client is not idle */ - FIELD_TIMESTAMP(since) - /** the user's activities */ - FIELD_STRUCT_PTR(activities, discord_activities, *) - /** the user's new status */ - FIELD_PTR(status, char, *) - /** whether or not the client is afk */ - FIELD(afk, bool, false) -STRUCT_END - -LIST(discord_presence_updates) - LISTTYPE_STRUCT(discord_presence_update) -LIST_END - STRUCT(discord_activity) /** the activity's name */ - COND_WRITE(this->name != NULL) FIELD_PTR(name, char, *) - COND_END /** activity type */ FIELD_ENUM(type, discord_activity_types) /** stream url, is validated when type is 1 */ - COND_WRITE(this->url != NULL) FIELD_PTR(url, char, *) - COND_END /** unix timestamp (in milliseconds)of when the activity was added to the user's session */ - COND_WRITE(this->created_at != 0) FIELD_TIMESTAMP(created_at) - COND_END /** unix timestamps for start and/or end of the game */ - COND_WRITE(this->timestamps != NULL) FIELD_STRUCT_PTR(timestamps, discord_activity_timestamps, *) - COND_END /** application ID for the game */ - COND_WRITE(this->application_id != 0) FIELD_SNOWFLAKE(application_id) - COND_END /** what the player is currently doing */ - COND_WRITE(this->details != NULL) FIELD_PTR(details, char, *) - COND_END /** the user's current party status */ - COND_WRITE(this->state != NULL) FIELD_PTR(state, char, *) - COND_END /** the emoji used for a custom status */ - COND_WRITE(this->emoji != NULL) FIELD_STRUCT_PTR(emoji, discord_activity_emoji, *) - COND_END /** information for the current party of the player */ - COND_WRITE(this->party != NULL) FIELD_STRUCT_PTR(party, discord_activity_party, *) - COND_END /** images for the presence and their hover texts */ - COND_WRITE(this->assets != NULL) FIELD_STRUCT_PTR(assets, discord_activity_assets, *) - COND_END /** secrets for Rich Presence joining and spectating */ - COND_WRITE(this->secrets != NULL) FIELD_STRUCT_PTR(secrets, discord_activity_secrets, *) - COND_END /** whether or not the activity is an instanced game session */ FIELD(instance, bool, false) /** activity flags bitwise mask, describes what they payload includes @see @ref DiscordActivityFlags */ FIELD_BITMASK(flags) /** the custom buttons shown in the Rich Presence (max 2) */ - COND_WRITE(this->buttons != NULL) FIELD_STRUCT_PTR(buttons, discord_activity_buttons, *) - COND_END STRUCT_END LIST(discord_activities) @@ -282,9 +194,7 @@ STRUCT(discord_activity_emoji) /** the name of the emoji */ FIELD_PTR(name, char, *) /** the ID of the emoji */ - COND_WRITE(this->id != 0) FIELD_SNOWFLAKE(id) - COND_END /** whether this emoji is animated */ FIELD(animated, bool, false) STRUCT_END @@ -328,6 +238,501 @@ LIST(discord_activity_buttons) LISTTYPE_STRUCT(discord_activity_button) LIST_END +/** @CCORD_pub_struct{discord_presence_update} */ +PUB_STRUCT(discord_presence_update) + /** the user presence is being updated for */ + COND_WRITE(self->user != NULL) + FIELD_STRUCT_PTR(user, discord_user, *) + COND_END + /** id of the guild */ + COND_WRITE(self->guild_id != 0) + FIELD_SNOWFLAKE(guild_id) + COND_END + /** either "idle", "dnd", "online", or "offline" */ + COND_WRITE(self->status != NULL) + FIELD_PTR(status, char, *) + COND_END + /** user's platform-dependent status */ + COND_WRITE(self->client_status != NULL) + FIELD_STRUCT_PTR(client_status, discord_client_status, *) + COND_END + /** user's current activities */ + COND_WRITE(self->activities != NULL) + FIELD_STRUCT_PTR(activities, discord_activities, *) + COND_END + /** unix time (in milliseconds) of when the client went idle, or null if + the client is not idle */ + COND_WRITE(self->since != 0) + FIELD_TIMESTAMP(since) + COND_END + /** whether or not the client is afk */ + FIELD(afk, bool, false) +STRUCT_END + +STRUCT(discord_client_status) + /** the user's status set for an active desktop (Windows, Linux, Mac) + * application session */ + FIELD_PTR(desktop, char, *) + /** the user's status set for an active mobile (iOS, Android) application + * session */ + FIELD_PTR(mobile, char, *) + /** the user's status set for an active web (browser, bot account) + * application session */ + FIELD_PTR(web, char, *) +STRUCT_END + +LIST(discord_presence_updates) + LISTTYPE_STRUCT(discord_presence_update) +LIST_END + +/* gateway command payloads only need to be encoded into JSON */ +#if !defined(GENCODECS_ON_JSON_DECODER) + +/** @CCORD_pub_struct{discord_identify} */ +PUB_STRUCT(discord_identify) + /** authentication token */ + FIELD_PTR(token, char, *) + /** connection properties */ + FIELD_STRUCT_PTR(properties, discord_identify_connection, *) + /** whether this connection supports compression packets */ + FIELD(compress, bool, false) + /** value between 50 and 250, total number of members where the gateway + will stop sending offline members in the guild member list */ + FIELD(large_threshold, int, 50) + /** array of two integers (shard_id, num_shards) */ + COND_WRITE(self->shard != NULL) + FIELD_STRUCT_PTR(shard, integers, *) + COND_END + /** presence structure for initial presence information */ + COND_WRITE(self->presence != NULL) + FIELD_STRUCT_PTR(presence, discord_presence_update, *) + COND_END + /** the gateway intents you wish to receive + @see @ref DiscordInternalGatewayIntents */ + FIELD_BITMASK(intents) +STRUCT_END + +STRUCT(discord_identify_connection) + /** your operating system */ + FIELD_CUSTOM(os, "$os", char, *, INIT_BLANK, CLEANUP_PTR, + GENCODECS_JSON_ENCODER_PTR_char, + GENCODECS_JSON_DECODER_PTR_char, NULL) + /** your library name */ + FIELD_CUSTOM(browser, "$browser", char, *, INIT_BLANK, CLEANUP_PTR, + GENCODECS_JSON_ENCODER_PTR_char, + GENCODECS_JSON_DECODER_PTR_char, NULL) + /** your library name */ + FIELD_CUSTOM(device, "$device", char, *, INIT_BLANK, CLEANUP_PTR, + GENCODECS_JSON_ENCODER_PTR_char, + GENCODECS_JSON_DECODER_PTR_char, NULL) +STRUCT_END + +/** @CCORD_pub_struct{discord_resume} */ +PUB_STRUCT(discord_resume) + /** session token */ + FIELD_PTR(token, char, *) + /** session id */ + FIELD_PTR(session_id, char, *) + /** last sequence number received */ + FIELD(seq, int, 0) +STRUCT_END + +/** @CCORD_pub_struct{discord_request_guild_members} */ +PUB_STRUCT(discord_request_guild_members) + /** id of the guild to get members for */ + FIELD_SNOWFLAKE(guild_id) + /** string that username starts with, or an empty string to return all + * members */ + FIELD_PTR(query, char, *) + /** maximum numberof members to send matching the `query`; a limit of `0` + * can be used with an empty string `query` to return all members */ + COND_WRITE(self->query != NULL) + FIELD(limit, int, 0) + COND_END + /** used to specify if we want the presences of the matched members */ + FIELD(presences, bool, false) + /** used to specify which users you wish to fetch */ + FIELD_STRUCT_PTR(user_ids, snowflakes, *) + /** nonce to identify the `Guild Members Chunk` response */ + COND_WRITE(self->nonce != NULL) + FIELD_PTR(nonce, char, *) + COND_END +STRUCT_END + +/** @CCORD_pub_struct{discord_update_voice_state} */ +PUB_STRUCT(discord_update_voice_state) + /** ID of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** ID of the voice channel client wants to join (null if disconnecting) */ + FIELD_SNOWFLAKE(channel_id) + /** is the client muted */ + FIELD(self_mute, bool, false) + /** is the client deafened */ + FIELD(self_deaf, bool, false) +STRUCT_END + +#endif /* GENCODECS_ON_JSON_DECODER */ + +/* event payloads only need to be decoded into structs */ +#if !defined(GENCODECS_ON_JSON_ENCODER) + +/** @CCORD_pub_struct{discord_ready} */ +PUB_STRUCT(discord_ready) + /** gateway version */ + FIELD(v, int, 0) + /** information about the user including email */ + FIELD_STRUCT_PTR(user, discord_user, *) + /** the guilds the user is in */ + FIELD_STRUCT_PTR(guilds, discord_guilds, *) + /** used for resuming connections */ + FIELD_PTR(session_id, char, *) + /** the shard information associated with this session, if sent when + * identifying*/ + FIELD_STRUCT_PTR(shard, integers, *) + /** contains `id` and `flags` */ + FIELD_STRUCT_PTR(application, discord_application, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_thread_list_sync} */ +PUB_STRUCT(discord_thread_list_sync) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** + * the parent channel ids whose threads are being synced. if omitted, then + * threads were synced for the entire guild. This array may contain + * channel_ids that have no active threads as well, so you know to + * clear data + */ + FIELD_STRUCT_PTR(channel_ids, snowflakes, *) + /** all active threads in the given channels that the current user can access */ + FIELD_STRUCT_PTR(threads, discord_channels, *) + /** all thread member objects from the synced threads for the current user, + * indicating which threads the current user has been added to */ + FIELD_STRUCT_PTR(members, discord_thread_members, *) +STRUCT_END + +/** + * @CCORD_pub_struct{discord_thread_members_update} + * @todo `added_members` may include guild_members and presence objects + */ +PUB_STRUCT(discord_thread_members_update) + /** the id of the thread */ + FIELD_SNOWFLAKE(id) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the approximate number of members in the thread, capped at 50 */ + FIELD(member_count, int, 0) + /** the users who were added to the thread */ + FIELD_STRUCT_PTR(added_members, discord_thread_members, *) + /** the id of the users who were removed from the thread */ + FIELD_STRUCT_PTR(removed_member_ids, snowflakes, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_channel_pins_update} */ +PUB_STRUCT(discord_channel_pins_update) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** the time at which the most recent pinned message was pinned */ + FIELD_TIMESTAMP(last_pin_timestamp) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_ban_add} */ +PUB_STRUCT(discord_guild_ban_add) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the banned user */ + FIELD_STRUCT_PTR(user, discord_user, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_ban_remove} */ +PUB_STRUCT(discord_guild_ban_remove) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the unbanned user */ + FIELD_STRUCT_PTR(user, discord_user, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_emojis_update} */ +PUB_STRUCT(discord_guild_emojis_update) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** array of emojis */ + FIELD_STRUCT_PTR(emojis, discord_emojis, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_stickers_update} */ +PUB_STRUCT(discord_guild_stickers_update) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** array of stickers */ + FIELD_STRUCT_PTR(stickers, discord_stickers, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_integrations_update} */ +PUB_STRUCT(discord_guild_integrations_update) + /** id of the guild whose integrations were updated */ + FIELD_SNOWFLAKE(guild_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_member_remove} */ +PUB_STRUCT(discord_guild_member_remove) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the user who was removed */ + FIELD_STRUCT_PTR(user, discord_user, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_member_update} */ +PUB_STRUCT(discord_guild_member_update) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** user role ids */ + FIELD_STRUCT_PTR(roles, snowflakes, *) + /** the user */ + FIELD_STRUCT_PTR(user, discord_user, *) + /** nickname of the user in the guild */ + FIELD_PTR(nick, char, *) + /** the member's guild avatar hash */ + FIELD_PTR(avatar, char, *) + /** when the user joined the guild */ + FIELD_TIMESTAMP(joined_at) + /** when the user started boosting the guild */ + FIELD_TIMESTAMP(premium_since) + /** whether the user is deafened in voice channels */ + FIELD(deaf, bool, false) + /** whether the user is muted in voice channels */ + FIELD(mute, bool, false) + /** whether the user has not yet passed the guild's `Membership Screening` + * requirements */ + FIELD(pending, bool, false) + /** + * when the user's timeout will expire and the user will be able to + * communicate in the guild again, `NULL` or a time in the past if the + * user is not timed out */ + FIELD_TIMESTAMP(communication_disabled_until) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_members_chunk} */ +PUB_STRUCT(discord_guild_members_chunk) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** set of guild members */ + FIELD_STRUCT_PTR(members, discord_guild_members, *) + /** the chunk index in the expected chunks for this response + * @note `0 <= chunk_index < chunk_count` */ + FIELD(chunk_index, int, 0) + /** the total number of expected chunks for this response */ + FIELD(chunk_count, int, 0) + /** if passing an invalid id to `REQUEST_GUILD_MEMBERS`, it will be returned + * here */ + FIELD_STRUCT_PTR(not_found, snowflakes, *) + /** if passing true to `REQUEST_GUILD_MEMBERS`, presences of the returned + * members will be here */ + FIELD_STRUCT_PTR(presences, discord_presence_updates, *) + /** the nonce used in the `Guild Members Request` */ + FIELD_PTR(nonce, char, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_role_create} */ +PUB_STRUCT(discord_guild_role_create) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the role created */ + FIELD_STRUCT_PTR(role, discord_role, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_role_update} */ +PUB_STRUCT(discord_guild_role_update) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the role updated */ + FIELD_STRUCT_PTR(role, discord_role, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_guild_role_delete} */ +PUB_STRUCT(discord_guild_role_delete) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the id of the role */ + FIELD_SNOWFLAKE(role_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_scheduled_event_user_add} */ +PUB_STRUCT(discord_guild_scheduled_event_user_add) + /** id of the guild scheduled event */ + FIELD_SNOWFLAKE(guild_scheduled_event_id) + /** id of the user */ + FIELD_SNOWFLAKE(user_id) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_scheduled_event_user_remove} */ +PUB_STRUCT(discord_guild_scheduled_event_user_remove) + /** id of the guild scheduled event */ + FIELD_SNOWFLAKE(guild_scheduled_event_id) + /** id of the user */ + FIELD_SNOWFLAKE(user_id) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_integration_delete} */ +PUB_STRUCT(discord_integration_delete) + /** integration id */ + FIELD_SNOWFLAKE(id) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** id of the bot/OAuth2 application for this Discord integration */ + FIELD_SNOWFLAKE(application_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_invite_create} */ +PUB_STRUCT(discord_invite_create) + /** the channel the invite is for */ + FIELD_SNOWFLAKE(channel_id) + /** the unique invite code */ + FIELD_PTR(code, char, *) + /** the time at which the invite was created */ + FIELD_TIMESTAMP(created_at) + /** the guild of the invite */ + FIELD_SNOWFLAKE(guild_id) + /** the user that created the invite */ + FIELD_STRUCT_PTR(inviter, discord_user, *) + /** how long the inviteis valid for (in seconds) */ + FIELD(max_age, int, 0) + /** the maximum number of times the invite can be used */ + FIELD(max_uses, int, 0) + /** the @ref discord_invite_target_types for this voice channel invite */ + FIELD_ENUM(target_type, discord_invite_target_types) + /** the user whose stream to display for this voice channel stream invite */ + FIELD_STRUCT_PTR(target_user, discord_user, *) + /** the embedded application to open for this voice channel embedded + * application invite*/ + FIELD_STRUCT_PTR(target_application, discord_application, *) + /** whether or not the invite is temporary (invited users will be kicked + * on disconnect unless they're assigned a role) */ + FIELD(temporary, bool, false) + /** how many times the invite has been used (always 0) */ + FIELD(uses, int, 0) +STRUCT_END + +/** @CCORD_pub_struct{discord_invite_delete} */ +PUB_STRUCT(discord_invite_delete) + /** the channel of the invite */ + FIELD_SNOWFLAKE(channel_id) + /** the guild of the invite */ + FIELD_SNOWFLAKE(guild_id) + /** the unique invite code */ + FIELD_PTR(code, char, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_message_delete} */ +PUB_STRUCT(discord_message_delete) + /** the id of the message */ + FIELD_SNOWFLAKE(id) + /** the id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_message_delete_bulk} */ +PUB_STRUCT(discord_message_delete_bulk) + /** the ids of the messages */ + FIELD_STRUCT_PTR(ids, snowflakes, *) + /** the id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_message_reaction_add} */ +PUB_STRUCT(discord_message_reaction_add) + /** the id of the user */ + FIELD_SNOWFLAKE(user_id) + /** the id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** the id of the message */ + FIELD_SNOWFLAKE(message_id) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the member who reacted if this happened in a guild */ + FIELD_STRUCT_PTR(member, discord_guild_member, *) + /** the emoji used to react */ + FIELD_STRUCT_PTR(emoji, discord_emoji, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_message_reaction_remove} */ +PUB_STRUCT(discord_message_reaction_remove) + /** the id of the user */ + FIELD_SNOWFLAKE(user_id) + /** the id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** the id of the message */ + FIELD_SNOWFLAKE(message_id) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the emoji used to react */ + FIELD_STRUCT_PTR(emoji, discord_emoji, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_message_reaction_remove_all} */ +PUB_STRUCT(discord_message_reaction_remove_all) + /** the id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** the id of the message */ + FIELD_SNOWFLAKE(message_id) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) +STRUCT_END + +/** @CCORD_pub_struct{discord_message_reaction_remove_emoji} */ +PUB_STRUCT(discord_message_reaction_remove_emoji) + /** the id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** the id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** the id of the message */ + FIELD_SNOWFLAKE(message_id) + /** the emoji that was removed */ + FIELD_STRUCT_PTR(emoji, discord_emoji, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_typing_start} */ +PUB_STRUCT(discord_typing_start) + /** id of the channel */ + FIELD_SNOWFLAKE(channel_id) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** id of the user */ + FIELD_SNOWFLAKE(user_id) + /** unix time (in seconds) of when the user started typing */ + FIELD_TIMESTAMP(timestamp) + /** the member who started typing if this happened in a guild */ + FIELD_STRUCT_PTR(member, discord_guild_member, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_voice_server_update} */ +PUB_STRUCT(discord_voice_server_update) + /** voice connection token */ + FIELD_PTR(token, char, *) + /** the guild this voice server update is for */ + FIELD_SNOWFLAKE(guild_id) + /** the voice server host */ + FIELD_PTR(endpoint, char, *) +STRUCT_END + +/** @CCORD_pub_struct{discord_webhooks_update} */ +PUB_STRUCT(discord_webhooks_update) + /** id of the guild */ + FIELD_SNOWFLAKE(guild_id) + /** id of the channel */ + FIELD_SNOWFLAKE(channel_id) +STRUCT_END + /** @CCORD_pub_struct{discord_session_start_limit} */ PUB_STRUCT(discord_session_start_limit) /** the total number of session starts the current user is allowed */ @@ -339,3 +744,5 @@ PUB_STRUCT(discord_session_start_limit) /** the number of identify requests allowed per 5 seconds */ FIELD(max_concurrency, int, 0) STRUCT_END + +#endif /* GENCODECS_ON_JSON_ENCODER */ diff --git a/gencodecs/api/guild.pre.h b/gencodecs/api/guild.pre.h index 2f6975739..b38e48b23 100644 --- a/gencodecs/api/guild.pre.h +++ b/gencodecs/api/guild.pre.h @@ -87,7 +87,7 @@ PUB_STRUCT(discord_guild) /** icon hash */ FIELD_PTR(icon, char, *) /** icon hash, returned when in the template object */ - COND_WRITE(this->icon_hash != NULL) + COND_WRITE(self->icon_hash != NULL) FIELD_PTR(icon_hash, char, *) COND_END /** splash hash */ @@ -99,7 +99,7 @@ PUB_STRUCT(discord_guild) /** id of owner */ FIELD_SNOWFLAKE(owner_id) /** total permissions for the user in the guild (excludes overwrites) */ - COND_WRITE(this->permissions != NULL) + COND_WRITE(self->permissions != NULL) FIELD_PTR(permissions, char, *) COND_END /** id of afk channel */ @@ -136,7 +136,7 @@ PUB_STRUCT(discord_guild) guidelines */ FIELD_SNOWFLAKE(rules_channel_id) /** when this guild was joined at */ - COND_WRITE(this->joined_at != 0) + COND_WRITE(self->joined_at != 0) FIELD_TIMESTAMP(joined_at) COND_END /** true if this is considered a large guild */ @@ -146,34 +146,34 @@ PUB_STRUCT(discord_guild) /** total number of members in this guild */ FIELD(member_count, int, 0) /** states of members currently in voice channels; lacks `guild_id` */ - COND_WRITE(this->voice_states != NULL) + COND_WRITE(self->voice_states != NULL) FIELD_STRUCT_PTR(voice_states, discord_voice_states, *) COND_END /** users in the guild */ - COND_WRITE(this->members != NULL) + COND_WRITE(self->members != NULL) FIELD_STRUCT_PTR(members, discord_guild_members, *) COND_END /** channels in the guild */ - COND_WRITE(this->channels != NULL) + COND_WRITE(self->channels != NULL) FIELD_STRUCT_PTR(channels, discord_channels, *) COND_END /** all active threads in the guild that current user has permission to view */ - COND_WRITE(this->threads != NULL) + COND_WRITE(self->threads != NULL) FIELD_STRUCT_PTR(threads, discord_channels, *) COND_END /** presences of the members in the guild, will only include non-offline members if the size is greater than `large threshold` */ - COND_WRITE(this->presences != NULL) + COND_WRITE(self->presences != NULL) FIELD_STRUCT_PTR(presences, discord_presence_updates, *) COND_END /** the maximum number of presences for the guild (null is always returned, apart from the largest of guilds) */ - COND_WRITE(this->max_presences != 0) + COND_WRITE(self->max_presences != 0) FIELD(max_presences, int, 0) COND_END /** the maximum number of members for the guild */ - COND_WRITE(this->max_members != 0) + COND_WRITE(self->max_members != 0) FIELD(max_members, int, 0) COND_END /** the vanity url code for the guild */ @@ -193,34 +193,34 @@ PUB_STRUCT(discord_guild) receive notices from Discord */ FIELD_SNOWFLAKE(public_updates_channel_id) /** the maximum amount of users in a video channel */ - COND_WRITE(this->max_video_channel_users != 0) + COND_WRITE(self->max_video_channel_users != 0) FIELD(max_video_channel_users, int, 0) COND_END /** approximate number of members in this guild */ - COND_WRITE(this->approximate_member_count != 0) + COND_WRITE(self->approximate_member_count != 0) FIELD(approximate_member_count, int, 0) COND_END /** approximate number of non-offline members in this guild */ - COND_WRITE(this->approximate_presence_count != 0) + COND_WRITE(self->approximate_presence_count != 0) FIELD(approximate_presence_count, int, 0) COND_END /** the welcome screen of a Community guild, shown to new members, returned in an invite's guild object */ - COND_WRITE(this->welcome_screen != NULL) + COND_WRITE(self->welcome_screen != NULL) FIELD_STRUCT_PTR(welcome_screen, discord_welcome_screen, *) COND_END /** guild NSFW level */ FIELD_ENUM(nsfw_level, discord_guild_nsfw_level) /** stage instances in the guild */ - COND_WRITE(this->stage_instances != NULL) + COND_WRITE(self->stage_instances != NULL) FIELD_STRUCT_PTR(stage_instances, discord_stage_instances, *) COND_END /** custom guild stickers */ - COND_WRITE(this->stickers != NULL) + COND_WRITE(self->stickers != NULL) FIELD_STRUCT_PTR(stickers, discord_stickers, *) COND_END /** the scheduled events in the guilds */ - COND_WRITE(this->guild_scheduled_events != NULL) + COND_WRITE(self->guild_scheduled_events != NULL) FIELD_STRUCT_PTR(guild_scheduled_events, discord_guild_scheduled_events, *) COND_END /** whether the guild has the boost progress bar enabled */ @@ -283,15 +283,15 @@ STRUCT_END /** @CCORD_pub_struct{discord_guild_member} */ PUB_STRUCT(discord_guild_member) /** the user this guild member represents */ - COND_WRITE(this->user != NULL) + COND_WRITE(self->user != NULL) FIELD_STRUCT_PTR(user, discord_user, *) COND_END /** this user's guild nickname */ - COND_WRITE(this->nick != NULL) + COND_WRITE(self->nick != NULL) FIELD_PTR(nick, char, *) COND_END /** the member's guild avatar hash */ - COND_WRITE(this->avatar != NULL) + COND_WRITE(self->avatar != NULL) FIELD_PTR(avatar, char, *) COND_END /** array of role object IDs */ @@ -299,7 +299,7 @@ PUB_STRUCT(discord_guild_member) /** when the user joined the guild */ FIELD_TIMESTAMP(joined_at) /** when the user started boosting the guild */ - COND_WRITE(this->premium_since != 0) + COND_WRITE(self->premium_since != 0) FIELD_TIMESTAMP(premium_since) COND_END /** whether the user is deafened in voice channels */ @@ -311,13 +311,15 @@ PUB_STRUCT(discord_guild_member) FIELD(pending, bool, false) /** total permission of the member in the channel, including overwrites, returned when in the interaction object */ - COND_WRITE(this->permissions != NULL) + COND_WRITE(self->permissions != NULL) FIELD_PTR(permissions, char, *) COND_END /** when the user's timeout will expire and the user will be able to communicate in the guild again, null or a time in the past if the user is not timed out */ FIELD_TIMESTAMP(communication_disabled_until) + /** the guild id @note extra field for `Guild Member Add` event */ + FIELD_SNOWFLAKE(guild_id) STRUCT_END /** @CCORD_pub_list{discord_guild_members} */ @@ -325,7 +327,8 @@ PUB_LIST(discord_guild_members) LISTTYPE_STRUCT(discord_guild_member) LIST_END -STRUCT(discord_integration) +/** @CCORD_pub_struct{discord_integration} */ +PUB_STRUCT(discord_integration) /** integration id */ FIELD_SNOWFLAKE(id) /** integration name */ @@ -357,6 +360,9 @@ STRUCT(discord_integration) FIELD(revoked, bool, false) /** the bot/OAuth2 application for discord integrations */ FIELD_STRUCT_PTR(application, discord_integration_application, *) + /** id of the guild @note extra field that may be included at + * `Integration Create` or `Integration Update` */ + FIELD_SNOWFLAKE(guild_id) STRUCT_END LIST(discord_integrations) @@ -382,7 +388,7 @@ STRUCT(discord_integration_application) /** the summary of the app */ FIELD_PTR(summary, char, *) /** the bot associated with this application */ - COND_WRITE(this->bot != NULL) + COND_WRITE(self->bot != NULL) FIELD_STRUCT_PTR(bot, discord_user, *) COND_END STRUCT_END @@ -405,7 +411,7 @@ PUB_STRUCT(discord_welcome_screen) /** the server description shown in the welcome screen */ FIELD_PTR(description, char, *) /** the channels shown in the welcome screen, up to 5 */ - COND_WRITE(this->welcome_channels != NULL) + COND_WRITE(self->welcome_channels != NULL) FIELD_STRUCT_PTR(welcome_channels, discord_welcome_screen_channels, *) COND_END STRUCT_END @@ -435,12 +441,12 @@ PUB_STRUCT(discord_create_guild) /** name of the guild (2-100 charaters) */ FIELD_PTR(name, char, *) /** voice region ID @deprecated deprecated field */ - COND_WRITE(this->region != NULL) + COND_WRITE(self->region != NULL) FIELD_PTR(region, char, *) COND_END /** base64 1024x1024 png/jpeg/gif image for the guild icon (can be animated gif when the server has the `ANIMATED_ICON` feature) */ - COND_WRITE(this->icon != NULL) + COND_WRITE(self->icon != NULL) FIELD_PTR(icon, char, *) COND_END /** verification level */ @@ -450,15 +456,15 @@ PUB_STRUCT(discord_create_guild) /** explicit content filter level */ FIELD_ENUM(explicit_content_filter, discord_explicit_content_filter_level) /** new guild roles */ - COND_WRITE(this->roles != NULL) + COND_WRITE(self->roles != NULL) FIELD_STRUCT_PTR(roles, discord_roles, *) COND_END /** new guild's channels */ - COND_WRITE(this->channels != NULL) + COND_WRITE(self->channels != NULL) FIELD_STRUCT_PTR(channels, discord_channels, *) COND_END /** ID for afk channel */ - COND_WRITE(this->afk_channel_id != 0) + COND_WRITE(self->afk_channel_id != 0) FIELD_SNOWFLAKE(afk_channel_id) COND_END /** afk timeout in seconds */ @@ -529,11 +535,11 @@ PUB_STRUCT(discord_create_guild_channel) /** channel topic (0-1024 characters) */ FIELD_PTR(topic, char, *) /** the bitrate (in bits) of the voice channel (voice only) */ - COND_WRITE(this->bitrate != 0) + COND_WRITE(self->bitrate != 0) FIELD(bitrate, int, 0) COND_END /** the user limit of the voice channel (voice only) */ - COND_WRITE(this->user_limit != 0) + COND_WRITE(self->user_limit != 0) FIELD(user_limit, int, 0) COND_END /** amount of seconds a user has to wait before sending another message @@ -545,7 +551,7 @@ PUB_STRUCT(discord_create_guild_channel) /** the channel's permission overwrites */ FIELD_STRUCT_PTR(permission_overwrites, discord_overwrites, *) /** ID of the parent category for a channel */ - COND_WRITE(this->parent_id != 0) + COND_WRITE(self->parent_id != 0) FIELD_SNOWFLAKE(parent_id) COND_END /** whether the channel is nsfw */ @@ -556,14 +562,14 @@ STRUCT(discord_modify_guild_channel_position) /** channel ID */ FIELD_SNOWFLAKE(id) /** sorting position of the channel */ - COND_WRITE(this->position != 0) + COND_WRITE(self->position != 0) FIELD(position, int, 0) COND_END /** syncs the permission overwrites with the new parent, if moving to a new category */ FIELD(lock_category, bool, false) /** the new parent ID for the channel that is moved */ - COND_WRITE(this->parent_id != 0) + COND_WRITE(self->parent_id != 0) FIELD_SNOWFLAKE(parent_id) COND_END STRUCT_END @@ -635,7 +641,7 @@ PUB_STRUCT(discord_modify_guild_member) to NULL to remove timeout. WIll throw a @ref CCORD_HTTP_ERROR (403) error if the user has the `ADMINISTRATOR` permission or is the owner of the guild */ - COND_WRITE(this->communication_disabled_until != 0) + COND_WRITE(self->communication_disabled_until != 0) FIELD_TIMESTAMP(communication_disabled_until) COND_END STRUCT_END @@ -643,7 +649,7 @@ STRUCT_END /** @CCORD_pub_struct{discord_modify_current_member} */ PUB_STRUCT(discord_modify_current_member) /** value to set user's nickname to */ - COND_WRITE(this->nick != NULL) + COND_WRITE(self->nick != NULL) FIELD_PTR(nick, char, *) COND_END STRUCT_END @@ -651,7 +657,7 @@ STRUCT_END /** @CCORD_pub_struct{discord_modify_current_user_nick} */ PUB_STRUCT(discord_modify_current_user_nick) /** value to set user's nickname to */ - COND_WRITE(this->nick != NULL) + COND_WRITE(self->nick != NULL) FIELD_PTR(nick, char, *) COND_END STRUCT_END @@ -659,11 +665,11 @@ STRUCT_END /** @CCORD_pub_struct{discord_create_guild_ban} */ PUB_STRUCT(discord_create_guild_ban) /** number of days to delete messages for (0-7) */ - COND_WRITE(this->delete_message_days >= 0 && this->delete_message_days <= 7) + COND_WRITE(self->delete_message_days >= 0 && self->delete_message_days <= 7) FIELD(delete_message_days, int, 0) COND_END /** reason for the ban @deprecated deprecated field */ - COND_WRITE(this->reason != NULL) + COND_WRITE(self->reason != NULL) FIELD_PTR(reason, char, *) COND_END STRUCT_END @@ -691,7 +697,7 @@ STRUCT(discord_modify_guild_role_position) /** role */ FIELD_SNOWFLAKE(id) /** sorting position of the role */ - COND_WRITE(this->position != 0) + COND_WRITE(self->position != 0) FIELD(position, int, 0) COND_END STRUCT_END @@ -723,7 +729,7 @@ STRUCT_END #if defined(GENCODECS_ON_STRUCT) STRUCT(discord_get_guild_prune_count) /** number of days to count prune for (1-30) */ - COND_WRITE(this->count != 0) + COND_WRITE(self->count != 0) FIELD(count, int, 7) COND_END /** role(s) to include */ @@ -734,7 +740,7 @@ STRUCT_END /** @CCORD_pub_struct{discord_begin_guild_prune} */ PUB_STRUCT(discord_begin_guild_prune) /** number of days to prune */ - COND_WRITE(this->days != 0) + COND_WRITE(self->days != 0) FIELD(days, int, 7) COND_END /** whether 'pruned' is returned, discouraged for large guilds */ @@ -742,7 +748,7 @@ PUB_STRUCT(discord_begin_guild_prune) /** role(s) to include */ FIELD_STRUCT_PTR(include_roles, snowflakes, *) /** reason for the prune @deprecated deprecated field */ - COND_WRITE(this->reason != NULL) + COND_WRITE(self->reason != NULL) FIELD_PTR(reason, char, *) COND_END STRUCT_END @@ -751,7 +757,7 @@ STRUCT_END STRUCT(discord_get_guild_widget_image) /** style of the widget image returned @see https://discord.com/developers/docs/resources/guild#membership-screening-object-widget-style-options */ - COND_WRITE(this->style != NULL) + COND_WRITE(self->style != NULL) FIELD_PTR(style, char, *) COND_END STRUCT_END @@ -764,7 +770,7 @@ PUB_STRUCT(discord_modify_guild_welcome_screen) /** channels linked in the welcome screen and their display options */ FIELD_STRUCT_PTR(welcome_channels, discord_welcome_screen_channels, *) /** the server description to show in the welcome screen */ - COND_WRITE(this->description != NULL) + COND_WRITE(self->description != NULL) FIELD_PTR(description, char, *) COND_END STRUCT_END @@ -777,7 +783,7 @@ PUB_STRUCT(discord_modify_current_user_voice_state) FIELD(suppress, bool, false) /* TODO: should be able to write `null` */ /** set the user's request to speak */ - COND_WRITE(this->request_to_speak_timestamp != 0) + COND_WRITE(self->request_to_speak_timestamp != 0) FIELD_TIMESTAMP(request_to_speak_timestamp) COND_END STRUCT_END diff --git a/gencodecs/api/guild_scheduled_event.pre.h b/gencodecs/api/guild_scheduled_event.pre.h index 93f667990..923726403 100644 --- a/gencodecs/api/guild_scheduled_event.pre.h +++ b/gencodecs/api/guild_scheduled_event.pre.h @@ -41,25 +41,25 @@ PUB_STRUCT(discord_guild_scheduled_event) @ref DISCORD_SCHEDULED_ENTITY_EXTERNAL */ FIELD_TIMESTAMP(scheduled_end_time) /** the privacy level of the scheduled event */ - COND_WRITE(this->privacy_level != 0) + COND_WRITE(self->privacy_level != 0) FIELD_ENUM(privacy_level, discord_guild_scheduled_event_privacy_level) COND_END /** the status of the scheduled event */ - COND_WRITE(this->status != 0) + COND_WRITE(self->status != 0) FIELD_ENUM(status, discord_guild_scheduled_event_status) COND_END /** the type of scheduled event */ - COND_WRITE(this->entity_type != 0) + COND_WRITE(self->entity_type != 0) FIELD_ENUM(entity_type, discord_guild_scheduled_event_entity_types) COND_END /** the ID of an entity associated with a guild scheduled event */ FIELD_SNOWFLAKE(entity_id) /** additional metadata for the guild scheduled event */ - COND_WRITE(this->entity_metadata != NULL) + COND_WRITE(self->entity_metadata != NULL) FIELD_STRUCT_PTR(entity_metadata, discord_guild_scheduled_event_entity_metadata, *) COND_END /** the user that created the scheduled event */ - COND_WRITE(this->creator != NULL) + COND_WRITE(self->creator != NULL) FIELD_STRUCT_PTR(creator, discord_user, *) COND_END /** the number of users subscribed to the scheduled event */ @@ -75,7 +75,7 @@ LIST_END STRUCT(discord_guild_scheduled_event_entity_metadata) /** location of the event (1-100 characters) */ - COND_WRITE(this->location != NULL) + COND_WRITE(self->location != NULL) FIELD_PTR(location, char, *) COND_END STRUCT_END @@ -84,12 +84,12 @@ STRUCT(discord_guild_scheduled_event_user) /** the scheduled event ID which the user subscribed to */ FIELD_SNOWFLAKE(guild_scheduled_event_id) /** user which subscribed to an event */ - COND_WRITE(this->user != NULL) + COND_WRITE(self->user != NULL) FIELD_STRUCT_PTR(user, discord_user, *) COND_END /** guild member data for this user for the guild which this event belongs to, if any */ - COND_WRITE(this->member != NULL) + COND_WRITE(self->member != NULL) FIELD_STRUCT_PTR(member, discord_guild_member, *) COND_END STRUCT_END @@ -107,33 +107,33 @@ STRUCT_END /** @CCORD_pub_struct{discord_create_guild_scheduled_event} */ PUB_STRUCT(discord_create_guild_scheduled_event) /** the channel ID of the scheduled event */ - COND_WRITE(this->channel_id != 0) + COND_WRITE(self->channel_id != 0) FIELD_SNOWFLAKE(channel_id) COND_END /** the entity metadata of the scheduled event */ - COND_WRITE(this->entity_metadata != NULL) + COND_WRITE(self->entity_metadata != NULL) FIELD_STRUCT_PTR(entity_metadata, discord_guild_scheduled_event_entity_metadata, *) COND_END /** the name of the scheduled event */ FIELD_PTR(name, char, *) /** the time the scheduled event will start */ - COND_WRITE(this->scheduled_start_time != 0) + COND_WRITE(self->scheduled_start_time != 0) FIELD_TIMESTAMP(scheduled_start_time) COND_END /** the time the scheduled event will end */ - COND_WRITE(this->scheduled_end_time != 0) + COND_WRITE(self->scheduled_end_time != 0) FIELD_TIMESTAMP(scheduled_end_time) COND_END /** the description of the scheduled event */ - COND_WRITE(this->description != NULL) + COND_WRITE(self->description != NULL) FIELD_PTR(description, char, *) COND_END /** the entity type of the scheduled event */ - COND_WRITE(this->entity_type != 0) + COND_WRITE(self->entity_type != 0) FIELD_ENUM(entity_type, discord_guild_scheduled_event_entity_types) COND_END /** the cover image of the scheduled event */ - COND_WRITE(this->image != NULL) + COND_WRITE(self->image != NULL) FIELD_PTR(image, char, *) COND_END STRUCT_END @@ -147,37 +147,37 @@ STRUCT_END /** @CCORD_pub_struct{discord_modify_guild_scheduled_event} */ PUB_STRUCT(discord_modify_guild_scheduled_event) /** the channel ID of the scheduled event */ - COND_WRITE(this->channel_id != 0) + COND_WRITE(self->channel_id != 0) FIELD_SNOWFLAKE(channel_id) COND_END /** the entity metadata of the scheduled event */ - COND_WRITE(this->entity_metadata != NULL) + COND_WRITE(self->entity_metadata != NULL) FIELD_STRUCT_PTR(entity_metadata, discord_guild_scheduled_event_entity_metadata, *) COND_END /** the name of the scheduled event */ FIELD_PTR(name, char, *) /** the time the scheduled event will start */ - COND_WRITE(this->scheduled_start_time != 0) + COND_WRITE(self->scheduled_start_time != 0) FIELD_TIMESTAMP(scheduled_start_time) COND_END /** the time the scheduled event will end */ - COND_WRITE(this->scheduled_end_time != 0) + COND_WRITE(self->scheduled_end_time != 0) FIELD_TIMESTAMP(scheduled_end_time) COND_END /** the description of the scheduled event */ - COND_WRITE(this->description != NULL) + COND_WRITE(self->description != NULL) FIELD_PTR(description, char, *) COND_END /** the entity type of the scheduled event */ - COND_WRITE(this->entity_type != 0) + COND_WRITE(self->entity_type != 0) FIELD_ENUM(entity_type, discord_guild_scheduled_event_entity_types) COND_END /** the status of the scheduled event */ - COND_WRITE(this->status != 0) + COND_WRITE(self->status != 0) FIELD_ENUM(status, discord_guild_scheduled_event_status) COND_END /** the cover image of the scheduled event */ - COND_WRITE(this->image != NULL) + COND_WRITE(self->image != NULL) FIELD_PTR(image, char, *) COND_END STRUCT_END @@ -189,11 +189,11 @@ PUB_STRUCT(discord_get_guild_scheduled_event_users) /** include guild member data if exists */ FIELD(with_member, bool, false) /** consider only users before given user ID */ - COND_WRITE(this->before != 0) + COND_WRITE(self->before != 0) FIELD_SNOWFLAKE(before) COND_END /** consider only users after given user ID */ - COND_WRITE(this->after != 0) + COND_WRITE(self->after != 0) FIELD_SNOWFLAKE(after) COND_END STRUCT_END diff --git a/gencodecs/api/guild_template.pre.h b/gencodecs/api/guild_template.pre.h index 149286b0e..36730fb97 100644 --- a/gencodecs/api/guild_template.pre.h +++ b/gencodecs/api/guild_template.pre.h @@ -37,7 +37,7 @@ PUB_STRUCT(discord_create_guild_from_guild_template) /** name of the guild (2-100 characters) */ FIELD_PTR(name, char, *) /** base64 128x128 image for the guild icon */ - COND_WRITE(this->icon != NULL) + COND_WRITE(self->icon != NULL) FIELD_PTR(icon, char, *) COND_END STRUCT_END @@ -47,7 +47,7 @@ PUB_STRUCT(discord_create_guild_template) /** name of the template (1-100 characters) */ FIELD_PTR(name, char, *) /** description for the template (0-120 characters) */ - COND_WRITE(this->description != NULL) + COND_WRITE(self->description != NULL) FIELD_PTR(description, char, *) COND_END STRUCT_END @@ -55,11 +55,11 @@ STRUCT_END /** @CCORD_pub_struct{discord_modify_guild_template} */ PUB_STRUCT(discord_modify_guild_template) /** name of the template (1-100 characters) */ - COND_WRITE(this->name != NULL) + COND_WRITE(self->name != NULL) FIELD_PTR(name, char, *) COND_END /** description for the template (0-120 characters) */ - COND_WRITE(this->description != NULL) + COND_WRITE(self->description != NULL) FIELD_PTR(description, char, *) COND_END STRUCT_END diff --git a/gencodecs/api/interactions.pre.h b/gencodecs/api/interactions.pre.h index 5359a6fd4..c13214957 100644 --- a/gencodecs/api/interactions.pre.h +++ b/gencodecs/api/interactions.pre.h @@ -115,37 +115,37 @@ PUB_STRUCT(discord_interaction_response) /** interaction callback type */ FIELD_ENUM(type, discord_interaction_callback_types) /** an optional response message */ - COND_WRITE(this->data != NULL) + COND_WRITE(self->data != NULL) FIELD_STRUCT_PTR(data, discord_interaction_callback_data, *) COND_END STRUCT_END STRUCT(discord_interaction_callback_data) /** message components */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /* MESSAGES */ /** is the response TTS */ - COND_WRITE(this->tts != false) + COND_WRITE(self->tts != false) FIELD(tts, bool, false) COND_END /** message content */ - COND_WRITE(this->content != NULL) + COND_WRITE(self->content != NULL) FIELD_PTR(content, char, *) COND_END /** supports up to 10 embeds */ - COND_WRITE(this->embeds != NULL) + COND_WRITE(self->embeds != NULL) FIELD_STRUCT_PTR(embeds, discord_embeds, *) COND_END /** @ref DiscordAPIChannelMessageFlags combined as a bitfield (only @ref DISCORD_MESSAGE_SUPRESS_EMBEDS and @ref DISCORD_MESSAGE_EPHEMERAL can be set) */ - COND_WRITE(this->flags != 0) + COND_WRITE(self->flags != 0) FIELD_BITMASK(flags) COND_END /** attachment objects with filename and description */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END /* AUTOCOMPLETE */ @@ -174,19 +174,19 @@ PUB_STRUCT(discord_edit_original_interaction_response) /** the message contents (up to 2000 characters) */ FIELD_PTR(content, char, *) /** embedded `rich` content */ - COND_WRITE(this->embeds != NULL) + COND_WRITE(self->embeds != NULL) FIELD_STRUCT_PTR(embeds, discord_embeds, *) COND_END /** allowed mentions for the message */ - COND_WRITE(this->allowed_mentions != NULL) + COND_WRITE(self->allowed_mentions != NULL) FIELD_STRUCT_PTR(allowed_mentions, discord_allowed_mention, *) COND_END /** the components to include with the message */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** attached files to keep and possible descriptions for new files */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END STRUCT_END @@ -210,24 +210,24 @@ PUB_STRUCT(discord_create_followup_message) /** true if this is a TTS message */ FIELD(tts, bool, false) /** embedded `rich` content */ - COND_WRITE(this->embeds != NULL) + COND_WRITE(self->embeds != NULL) FIELD_STRUCT_PTR(embeds, discord_embeds, *) COND_END /** allowed mentions for the message */ - COND_WRITE(this->allowed_mentions != NULL) + COND_WRITE(self->allowed_mentions != NULL) FIELD_STRUCT_PTR(allowed_mentions, discord_allowed_mention, *) COND_END /** the components to include with the message */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** attachment objects with filename and description */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END /** @ref DiscordAPIChannelMessageFlags combined as a bitfield (only `SUPPRESS_EMBEDS` can be set) */ - COND_WRITE(this->flags != 0) + COND_WRITE(self->flags != 0) FIELD_BITMASK(flags) COND_END STRUCT_END @@ -244,19 +244,19 @@ PUB_STRUCT(discord_edit_followup_message) /** the message contents (up to 2000 characters) */ FIELD_PTR(content, char, *) /** embedded `rich` content */ - COND_WRITE(this->embeds != NULL) + COND_WRITE(self->embeds != NULL) FIELD_STRUCT_PTR(embeds, discord_embeds, *) COND_END /** allowed mentions for the message */ - COND_WRITE(this->allowed_mentions != NULL) + COND_WRITE(self->allowed_mentions != NULL) FIELD_STRUCT_PTR(allowed_mentions, discord_allowed_mention, *) COND_END /** the components to include with the message */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** attached files to keep and possible descriptions for new files */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END STRUCT_END diff --git a/gencodecs/api/invite.pre.h b/gencodecs/api/invite.pre.h index ce9966f35..7a741dae3 100644 --- a/gencodecs/api/invite.pre.h +++ b/gencodecs/api/invite.pre.h @@ -12,26 +12,26 @@ PUB_STRUCT(discord_invite) /** the invite code (unique ID) */ FIELD_PTR(code, char, *) /** the guild this invite is for */ - COND_WRITE(this->guild != NULL) + COND_WRITE(self->guild != NULL) FIELD_STRUCT_PTR(guild, discord_guild, *) COND_END /** the channel this invite is for */ FIELD_STRUCT_PTR(channel, discord_channel, *) /** the user who created the invite */ - COND_WRITE(this->inviter != NULL) + COND_WRITE(self->inviter != NULL) FIELD_STRUCT_PTR(inviter, discord_user, *) COND_END /** the type of target for this voice channel invite */ - COND_WRITE(this->target_type != 0) + COND_WRITE(self->target_type != 0) FIELD_ENUM(target_type, discord_invite_target_types) COND_END /** the user whose stream to display for this voice channel stream invite */ - COND_WRITE(this->target_user != NULL) + COND_WRITE(self->target_user != NULL) FIELD_STRUCT_PTR(target_user, discord_user, *) COND_END /** the embedded application to open for this voice channel embedded application invite */ - COND_WRITE(this->target_application != NULL) + COND_WRITE(self->target_application != NULL) FIELD_STRUCT_PTR(target_application, discord_application, *) COND_END /** approximate count of online members */ @@ -40,17 +40,17 @@ PUB_STRUCT(discord_invite) FIELD(approximate_member_count, int, 0) /* TODO: nullable */ /** the expiration date of this invite */ - COND_WRITE(this->expires_at != 0) + COND_WRITE(self->expires_at != 0) FIELD_TIMESTAMP(expires_at) COND_END /** stage instance data if there is a public stage instance in the stage channel this invite is for */ - COND_WRITE(this->stage_instance != NULL) + COND_WRITE(self->stage_instance != NULL) FIELD_STRUCT_PTR(stage_instance, discord_invite_stage_instance, *) COND_END /** guild scheduled event data, only included if `guild_scheduled_event_id` contains a valid guild scheduled event ID */ - COND_WRITE(this->guild_scheduled_event != NULL) + COND_WRITE(self->guild_scheduled_event != NULL) FIELD_STRUCT_PTR(guild_scheduled_event, discord_guild_scheduled_event, *) COND_END STRUCT_END @@ -70,14 +70,14 @@ STRUCT(discord_invite_metadata) /** whether this invite only grants temporary membership */ FIELD(temporary, bool, false) /** when this invite was created */ - COND_WRITE(this->created_at != 0) + COND_WRITE(self->created_at != 0) FIELD_TIMESTAMP(created_at) COND_END STRUCT_END STRUCT(discord_invite_stage_instance) /** the members speaking in the Stage */ - COND_WRITE(this->members != NULL) + COND_WRITE(self->members != NULL) FIELD_STRUCT_PTR(members, discord_guild_members, *) COND_END /** the number of users in the Stage */ @@ -99,7 +99,7 @@ PUB_STRUCT(discord_get_invite) /** whether the invite should contain the expiration date */ FIELD(with_expiration, bool, false) /** the guild scheduled event to include with the invite */ - COND_WRITE(this->guild_scheduled_event_id != 0) + COND_WRITE(self->guild_scheduled_event_id != 0) FIELD_SNOWFLAKE(guild_scheduled_event_id) COND_END STRUCT_END diff --git a/gencodecs/api/message_components.pre.h b/gencodecs/api/message_components.pre.h index 0c7382339..7e1867924 100644 --- a/gencodecs/api/message_components.pre.h +++ b/gencodecs/api/message_components.pre.h @@ -35,7 +35,7 @@ ENUM_END /** @CCORD_pub_struct{discord_component} */ PUB_STRUCT(discord_component) /** component type */ - COND_WRITE(this->type != 0) + COND_WRITE(self->type != 0) FIELD_ENUM(type, discord_component_types) COND_END /** a developer-defined identifier for the component, max 100 characters */ @@ -43,34 +43,34 @@ PUB_STRUCT(discord_component) /** whether the component is disabled, default `false` */ FIELD(disabled, bool, false) /** one of button or text styles */ - COND_WRITE(this->style != 0) + COND_WRITE(self->style != 0) FIELD_ENUM(style, discord_component_styles) COND_END /** text that appears on the button, max 80 characters */ FIELD_PTR(label, char, *) /** `name`, `id`, and `animated` */ - COND_WRITE(this->emoji != NULL) + COND_WRITE(self->emoji != NULL) FIELD_STRUCT_PTR(emoji, discord_emoji, *) COND_END /** a url for link-style buttons */ FIELD_PTR(url, char, *) /** the choices in the select, max 25 */ - COND_WRITE(this->options != NULL) + COND_WRITE(self->options != NULL) FIELD_STRUCT_PTR(options, discord_select_options, *) COND_END /** custom placeholder text if nothing is selected, max 100 characters */ FIELD_PTR(placeholder, char, *) /** the minimum number of items that must be chosen: default 1, min 0, max 25 */ - COND_WRITE(this->min_values >= 0 && this->max_values <= 25) + COND_WRITE(self->min_values >= 0 && self->max_values <= 25) FIELD(min_values, int, 1) COND_END /** the maximum number of items that must be chosen: default 1, max 25 */ - COND_WRITE(this->max_values <= 25) + COND_WRITE(self->max_values <= 25) FIELD(max_values, int, 1) COND_END /** a list of child components */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** the minimum input length for a text input */ @@ -96,7 +96,7 @@ STRUCT(discord_select_option) /** an additional description of the option, max 100 characters */ FIELD_PTR(description, char, *) /** `id`, `name`, and `animated` */ - COND_WRITE(this->emoji != NULL) + COND_WRITE(self->emoji != NULL) FIELD_STRUCT_PTR(emoji, discord_emoji, *) COND_END /** will render this option as selected by default */ diff --git a/gencodecs/api/permissions.pre.h b/gencodecs/api/permissions.pre.h index 8520ba2f9..4176486fc 100644 --- a/gencodecs/api/permissions.pre.h +++ b/gencodecs/api/permissions.pre.h @@ -111,11 +111,11 @@ PUB_STRUCT(discord_role) /** if this role is pinned in the user listing */ FIELD(hoist, bool, false) /** role icon hash */ - COND_WRITE(this->icon != NULL) + COND_WRITE(self->icon != NULL) FIELD_PTR(icon, char, *) COND_END /** role unicode emoji */ - COND_WRITE(this->unicode_emoji != NULL) + COND_WRITE(self->unicode_emoji != NULL) FIELD_PTR(unicode_emoji, char, *) COND_END /** position of this role */ @@ -127,7 +127,7 @@ PUB_STRUCT(discord_role) /** whether this roleis mentionable */ FIELD(mentionable, bool, false) /** the tags this role has */ - COND_WRITE(this->tags != NULL) + COND_WRITE(self->tags != NULL) FIELD_STRUCT_PTR(tags, discord_role_tag, *) COND_END STRUCT_END @@ -139,11 +139,11 @@ LIST_END STRUCT(discord_role_tag) /** the id of the bot this role belongs to */ - COND_WRITE(this->bot_id != 0) + COND_WRITE(self->bot_id != 0) FIELD_SNOWFLAKE(bot_id) COND_END /** the id of the integration this role belongs to */ - COND_WRITE(this->integration_id != 0) + COND_WRITE(self->integration_id != 0) FIELD_SNOWFLAKE(integration_id) COND_END /** whether this is the guild's premium subscribe role */ diff --git a/gencodecs/api/stage_instance.pre.h b/gencodecs/api/stage_instance.pre.h index db7d609bc..7f08316f6 100644 --- a/gencodecs/api/stage_instance.pre.h +++ b/gencodecs/api/stage_instance.pre.h @@ -20,7 +20,7 @@ PUB_STRUCT(discord_stage_instance) /** the topic of the Stage instance (1-120 characters) */ FIELD_PTR(topic, char, *) /** the privacy level of the stage instance */ - COND_WRITE(this->privacy_level != 0) + COND_WRITE(self->privacy_level != 0) FIELD_ENUM(privacy_level, discord_privacy_level) COND_END /** whether or not stage discovery is disabled @deprecated deprecated field */ @@ -42,7 +42,7 @@ PUB_STRUCT(discord_create_stage_instance) /** the topic of the Stage instance (1-120 characters) */ FIELD_PTR(topic, char, *) /** the privacy level of the stage instance */ - COND_WRITE(this->privacy_level != 0) + COND_WRITE(self->privacy_level != 0) FIELD_ENUM(privacy_level, discord_privacy_level) COND_END STRUCT_END @@ -52,7 +52,7 @@ PUB_STRUCT(discord_modify_stage_instance) /** the topic of the Stage instance (1-120 characters) */ FIELD_PTR(topic, char, *) /** the privacy level of the stage instance */ - COND_WRITE(this->privacy_level != 0) + COND_WRITE(self->privacy_level != 0) FIELD_ENUM(privacy_level, discord_privacy_level) COND_END STRUCT_END diff --git a/gencodecs/api/sticker.pre.h b/gencodecs/api/sticker.pre.h index 0278f9082..31b4c37ee 100644 --- a/gencodecs/api/sticker.pre.h +++ b/gencodecs/api/sticker.pre.h @@ -21,7 +21,7 @@ PUB_STRUCT(discord_sticker) /** ID of the sticker */ FIELD_SNOWFLAKE(id) /** for standard stickers, ID of the pack the sticker is from */ - COND_WRITE(this->pack_id != 0) + COND_WRITE(self->pack_id != 0) FIELD_SNOWFLAKE(pack_id) COND_END /** name of the sticker */ @@ -31,22 +31,22 @@ PUB_STRUCT(discord_sticker) /** autocomplete/suggestion tags for the sticker (max 200 characters) */ FIELD_PTR(tags, char, *) /** type of sticker */ - COND_WRITE(this->type != 0) + COND_WRITE(self->type != 0) FIELD_ENUM(type, discord_sticker_types) COND_END /** type of sticker format */ - COND_WRITE(this->format_type != 0) + COND_WRITE(self->format_type != 0) FIELD_ENUM(format_type, discord_sticker_format_types) COND_END /** whether this guild sticker can be used, may be false due to loss of Server Boosts */ FIELD(available, bool, false) /** ID of the guild that owns this sticker */ - COND_WRITE(this->guild_id != 0) + COND_WRITE(self->guild_id != 0) FIELD_SNOWFLAKE(guild_id) COND_END /** the user that uploaded the guild sticker */ - COND_WRITE(this->user != NULL) + COND_WRITE(self->user != NULL) FIELD_STRUCT_PTR(user, discord_user, *) COND_END /** the standard sticker's sort order within its pack */ @@ -63,7 +63,7 @@ STRUCT(discord_sticker_item) /** name of the sticker */ FIELD_PTR(name, char, *) /** type of sticker format */ - COND_WRITE(this->format_type != 0) + COND_WRITE(self->format_type != 0) FIELD_ENUM(format_type, discord_sticker_format_types) COND_END STRUCT_END @@ -76,7 +76,7 @@ STRUCT(discord_sticker_pack) /** ID of the sticker */ FIELD_SNOWFLAKE(id) /** the stickers in the pack */ - COND_WRITE(this->stickers != NULL) + COND_WRITE(self->stickers != NULL) FIELD_STRUCT_PTR(stickers, discord_stickers, *) COND_END /** name of the sticker pack */ @@ -84,13 +84,13 @@ STRUCT(discord_sticker_pack) /** ID of the pack's SKU */ FIELD_SNOWFLAKE(sku_id) /** ID of a sticker in the pack which is shown as the pack's icon */ - COND_WRITE(this->cover_sticker_id != 0) + COND_WRITE(self->cover_sticker_id != 0) FIELD_SNOWFLAKE(cover_sticker_id) COND_END /** description of the sticker pack */ FIELD_PTR(description, char, *) /** ID of the sticker pack's banner image */ - COND_WRITE(this->banner_asset_id != 0) + COND_WRITE(self->banner_asset_id != 0) FIELD_SNOWFLAKE(banner_asset_id) COND_END STRUCT_END diff --git a/gencodecs/api/teams.pre.h b/gencodecs/api/teams.pre.h index ffe137055..ca63efd28 100644 --- a/gencodecs/api/teams.pre.h +++ b/gencodecs/api/teams.pre.h @@ -14,7 +14,7 @@ PUB_STRUCT(discord_team) /** the unique ID of the team */ FIELD_SNOWFLAKE(id) /** the members of the team */ - COND_WRITE(this->members != NULL) + COND_WRITE(self->members != NULL) FIELD_STRUCT_PTR(members, discord_team_members, *) COND_END /** the name of the team */ @@ -27,13 +27,13 @@ STRUCT(discord_team_member) /** the user's membership state on the team */ FIELD_ENUM(membership_state, discord_membership_state) /** will always be \"[\"*\"]\" */ - COND_WRITE(this->permissions != NULL) + COND_WRITE(self->permissions != NULL) FIELD_STRUCT_PTR(permissions, strings, *) COND_END /** the ID of the parent team of which they are a member */ FIELD_SNOWFLAKE(team_id) /** the avatar, discriminator, id,and username of the user */ - COND_WRITE(this->user != NULL) + COND_WRITE(self->user != NULL) FIELD_STRUCT_PTR(user, discord_user, *) COND_END STRUCT_END diff --git a/gencodecs/api/user.pre.h b/gencodecs/api/user.pre.h index 497fd22fb..4ef4e8ee8 100644 --- a/gencodecs/api/user.pre.h +++ b/gencodecs/api/user.pre.h @@ -106,7 +106,7 @@ STRUCT(discord_connection) /** whether the connection is revoked */ FIELD(revoked, bool, false) /** an array of partial server integrations */ - COND_WRITE(this->integrations != NULL) + COND_WRITE(self->integrations != NULL) FIELD_STRUCT_PTR(integrations, discord_integrations, *) COND_END /** whether the connection is verified */ @@ -133,11 +133,11 @@ LIST_END PUB_STRUCT(discord_modify_current_user) /** user's username, if changed may cause the user's discriminator to be randomized */ - COND_WRITE(this->username != NULL) + COND_WRITE(self->username != NULL) FIELD_PTR(username, char, *) COND_END /** if passed, modified the user's avatar */ - COND_WRITE(this->avatar != NULL) + COND_WRITE(self->avatar != NULL) FIELD_PTR(avatar, char, *) COND_END STRUCT_END @@ -145,15 +145,15 @@ STRUCT_END #if defined(GENCODECS_ON_STRUCT) STRUCT(discord_get_current_user_guilds) /** get guilds before this guild ID */ - COND_WRITE(this->before != 0) + COND_WRITE(self->before != 0) FIELD_SNOWFLAKE(before) COND_END /** get guilds after this guild ID */ - COND_WRITE(this->after != 0) + COND_WRITE(self->after != 0) FIELD_SNOWFLAKE(after) COND_END /** max number of guilds to return (1-200) */ - COND_WRITE(this->limit >= 1 && this->limit <= 200) + COND_WRITE(self->limit >= 1 && self->limit <= 200) FIELD(limit, int, 200) COND_END STRUCT_END @@ -162,7 +162,7 @@ STRUCT_END /** @CCORD_pub_struct{discord_create_dm} */ PUB_STRUCT(discord_create_dm) /** the recipient to open a DM channel with */ - COND_WRITE(this->recipient_id != 0) + COND_WRITE(self->recipient_id != 0) FIELD_SNOWFLAKE(recipient_id) COND_END STRUCT_END @@ -170,11 +170,11 @@ STRUCT_END /** @CCORD_pub_struct{discord_create_group_dm} */ PUB_STRUCT(discord_create_group_dm) /** access tokens of users that have grantes your app `gdm.join` scope */ - COND_WRITE(this->access_tokens != NULL) + COND_WRITE(self->access_tokens != NULL) FIELD_STRUCT_PTR(access_tokens, snowflakes, *) COND_END /** a dictionary of user IDs to their respective nicknames */ - COND_WRITE(this->nicks != NULL) + COND_WRITE(self->nicks != NULL) FIELD_STRUCT_PTR(nicks, strings, *) COND_END STRUCT_END diff --git a/gencodecs/api/voice.pre.h b/gencodecs/api/voice.pre.h index c61a0a597..fbef27ad5 100644 --- a/gencodecs/api/voice.pre.h +++ b/gencodecs/api/voice.pre.h @@ -30,7 +30,7 @@ PUB_STRUCT(discord_voice_state) FIELD(suppress, bool, false) /* TODO: nullable */ /** the time at which the user requested to speak */ - COND_WRITE(this->request_to_speak_timestamp) + COND_WRITE(self->request_to_speak_timestamp) FIELD_TIMESTAMP(request_to_speak_timestamp) COND_END STRUCT_END diff --git a/gencodecs/api/webhook.pre.h b/gencodecs/api/webhook.pre.h index 5f38f176f..d57102542 100644 --- a/gencodecs/api/webhook.pre.h +++ b/gencodecs/api/webhook.pre.h @@ -17,7 +17,7 @@ PUB_STRUCT(discord_webhook) /** the ID of the webhook */ FIELD_SNOWFLAKE(id) /** the type of the webhook */ - COND_WRITE(this->type != 0) + COND_WRITE(self->type != 0) FIELD_ENUM(type, discord_webhook_types) COND_END /** the guild ID this webhook is for, if any */ @@ -26,7 +26,7 @@ PUB_STRUCT(discord_webhook) FIELD_SNOWFLAKE(channel_id) /** the user this webhook was created by (not returned when getting a webhook with its token) */ - COND_WRITE(this->user != NULL) + COND_WRITE(self->user != NULL) FIELD_STRUCT_PTR(user, discord_user, *) COND_END /** the default name of the webhook */ @@ -37,7 +37,7 @@ PUB_STRUCT(discord_webhook) FIELD_SNOWFLAKE(application_id) /** the guild of the channel that this webhook is following (returned for Channel Follower Webhooks) */ - COND_WRITE(this->source_channel != NULL) + COND_WRITE(self->source_channel != NULL) FIELD_STRUCT_PTR(source_channel, discord_channel, *) COND_END /** the url used for executing the webhook (returned by the webhooks @@ -60,7 +60,7 @@ PUB_STRUCT(discord_create_webhook) FIELD_PTR(name, char, *) /* TODO: base64 conv */ /** image for the default webhook avatar */ - COND_WRITE(this->avatar != NULL) + COND_WRITE(self->avatar != NULL) FIELD_PTR(avatar, char, *) COND_END STRUCT_END @@ -71,7 +71,7 @@ PUB_STRUCT(discord_modify_webhook) FIELD_PTR(name, char, *) /* TODO: base64 conv */ /** image for the default webhook avatar */ - COND_WRITE(this->avatar != NULL) + COND_WRITE(self->avatar != NULL) FIELD_PTR(avatar, char, *) COND_END /** the new channel ID for this webhook should be moved to */ @@ -84,7 +84,7 @@ PUB_STRUCT(discord_modify_webhook_with_token) FIELD_PTR(name, char, *) /* TODO: base64 conv */ /** image for the default webhook avatar */ - COND_WRITE(this->avatar != NULL) + COND_WRITE(self->avatar != NULL) FIELD_PTR(avatar, char, *) COND_END STRUCT_END @@ -112,24 +112,24 @@ PUB_STRUCT(discord_execute_webhook) /** true if this is a TTS message */ FIELD(tts, bool, false) /** embedded `rich` content */ - COND_WRITE(this->embeds != NULL) + COND_WRITE(self->embeds != NULL) FIELD_STRUCT_PTR(embeds, discord_embeds, *) COND_END /** allowed mentions for the message */ - COND_WRITE(this->allowed_mentions != NULL) + COND_WRITE(self->allowed_mentions != NULL) FIELD_STRUCT_PTR(allowed_mentions, discord_allowed_mention, *) COND_END /** the components to include with the message */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** attachment objects with filename and description */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END /** @ref DiscordAPIChannelMessageFlags combined as a bitfield (only `SUPPRESS_EMBEDS` can be set) */ - COND_WRITE(this->flags != 0) + COND_WRITE(self->flags != 0) FIELD_BITMASK(flags) COND_END STRUCT_END @@ -137,7 +137,7 @@ STRUCT_END #if defined(GENCODECS_ON_STRUCT) STRUCT(discord_get_webhook_message) /** ID of the thread the message is in */ - COND_WRITE(this->thread_id != 0) + COND_WRITE(self->thread_id != 0) FIELD_SNOWFLAKE(thread_id) COND_END STRUCT_END @@ -155,19 +155,19 @@ PUB_STRUCT(discord_edit_webhook_message) /** the message contents (up to 2000 characters) */ FIELD_PTR(content, char, *) /** embedded `rich` content */ - COND_WRITE(this->embeds != NULL) + COND_WRITE(self->embeds != NULL) FIELD_STRUCT_PTR(embeds, discord_embeds, *) COND_END /** allowed mentions for the message */ - COND_WRITE(this->allowed_mentions != NULL) + COND_WRITE(self->allowed_mentions != NULL) FIELD_STRUCT_PTR(allowed_mentions, discord_allowed_mention, *) COND_END /** the components to include with the message */ - COND_WRITE(this->components != NULL) + COND_WRITE(self->components != NULL) FIELD_STRUCT_PTR(components, discord_components, *) COND_END /** attached files to keep and possible descriptions for new files */ - COND_WRITE(this->attachments != NULL) + COND_WRITE(self->attachments != NULL) FIELD_STRUCT_PTR(attachments, discord_attachments, *) COND_END STRUCT_END @@ -175,7 +175,7 @@ STRUCT_END #if defined(GENCODECS_ON_STRUCT) STRUCT(discord_delete_webhook_message) /** ID of the thread the message is in */ - COND_WRITE(this->thread_id != 0) + COND_WRITE(self->thread_id != 0) FIELD_SNOWFLAKE(thread_id) COND_END STRUCT_END diff --git a/gencodecs/recipes/json-decoder.h b/gencodecs/recipes/json-decoder.h index 3dbadcd91..86e220b5b 100644 --- a/gencodecs/recipes/json-decoder.h +++ b/gencodecs/recipes/json-decoder.h @@ -28,9 +28,9 @@ #define GENCODECS_PUB_STRUCT(_type) \ long _type##_from_jsmnf(jsmnf_pair *root, const char *js, \ - struct _type *this); \ + struct _type *self); \ size_t _type##_from_json(const char buf[], size_t size, \ - struct _type *this); + struct _type *self); #define GENCODECS_PUB_LIST(_type) GENCODECS_PUB_STRUCT(_type) #include "gencodecs-gen.pre.h" @@ -39,7 +39,7 @@ #define GENCODECS_STRUCT(_type) \ static long _type##_from_jsmnf(jsmnf_pair *root, const char *js, \ - struct _type *this); + struct _type *self); #define GENCODECS_LIST(_type) GENCODECS_STRUCT(_type) #include "gencodecs-gen.pre.h" @@ -48,7 +48,7 @@ #define GENCODECS_PUB_STRUCT(_type) \ long _type##_from_jsmnf(jsmnf_pair *root, const char *js, \ - struct _type *this) \ + struct _type *self) \ { \ jsmnf_pair *f; \ long ret = 0; @@ -57,49 +57,49 @@ #define GENCODECS_FIELD_CUSTOM(_name, _key, _type, _decor, _init, _cleanup, \ _encoder, _decoder, _default_value) \ f = jsmnf_find(root, js, _key, sizeof(_key) - 1); \ - _decoder(f, js, this->_name, _type); + _decoder(f, js, self->_name, _type); #define GENCODECS_FIELD_PRINTF(_name, _type, _printf_type, _scanf_type) \ f = jsmnf_find(root, js, #_name, sizeof(#_name) - 1); \ - if (f) sscanf(js + f->v.pos, _scanf_type, &this->_name); + if (f) sscanf(js + f->v.pos, _scanf_type, &self->_name); #define GENCODECS_STRUCT_END \ return ret; \ } #define GENCODECS_PUB_LIST(_type) \ long _type##_from_jsmnf(jsmnf_pair *root, const char *js, \ - struct _type *this) \ + struct _type *self) \ { \ - long ret = sizeof *this * root->size; \ + long ret = sizeof *self * root->size; \ int i; \ if (!ret) return 0; #define GENCODECS_LIST(_type) \ static GENCODECS_PUB_LIST(_type) #define GENCODECS_LISTTYPE(_type) \ - __carray_init(this, root->size, _type, , ); \ + __carray_init(self, root->size, _type, , ); \ for (i = 0; i < root->size; ++i) { \ jsmnf_pair *f = root->fields + i; \ _type o; \ GENCODECS_JSON_DECODER_##_type(f, js, o, _type); \ - carray_insert(this, i, o); \ + carray_insert(self, i, o); \ } #define GENCODECS_LISTTYPE_STRUCT(_type) \ - __carray_init(this, root->size, struct _type, , ); \ + __carray_init(self, root->size, struct _type, , ); \ for (i = 0; i < root->size; ++i) { \ jsmnf_pair *f = root->fields + i; \ struct _type o = { 0 }; \ long _ret = _type##_from_jsmnf(f, js, &o); \ if (_ret < 0) return _ret; \ ret += _ret; \ - carray_insert(this, i, o); \ + carray_insert(self, i, o); \ } #define GENCODECS_LISTTYPE_PTR(_type, _decor) \ - __carray_init(this, root->size, _type _decor, , ); \ + __carray_init(self, root->size, _type _decor, , ); \ for (i = 0; i < root->size; ++i) { \ jsmnf_pair *f = root->fields + i; \ _type *o; \ GENCODECS_JSON_DECODER_PTR_##_type(f, js, o, _type); \ - carray_insert(this, i, o); \ + carray_insert(self, i, o); \ } #define GENCODECS_LIST_END \ return ret; \ @@ -109,7 +109,7 @@ #define GENCODECS_PUB_STRUCT(_type) \ size_t _type##_from_json(const char buf[], size_t size, \ - struct _type *this) \ + struct _type *self) \ { \ size_t nbytes = 0; \ jsmn_parser parser; \ @@ -124,7 +124,7 @@ if (0 < jsmnf_load_auto(&loader, buf, tokens, parser.toknext, \ &pairs, &tmp)) { \ long ret; \ - if (0 < (ret = _type##_from_jsmnf(pairs, buf, this))) \ + if (0 < (ret = _type##_from_jsmnf(pairs, buf, self))) \ nbytes = ret; \ free(pairs); \ } \ diff --git a/gencodecs/recipes/json-encoder.h b/gencodecs/recipes/json-encoder.h index e7a326e26..ef4c04eee 100644 --- a/gencodecs/recipes/json-encoder.h +++ b/gencodecs/recipes/json-encoder.h @@ -14,8 +14,8 @@ #define GENCODECS_PUB_STRUCT(_type) \ jsonbcode _type##_to_jsonb(jsonb *b, char buf[], size_t size, \ - const struct _type *this); \ - size_t _type##_to_json(char buf[], size_t size, const struct _type *this); + const struct _type *self); \ + size_t _type##_to_json(char buf[], size_t size, const struct _type *self); #define GENCODECS_PUB_LIST(_type) GENCODECS_PUB_STRUCT(_type) #include "gencodecs-gen.pre.h" @@ -24,7 +24,7 @@ #define GENCODECS_STRUCT(_type) \ static jsonbcode _type##_to_jsonb(jsonb *b, char buf[], size_t size, \ - const struct _type *this); + const struct _type *self); #define GENCODECS_LIST(_type) GENCODECS_STRUCT(_type) #include "gencodecs-gen.pre.h" @@ -38,25 +38,25 @@ #define GENCODECS_PUB_STRUCT(_type) \ jsonbcode _type##_to_jsonb(jsonb *b, char buf[], size_t size, \ - const struct _type *this) \ + const struct _type *self) \ { \ jsonbcode code; \ if (0 > (code = jsonb_object(b, buf, size))) return code; \ - if (this != NULL) { + if (self != NULL) { #define GENCODECS_STRUCT(_type) \ static GENCODECS_PUB_STRUCT(_type) #define GENCODECS_FIELD_CUSTOM(_name, _key, _type, _decor, _init, _cleanup, \ _encoder, _decoder, _default_value) \ if (0 > (code = jsonb_key(b, buf, size, _key, sizeof(_key) - 1))) \ return code; \ - _encoder(b, buf, size, this->_name, _type); + _encoder(b, buf, size, self->_name, _type); #define GENCODECS_FIELD_PRINTF(_name, _type, _printf_type, _scanf_type) \ if (0 > (code = jsonb_key(b, buf, size, #_name, sizeof(#_name) - 1))) \ return code; \ else { \ char tok[64]; \ int toklen; \ - toklen = sprintf(tok, _printf_type, this->_name); \ + toklen = sprintf(tok, _printf_type, self->_name); \ if (0 > (code = jsonb_token(b, buf, size, tok, toklen))) \ return code; \ } @@ -68,26 +68,26 @@ #define GENCODECS_PUB_LIST(_type) \ jsonbcode _type##_to_jsonb(jsonb *b, char buf[], size_t size, \ - const struct _type *this) \ + const struct _type *self) \ { \ jsonbcode code; \ if (0 > (code = jsonb_array(b, buf, size))) return code; \ - if (this != NULL) { \ + if (self != NULL) { \ int i; #define GENCODECS_LIST(_type) \ static GENCODECS_PUB_LIST(_type) #define GENCODECS_LISTTYPE(_type) \ - for (i = 0; i < this->size; ++i) \ - GENCODECS_JSON_ENCODER_##_type(b, buf, size, this->array[i], \ + for (i = 0; i < self->size; ++i) \ + GENCODECS_JSON_ENCODER_##_type(b, buf, size, self->array[i], \ _type); #define GENCODECS_LISTTYPE_STRUCT(_type) \ - for (i = 0; i < this->size; ++i) \ + for (i = 0; i < self->size; ++i) \ if (0 > (code = _type##_to_jsonb(b, buf, size, \ - &this->array[i]))) \ + &self->array[i]))) \ return code; #define GENCODECS_LISTTYPE_PTR(_type, _decor) \ - for (i = 0; i < this->size; ++i) \ - GENCODECS_JSON_ENCODER_PTR_##_type(b, buf, size, this->array[i], \ + for (i = 0; i < self->size; ++i) \ + GENCODECS_JSON_ENCODER_PTR_##_type(b, buf, size, self->array[i], \ _type); #define GENCODECS_LIST_END \ } \ @@ -99,12 +99,12 @@ #define GENCODECS_PUB_STRUCT(_type) \ size_t _type##_to_json(char buf[], size_t size, \ - const struct _type *this) \ + const struct _type *self) \ { \ jsonb b; \ jsonbcode code; \ jsonb_init(&b); \ - code = _type##_to_jsonb(&b, buf, size, this); \ + code = _type##_to_jsonb(&b, buf, size, self); \ return code < 0 ? 0 : b.pos; \ } #define GENCODECS_PUB_LIST(_type) GENCODECS_PUB_STRUCT(_type) diff --git a/gencodecs/recipes/struct.h b/gencodecs/recipes/struct.h index 859c42350..785ef00ff 100644 --- a/gencodecs/recipes/struct.h +++ b/gencodecs/recipes/struct.h @@ -69,65 +69,65 @@ #ifdef GENCODECS_HEADER #define GENCODECS_PUB_STRUCT(_type) \ - void _type##_init(struct _type *this); \ - void _type##_cleanup(struct _type *this); + void _type##_init(struct _type *self); \ + void _type##_cleanup(struct _type *self); #define GENCODECS_PUB_LIST(_type) \ - void _type##_cleanup(struct _type *this); + void _type##_cleanup(struct _type *self); #include "gencodecs-gen.pre.h" #elif defined(GENCODECS_FORWARD) #define GENCODECS_STRUCT(_type) \ - static void _type##_init(struct _type *this); \ - static void _type##_cleanup(struct _type *this); + static void _type##_init(struct _type *self); \ + static void _type##_cleanup(struct _type *self); #define GENCODECS_LIST(_type) \ - static void _type##_cleanup(struct _type *this); + static void _type##_cleanup(struct _type *self); #include "gencodecs-gen.pre.h" #else #define GENCODECS_PUB_STRUCT(_type) \ - void _type##_init(struct _type *this) \ + void _type##_init(struct _type *self) \ { #define GENCODECS_STRUCT(_type) \ static GENCODECS_PUB_STRUCT(_type) #define GENCODECS_FIELD_CUSTOM(_name, _key, _type, _decor, _init, _cleanup, \ _encoder, _decoder, _default_value) \ - this->_name = _default_value; + self->_name = _default_value; #define GENCODECS_FIELD_PRINTF(_name, _type, printf_type, _scanf_type) \ - this->_name = (_type)0; + self->_name = (_type)0; #define GENCODECS_STRUCT_END \ } #include "gencodecs-gen.pre.h" #define GENCODECS_PUB_STRUCT(_type) \ - void _type##_cleanup(struct _type *this) \ + void _type##_cleanup(struct _type *self) \ { #define GENCODECS_STRUCT(_type) \ static GENCODECS_PUB_STRUCT(_type) #define GENCODECS_FIELD(_name, _type, _default_value) \ - (void)this->_name; + (void)self->_name; #define GENCODECS_FIELD_CUSTOM(_name, _key, _type, _decor, _init, _cleanup, \ _encoder, _decoder, _default_value) \ - _cleanup(this->_name, _type); + _cleanup(self->_name, _type); #define GENCODECS_STRUCT_END \ } #define GENCODECS_PUB_LIST(_type) \ - void _type##_cleanup(struct _type *this) \ + void _type##_cleanup(struct _type *self) \ { #define GENCODECS_LIST(_type) \ static GENCODECS_PUB_LIST(_type) #define GENCODECS_LISTTYPE(_type) \ - __carray_free(this, _type, NULL, NULL); + __carray_free(self, _type, NULL, NULL); #define GENCODECS_LISTTYPE_STRUCT(_type) \ - __carray_free(this, struct _type, NULL, \ + __carray_free(self, struct _type, NULL, \ _type##_cleanup(&__CARRAY_OPERAND_A)); #define GENCODECS_LISTTYPE_PTR(_type, _decor) \ - __carray_free(this, _type _decor, NULL, free(__CARRAY_OPERAND_A)); + __carray_free(self, _type _decor, NULL, free(__CARRAY_OPERAND_A)); #define GENCODECS_LIST_END \ } diff --git a/include/discord-events.h b/include/discord-events.h index 0eaf02942..b1e2793f0 100644 --- a/include/discord-events.h +++ b/include/discord-events.h @@ -80,9 +80,9 @@ void discord_remove_intents(struct discord *client, uint64_t code); * Example: If @a 'help' is a command and @a '!' prefix is set, the command * will only be validated if @a '!help' is sent * @param client the client created with discord_init() - * @param prefix the prefix that should accompany any command + * @param prefix the mandatory command prefix */ -void discord_set_prefix(struct discord *client, char *prefix); +void discord_set_prefix(struct discord *client, const char prefix[]); /** @defgroup DiscordEventCallbackTypes Callback types * @brief Callback types for Discord events @@ -90,98 +90,152 @@ void discord_set_prefix(struct discord *client, char *prefix); /** @brief Idle callback */ typedef void (*discord_ev_idle)(struct discord *client); + +/** @brief Ready callback */ +typedef void (*discord_ev_ready)(struct discord *client, + const struct discord_ready *event); + /** @brief Application Command callback */ typedef void (*discord_ev_application_command)( - struct discord *client, const struct discord_application_command *app_cmd); + struct discord *client, const struct discord_application_command *event); + /** @brief Channel callback */ typedef void (*discord_ev_channel)(struct discord *client, - const struct discord_channel *channel); + const struct discord_channel *event); +/** @brief Thread List Sync callback */ +typedef void (*discord_ev_thread_list_sync)( + struct discord *client, const struct discord_thread_list_sync *event); +/** @brief Thread Member Update callback */ +typedef void (*discord_ev_thread_member)( + struct discord *client, const struct discord_thread_member *event); +/** @brief Thread Members Update callback */ +typedef void (*discord_ev_thread_members_update)( + struct discord *client, const struct discord_thread_members_update *event); /** @brief Channel Pins Update callback */ -typedef void (*discord_ev_channel_pins_update)(struct discord *client, - u64snowflake guild_id, - u64snowflake channel_id, - u64unix_ms last_pin_timestamp); +typedef void (*discord_ev_channel_pins_update)( + struct discord *client, const struct discord_channel_pins_update *event); + +/** @brief Guild Ban Add callback */ +typedef void (*discord_ev_guild_ban_add)( + struct discord *client, const struct discord_guild_ban_add *event); +/** @brief Guild Ban Remove callback */ +typedef void (*discord_ev_guild_ban_remove)( + struct discord *client, const struct discord_guild_ban_remove *event); + /** @brief Guild callback */ typedef void (*discord_ev_guild)(struct discord *client, - const struct discord_guild *guild); -/** @brief Guild Delete callback */ -typedef void (*discord_ev_guild_delete)(struct discord *client, - u64snowflake guild_id); -/** @brief Guild Role callback */ -typedef void (*discord_ev_guild_role)(struct discord *client, - u64snowflake guild_id, - const struct discord_role *role); -/** @brief Guild Role Delete callback */ -typedef void (*discord_ev_guild_role_delete)(struct discord *client, - u64snowflake guild_id, - u64snowflake role_id); -/** @brief Guild Member callback */ -typedef void (*discord_ev_guild_member)( + const struct discord_guild *event); +/** @brief Guild Emojis Update callback */ +typedef void (*discord_ev_guild_emojis_update)( + struct discord *client, const struct discord_guild_emojis_update *event); +/** @brief Guild Stickers Update callback */ +typedef void (*discord_ev_guild_stickers_update)( + struct discord *client, const struct discord_guild_stickers_update *event); +/** @brief Guild Integrations Update callback */ +typedef void (*discord_ev_guild_integrations_update)( struct discord *client, - u64snowflake guild_id, - const struct discord_guild_member *member); + const struct discord_guild_integrations_update *event); +/** @brief Guild Member Add callback */ +typedef void (*discord_ev_guild_member)( + struct discord *client, const struct discord_guild_member *event); /** @brief Guild Member Remove callback */ typedef void (*discord_ev_guild_member_remove)( + struct discord *client, const struct discord_guild_member_remove *event); +/** @brief Guild Member Update callback */ +typedef void (*discord_ev_guild_member_update)( + struct discord *client, const struct discord_guild_member_update *event); +/** @brief Guild Members Chunk callback */ +typedef void (*discord_ev_guild_members_chunk)( + struct discord *client, const struct discord_guild_members_chunk *event); +/** @brief Guild Role Create callback */ +typedef void (*discord_ev_guild_role_create)( + struct discord *client, const struct discord_guild_role_create *event); +/** @brief Guild Role Update callback */ +typedef void (*discord_ev_guild_role_update)( + struct discord *client, const struct discord_guild_role_update *event); +/** @brief Guild Role Delete callback */ +typedef void (*discord_ev_guild_role_delete)( + struct discord *client, const struct discord_guild_role_delete *event); + +/** @brief Guild Scheduled Event User Add callback */ +typedef void (*discord_ev_guild_scheduled_event_user_add)( struct discord *client, - u64snowflake guild_id, - const struct discord_user *user); -/** @brief Guild Ban callback */ -typedef void (*discord_ev_guild_ban)(struct discord *client, - u64snowflake guild_id, - const struct discord_user *user); -/** @brief Interaction callback */ -typedef void (*discord_ev_interaction)( - struct discord *client, const struct discord_interaction *interaction); + const struct discord_guild_scheduled_event_user_add *event); +/** @brief Guild Scheduled Event User Remove callback */ +typedef void (*discord_ev_guild_scheduled_event_user_remove)( + struct discord *client, + const struct discord_guild_scheduled_event_user_remove *event); + +/** @brief Integration Create callback */ +typedef void (*discord_ev_integration)( + struct discord *client, const struct discord_integration *event); + +/** @brief Integration Delete callback */ +typedef void (*discord_ev_integration_delete)( + struct discord *client, const struct discord_integration_delete *event); + +/** @brief Invite Create Event callback */ +typedef void (*discord_ev_invite_create)( + struct discord *client, const struct discord_invite_create *event); +/** @brief Invite Delete Event callback */ +typedef void (*discord_ev_invite_delete)( + struct discord *client, const struct discord_invite_delete *event); + /** @brief Message callback */ typedef void (*discord_ev_message)(struct discord *client, - const struct discord_message *message); + const struct discord_message *event); /** @brief Message Delete callback */ -typedef void (*discord_ev_message_delete)(struct discord *client, - u64snowflake id, - u64snowflake channel_id, - u64snowflake guild_id); +typedef void (*discord_ev_message_delete)( + struct discord *client, const struct discord_message_delete *event); /** @brief Message Delete Bulk callback */ -typedef void (*discord_ev_message_delete_bulk)(struct discord *client, - const struct snowflakes *ids, - u64snowflake channel_id, - u64snowflake guild_id); -/** @brief Message Reaction callback */ +typedef void (*discord_ev_message_delete_bulk)( + struct discord *client, const struct discord_message_delete_bulk *event); +/** @brief Message Reaction Add callback */ typedef void (*discord_ev_message_reaction_add)( - struct discord *client, - u64snowflake user_id, - u64snowflake channel_id, - u64snowflake message_id, - u64snowflake guild_id, - const struct discord_guild_member *member, - const struct discord_emoji *emoji); + struct discord *client, const struct discord_message_reaction_add *member); /** @brief Message Reaction Remove callback */ typedef void (*discord_ev_message_reaction_remove)( struct discord *client, - u64snowflake user_id, - u64snowflake channel_id, - u64snowflake message_id, - u64snowflake guild_id, - const struct discord_emoji *emoji); + const struct discord_message_reaction_remove *member); /** @brief Message Reaction Remove All callback */ -typedef void (*discord_ev_message_reaction_remove_all)(struct discord *client, - u64snowflake channel_id, - u64snowflake message_id, - u64snowflake guild_id); +typedef void (*discord_ev_message_reaction_remove_all)( + struct discord *client, + const struct discord_message_reaction_remove_all *event); /** @brief Message Reaction Remove callback */ typedef void (*discord_ev_message_reaction_remove_emoji)( struct discord *client, - u64snowflake channel_id, - u64snowflake message_id, - u64snowflake guild_id, - const struct discord_emoji *emoji); + const struct discord_message_reaction_remove_emoji *event); + +/** @brief Presence Update callback */ +typedef void (*discord_ev_presence_update)( + struct discord *client, const struct discord_presence_update *event); + +/** @brief Stage Instance callback */ +typedef void (*discord_ev_stage_instance)( + struct discord *client, const struct discord_stage_instance *event); + +/** @brief Typing Start callback */ +typedef void (*discord_ev_typing_start)( + struct discord *client, const struct discord_typing_start *event); + +/** @brief User callback */ +typedef void (*discord_ev_user)(struct discord *client, + const struct discord_user *event); + /** @brief Voice State Update callback */ typedef void (*discord_ev_voice_state_update)( - struct discord *client, const struct discord_voice_state *voice_state); + struct discord *client, const struct discord_voice_state *event); /** @brief Voice Server Update callback */ -typedef void (*discord_ev_voice_server_update)(struct discord *client, - const char *token, - u64snowflake guild_id, - const char *endpoint); +typedef void (*discord_ev_voice_server_update)( + struct discord *client, const struct discord_voice_server_update *event); + +/** @brief Webhooks Update callback */ +typedef void (*discord_ev_webhooks_update)( + struct discord *client, const struct discord_webhooks_update *event); + +/** @brief Interaction callback */ +typedef void (*discord_ev_interaction)( + struct discord *client, const struct discord_interaction *event); /** @} DiscordEventCallbackTypes */ @@ -206,14 +260,16 @@ void discord_set_on_command(struct discord *client, * The callback is triggered when a user types one of the assigned commands in * a chat visble to the client * @param client the client created with discord_init() + * @param commands array of commands to trigger the callback + * @param amount amount of commands provided * @param callback the callback to be triggered on event - * @param ... commands and a NULL terminator * @note The command and any subjacent empty space is left out of * the message content */ void discord_set_on_commands(struct discord *client, - discord_ev_message callback, - ...); + char *const commands[], + int amount, + discord_ev_message callback); /** * @brief Set the time for wakeup function to be called @@ -254,7 +310,7 @@ void discord_set_on_cycle(struct discord *client, discord_ev_idle callback); * @param client the client created with discord_init() * @param callback the callback to be triggered on event */ -void discord_set_on_ready(struct discord *client, discord_ev_idle callback); +void discord_set_on_ready(struct discord *client, discord_ev_ready callback); /** * @brief Triggers when a application command is created @@ -371,7 +427,7 @@ void discord_set_on_guild_update(struct discord *client, * @param callback the callback to be triggered on event */ void discord_set_on_guild_delete(struct discord *client, - discord_ev_guild_delete callback); + discord_ev_guild callback); /** * @brief Triggers when a guild role is created @@ -380,7 +436,7 @@ void discord_set_on_guild_delete(struct discord *client, * @param callback the callback to be triggered on event */ void discord_set_on_guild_role_create(struct discord *client, - discord_ev_guild_role callback); + discord_ev_guild_role_create callback); /** * @brief Triggers when a guild role is updated @@ -389,7 +445,7 @@ void discord_set_on_guild_role_create(struct discord *client, * @param callback the callback to be triggered on event */ void discord_set_on_guild_role_update(struct discord *client, - discord_ev_guild_role callback); + discord_ev_guild_role_update callback); /** * @brief Triggers when a guild role is deleted @@ -415,8 +471,8 @@ void discord_set_on_guild_member_add(struct discord *client, * @param client the client created with discord_init() * @param callback the callback to be triggered on event */ -void discord_set_on_guild_member_update(struct discord *client, - discord_ev_guild_member callback); +void discord_set_on_guild_member_update( + struct discord *client, discord_ev_guild_member_update callback); /** * @brief Triggers when a guild member is removed @@ -434,7 +490,7 @@ void discord_set_on_guild_member_remove( * @param callback the callback to be triggered on event */ void discord_set_on_guild_ban_add(struct discord *client, - discord_ev_guild_ban callback); + discord_ev_guild_ban_add callback); /** * @brief Triggers when a guild ban is removed @@ -443,7 +499,7 @@ void discord_set_on_guild_ban_add(struct discord *client, * @param callback the callback to be triggered on event */ void discord_set_on_guild_ban_remove(struct discord *client, - discord_ev_guild_ban callback); + discord_ev_guild_ban_remove callback); /** * @brief Triggers when a interaction is created diff --git a/include/discord-internal.h b/include/discord-internal.h index 2c9a81d73..2c87d4cde 100644 --- a/include/discord-internal.h +++ b/include/discord-internal.h @@ -8,6 +8,10 @@ #ifndef DISCORD_INTERNAL_H #define DISCORD_INTERNAL_H +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + #include #define JSONB_HEADER @@ -17,13 +21,11 @@ #include "jsmn.h" #include "jsmn-find.h" -#include "logconf.h" /* struct logconf */ +#include "logconf.h" #include "user-agent.h" #include "websockets.h" -#include "work.h" #include "cog-utils.h" #include "io_poller.h" - #include "queue.h" #include "priority_queue.h" @@ -32,9 +34,9 @@ /** * @brief Get container `type` from a field `ptr` * - * @param ptr the field contained in `type` - * @param type the container datatype - * @param path the path to the field from the container POV + * @param[in] ptr the field contained in `type` + * @param[in] type the container datatype + * @param[in] path the path to the field from the container POV */ #define CONTAINEROF(ptr, type, path) \ ((type *)((char *)(ptr)-offsetof(type, path))) @@ -49,10 +51,11 @@ /** * @brief log and return `code` if `expect` condition is false * - * @param expect the expected outcome - * @param client the discord client - * @param error return CCORDcode error - * @param reason for return + * @param[in] client the Discord client + * @param[in] expect the expected outcome + * @param[in] code return CCORDcode error code + * @param[in] reason for return + * @return the provided @ref CCORDcode `code` parameter */ #define CCORD_EXPECT(client, expect, code, reason) \ do { \ @@ -66,242 +69,202 @@ * @brief Shortcut for checking OOB-write attempts * @note unsigned values are expected * - * @param nbytes amount of bytes to be written - * @param destsz size of dest in bytes + * @param[in] nbytes amount of bytes to be written + * @param[in] destsz size of dest in bytes */ #define ASSERT_NOT_OOB(nbytes, destsz) \ ASSERT_S((size_t)nbytes < (size_t)destsz, "Out of bounds write attempt"); -/** @defgroup DiscordInternalAdapter REST API - * @brief Wrapper to the Discord REST API - * @{ */ - -/** @brief Request's return context */ -struct discord_ret_generic { - /** `true` if may receive a datatype from response*/ - bool has_type; - - /** optional callback to be executed on a successful request */ - union { - void (*typed)(struct discord *client, void *data, const void *ret); - void (*typeless)(struct discord *client, void *data); - } done; - - DISCORDT_RET_DEFAULT_FIELDS; - - /** if an address is provided, then request will block the thread and - * perform on-spot. On success the response object will be written to - * the address. */ - void *sync; -}; - -/** @brief Attributes of response datatype */ -struct discord_generic { - /** pointer to the datatype in memory */ - void *data; - /** size of datatype in bytes */ - size_t size; - /** initializer function for datatype fields */ - void (*init)(void *data); - /** populate datatype with JSON values */ - size_t (*from_json)(const char *json, size_t len, void *data); - /** cleanup function for datatype */ - void (*cleanup)(void *data); -}; - -/** @brief Behavior of request return struct */ -struct discord_request { - /** request response's return datatype attributes */ - struct discord_generic gnrc; - /** request attributes set by client */ - struct discord_ret_generic ret; - /** in case of HTTP_MIMEPOST, provide attachments */ - struct discord_attachments attachments; -}; - /** URL endpoint threshold length */ #define DISCORD_ENDPT_LEN 512 /** Route's unique key threshold length */ #define DISCORD_ROUTE_LEN 256 -/** - * @brief Context of individual requests that are scheduled to run - * asynchronously - */ -struct discord_context { - /** request return struct attributes */ - struct discord_request req; - - /** the request's bucket */ - struct discord_bucket *b; +/** @defgroup DiscordInternalTimer Timer API + * @brief Callback scheduling API + * @{ */ - /** request body handle @note buffer is kept and recycled */ +struct discord_timers { + priority_queue *q; struct { - /** the request body contents */ - struct sized_buffer buf; - /** the real size occupied in memory by `buf.start` */ - size_t memsize; - } body; - - /** the request's http method */ - enum http_method method; - /** the request's endpoint */ - char endpoint[DISCORD_ENDPT_LEN]; - /** the request bucket's key */ - char key[DISCORD_ROUTE_LEN]; - /** the connection handler assigned */ - struct ua_conn *conn; - /** the request bucket's queue entry */ - QUEUE entry; - - /** current retry attempt (stop at adapter->retry_limit) */ - int retry_attempt; + struct discord_timer *timer; + bool skip_update_phase; + } active; }; -/** @brief The handle used for performing HTTP Requests */ -struct discord_adapter { - /** DISCORD_HTTP or DISCORD_WEBHOOK logging module */ - struct logconf conf; - /** the user agent handle for performing requests */ - struct user_agent *ua; - /** curl_multi handle for performing non-blocking requests */ - CURLM *mhandle; - /** user's data reference counter for automatic cleanup */ - struct discord_refcounter *refcounter; - - /** buckets discovered (declared at discord-adapter_ratelimit.c) */ - struct discord_ratelimiter *ratelimiter; - - /** idle request handles */ - QUEUE(struct discord_context) * idleq; +/** + * @brief Prepare timers for usage + * + * @param timers the 'struct discord_timers' to init + */ +void discord_timers_init(struct discord_timers *timers); - /** max amount of retries before a failed request gives up */ - int retry_limit; -}; +/** + * @brief Cleanup timers and call cancel any running ones + * + * @param client the client created with discord_init() + * @param timers the 'struct discord_timers' to cleanup + */ +void discord_timers_cleanup(struct discord *client, + struct discord_timers *timers); /** - * @brief Initialize the fields of a Discord Adapter handle + * @brief Get earliest trigger time from a group of timers * - * @param adapter the adapter handle to be initialized - * @param conf optional pointer to a parent logconf - * @param token the bot token + * @param timers array of timers + * @param n number of timers in array + * @param now current time + * @param max_time max time to allowed + * @return time in microseconds until next timer, or max */ -void discord_adapter_init(struct discord_adapter *adapter, - struct logconf *conf, - struct sized_buffer *token); +int64_t discord_timers_get_next_trigger(struct discord_timers *const timers[], + size_t n, + int64_t now, + int64_t max_time); /** - * @brief Free a Discord Adapter handle + * @brief Run all timers that are due * - * @param adapter the handle initialized with discord_adapter_init() + * @param client the client created with discord_init() + * @param timers the timers to run */ -void discord_adapter_cleanup(struct discord_adapter *adapter); +void discord_timers_run(struct discord *client, struct discord_timers *timers); /** - * @brief Perform a request to Discord + * @brief Modifies or creates a timer * - * This functions is a selector over discord_adapter_run() or - * discord_adapter_run_async() - * @param adapter the handle initialized with discord_adapter_init() - * @param req return object of request - * @param body the body sent for methods that require (ex: post), leave as - * null if unecessary - * @param method the method in opcode format of the request being sent - * @param endpoint_fmt the printf-like endpoint formatting string - * @CCORD_return - * @note if sync is set then this function will block the thread and perform it - * immediately + * @param client the client created with discord_init() + * @param timers the timer group to perform this operation on + * @param timer the timer that should be modified + * @return the id of the timer */ -CCORDcode discord_adapter_run(struct discord_adapter *adapter, - struct discord_request *req, - struct sized_buffer *body, - enum http_method method, - char endpoint_fmt[], - ...); +unsigned _discord_timer_ctl(struct discord *client, + struct discord_timers *timers, + struct discord_timer *timer); /** - * @brief Check and manage on-going, pending and timed-out requests + * @brief Modifies or creates a timer * - * @param adapter the handle initialized with discord_adapter_init() - * @CCORD_return + * @param client the client created with discord_init() + * @param timer the timer that should be modified + * @return the id of the timer */ -CCORDcode discord_adapter_perform(struct discord_adapter *adapter); +unsigned discord_internal_timer_ctl(struct discord *client, + struct discord_timer *timer); /** - * @brief Stop all bucket's on-going, pending and timed-out requests + * @brief Creates a one shot timer that automatically deletes itself upon + * completion * - * The requests will be moved over to client's 'idleq' queue - * @param adapter the handle initialized with discord_adapter_init() + * @param client the client created with discord_init() + * @param cb the callback that should be called when timer triggers + * @param data user data + * @param delay delay before timer should start in milliseconds + * @return the id of the timer */ -void discord_adapter_stop_buckets(struct discord_adapter *adapter); +unsigned discord_internal_timer(struct discord *client, + discord_ev_timer cb, + void *data, + int64_t delay); -/** @defgroup DiscordInternalAdapterRefcount Reference counter - * @brief Handle automatic cleanup of user's data +/** @} DiscordInternalTimer */ + +/** @defgroup DiscordInternalREST REST API + * @brief Wrapper to the Discord REST API * @{ */ -/** @brief Automatically cleanup user data - * - * Automatically cleanup user data that is passed around Discord event's - * callbacks once its reference counter reaches 0, meaning there are no - * more callbacks expecting the data */ -struct discord_refcounter { - /** DISCORD_REFCOUNT logging module */ +/** @defgroup DiscordInternalRESTRequest Request's handling + * @brief Store, manage and dispatch individual requests + * @{ */ + +/** @defgroup DiscordInternalRESTRequestRatelimit Ratelimiting + * @brief Enforce ratelimiting per the official Discord Documentation + * @{ */ + +/** + * @brief Value assigned to @ref discord_bucket `busy_req` field in case + * it's being timed-out + */ +#define DISCORD_BUCKET_TIMEOUT (void *)(0xf) + +/** + * @brief The ratelimiter struct for handling ratelimiting + * @note this struct **SHOULD** only be handled from the `REST` manager thread + */ +struct discord_ratelimiter { + /** `DISCORD_RATELIMIT` logging module */ struct logconf conf; - /** amount of individual user's data held for automatic cleanup */ + /** amount of bucket's routes discovered */ int length; - /** cap before increase */ + /** route's cap before increase */ int capacity; /** - * individual user's data held for automatic cleanup - * @note datatype declared at discord-adapter_refcount.c + * routes matched to individual buckets + * @note datatype declared at discord-rest_ratelimit.c */ - struct _discord_ref *refs; + struct _discord_route *routes; + /** singleton bucket for requests that haven't been matched to a + * known or new bucket (i.e first time running the request) */ + struct discord_bucket *null; + /** singleton bucket for requests that are not part of any known + * ratelimiting group */ + struct discord_bucket *miss; + + /* client-wide global ratelimiting */ + u64unix_ms *global_wait_ms; + + /** bucket queues */ + struct { + /** buckets that are currently pending (have pending requests) */ + QUEUE(struct discord_bucket) pending; + } queues; }; /** - * @brief Initialize reference counter handle + * @brief Initialize ratelimiter handle * - * A hashtable shall be used for storage and retrieval of user data - * @param conf optional pointer to a parent logconf - * @return the reference counter handle + * A hashtable shall be used for storage and retrieval of discovered buckets + * @param rl the ratelimiter handle to be initialized + * @param conf pointer to @ref discord_rest logging module */ -struct discord_refcounter *discord_refcounter_init(struct logconf *conf); +void discord_ratelimiter_init(struct discord_ratelimiter *rl, + struct logconf *conf); /** - * @brief Cleanup refcounter and all user data currently held + * @brief Cleanup all buckets that have been discovered * - * @param rc the handle initialized with discord_refcounter_init() + * @note pending requests will be moved to `rest.queues->recycling` + * @param rl the handle initialized with discord_ratelimiter_init() */ -void discord_refcounter_cleanup(struct discord_refcounter *rc); +void discord_ratelimiter_cleanup(struct discord_ratelimiter *rl); /** - * @brief Increment the reference counter for `ret->data` + * @brief Build unique key formed from the HTTP method and endpoint + * @see https://discord.com/developers/docs/topics/rate-limits * - * @param rc the handle initialized with discord_refcounter_init() - * @param data the user arbitrary data to have its reference counter - * @param cleanup user-defined function for cleaning `data` resources once its - * no longer referenced + * @param[in] method the request method + * @param[out] key unique key for matching to buckets + * @param[in] endpoint_fmt the printf-like endpoint formatting string + * @param[in] args variadic arguments matched to `endpoint_fmt` */ -void discord_refcounter_incr(struct discord_refcounter *rc, - void *data, - void (*cleanup)(void *data)); +void discord_ratelimiter_build_key(enum http_method method, + char key[DISCORD_ROUTE_LEN], + const char endpoint_fmt[], + va_list args); /** - * @brief Decrement the reference counter for `data` + * @brief Update the bucket with response header data * - * If the count reaches zero then `data` shall be cleanup up with its - * user-defined cleanup function - * @param rc the handle initialized with discord_refcounter_init() - * @param data the user arbitrary data to have its reference counter - * decremented + * @param rl the handle initialized with discord_ratelimiter_init() + * @param bucket NULL when bucket is first discovered + * @param key obtained from discord_ratelimiter_build_key() + * @param info informational struct containing details on the current transfer + * @note If the bucket was just discovered it will be created here. */ -void discord_refcounter_decr(struct discord_refcounter *rc, void *data); - -/** @} DiscordInternalAdapterRefcount */ - -/** @defgroup DiscordInternalAdapterRatelimit Ratelimiting - * @brief Enforce ratelimiting per the official Discord Documentation - * @{ */ +void discord_ratelimiter_build(struct discord_ratelimiter *rl, + struct discord_bucket *bucket, + const char key[], + struct ua_info *info); /** @brief The Discord bucket for handling per-group ratelimits */ struct discord_bucket { @@ -309,230 +272,366 @@ struct discord_bucket { char hash[64]; /** maximum connections this bucket can handle before ratelimit */ long limit; - /** connections this bucket can do before waiting for cooldown */ + /** connections this bucket can do before pending for cooldown */ long remaining; /** timestamp of when cooldown timer resets */ u64unix_ms reset_tstamp; - /** synchronize ratelimiting between threads */ - pthread_mutex_t lock; - /** pending requests */ - QUEUE(struct discord_context) waitq; - /** busy requests */ - QUEUE(struct discord_context) busyq; + + /** + * pointer to this bucket's currently busy request + * @note @ref DISCORD_BUCKET_TIMEOUT if bucket is being ratelimited + */ + struct discord_request *busy_req; + + /** request queues */ + struct { + /** next requests queue */ + QUEUE(struct discord_request) next; + } queues; + /** entry for @ref discord_ratelimiter pending buckets queue */ + QUEUE entry; +}; + +/** + * @brief Return bucket timeout timestamp + * + * @param rl the handle initialized with discord_ratelimiter_init() + * @param bucket the bucket to be checked for time out + * @return the timeout timestamp + */ +u64unix_ms discord_bucket_get_timeout(struct discord_ratelimiter *rl, + struct discord_bucket *bucket); + +/** + * @brief Get a `struct discord_bucket` assigned to `key` + * + * @param rl the handle initialized with discord_ratelimiter_init() + * @param key obtained from discord_ratelimiter_build_key() + * @return bucket matched to `key` + */ +struct discord_bucket *discord_bucket_get(struct discord_ratelimiter *rl, + const char key[]); + +/** + * @brief Insert into bucket's next requests queue + * + * @param rl the handle initialized with discord_ratelimiter_init() + * @param b the bucket to insert the request to + * @param req the request to be inserted to bucket + * @param high_priority if high priority then request shall be prioritized over + * already enqueued requests + */ +void discord_bucket_insert(struct discord_ratelimiter *rl, + struct discord_bucket *b, + struct discord_request *req, + bool high_priority); + +/** + * @brief Iterate and select next requests + * @note discord_bucket_unselect() must be called once bucket's current request + * is done and its next one should be selected + * + * @param rl the handle initialized with discord_ratelimiter_init() + * @param data user arbitrary data + * @param iter the user callback to be called per bucket + */ +void discord_bucket_request_selector( + struct discord_ratelimiter *rl, + void *data, + void (*iter)(void *data, struct discord_request *req)); + +/** + * @brief Unselect a request provided at discord_ratelimiter_request_selector() + * @note counterpart to discord_ratelimiter_request_selector() + * + * @param rl the handle initialized with discord_ratelimiter_init() + * @param b the request's bucket + * @param req the request to unslect + */ +void discord_bucket_request_unselect(struct discord_ratelimiter *rl, + struct discord_bucket *b, + struct discord_request *req); + +/** @} DiscordInternalRESTRequestRatelimit */ + +/** @brief Generic request dispatcher */ +struct discord_ret_dispatch { + DISCORD_RET_DEFAULT_FIELDS; + /** `true` if may receive a datatype from response */ + bool has_type; + + /** + * optional callback to be executed on a successful request + * @todo should be cast to the original callback signature before calling, + * otherwise its UB + */ + union { + void (*typed)(struct discord *client, + struct discord_response *resp, + const void *ret); + void (*typeless)(struct discord *client, + struct discord_response *resp); + } done; + + /** if an address is provided, then request will block the thread and + * perform on-spot. On success the response object will be written to + * the address. */ + void *sync; +}; + +/** @brief Attributes of response datatype */ +struct discord_ret_response { + /** pointer to datatype */ + void *data; + /** size of datatype in bytes */ + size_t size; + /** initializer function for datatype fields */ + void (*init)(void *data); + /** populate datatype with JSON values */ + size_t (*from_json)(const char *json, size_t len, void *data); + /** cleanup function for datatype */ + void (*cleanup)(void *data); +}; + +/** + * @brief Macro containing @ref discord_attributes fields + * @note this exists for @ref discord_request alignment purposes + */ +#define DISCORD_ATTRIBUTES_FIELDS \ + /** attributes set by client for request dispatch behavior */ \ + struct discord_ret_dispatch dispatch; \ + /** information for parsing response into a datatype (if possible) */ \ + struct discord_ret_response response; \ + /** in case of `HTTP_MIMEPOST` provide attachments for file transfer */ \ + struct discord_attachments attachments + +/** @brief Request to be performed */ +struct discord_attributes { + DISCORD_ATTRIBUTES_FIELDS; +}; + +/** + * @brief Individual requests that are scheduled to run asynchronously + * @note this struct **SHOULD NOT** be handled from the `REST` manager thread + * @note its fields are aligned with @ref discord_attributes + */ +struct discord_request { + DISCORD_ATTRIBUTES_FIELDS; + + /** the request's bucket */ + struct discord_bucket *b; + /** request body handle @note buffer is kept and reused */ + struct ccord_szbuf_reusable body; + /** the request's http method */ + enum http_method method; + /** the request's endpoint */ + char endpoint[DISCORD_ENDPT_LEN]; + /** the request bucket's key */ + char key[DISCORD_ROUTE_LEN]; + /** the connection handler assigned */ + struct ua_conn *conn; + + /** request's status code */ + CCORDcode code; + /** how long to wait for in case of request being ratelimited */ + int64_t wait_ms; + + /** current retry attempt (stop at rest->retry_limit) */ + int retry_attempt; + /** synchronize synchronous requests */ + pthread_cond_t *cond; + /** entry for @ref discord_ratelimiter and @ref discord_bucket queues */ + QUEUE entry; +}; + +/** @brief The handle used for handling asynchronous requests */ +struct discord_requestor { + /** `DISCORD_REQUEST` logging module */ + struct logconf conf; + /** the user agent handle for performing requests */ + struct user_agent *ua; + /** curl_multi handle for performing asynchronous requests */ + CURLM *mhandle; + /** enforce Discord's ratelimiting for requests */ + struct discord_ratelimiter ratelimiter; + + /** max amount of retries before a failed request gives up */ + int retry_limit; + + /** request queues */ + struct { + /** requests for recycling */ + QUEUE(struct discord_request) recycling; + /** pending requests waiting to be assigned to a bucket */ + QUEUE(struct discord_request) pending; + /** + * finished requests that are done performing and waiting for + * their callbacks to be called from the main thread + */ + QUEUE(struct discord_request) finished; + } * queues; + + /** queue locks */ + struct { + /** recycling queue lock */ + pthread_mutex_t recycling; + /** pending queue lock */ + pthread_mutex_t pending; + /** finished queue lock */ + pthread_mutex_t finished; + } * qlocks; }; /** - * @brief Return bucket timeout timestamp + * @brief Initialize the request handler * - * @param rl the handle initialized with discord_ratelimiter_init() - * @param bucket the bucket to be checked for time out - * @return the timeout timestamp + * This shall initialize a `CURLM` multi handle for performing requests + * asynchronously, and a queue for storing individual requests + * @param rqtor the requestor handle to be initialized + * @param conf pointer to @ref discord_rest logging module + * @param token the bot token */ -u64unix_ms discord_bucket_get_timeout(struct discord_ratelimiter *rl, - struct discord_bucket *bucket); +void discord_requestor_init(struct discord_requestor *rqtor, + struct logconf *conf, + const char token[]); /** - * @brief Sleep for bucket's cooldown time - * @note this **WILL** block the bucket's execution thread + * @brief Free the request handler * - * @param rl the handle initialized with discord_ratelimiter_init() - * @param bucket the bucket to wait on cooldown + * @param rqtor the handle initialized with discord_requestor_init() */ -void discord_bucket_try_sleep(struct discord_ratelimiter *rl, - struct discord_bucket *bucket); +void discord_requestor_cleanup(struct discord_requestor *rqtor); /** - * @brief Get a `struct discord_bucket` assigned to `key` + * @brief Check for and start pending bucket's requests * - * @param rl the handle initialized with discord_ratelimiter_init() - * @param key obtained from discord_ratelimiter_get_key() - * @return bucket matched to `key` + * @param rqtor the handle initialized with discord_requestor_init() + * @CCORD_return */ -struct discord_bucket *discord_bucket_get(struct discord_ratelimiter *rl, - const char key[DISCORD_ROUTE_LEN]); +CCORDcode discord_requestor_start_pending(struct discord_requestor *rqtor); -/** @brief The ratelimiter struct for handling ratelimiting */ -struct discord_ratelimiter { - /** DISCORD_RATELIMIT logging module */ - struct logconf conf; - /** amount of bucket's routes discovered */ - int length; - /** route's cap before increase */ - int capacity; - /** - * routes matched to individual buckets - * @note datatype declared at discord-adapter_ratelimit.c - */ - struct _discord_route *routes; - /** singleton bucket for requests that haven't been matched to a - * known or new bucket (i.e first time running the request) */ - struct discord_bucket *null; - /** singleton bucket for requests that are not part of any known - * ratelimiting group */ - struct discord_bucket *miss; +/** + * @brief Poll for request's completion + * + * @param rqtor the handle initialized with discord_requestor_init() + * @CCORD_return + */ +CCORDcode discord_requestor_info_read(struct discord_requestor *rqtor); - /* client-wide ratelimiting timeout */ - struct { - /** global ratelimit */ - u64unix_ms wait_ms; - /** global rwlock */ - pthread_rwlock_t rwlock; - /** global lock */ - pthread_mutex_t lock; - } global; -}; +/** + * @brief Run pending callbacks from completed requests + * + * @param req the request containing preliminary information for its dispatch + */ +void discord_requestor_dispatch_responses(struct discord_requestor *rqtor); /** - * @brief Initialize ratelimiter handle + * @brief Mark request as canceled and move it to the recycling queue * - * A hashtable shall be used for storage and retrieval of discovered buckets - * @param conf optional pointer to a parent logconf - * @return the ratelimiter handle + * @param rqtor the requestor handle initialized with discord_requestor_init() + * @param req the on-going request to be canceled */ -struct discord_ratelimiter *discord_ratelimiter_init(struct logconf *conf); +void discord_request_cancel(struct discord_requestor *rqtor, + struct discord_request *req); /** - * @brief Cleanup all buckets that have been discovered + * @brief Begin a new request * - * @note pending requests will be moved to `adapter.idleq` - * @param rl the handle initialized with discord_ratelimiter_init() + * The returned request automatically be performed from the `REST` thread + * @param rqtor the requestor handle initialized with discord_requestor_init() + * @param req the request containing preliminary information for its dispatch + * and response's parsing + * @param body the request's body + * @param method the request's HTTP method + * @param endpoint the request's endpoint + * @param key the request bucket's group for ratelimiting + * @CCORD_return */ -void discord_ratelimiter_cleanup(struct discord_ratelimiter *rl); +CCORDcode discord_request_begin(struct discord_requestor *rqtor, + struct discord_attributes *req, + struct ccord_szbuf *body, + enum http_method method, + char endpoint[DISCORD_ENDPT_LEN], + char key[DISCORD_ROUTE_LEN]); + +/** @} DiscordInternalRESTRequest */ /** - * @brief Iterate known buckets + * @brief The handle used for interfacing with Discord's REST API * - * @param rl the handle initialized with discord_ratelimiter_init() - * @param adapter the handle initialized with discord_adapter_init() - * @param iter the user callback to be called per bucket + * This handle will manage the special REST thread where requests are performed + * in */ -void discord_ratelimiter_foreach(struct discord_ratelimiter *rl, - struct discord_adapter *adapter, - void (*iter)(struct discord_adapter *adapter, - struct discord_bucket *b)); +struct discord_rest { + /** `DISCORD_HTTP` or `DISCORD_WEBHOOK` logging module */ + struct logconf conf; + /** the requests handler */ + struct discord_requestor requestor; + /** the timer queue for the rest thread */ + struct discord_timers timers; + /** poller for REST requests */ + struct io_poller *io_poller; + /** threadpool for managing the REST thread */ + struct threadpool_t *tpool; +}; /** - * @brief Build unique key formed from the HTTP method and endpoint - * @see https://discord.com/developers/docs/topics/rate-limits + * @brief Initialize an REST handle * - * @param[in] method the request method - * @param[out] key unique key for matching to buckets - * @param[in] endpoint_fmt the printf-like endpoint formatting string - * @param[in] args variadic arguments matched to `endpoint_fmt` + * Structure used for interfacing with the Discord's REST API + * @param rest the REST handle to be initialized + * @param conf pointer to @ref discord logging module + * @param token the bot token */ -void discord_ratelimiter_build_key(enum http_method method, - char key[DISCORD_ROUTE_LEN], - const char endpoint_fmt[], - va_list args); +void discord_rest_init(struct discord_rest *rest, + struct logconf *conf, + const char token[]); /** - * @brief Get global timeout timestamp + * @brief Free an REST handle * - * @param rl the handle initialized with discord_ratelimiter_init() - * @return the most recent global timeout timestamp + * @param rest the handle initialized with discord_rest_init() */ -u64unix_ms discord_ratelimiter_get_global_wait(struct discord_ratelimiter *rl); +void discord_rest_cleanup(struct discord_rest *rest); /** - * @brief Update the bucket with response header data + * @brief Perform a request to Discord * - * @param rl the handle initialized with discord_ratelimiter_init() - * @param bucket NULL when bucket is first discovered - * @param key obtained from discord_ratelimiter_get_key() - * @param info informational struct containing details on the current transfer - * @note If the bucket was just discovered it will be created here. + * This functions is a selector over discord_rest_run() or + * discord_rest_run_requestor() + * @param rest the handle initialized with discord_rest_init() + * @param req return object of request + * @param body the body sent for methods that require (ex: post), leave as + * null if unecessary + * @param method the method in opcode format of the request being sent + * @param endpoint_fmt the printf-like endpoint formatting string + * @CCORD_return + * @note if sync is set then this function will block the thread and perform it + * immediately */ -void discord_ratelimiter_build(struct discord_ratelimiter *rl, - struct discord_bucket *bucket, - const char key[DISCORD_ROUTE_LEN], - struct ua_info *info); +CCORDcode discord_rest_run(struct discord_rest *rest, + struct discord_attributes *req, + struct ccord_szbuf *body, + enum http_method method, + char endpoint_fmt[], + ...); -/** @} DiscordInternalAdapterRatelimit */ +/** + * @brief Stop all bucket's on-going, pending and timed-out requests + * + * The requests will be moved over to client's 'queues->recycling' queue + * @param rest the handle initialized with discord_rest_init() + */ +void discord_rest_stop_buckets(struct discord_rest *rest); -/** @} DiscordInternalAdapter */ +/** @} DiscordInternalREST */ /** @defgroup DiscordInternalGateway WebSockets API * @brief Wrapper to the Discord Gateway API * @{ */ -struct discord_gateway_cbs { - /** triggers when connection first establishes */ - discord_ev_idle on_ready; - - /** triggers when a command is created */ - discord_ev_application_command on_application_command_create; - /** triggers when a command is updated */ - discord_ev_application_command on_application_command_update; - /** triggers when a command is deleted */ - discord_ev_application_command on_application_command_delete; - - /** triggers when a channel is created */ - discord_ev_channel on_channel_create; - /** triggers when a channel is updated */ - discord_ev_channel on_channel_update; - /** triggers when a channel is deleted */ - discord_ev_channel on_channel_delete; - /** triggers when a channel pinned messages updates */ - discord_ev_channel_pins_update on_channel_pins_update; - /** triggers when a thread is created */ - discord_ev_channel on_thread_create; - /** triggers when a thread is updated */ - discord_ev_channel on_thread_update; - /** triggers when a thread is deleted */ - discord_ev_channel on_thread_delete; - - /** triggers when guild info is ready, or a guild has joined */ - discord_ev_guild on_guild_create; - /** triggers when a guild's information is updated */ - discord_ev_guild on_guild_update; - /** triggers when removed from guild */ - discord_ev_guild_delete on_guild_delete; - - /** triggers when a ban occurs */ - discord_ev_guild_ban on_guild_ban_add; - /** triggers when a ban is removed */ - discord_ev_guild_ban on_guild_ban_remove; - - /** triggers when a guild member joins a guild */ - discord_ev_guild_member on_guild_member_add; - /** triggers when a guild member is removed from a guild */ - discord_ev_guild_member_remove on_guild_member_remove; - /** triggers when a guild member status is updated (ex: receive role) */ - discord_ev_guild_member on_guild_member_update; - - /** triggers when a guild role is created */ - discord_ev_guild_role on_guild_role_create; - /** triggers when a guild role is updated */ - discord_ev_guild_role on_guild_role_update; - /** triggers when a guild role is deleted */ - discord_ev_guild_role_delete on_guild_role_delete; - - /** triggers when a interaction is created */ - discord_ev_interaction on_interaction_create; - - /** triggers when a message is created */ - discord_ev_message on_message_create; - /** trigger when a message is updated */ - discord_ev_message on_message_update; - /** triggers when a message is deleted */ - discord_ev_message_delete on_message_delete; - /** triggers when a bulk of messages is deleted */ - discord_ev_message_delete_bulk on_message_delete_bulk; - /** triggers when a reaction is added to a message */ - discord_ev_message_reaction_add on_message_reaction_add; - /** triggers when a reaction is removed from a message */ - discord_ev_message_reaction_remove on_message_reaction_remove; - /** triggers when all reactions are removed from a message */ - discord_ev_message_reaction_remove_all on_message_reaction_remove_all; - /** triggers when all occurences of a specific reaction is removed from a - * message */ - discord_ev_message_reaction_remove_emoji on_message_reaction_remove_emoji; - - /** triggers when a voice state is updated */ - discord_ev_voice_state_update on_voice_state_update; - /** triggers when a voice server is updated */ - discord_ev_voice_server_update on_voice_server_update; -}; +/** Generic event callback */ +typedef void (*discord_ev)(struct discord *client, void *event); /** @defgroup DiscordInternalGatewaySessionStatus Client's session status * @brief Client's session status @@ -545,9 +644,69 @@ struct discord_gateway_cbs { #define DISCORD_SESSION_SHUTDOWN 1u << 1 /** @} DiscordInternalGatewaySessionStatus */ -/** @brief The handle used for establishing a WebSockets connection */ +/** @brief The handle for storing the Discord Gateway session */ +struct discord_gateway_session { + /** whether client is ready to start sending/receiving events */ + bool is_ready; + /** session id for resuming lost connections */ + char id[64]; + /** amount of shards being used by this session */ + int shards; + /** the session base url */ + char base_url[256]; + /** session limits */ + struct discord_session_start_limit start_limit; + /** active concurrent sessions */ + int concurrent; + /** event counter to avoid reaching limit of 120 events per 60 sec */ + int event_count; + /** @ref DiscordInternalGatewaySessionStatus */ + unsigned status; + + /** retry connection structure */ + struct { + /** will attempt reconnecting if true */ + bool enable; + /** current retry attempt (resets to 0 when succesful) */ + int attempt; + /** max amount of retries before giving up */ + int limit; + } retry; +}; + +/** @brief The handle for storing the Discord response payload */ +struct discord_gateway_payload { + /** current iteration JSON */ + struct { + /** the JSON text */ + char *start; + /** the text length */ + size_t size; + /** jsmn tokens */ + jsmntok_t *tokens; + /** amount of jsmn tokens */ + unsigned ntokens; + /** jsmn-find key/value pairs */ + jsmnf_pair *pairs; + /** amount of jsmn-find key/value pairs */ + unsigned npairs; + } json; + + /** field 'op' */ + enum discord_gateway_opcodes opcode; + /** field 's' */ + int seq; + /** field 't' */ + char name[32]; + /** field 't' enumerator value */ + enum discord_gateway_events event; + /** field 'd' */ + jsmnf_pair *data; +}; + +/** @brief The handle used for interfacing with Discord's Gateway API */ struct discord_gateway { - /** DISCORD_GATEWAY logging module */ + /** `DISCORD_GATEWAY` logging module */ struct logconf conf; /** the websockets handle that connects to Discord */ struct websockets *ws; @@ -556,18 +715,39 @@ struct discord_gateway { /** timers kept for synchronization */ struct { - /** fixed interval between heartbeats */ - u64unix_ms interval; - /** last heartbeat pulse timestamp */ - u64unix_ms hbeat; - /** Gateway's concept of "now" */ + /** + * fixed milliseconds interval between heartbeats + * @note obtained at `HELLO` + */ + int64_t hbeat_interval; + /** + * Gateway's concept of "now" + * @note updated at discord_gateway_perform() + */ u64unix_ms now; - /** timestamp of last succesful identify request */ - u64unix_ms identify; - /** timestamp of last succesful event timestamp in ms - * (resets every 60s) */ + /** + * last heartbeat pulse timestamp + * @note first sent at `READY` and `RESUME`, then updated every + * `hbeat_interval` + */ + u64unix_ms hbeat_last; + /** + * timestamp of last succesful identify request + * @note updated at discord_gateway_send_identify() + */ + u64unix_ms identify_last; + /** + * timestamp of last succesful event + * @note resets every 60s + */ u64unix_ms event; - /** latency obtained from HEARTBEAT and HEARTBEAT_ACK interval */ + /** timer id for heartbeat timer */ + unsigned hbeat_timer; + + /** + * latency obtained from `HEARTBEAT` and `HEARTBEAT_ACK` response + * interval + */ int ping_ms; /** ping rwlock */ pthread_rwlock_t rwlock; @@ -577,100 +757,34 @@ struct discord_gateway { struct discord_identify id; /** on-going session structure */ - struct { - /** whether client is ready to start sending/receiving events */ - bool is_ready; - /** session id for resuming lost connections */ - char id[64]; - /** amount of shards being used by this session */ - int shards; - /** session limits */ - struct discord_session_start_limit start_limit; - /** active concurrent sessions */ - int concurrent; - /** event counter to avoid reaching limit of 120 events per 60 sec */ - int event_count; - /** @ref DiscordInternalGatewaySessionStatus */ - unsigned status; - - /** retry connection structure */ - struct { - /** will attempt reconnecting if true */ - bool enable; - /** current retry attempt (resets to 0 when succesful) */ - int attempt; - /** max amount of retries before giving up */ - int limit; - } retry; - } * session; - - /** current iteration JSON string data */ - char *json; - /** current iteration JSON string data length */ - size_t length; - - /** parse JSON tokens into a `jsmnf_pairs` key/value pairs hashtable */ - struct { - /** current iteration JSON key/value pairs */ - jsmnf_pair *pairs; - /** current iteration number of JSON key/value pairs */ - unsigned npairs; - /** current iteration JSON tokens (fed to `jsmnf_pair`) */ - jsmntok_t *tokens; - /** current iteration number of JSON tokens */ - unsigned ntokens; - } parse; + struct discord_gateway_session *session; /** response-payload structure */ - struct { - /** field 'op' */ - enum discord_gateway_opcodes opcode; - /** field 's' */ - int seq; - /** field 't' */ - char name[32]; - /** field 'd' */ - jsmnf_pair *data; - } payload; - - /** user-commands structure */ - struct { - /** the prefix expected for every command */ - struct sized_buffer prefix; - /** user's command/callback pair @see discord_set_on_command() */ - struct { - /** the command string contents */ - char *start; - /** the command string length */ - size_t size; - /** the assigned callback for the command */ - discord_ev_message cb; - } * pool, fallback; - /** amount of command/callback pairs in pool */ - size_t amt; - /** actual size of command/callback pairs in pool */ - size_t cap; - - /** the user's callbacks for Discord events */ - struct discord_gateway_cbs cbs; - /** the event scheduler callback */ - discord_ev_scheduler scheduler; - } cmds; + struct discord_gateway_payload payload; + /** + * the user's callbacks for Discord events + * @todo should be cast to the original callback signature before calling, + * otherwise its UB + */ + discord_ev cbs[DISCORD_EV_MAX]; + /** the event scheduler callback */ + discord_ev_scheduler scheduler; }; /** - * @brief Initialize the fields of Discord Gateway handle + * @brief Initialize a Gateway handle * + * Structure used for interfacing with the Discord's Gateway API * @param gw the gateway handle to be initialized - * @param conf optional pointer to a parent logconf + * @param conf pointer to @ref discord logging module * @param token the bot token */ void discord_gateway_init(struct discord_gateway *gw, struct logconf *conf, - struct sized_buffer *token); + const char token[]); /** - * @brief Free a Discord Gateway handle + * @brief Free a Gateway handle * * @param gw the handle initialized with discord_gateway_init() */ @@ -717,87 +831,293 @@ void discord_gateway_shutdown(struct discord_gateway *gw); */ void discord_gateway_reconnect(struct discord_gateway *gw, bool resume); +/** + * @brief Trigger the initial handshake with the gateway + * + * @param gw the handle initialized with discord_gateway_init() + * @param event provide client identification information + */ +void discord_gateway_send_identify(struct discord_gateway *gw, + struct discord_identify *event); + +/** + * @brief Replay missed events when a disconnected client resumes + * + * @param gw the handle initialized with discord_gateway_init() + * @param event session resume information + */ +void discord_gateway_send_resume(struct discord_gateway *gw, + struct discord_resume *event); + +/** + * @brief Maintain an active gateway connection + * + * @param gw the handle initialized with discord_gateway_init() + * @param seq the last session sequence number + */ +void discord_gateway_send_heartbeat(struct discord_gateway *gw, int seq); + +/** + * @brief Request all members for a guild or a list of guilds. + * + * @param gw the handle initialized with discord_gateway_init() + * @param event request guild members information + */ +void discord_gateway_send_request_guild_members( + struct discord_gateway *gw, struct discord_request_guild_members *event); + +/** + * @brief Sent when a client wants to join, move or disconnect from a voice + * channel + * + * @param gw the handle initialized with discord_gateway_init() + * @param event request guild members information + */ +void discord_gateway_send_update_voice_state( + struct discord_gateway *gw, struct discord_update_voice_state *event); + /** * @brief Send client's presence status update payload * * @param gw the handle initialized with discord_gateway_init() + * @param event the presence to be set + */ +void discord_gateway_send_presence_update( + struct discord_gateway *gw, struct discord_presence_update *event); + +/** + * @brief Dispatch user callback matched to event + * + * @param gw the handle initialized with discord_gateway_init() */ -void discord_gateway_send_presence_update(struct discord_gateway *gw); +void discord_gateway_dispatch(struct discord_gateway *gw); /** @} DiscordInternalGateway */ -/** @defgroup DiscordInternalTimer Timer API - * @brief Callback scheduling API +/** @defgroup DiscordInternalRefcount Reference counter + * @brief Handle automatic cleanup of user's data * @{ */ -struct discord_timers { - priority_queue *q; - struct { - struct discord_timer *timer; - bool skip_update_phase; - } active; +/** + * @brief Automatically cleanup user data + * + * Automatically cleanup user data that is passed around Discord event's + * callbacks once its reference counter reaches 0, meaning there are no + * more callbacks expecting the data + */ +struct discord_refcounter { + /** `DISCORD_REFCOUNT` logging module */ + struct logconf conf; + /** amount of individual user's data held for automatic cleanup */ + int length; + /** cap before increase */ + int capacity; + /** + * individual user's data held for automatic cleanup + * @note datatype declared at discord-refcount.c + */ + struct _discord_ref *refs; + /** global lock */ + pthread_mutex_t *g_lock; }; /** - * @brief prepare timers for usage + * @brief Initialize reference counter handle * - * @param client the client created with discord_init() + * A hashtable shall be used for storage and retrieval of user data + * @param rc the reference counter handle to be initialized + * @param conf pointer to @ref discord logging module */ -void discord_timers_init(struct discord *client); +void discord_refcounter_init(struct discord_refcounter *rc, + struct logconf *conf); /** - * @brief cleanup timers and call cancel any running ones + * @brief Add a new internal reference to the reference counter * - * @param client the client created with discord_init() + * @param rc the handle initialized with discord_refcounter_init() + * @param data the data address to be referenced + * @param cleanup function for cleaning `data` resources once its + * no longer referenced + * @param should_free whether `data` cleanup should be followed by a free() */ -void discord_timers_cleanup(struct discord *client); +void discord_refcounter_add_internal(struct discord_refcounter *rc, + void *data, + void (*cleanup)(void *data), + bool should_free); /** - * @brief run all timers that are due + * @brief Add a new client reference to the reference counter * - * @param client the client created with discord_init() - * @param timers the timers to run + * @param rc the handle initialized with discord_refcounter_init() + * @param data the data address to be referenced + * @param cleanup function for cleaning `data` resources once its + * no longer referenced + * @param should_free whether `data` cleanup should be followed by a free() */ -void discord_timers_run(struct discord *client, struct discord_timers *timers); +void discord_refcounter_add_client(struct discord_refcounter *rc, + void *data, + void (*cleanup)(struct discord *client, + void *data), + bool should_free); /** - * @brief modifies or creates a timer + * @brief Cleanup refcounter and all user data currently held * - * @param client the client created with discord_init() - * @param timers the timer group to perform this operation on - * @param timer the timer that should be modified - * @return the id of the timer + * @param rc the handle initialized with discord_refcounter_init() */ -unsigned _discord_timer_ctl(struct discord *client, - struct discord_timers *timers, - struct discord_timer *timer); +void discord_refcounter_cleanup(struct discord_refcounter *rc); /** - * @brief modifies or creates a timer + * @brief Claim ownership of `data` + * @see discord_refcounter_unclaim() * - * @param client the client created with discord_init() - * @param timer the timer that should be modified - * @return unsigned the id of the timer + * After ownership is claimed `data` will no longer be cleaned automatically, + * instead shall be cleaned only when discord_refcounter_unclaim() is + * called + * @param rc the handle initialized with discord_refcounter_init() + * @param data the data to have its ownership claimed + * @return `true` if `data` was found and claimed */ -unsigned discord_internal_timer_ctl(struct discord *client, - struct discord_timer *timer); +bool discord_refcounter_claim(struct discord_refcounter *rc, const void *data); /** - * @brief creates a one shot timer that automatically - * deletes itself upon completion + * @brief Unclaim ownership of `data` + * @see discord_refcounter_claim() * - * @param client the client created with discord_init() - * @param cb the callback that should be called when timer triggers - * @param data user data - * @param delay delay before timer should start in milliseconds - * @return unsigned + * This function will have `data` cleanup method be called immediately + * @param rc the handle initialized with discord_refcounter_init() + * @param data the data to have its ownership unclaimed + * @return `true` if `data` was found, unclaimed, and free'd */ -unsigned discord_internal_timer(struct discord *client, - discord_ev_timer cb, - void *data, - int64_t delay); +bool discord_refcounter_unclaim(struct discord_refcounter *rc, void *data); + +/** + * @brief Increment the reference counter for `ret->data` + * @see discord_refcounter_decr() + * + * @param rc the handle initialized with discord_refcounter_init() + * @param data the data to have its reference counter incremented + * @retval CCORD_OK counter for `data` has been incremented + * @retval CCORD_UNAVAILABLE couldn't find a match to `data` + * @retval CCORD_OWNERSHIP `data` has been claimed by client with + * discord_claim() + */ +CCORDcode discord_refcounter_incr(struct discord_refcounter *rc, void *data); + +/** + * @brief Decrement the reference counter for `data` + * @see discord_refcounter_incr() + * + * If the count reaches zero then `data` shall be cleanup up with its + * user-defined cleanup function + * @param rc the handle initialized with discord_refcounter_init() + * @param data the data to have its reference counter decremented + * @retval CCORD_OK counter for `data` has been decremented + * @retval CCORD_UNAVAILABLE couldn't find a match to `data` + * @retval CCORD_OWNERSHIP `data` has been claimed by client with + * discord_claim() + */ +CCORDcode discord_refcounter_decr(struct discord_refcounter *rc, void *data); + +/** @} DiscordInternalRefcount */ + +/** @defgroup DiscordInternalMessageCommands Message Commands API + * @brief The Message Commands API for registering and parsing user commands + * @{ */ + +/** + * @brief The handle for storing user's message commands + * @see discord_set_on_command() + */ +struct discord_message_commands { + /** `DISCORD_MESSAGE_COMMANDS` logging module */ + struct logconf conf; + /** the prefix expected for every command */ + struct ccord_szbuf prefix; + /** fallback message command @see discord_set_on_command() */ + discord_ev_message fallback; + /** amount of message commands created */ + int length; + /** message commands cap before increase */ + int capacity; + /** + * message command entries + * @note datatype declared at discord-messagecommands.c + */ + struct _discord_message_commands_entry *entries; +}; + +/** + * @brief Initialize a Message Commands handle + * + * @param cmds the message commands handle to be initialized + * @param conf pointer to @ref discord logging module + */ +void discord_message_commands_init(struct discord_message_commands *cmds, + struct logconf *conf); + +/** + * @brief Free a Message Commands handle + * + * @param cmds the handle initialized with discord_message_commands_init() + */ +void discord_message_commands_cleanup(struct discord_message_commands *cmds); + +/** + * @brief Search for a callback matching the command + * + * @param cmds the handle initialized with discord_message_commands_init() + * @param command the command to be searched for + * @param length the command length + * @return the callback match, `NULL` in case there wasn't a match + */ +discord_ev_message discord_message_commands_find( + struct discord_message_commands *cmds, + const char command[], + size_t length); + +/** + * @brief Add a new command/callback pair, or update an existing command + * + * @param cmds the handle initialized with discord_message_commands_init() + * @param command the message command to be matched with callback + * @param length the command length + * @param callback the callback to be triggered when the command is sent + */ +void discord_message_commands_append(struct discord_message_commands *cmds, + const char command[], + size_t length, + discord_ev_message callback); + +/** + * @brief Set a mandatory prefix before commands + * @see discord_set_on_command() + * + * Example: If @a 'help' is a command and @a '!' prefix is set, the command + * will only be validated if @a '!help' is sent + * @param cmds the handle initialized with discord_message_commands_init() + * @param prefix the mandatory command prefix + * @param length the prefix length + */ +void discord_message_commands_set_prefix(struct discord_message_commands *cmds, + const char prefix[], + size_t length); + +/** + * @brief Read the current @ref DISCORD_EV_MESSAGE_CREATE payload and attempt + * to perform its matching callback + * + * @param cmds the handle initialized with discord_message_commands_init() + * @param payload the event payload to read from + * (assumes its from `MESSAGE_CREATE`) + * @return `true` if the callback has been performed + */ +bool discord_message_commands_try_perform( + struct discord_message_commands *cmds, + struct discord_gateway_payload *payload); + +/** @} DiscordInternalMessageCommands */ -/** @} DiscordInternalTimer */ /** * @brief The Discord client handler * @@ -805,17 +1125,23 @@ unsigned discord_internal_timer(struct discord *client, * @see discord_init(), discord_config_init(), discord_cleanup() */ struct discord { - /** DISCORD logging module */ + /** `DISCORD` logging module */ struct logconf conf; /** whether this is the original client or a clone */ bool is_original; /** the bot token */ - struct sized_buffer token; + char *token; /** the io poller for listening to file descriptors */ struct io_poller *io_poller; - /** the HTTP adapter for performing requests */ - struct discord_adapter adapter; - /** the WebSockets handle for establishing a connection to Discord */ + + /** the user's message commands @see discord_set_on_command() */ + struct discord_message_commands commands; + /** user's data reference counter for automatic cleanup */ + struct discord_refcounter refcounter; + + /** the handle for interfacing with Discord's REST API */ + struct discord_rest rest; + /** the handle for interfacing with Discord's Gateway API */ struct discord_gateway gw; /** the client's user structure */ struct discord_user self; @@ -833,20 +1159,34 @@ struct discord { unsigned id; } wakeup_timer; - /** triggers when idle. */ + /** triggers when idle */ discord_ev_idle on_idle; /** triggers once per loop cycle */ discord_ev_idle on_cycle; - /** space for user arbitrary data */ + /** user arbitrary data @see discord_set_data() */ void *data; + /** keep tab of amount of worker threads being used by client */ + struct { + /** amount of worker-threads currently being used by client */ + int count; + /** synchronize `count` between workers */ + pthread_mutex_t lock; + /** notify of `count` decrement */ + pthread_cond_t cond; + } * workers; + #ifdef CCORD_VOICE struct discord_voice vcs[DISCORD_MAX_VCS]; - struct discord_voice_cbs voice_cbs; + struct discord_voice_evcallbacks voice_cbs; #endif /* CCORD_VOICE */ }; /** @} DiscordInternal */ +#ifdef __cplusplus +} +#endif /* __cplusplus */ + #endif /* DISCORD_INTERNAL_H */ diff --git a/include/discord-request.h b/include/discord-request.h index ae66d8907..4348b7fbf 100644 --- a/include/discord-request.h +++ b/include/discord-request.h @@ -1,77 +1,81 @@ /** * @file discord-request.h - * @ingroup DiscordInternal + * @ingroup DiscordInternalREST * @author Cogmasters - * @brief Generic macros for initializing a @ref discord_request + * @brief Generic macros for initializing a @ref discord_attributes */ #ifndef DISCORD_REQUEST_H #define DISCORD_REQUEST_H -#define _RET_SAFECOPY_TYPED(dest, src) \ +/* helper typedefs for casting */ +typedef void (*cast_done_typed)(struct discord *, + struct discord_response *, + const void *); +typedef void (*cast_init)(void *); +typedef void (*cast_cleanup)(void *); +typedef size_t (*cast_from_json)(const char *, size_t, void *); + +/* helper typedef for getting sizeof of `struct discord_ret` common fields */ +typedef struct { + DISCORD_RET_DEFAULT_FIELDS; +} discord_ret_default_fields; + +#define _RET_COPY_TYPED(dest, src) \ do { \ + memcpy(&(dest), &(src), sizeof(discord_ret_default_fields)); \ (dest).has_type = true; \ - (dest).done.typed = (void (*)(struct discord * client, void *data, \ - const void *ret))(src) \ - .done; \ - (dest).fail = (src).fail; \ - (dest).data = (src).data; \ - (dest).cleanup = (src).cleanup; \ - (dest).high_p = (src).high_p; \ + (dest).done.typed = (cast_done_typed)(src).done; \ (dest).sync = (src).sync; \ } while (0) -#define _RET_SAFECOPY_TYPELESS(dest, src) \ +#define _RET_COPY_TYPELESS(dest, src) \ do { \ + memcpy(&(dest), &(src), sizeof(discord_ret_default_fields)); \ (dest).has_type = false; \ (dest).done.typeless = (src).done; \ - (dest).fail = (src).fail; \ - (dest).data = (src).data; \ - (dest).cleanup = (src).cleanup; \ - (dest).high_p = (src).high_p; \ (dest).sync = (void *)(src).sync; \ } while (0) /** * @brief Helper for setting attributes for a specs-generated return struct * - * @param req request handler to be initialized + * @param attr attributes handler to be initialized * @param type datatype of the struct - * @param ret request attributes + * @param ret dispatch attributes */ -#define DISCORD_REQ_INIT(req, type, ret) \ +#define DISCORD_ATTR_INIT(attr, type, ret) \ do { \ - (req).gnrc.size = sizeof(struct type); \ - (req).gnrc.init = (void (*)(void *))type##_init; \ - (req).gnrc.from_json = \ - (size_t(*)(const char *, size_t, void *))type##_from_json; \ - (req).gnrc.cleanup = (void (*)(void *))type##_cleanup; \ - if (ret) _RET_SAFECOPY_TYPED(req.ret, *ret); \ + (attr).response.size = sizeof(struct type); \ + (attr).response.init = (cast_init)type##_init; \ + (attr).response.from_json = (cast_from_json)type##_from_json; \ + (attr).response.cleanup = (cast_cleanup)type##_cleanup; \ + if (ret) _RET_COPY_TYPED(attr.dispatch, *ret); \ } while (0) /** * @brief Helper for setting attributes for a specs-generated list * - * @param req request handler to be initialized + * @param attr attributes handler to be initialized * @param type datatype of the list - * @param ret request attributes + * @param ret dispatch attributes */ -#define DISCORD_REQ_LIST_INIT(req, type, ret) \ +#define DISCORD_ATTR_LIST_INIT(attr, type, ret) \ do { \ - (req).gnrc.size = sizeof(struct type); \ - (req).gnrc.from_json = \ - (size_t(*)(const char *, size_t, void *))type##_from_json; \ - (req).gnrc.cleanup = (void (*)(void *))type##_cleanup; \ - if (ret) _RET_SAFECOPY_TYPED(req.ret, *ret); \ + (attr).response.size = sizeof(struct type); \ + (attr).response.from_json = (cast_from_json)type##_from_json; \ + (attr).response.cleanup = (cast_cleanup)type##_cleanup; \ + if (ret) _RET_COPY_TYPED(attr.dispatch, *ret); \ } while (0) /** - * @brief Helper for setting request attributes expecting no response + * @brief Helper for setting attributes for attruests that doensn't expect a + * response object * - * @param req request handler to be initialized - * @param ret request attributes + * @param attr attributes handler to be initialized + * @param ret dispatch attributes */ -#define DISCORD_REQ_BLANK_INIT(req, ret) \ - if (ret) _RET_SAFECOPY_TYPELESS(req.ret, *ret) +#define DISCORD_ATTR_BLANK_INIT(attr, ret) \ + if (ret) _RET_COPY_TYPELESS(attr.dispatch, *ret) #endif /* DISCORD_REQUEST_H */ diff --git a/include/discord-response.h b/include/discord-response.h new file mode 100644 index 000000000..e32f9611c --- /dev/null +++ b/include/discord-response.h @@ -0,0 +1,151 @@ +/** + * @file discord-response.h + * @author Cogmasters + * @brief Generic macros for initializing a @ref discord_response and return + * handles + */ + +#ifndef DISCORD_RESPONSE_H +#define DISCORD_RESPONSE_H + +/** @brief The response for the completed request */ +struct discord_response { + /** user arbitrary data provided at @ref discord_ret */ + void *data; + /** kept concord's parameter provided at @ref discord_ret */ + const void *keep; + /** request completion status @see @ref ConcordError */ + CCORDcode code; +}; + +/****************************************************************************** + * Templates for generating type-safe return handles for async requests + ******************************************************************************/ + +/** + * @brief Macro containing common fields for `struct discord_ret*` datatypes + * @note this exists for alignment purposes + */ +#define DISCORD_RET_DEFAULT_FIELDS \ + /** user arbitrary data to be passed to `done` or `fail` callbacks */ \ + void *data; \ + /** cleanup method to be called for `data`, once its no longer \ + being referenced */ \ + void (*cleanup)(struct discord * client, void *data); \ + /** Concord callback parameter the client wish to keep reference */ \ + const void *keep; \ + /** if `true` then request will be prioritized over already enqueued \ + requests */ \ + bool high_priority; \ + /** optional callback to be executed on a failed request */ \ + void (*fail)(struct discord * client, struct discord_response * resp) + +#define DISCORD_RETURN(_type) \ + /** @brief Request's return context */ \ + struct discord_ret_##_type { \ + DISCORD_RET_DEFAULT_FIELDS; \ + /** optional callback to be executed on a successful request */ \ + void (*done)(struct discord * client, \ + struct discord_response *resp, \ + const struct discord_##_type *ret); \ + /** if an address is provided, then request will block the thread and \ + perform on-spot. \ + On success the response object will be written to the address, \ + unless enabled with @ref DISCORD_SYNC_FLAG */ \ + struct discord_##_type *sync; \ + } + +/** @brief Request's return context */ +struct discord_ret { + DISCORD_RET_DEFAULT_FIELDS; + /** optional callback to be executed on a successful request */ + void (*done)(struct discord *client, struct discord_response *resp); + /** if `true`, request will block the thread and perform on-spot */ + bool sync; +}; + +/** @brief flag for enabling `sync` mode without expecting a datatype return */ +#define DISCORD_SYNC_FLAG ((void *)-1) + +/** @addtogroup DiscordAPIAuditLog + * @{ */ +DISCORD_RETURN(audit_log); +/** @} DiscordAPIAuditLog */ + +/** @addtogroup DiscordAPIChannel + * @{ */ +DISCORD_RETURN(channel); +DISCORD_RETURN(channels); +DISCORD_RETURN(message); +DISCORD_RETURN(messages); +DISCORD_RETURN(followed_channel); +DISCORD_RETURN(thread_members); +DISCORD_RETURN(thread_response_body); +/** @} DiscordAPIChannel */ + +/** @addtogroup DiscordAPIEmoji + * @{ */ +DISCORD_RETURN(emoji); +DISCORD_RETURN(emojis); +/** @} DiscordAPIEmoji */ + +/** @addtogroup DiscordAPIGuild + * @{ */ +DISCORD_RETURN(guild); +DISCORD_RETURN(guilds); +DISCORD_RETURN(guild_preview); +DISCORD_RETURN(guild_member); +DISCORD_RETURN(guild_members); +DISCORD_RETURN(ban); +DISCORD_RETURN(bans); +DISCORD_RETURN(role); +DISCORD_RETURN(roles); +DISCORD_RETURN(welcome_screen); +/** @} DiscordAPIGuild */ + +/** @addtogroup DiscordAPIGuildTemplate + * @{ */ +DISCORD_RETURN(guild_template); +/** @} DiscordAPIGuildTemplate */ + +/** @addtogroup DiscordAPIInvite + * @{ */ +DISCORD_RETURN(invite); +DISCORD_RETURN(invites); +/** @} DiscordAPIInvite */ + +/** @addtogroup DiscordAPIUser + * @{ */ +DISCORD_RETURN(user); +DISCORD_RETURN(users); +DISCORD_RETURN(connections); +/** @} DiscordAPIUser */ + +/** @addtogroup DiscordAPIVoice + * @{ */ +DISCORD_RETURN(voice_regions); +/** @} DiscordAPIVoice */ + +/** @addtogroup DiscordAPIWebhook + * @{ */ +DISCORD_RETURN(webhook); +DISCORD_RETURN(webhooks); +/** @} DiscordAPIWebhook */ + +/** @addtogroup DiscordAPIInteractionsApplicationCommand + * @ingroup DiscordAPIInteractions + * @{ */ +DISCORD_RETURN(application_command); +DISCORD_RETURN(application_commands); +DISCORD_RETURN(application_command_permission); +DISCORD_RETURN(application_command_permissions); +DISCORD_RETURN(guild_application_command_permissions); +/** @} DiscordAPIInteractionsApplicationCommand */ + +/** @addtogroup DiscordAPIInteractionsReact + * @ingroup DiscordAPIInteractions + * @{ */ +DISCORD_RETURN(interaction_response); +/** @} DiscordAPIInteractionsReact */ + +#endif /* DISCORD_RESPONSE_H */ diff --git a/include/discord-templates.h b/include/discord-templates.h deleted file mode 100644 index 0b92dd852..000000000 --- a/include/discord-templates.h +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @file discord-templates.h - * @author Cogmasters - * @brief Macro template for generating type-safe return handles for async - * requests - */ - -#ifndef DISCORD_TEMPLATES_H -#define DISCORD_TEMPLATES_H - -/****************************************************************************** - * Templates for generating type-safe return handles for async requests - ******************************************************************************/ - -#define DISCORDT_RET_DEFAULT_FIELDS \ - /** optional callback to be executed on a failed request */ \ - void (*fail)(struct discord * client, CCORDcode code, void *data); \ - /** user arbitrary data to be retrieved at `done` or `fail` callbacks */ \ - void *data; \ - /** cleanup for when `data` is no longer needed \ - @note this only has to be defined once, it shall be called once \ - `data` is no longer referenced by any callback */ \ - void (*cleanup)(void *data); \ - /** if `true` then request will take priority over already enqueued \ - requests */ \ - bool high_p - -#define DISCORDT_RETURN(_type) \ - /** @brief Request's return context */ \ - struct discord_ret_##_type { \ - /** optional callback to be executed on a successful request */ \ - void (*done)(struct discord * client, \ - void *data, \ - const struct discord_##_type *ret); \ - DISCORDT_RET_DEFAULT_FIELDS; \ - /** if an address is provided, then request will block the thread and \ - perform on-spot. \ - On success the response object will be written to the address, \ - unless enabled with @ref DISCORD_SYNC_FLAG */ \ - struct discord_##_type *sync; \ - } - -/** @brief Request's return context */ -struct discord_ret { - /** optional callback to be executed on a successful request */ - void (*done)(struct discord *client, void *data); - DISCORDT_RET_DEFAULT_FIELDS; - /** if `true`, request will block the thread and perform on-spot */ - bool sync; -}; - -/** @brief flag for enabling `sync` mode without expecting a datatype return */ -#define DISCORD_SYNC_FLAG ((void *)-1) - -/** @addtogroup DiscordAPIAuditLog - * @{ */ -DISCORDT_RETURN(audit_log); -/** @} DiscordAPIAuditLog */ - -/** @addtogroup DiscordAPIChannel - * @{ */ -DISCORDT_RETURN(channel); -DISCORDT_RETURN(channels); -DISCORDT_RETURN(message); -DISCORDT_RETURN(messages); -DISCORDT_RETURN(followed_channel); -DISCORDT_RETURN(thread_members); -DISCORDT_RETURN(thread_response_body); -/** @} DiscordAPIChannel */ - -/** @addtogroup DiscordAPIEmoji - * @{ */ -DISCORDT_RETURN(emoji); -DISCORDT_RETURN(emojis); -/** @} DiscordAPIEmoji */ - -/** @addtogroup DiscordAPIGuild - * @{ */ -DISCORDT_RETURN(guild); -DISCORDT_RETURN(guilds); -DISCORDT_RETURN(guild_preview); -DISCORDT_RETURN(guild_member); -DISCORDT_RETURN(guild_members); -DISCORDT_RETURN(ban); -DISCORDT_RETURN(bans); -DISCORDT_RETURN(role); -DISCORDT_RETURN(roles); -DISCORDT_RETURN(welcome_screen); -/** @} DiscordAPIGuild */ - -/** @addtogroup DiscordAPIGuildTemplate - * @{ */ -DISCORDT_RETURN(guild_template); -/** @} DiscordAPIGuildTemplate */ - -/** @addtogroup DiscordAPIInvite - * @{ */ -DISCORDT_RETURN(invite); -DISCORDT_RETURN(invites); -/** @} DiscordAPIInvite */ - -/** @addtogroup DiscordAPIUser - * @{ */ -DISCORDT_RETURN(user); -DISCORDT_RETURN(users); -DISCORDT_RETURN(connections); -/** @} DiscordAPIUser */ - -/** @addtogroup DiscordAPIVoice - * @{ */ -DISCORDT_RETURN(voice_regions); -/** @} DiscordAPIVoice */ - -/** @addtogroup DiscordAPIWebhook - * @{ */ -DISCORDT_RETURN(webhook); -DISCORDT_RETURN(webhooks); -/** @} DiscordAPIWebhook */ - -/** @addtogroup DiscordAPIInteractionsApplicationCommand - * @ingroup DiscordAPIInteractions - * @{ */ -DISCORDT_RETURN(application_command); -DISCORDT_RETURN(application_commands); -DISCORDT_RETURN(application_command_permission); -DISCORDT_RETURN(application_command_permissions); -DISCORDT_RETURN(guild_application_command_permissions); -/** @} DiscordAPIInteractionsApplicationCommand */ - -/** @addtogroup DiscordAPIInteractionsReact - * @ingroup DiscordAPIInteractions - * @{ */ -DISCORDT_RETURN(interaction_response); -/** @} DiscordAPIInteractionsReact */ - - -#endif /* DISCORD_TEMPLATES_H */ diff --git a/include/discord-voice.h b/include/discord-voice.h index 0e8b23b0f..dc3c0b498 100644 --- a/include/discord-voice.h +++ b/include/discord-voice.h @@ -62,7 +62,7 @@ typedef void (*discord_ev_voice_codec)(struct discord *client, const char video_codec[]); /* CALLBACKS STRUCTURE */ -struct discord_voice_cbs { +struct discord_voice_evcallbacks { /** triggers on every event loop iteration */ discord_ev_voice_idle on_idle; /** triggers when a user start speaking */ @@ -86,7 +86,7 @@ struct discord_voice_cbs { * @see discord_voice_get_vc() */ struct discord_voice { - /** DISCORD_VOICE logging module */ + /** `DISCORD_VOICE` logging module */ struct logconf conf; /** the session guild id @note obtained from discord_voice_join() */ u64snowflake guild_id; @@ -109,7 +109,7 @@ struct discord_voice { struct websockets *ws; /** @brief handle reconnect logic */ - /* RECONNECT STRUCTURE */ + /* reconnect structure */ struct { /** will attempt reconnecting if true */ bool enable; @@ -126,6 +126,11 @@ struct discord_voice { /** can start sending/receiving additional events to discord */ bool is_ready; + /** current iteration JSON string data */ + char *json; + /** current iteration JSON string data length */ + size_t length; + /** parse JSON tokens into a `jsmnf_pairs` key/value pairs hashtable */ struct { /** current iteration JSON key/value pairs */ @@ -171,7 +176,7 @@ struct discord_voice { uintmax_t start_time; } udp_service; - struct discord_voice_cbs *p_voice_cbs; + struct discord_voice_evcallbacks *p_voice_cbs; /** * @brief Interval to divide the received packets @@ -237,26 +242,21 @@ void discord_send_speaking(struct discord_voice *vc, * @brief Update the voice session with a new session_id * * @param client the client created with discord_init() - * @param vs the voice state that has been updated + * @param event the voice state that has been updated * @todo move to discord-internal.h */ void _discord_on_voice_state_update(struct discord *client, - struct discord_voice_state *vs); + struct discord_voice_state *event); /** * @brief Update the voice session with a new token and url * * @param client the client created with discord_init() - * @param guild_id the guild that houses the voice channel - * @param token the unique token identifier - * @param endpoint unique wss url received - * @todo move to discord-internal.h + * @param event the event contents for server update * @note will prepend with "wss://" and append with "?v=4" */ -void _discord_on_voice_server_update(struct discord *client, - u64snowflake guild_id, - char token[], - char endpoint[]); +void _discord_on_voice_server_update( + struct discord *client, struct discord_voice_server_update *event); /** * @brief Gracefully exits a ongoing Discord Voice connection @@ -285,7 +285,7 @@ void discord_voice_reconnect(struct discord_voice *vc, bool resume); * @param callbacks the voice callbacks that will be executed */ void discord_set_voice_cbs(struct discord *client, - struct discord_voice_cbs *callbacks); + struct discord_voice_evcallbacks *callbacks); /** * @brief Check if a Discord Voice connection is alive diff --git a/include/discord-worker.h b/include/discord-worker.h new file mode 100644 index 000000000..e8b341616 --- /dev/null +++ b/include/discord-worker.h @@ -0,0 +1,52 @@ +/** + * @file discord-worker.h + * @author Cogmasters + * @brief Global threadpool + */ + +#ifndef DISCORD_WORKER_H +#define DISCORD_WORKER_H + +#include "error.h" + +/* forward declaration */ +struct discord; +/**/ + +/** @defgroup DiscordInternalWorker Global threadpool + * @ingroup DiscordInternal + * @brief A global threadpool for worker-threads handling + * @{ */ + +/** + * @brief Initialize global threadpool and priority queue + * @return `0` on success, `1` if it has already been initialized + */ +int discord_worker_global_init(void); + +/** @brief Cleanup global threadpool and priority queue */ +void discord_worker_global_cleanup(void); + +/** + * @brief Run a callback from a worker thread + * + * @param client the client that will be using the worker thread + * @param callback user callback to be executed + * @param data user data to be passed to callback + * @CCORD_return + */ +CCORDcode discord_worker_add(struct discord *client, + void (*callback)(void *data), + void *data); + +/** + * @brief Wait until worker-threads being used by `client` have been joined + * + * @param client the client currently using a worker thread + * @CCORD_return + */ +CCORDcode discord_worker_join(struct discord *client); + +/** @} DiscordInternalWorker */ + +#endif /* DISCORD_WORKER_H */ diff --git a/include/discord.h b/include/discord.h index 7fc92d5f7..6c0ed8007 100644 --- a/include/discord.h +++ b/include/discord.h @@ -11,6 +11,10 @@ #ifndef DISCORD_H #define DISCORD_H +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + #include #include @@ -31,7 +35,7 @@ struct discord; #ifdef CCORD_VOICE #include "discord-voice.h" #endif /* CCORD_VOICE */ -#include "discord-templates.h" +#include "discord-response.h" /** @defgroup DiscordConstants Constants * @brief Macros for constants defined by Discord @@ -139,6 +143,29 @@ const char *discord_strerror(CCORDcode code, struct discord *client); #include "discord-events.h" +/** + * @brief Claim ownership of a function parameter provided by Concord + * @see discord_unclaim() + * + * @param client the client initialized with discord_init() + * @param param a function parameter provided by Concord + * @return pointer to `param` (for one-liners) + */ +#define discord_claim(client, param) (__discord_claim(client, param), param) +void __discord_claim(struct discord *client, const void *data); + +/** + * @brief Unclaim ownership of a function parameter provided by Concord + * @note this will trigger the cleanup method of the parameter, so this should + * only be called when you no longer plan to use it + * @see discord_claim() + * + * @param client the client initialized with discord_init() + * @param param a function parameter provided by Concord, that has been + * previously claimed with discord_claim() + */ +void discord_unclaim(struct discord *client, const void *data); + /** * @brief Create a Discord Client handle by its token * @see discord_get_logconf() to configure logging behavior @@ -149,13 +176,26 @@ const char *discord_strerror(CCORDcode code, struct discord *client); struct discord *discord_init(const char token[]); /** - * @brief Create a Discord Client handle by a bot.config file + * @brief Create a Discord Client handle by a `config.json` file * - * @param config_file the bot.config file name + * @param config_file the `config.json` file name * @return the newly created Discord Client handle */ struct discord *discord_config_init(const char config_file[]); +/** + * @brief Get the contents from the config file field + * @note only works if your bot has been initialized with discord_config_init() + * + * @param client the client created with discord_config_init() + * @param path the JSON key path + * @param depth the path depth + * @return a read-only sized buffer containing the field's contents + */ +struct ccord_szbuf_readonly discord_config_get_field(struct discord *client, + char *const path[], + unsigned depth); + /** * @brief Clone a discord client * @@ -230,15 +270,45 @@ void *discord_set_data(struct discord *client, void *data); void *discord_get_data(struct discord *client); /** - * @brief Set the Client presence state + * @brief Set the client presence status + * @deprecated since v2.0.0, use discord_update_presence() instead * @see discord_presence_add_activity() * * @param client the client created with discord_init() - * @param presence change the client's status to it + * @param presence status to update the client's to */ void discord_set_presence(struct discord *client, struct discord_presence_update *presence); +/** + * @brief Request all members for a guild or a list of guilds + * + * @param client the client created with discord_init() + * @param request request guild members information + */ +void discord_request_guild_members( + struct discord *client, struct discord_request_guild_members *request); + +/** + * @brief Sent when a client wants to join, move or disconnect from a voice + * channel + * + * @param client the client created with discord_init() + * @param update request guild members information + */ +void discord_update_voice_state(struct discord *client, + struct discord_update_voice_state *update); + +/** + * @brief Update the client presence status + * @see discord_presence_add_activity() + * + * @param client the client created with discord_init() + * @param presence status to update the client's to + */ +void discord_update_presence(struct discord *client, + struct discord_presence_update *presence); + /** * @brief Get the client WebSockets ping * @note Only works after a connection has been established via discord_run() @@ -438,4 +508,8 @@ bool discord_timer_cancel_and_delete(struct discord *client, unsigned id); /** @} DiscordTimer */ /** @} Discord */ +#ifdef __cplusplus +} +#endif /* __cplusplus */ + #endif /* DISCORD_H */ diff --git a/include/gateway.h b/include/gateway.h index f7f73d60f..05dab2574 100644 --- a/include/gateway.h +++ b/include/gateway.h @@ -19,12 +19,12 @@ * @warning This function blocks the running thread * * @param client the client created with discord_init() - * @param ret if successful, a @ref sized_buffer containing the JSON response + * @param ret if successful, a @ref ccord_szbuf containing the JSON response * @param ret a sized buffer containing the response JSON * @CCORD_return */ CCORDcode discord_get_gateway(struct discord *client, - struct sized_buffer *ret); + struct ccord_szbuf *ret); /** * @brief Get a single valid WSS URL, and additional metadata that can help @@ -35,12 +35,12 @@ CCORDcode discord_get_gateway(struct discord *client, * @warning This function blocks the running thread * * @param client the client created with discord_init() - * @param ret if successful, a @ref sized_buffer containing the JSON response + * @param ret if successful, a @ref ccord_szbuf containing the JSON response * @param ret a sized buffer containing the response JSON * @CCORD_return */ CCORDcode discord_get_gateway_bot(struct discord *client, - struct sized_buffer *ret); + struct ccord_szbuf *ret); /** @defgroup DiscordAPIGatewayHelper Helper functions * @brief Custom helper functions diff --git a/src/application_command.c b/src/application_command.c index d32642897..9e2aa2b45 100644 --- a/src/application_command.c +++ b/src/application_command.c @@ -12,15 +12,15 @@ discord_get_global_application_commands( u64snowflake application_id, struct discord_ret_application_commands *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_application_commands, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_application_commands, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/applications/%" PRIu64 "/commands", - application_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/applications/%" PRIu64 "/commands", + application_id); } CCORDcode @@ -30,8 +30,8 @@ discord_create_global_application_command( struct discord_create_global_application_command *params, struct discord_ret_application_command *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -40,15 +40,15 @@ discord_create_global_application_command( CCORD_EXPECT(client, NOT_EMPTY_STR(params->description), CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_application_command, ret); + DISCORD_ATTR_INIT(attr, discord_application_command, ret); body.size = discord_create_global_application_command_to_json( buf, sizeof(buf), params); body.start = buf; - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/applications/%" PRIu64 "/commands", - application_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/applications/%" PRIu64 "/commands", + application_id); } CCORDcode @@ -58,16 +58,16 @@ discord_get_global_application_command( u64snowflake command_id, struct discord_ret_application_command *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, command_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_application_command, ret); + DISCORD_ATTR_INIT(attr, discord_application_command, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/applications/%" PRIu64 "/commands/%" PRIu64, - application_id, command_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/applications/%" PRIu64 "/commands/%" PRIu64, + application_id, command_id); } CCORDcode @@ -78,8 +78,8 @@ discord_edit_global_application_command( struct discord_edit_global_application_command *params, struct discord_ret_application_command *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -89,11 +89,11 @@ discord_edit_global_application_command( buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_application_command, ret); + DISCORD_ATTR_INIT(attr, discord_application_command, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/applications/%" PRIu64 "/commands/%" PRIu64, - application_id, command_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/applications/%" PRIu64 "/commands/%" PRIu64, + application_id, command_id); } CCORDcode @@ -102,16 +102,16 @@ discord_delete_global_application_command(struct discord *client, u64snowflake command_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, command_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/applications/%" PRIu64 "/commands/%" PRIu64, - application_id, command_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/applications/%" PRIu64 "/commands/%" PRIu64, + application_id, command_id); } CCORDcode @@ -121,8 +121,8 @@ discord_bulk_overwrite_global_application_command( struct discord_application_commands *params, struct discord_ret_application_commands *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[8192]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -131,11 +131,11 @@ discord_bulk_overwrite_global_application_command( body.size = discord_application_commands_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_LIST_INIT(req, discord_application_commands, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_application_commands, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/applications/%" PRIu64 "/commands", - application_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/applications/%" PRIu64 "/commands", + application_id); } CCORDcode @@ -145,17 +145,17 @@ discord_get_guild_application_commands( u64snowflake guild_id, struct discord_ret_application_commands *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_application_commands, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_application_commands, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands", - application_id, guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands", + application_id, guild_id); } CCORDcode @@ -166,8 +166,8 @@ discord_create_guild_application_command( struct discord_create_guild_application_command *params, struct discord_ret_application_command *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -181,12 +181,12 @@ discord_create_guild_application_command( buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_application_command, ret); + DISCORD_ATTR_INIT(attr, discord_application_command, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands", - application_id, guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands", + application_id, guild_id); } CCORDcode @@ -197,18 +197,18 @@ discord_get_guild_application_command( u64snowflake command_id, struct discord_ret_application_command *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, command_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_application_command, ret); + DISCORD_ATTR_INIT(attr, discord_application_command, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands/%" PRIu64, - application_id, guild_id, command_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands/%" PRIu64, + application_id, guild_id, command_id); } CCORDcode @@ -220,8 +220,8 @@ discord_edit_guild_application_command( struct discord_edit_guild_application_command *params, struct discord_ret_application_command *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -232,12 +232,12 @@ discord_edit_guild_application_command( buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_application_command, ret); + DISCORD_ATTR_INIT(attr, discord_application_command, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands/%" PRIu64, - application_id, guild_id, command_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands/%" PRIu64, + application_id, guild_id, command_id); } CCORDcode @@ -247,18 +247,18 @@ discord_delete_guild_application_command(struct discord *client, u64snowflake command_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, command_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands/%" PRIu64, - application_id, guild_id, command_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands/%" PRIu64, + application_id, guild_id, command_id); } CCORDcode @@ -269,8 +269,8 @@ discord_bulk_overwrite_guild_application_command( struct discord_application_commands *params, struct discord_ret_application_commands *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[8192]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -280,12 +280,12 @@ discord_bulk_overwrite_guild_application_command( body.size = discord_application_commands_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_LIST_INIT(req, discord_application_commands, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_application_commands, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands", - application_id, guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands", + application_id, guild_id); } CCORDcode @@ -295,17 +295,17 @@ discord_get_guild_application_command_permissions( u64snowflake guild_id, struct discord_ret_guild_application_command_permissions *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_application_command_permissions, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_application_command_permissions, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands/permissions", - application_id, guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands/permissions", + application_id, guild_id); } CCORDcode @@ -316,18 +316,18 @@ discord_get_application_command_permissions( u64snowflake command_id, struct discord_ret_application_command_permission *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, command_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_application_command_permission, ret); + DISCORD_ATTR_INIT(attr, discord_application_command_permission, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands/%" PRIu64 "/permissions", - application_id, guild_id, command_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands/%" PRIu64 "/permissions", + application_id, guild_id, command_id); } CCORDcode @@ -339,8 +339,8 @@ discord_edit_application_command_permissions( struct discord_edit_application_command_permissions *params, struct discord_ret_application_command_permission *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[8192]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -351,12 +351,12 @@ discord_edit_application_command_permissions( buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_application_command_permission, ret); + DISCORD_ATTR_INIT(attr, discord_application_command_permission, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands/%" PRIu64 "/permissions", - application_id, guild_id, command_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands/%" PRIu64 "/permissions", + application_id, guild_id, command_id); } CCORDcode @@ -367,8 +367,8 @@ discord_batch_edit_application_command_permissions( struct discord_guild_application_command_permissions *params, struct discord_ret_guild_application_command_permissions *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[8192]; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); @@ -379,10 +379,10 @@ discord_batch_edit_application_command_permissions( buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_LIST_INIT(req, discord_application_command_permissions, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_application_command_permissions, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/applications/%" PRIu64 "/guilds/%" PRIu64 - "/commands/permissions", - application_id, guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/applications/%" PRIu64 "/guilds/%" PRIu64 + "/commands/permissions", + application_id, guild_id); } diff --git a/src/audit_log.c b/src/audit_log.c index 60e3bc9ec..58fd10c07 100644 --- a/src/audit_log.c +++ b/src/audit_log.c @@ -6,15 +6,13 @@ #include "discord-internal.h" #include "discord-request.h" -/* FIXME: when response JSON is too large, jsmn crashes on error, most likely - * json_extract() is handling the tokens incorrectly. */ CCORDcode discord_get_guild_audit_log(struct discord *client, u64snowflake guild_id, struct discord_get_guild_audit_log *params, struct discord_ret_audit_log *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char query[1024] = ""; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -47,9 +45,9 @@ discord_get_guild_audit_log(struct discord *client, } } - DISCORD_REQ_INIT(req, discord_audit_log, ret); + DISCORD_ATTR_INIT(attr, discord_audit_log, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/audit-logs%s", guild_id, - query); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/audit-logs%s", guild_id, + query); } diff --git a/src/channel.c b/src/channel.c index 026b64700..db6d01acc 100644 --- a/src/channel.c +++ b/src/channel.c @@ -10,42 +10,44 @@ * Custom functions ******************************************************************************/ -struct _discord_get_channel_at_pos_cxt { +struct _discord_get_channel_at_pos { enum discord_channel_types type; int position; struct discord_ret_channel ret; }; /* XXX: placeholder until channel is obtained via cache at - * discord-get_channel_at_pos() */ + * discord_get_channel_at_pos() */ static void _done_get_channels(struct discord *client, - void *data, + struct discord_response *resp, const struct discord_channels *chs) { - struct _discord_get_channel_at_pos_cxt *cxt = data; - + struct _discord_get_channel_at_pos *cxt = resp->data; const struct discord_channel *found_ch = NULL; - int pos; - int i; - for (i = 0, pos = 0; i < chs->size; ++i) { + for (int i = 0, pos = 0; i < chs->size; ++i) { if (cxt->type == chs->array[i].type && pos++ == cxt->position) { found_ch = &chs->array[i]; break; } } - /* TODO: the following should be replaced by @ref DiscordInternalTimer - * implementation */ + resp->data = cxt->ret.data; + resp->keep = cxt->ret.keep; + if (found_ch) { - if (cxt->ret.done) cxt->ret.done(client, cxt->ret.data, found_ch); + if (cxt->ret.done) cxt->ret.done(client, resp, found_ch); } else if (cxt->ret.fail) { - cxt->ret.fail(client, CCORD_BAD_PARAMETER, cxt->ret.data); + resp->code = CCORD_BAD_PARAMETER; + cxt->ret.fail(client, resp); } - discord_refcounter_decr(client->adapter.refcounter, cxt->ret.data); + if (cxt->ret.keep) + discord_refcounter_decr(&client->refcounter, (void *)cxt->ret.keep); + if (cxt->ret.data) + discord_refcounter_decr(&client->refcounter, cxt->ret.data); } CCORDcode @@ -55,33 +57,39 @@ discord_get_channel_at_pos(struct discord *client, int position, struct discord_ret_channel *ret) { - struct _discord_get_channel_at_pos_cxt *cxt; - struct discord_ret_channels _ret = { 0 }; + struct _discord_get_channel_at_pos *cxt; + struct discord_ret_channels channels_ret = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, ret != NULL, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, ret->done != NULL, CCORD_BAD_PARAMETER, ""); cxt = malloc(sizeof *cxt); - cxt->type = type; - cxt->position = position; - cxt->ret = *ret; - - _ret.done = &_done_get_channels; - _ret.fail = ret->fail; - _ret.data = cxt; - _ret.cleanup = &free; - - /* TODO: the following should be replaced by @ref DiscordInternalTimer - * implementation */ - if (ret->data) { - discord_refcounter_incr(client->adapter.refcounter, ret->data, - ret->cleanup); + *cxt = (struct _discord_get_channel_at_pos){ .type = type, + .position = position, + .ret = *ret }; + + channels_ret.done = &_done_get_channels; + channels_ret.fail = ret->fail; + channels_ret.data = cxt; + + if (ret->keep) { + CCORDcode code = + discord_refcounter_incr(&client->refcounter, (void *)ret->keep); + ASSERT_S(code == CCORD_OK, + "'.keep' data must be a Concord callback parameter"); + } + if (ret->data + && CCORD_UNAVAILABLE + == discord_refcounter_incr(&client->refcounter, ret->data)) + { + discord_refcounter_add_client(&client->refcounter, ret->data, + ret->cleanup, false); } /* TODO: fetch channel via caching, and return if results are non-existent */ - return discord_get_guild_channels(client, guild_id, &_ret); + return discord_get_guild_channels(client, guild_id, &channels_ret); } /****************************************************************************** @@ -93,14 +101,14 @@ discord_get_channel(struct discord *client, u64snowflake channel_id, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64, channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64, channel_id); } CCORDcode @@ -109,8 +117,8 @@ discord_modify_channel(struct discord *client, struct discord_modify_channel *params, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -119,10 +127,10 @@ discord_modify_channel(struct discord *client, body.size = discord_modify_channel_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/channels/%" PRIu64, channel_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/channels/%" PRIu64, channel_id); } CCORDcode @@ -130,14 +138,14 @@ discord_delete_channel(struct discord *client, u64snowflake channel_id, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64, channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64, channel_id); } CCORDcode @@ -146,7 +154,7 @@ discord_get_channel_messages(struct discord *client, struct discord_get_channel_messages *params, struct discord_ret_messages *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char query[1024] = ""; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -179,11 +187,11 @@ discord_get_channel_messages(struct discord *client, } } - DISCORD_REQ_LIST_INIT(req, discord_messages, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_messages, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/messages%s%s", - channel_id, *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/messages%s%s", channel_id, + *query ? "?" : "", query); } CCORDcode @@ -192,16 +200,16 @@ discord_get_channel_message(struct discord *client, u64snowflake message_id, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/messages/%" PRIu64, - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/messages/%" PRIu64, + channel_id, message_id); } CCORDcode @@ -210,8 +218,8 @@ discord_create_message(struct discord *client, struct discord_create_message *params, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; enum http_method method; char buf[16384]; /**< @todo dynamic buffer */ @@ -223,16 +231,16 @@ discord_create_message(struct discord *client, if (params->attachments) { method = HTTP_MIMEPOST; - req.attachments = *params->attachments; + attr.attachments = *params->attachments; } else { method = HTTP_POST; } - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, &body, method, - "/channels/%" PRIu64 "/messages", channel_id); + return discord_rest_run(&client->rest, &attr, &body, method, + "/channels/%" PRIu64 "/messages", channel_id); } CCORDcode @@ -241,17 +249,17 @@ discord_crosspost_message(struct discord *client, u64snowflake message_id, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_POST, - "/channels/%" PRIu64 "/messages/%" PRIu64 - "/crosspost", - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_POST, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/crosspost", + channel_id, message_id); } CCORDcode @@ -262,7 +270,7 @@ discord_create_reaction(struct discord *client, const char emoji_name[], struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char *pct_emoji_name; char emoji_endpoint[256]; CCORDcode code; @@ -279,12 +287,12 @@ discord_create_reaction(struct discord *client, else snprintf(emoji_endpoint, sizeof(emoji_endpoint), "%s", pct_emoji_name); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - code = discord_adapter_run(&client->adapter, &req, NULL, HTTP_PUT, - "/channels/%" PRIu64 "/messages/%" PRIu64 - "/reactions/%s/@me", - channel_id, message_id, emoji_endpoint); + code = discord_rest_run(&client->rest, &attr, NULL, HTTP_PUT, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/reactions/%s/@me", + channel_id, message_id, emoji_endpoint); curl_free(pct_emoji_name); @@ -299,7 +307,7 @@ discord_delete_own_reaction(struct discord *client, const char emoji_name[], struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char *pct_emoji_name; char emoji_endpoint[256]; CCORDcode code; @@ -316,12 +324,12 @@ discord_delete_own_reaction(struct discord *client, else snprintf(emoji_endpoint, sizeof(emoji_endpoint), "%s", pct_emoji_name); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - code = discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/messages/%" PRIu64 - "/reactions/%s/@me", - channel_id, message_id, emoji_endpoint); + code = discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/reactions/%s/@me", + channel_id, message_id, emoji_endpoint); curl_free(pct_emoji_name); @@ -337,7 +345,7 @@ discord_delete_user_reaction(struct discord *client, const char emoji_name[], struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char *pct_emoji_name; char emoji_endpoint[256]; CCORDcode code; @@ -355,12 +363,12 @@ discord_delete_user_reaction(struct discord *client, else snprintf(emoji_endpoint, sizeof(emoji_endpoint), "%s", pct_emoji_name); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - code = discord_adapter_run( - &client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/messages/%" PRIu64 "/reactions/%s/%" PRIu64, - channel_id, message_id, emoji_endpoint, user_id); + code = discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/reactions/%s/%" PRIu64, + channel_id, message_id, emoji_endpoint, user_id); curl_free(pct_emoji_name); @@ -376,7 +384,7 @@ discord_get_reactions(struct discord *client, struct discord_get_reactions *params, struct discord_ret_users *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char emoji_endpoint[256]; char query[1024] = ""; char *pct_emoji_name; @@ -415,12 +423,12 @@ discord_get_reactions(struct discord *client, else snprintf(emoji_endpoint, sizeof(emoji_endpoint), "%s", pct_emoji_name); - DISCORD_REQ_LIST_INIT(req, discord_users, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_users, ret); - code = discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/messages/%" PRIu64 - "/reactions/%s%s", - channel_id, message_id, emoji_endpoint, query); + code = discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/reactions/%s%s", + channel_id, message_id, emoji_endpoint, query); curl_free(pct_emoji_name); @@ -433,17 +441,17 @@ discord_delete_all_reactions(struct discord *client, u64snowflake message_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/messages/%" PRIu64 - "/reactions", - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/reactions", + channel_id, message_id); } CCORDcode @@ -454,7 +462,7 @@ discord_delete_all_reactions_for_emoji(struct discord *client, const char emoji_name[], struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char *pct_emoji_name; char emoji_endpoint[256]; CCORDcode code; @@ -471,12 +479,12 @@ discord_delete_all_reactions_for_emoji(struct discord *client, else snprintf(emoji_endpoint, sizeof(emoji_endpoint), "%s", pct_emoji_name); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - code = discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/messages/%" PRIu64 - "/reactions/%s", - channel_id, message_id, emoji_endpoint); + code = discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/reactions/%s", + channel_id, message_id, emoji_endpoint); curl_free(pct_emoji_name); @@ -490,8 +498,8 @@ discord_edit_message(struct discord *client, struct discord_edit_message *params, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[16384]; /**< @todo dynamic buffer */ CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -501,11 +509,11 @@ discord_edit_message(struct discord *client, body.size = discord_edit_message_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/channels/%" PRIu64 "/messages/%" PRIu64, - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/channels/%" PRIu64 "/messages/%" PRIu64, + channel_id, message_id); } CCORDcode @@ -514,16 +522,16 @@ discord_delete_message(struct discord *client, u64snowflake message_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/messages/%" PRIu64, - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/messages/%" PRIu64, + channel_id, message_id); } /** @todo add duplicated ID verification */ @@ -533,9 +541,9 @@ discord_bulk_delete_messages(struct discord *client, struct snowflakes *messages, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; u64unix_ms now = discord_timestamp(client); - struct sized_buffer body; + struct ccord_szbuf body; char buf[4096] = ""; int i; @@ -556,11 +564,11 @@ discord_bulk_delete_messages(struct discord *client, CCORD_EXPECT(client, buf != NULL, CCORD_BAD_JSON, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/channels/%" PRIu64 "/messages/bulk-delete", - channel_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/channels/%" PRIu64 "/messages/bulk-delete", + channel_id); } CCORDcode @@ -571,8 +579,8 @@ discord_edit_channel_permissions( struct discord_edit_channel_permissions *params, struct discord_ret *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -583,11 +591,11 @@ discord_edit_channel_permissions( discord_edit_channel_permissions_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/channels/%" PRIu64 "/permissions/%" PRIu64, - channel_id, overwrite_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/channels/%" PRIu64 "/permissions/%" PRIu64, + channel_id, overwrite_id); } CCORDcode @@ -595,14 +603,14 @@ discord_get_channel_invites(struct discord *client, u64snowflake channel_id, struct discord_ret_invites *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_invites, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_invites, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/invites", channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/invites", channel_id); } CCORDcode @@ -611,8 +619,8 @@ discord_create_channel_invite(struct discord *client, struct discord_create_channel_invite *params, struct discord_ret_invite *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024] = "{}"; size_t len = 2; @@ -624,10 +632,10 @@ discord_create_channel_invite(struct discord *client, body.start = buf; body.size = len; - DISCORD_REQ_INIT(req, discord_invite, ret); + DISCORD_ATTR_INIT(attr, discord_invite, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/channels/%" PRIu64 "/invites", channel_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/channels/%" PRIu64 "/invites", channel_id); } CCORDcode @@ -636,16 +644,16 @@ discord_delete_channel_permission(struct discord *client, u64snowflake overwrite_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, overwrite_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/permissions/%" PRIu64, - channel_id, overwrite_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/permissions/%" PRIu64, + channel_id, overwrite_id); } CCORDcode @@ -654,8 +662,8 @@ discord_follow_news_channel(struct discord *client, struct discord_follow_news_channel *params, struct discord_ret_followed_channel *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[256]; /* should be more than enough for this */ CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -666,10 +674,10 @@ discord_follow_news_channel(struct discord *client, body.size = discord_follow_news_channel_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/channels/%" PRIu64 "/followers", channel_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/channels/%" PRIu64 "/followers", channel_id); } CCORDcode @@ -677,14 +685,14 @@ discord_trigger_typing_indicator(struct discord *client, u64snowflake channel_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_POST, - "/channels/%" PRIu64 "/typing", channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_POST, + "/channels/%" PRIu64 "/typing", channel_id); } CCORDcode @@ -692,14 +700,14 @@ discord_get_pinned_messages(struct discord *client, u64snowflake channel_id, struct discord_ret_messages *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_messages, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_messages, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/pins", channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/pins", channel_id); } CCORDcode @@ -708,16 +716,16 @@ discord_pin_message(struct discord *client, u64snowflake message_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_PUT, - "/channels/%" PRIu64 "/pins/%" PRIu64, - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_PUT, + "/channels/%" PRIu64 "/pins/%" PRIu64, channel_id, + message_id); } CCORDcode @@ -726,16 +734,16 @@ discord_unpin_message(struct discord *client, u64snowflake message_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/pins/%" PRIu64, - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/pins/%" PRIu64, channel_id, + message_id); } CCORDcode @@ -745,8 +753,8 @@ discord_group_dm_add_recipient(struct discord *client, struct discord_group_dm_add_recipient *params, struct discord_ret *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -757,11 +765,11 @@ discord_group_dm_add_recipient(struct discord *client, discord_group_dm_add_recipient_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/channels/%" PRIu64 "/recipients/%" PRIu64, - channel_id, user_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/channels/%" PRIu64 "/recipients/%" PRIu64, + channel_id, user_id); } CCORDcode @@ -770,16 +778,16 @@ discord_group_dm_remove_recipient(struct discord *client, u64snowflake user_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/recipients/%" PRIu64, - channel_id, user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/recipients/%" PRIu64, + channel_id, user_id); } CCORDcode @@ -790,8 +798,8 @@ discord_start_thread_with_message( struct discord_start_thread_with_message *params, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -802,12 +810,12 @@ discord_start_thread_with_message( discord_start_thread_with_message_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/channels/%" PRIu64 "/messages/%" PRIu64 - "/threads", - channel_id, message_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/channels/%" PRIu64 "/messages/%" PRIu64 + "/threads", + channel_id, message_id); } CCORDcode @@ -817,8 +825,8 @@ discord_start_thread_without_message( struct discord_start_thread_without_message *params, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -828,10 +836,10 @@ discord_start_thread_without_message( discord_start_thread_without_message_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/channels/%" PRIu64 "/threads", channel_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/channels/%" PRIu64 "/threads", channel_id); } CCORDcode @@ -839,15 +847,15 @@ discord_join_thread(struct discord *client, u64snowflake channel_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_PUT, - "/channels/%" PRIu64 "/thread-members/@me", - channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_PUT, + "/channels/%" PRIu64 "/thread-members/@me", + channel_id); } CCORDcode @@ -856,16 +864,16 @@ discord_add_thread_member(struct discord *client, u64snowflake user_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_PUT, - "/channels/%" PRIu64 "/thread-members/" PRIu64, - channel_id, user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_PUT, + "/channels/%" PRIu64 "/thread-members/" PRIu64, + channel_id, user_id); } CCORDcode @@ -873,15 +881,15 @@ discord_leave_thread(struct discord *client, u64snowflake channel_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/thread-members/@me", - channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/thread-members/@me", + channel_id); } CCORDcode @@ -890,16 +898,16 @@ discord_remove_thread_member(struct discord *client, u64snowflake user_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/channels/%" PRIu64 "/thread-members/" PRIu64, - channel_id, user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/channels/%" PRIu64 "/thread-members/" PRIu64, + channel_id, user_id); } CCORDcode @@ -907,15 +915,15 @@ discord_list_thread_members(struct discord *client, u64snowflake channel_id, struct discord_ret_thread_members *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_thread_members, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_thread_members, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/thread-members", - channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/thread-members", + channel_id); } CCORDcode @@ -923,15 +931,15 @@ discord_list_active_threads(struct discord *client, u64snowflake channel_id, struct discord_ret_thread_response_body *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_thread_response_body, ret); + DISCORD_ATTR_INIT(attr, discord_thread_response_body, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/threads/active", - channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/threads/active", + channel_id); } CCORDcode @@ -942,7 +950,7 @@ discord_list_public_archived_threads( int limit, struct discord_ret_thread_response_body *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char query[1024] = ""; int offset = 0; @@ -959,12 +967,12 @@ discord_list_public_archived_threads( ASSERT_NOT_OOB(offset, sizeof(query)); } - DISCORD_REQ_INIT(req, discord_thread_response_body, ret); + DISCORD_ATTR_INIT(attr, discord_thread_response_body, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 - "/threads/archived/public%s%s", - channel_id, *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 + "/threads/archived/public%s%s", + channel_id, *query ? "?" : "", query); } CCORDcode @@ -975,7 +983,7 @@ discord_list_private_archived_threads( int limit, struct discord_ret_thread_response_body *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char query[1024] = ""; int offset = 0; @@ -992,12 +1000,12 @@ discord_list_private_archived_threads( ASSERT_NOT_OOB(offset, sizeof(query)); } - DISCORD_REQ_INIT(req, discord_thread_response_body, ret); + DISCORD_ATTR_INIT(attr, discord_thread_response_body, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 - "/threads/archived/private%s%s", - channel_id, *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 + "/threads/archived/private%s%s", + channel_id, *query ? "?" : "", query); } CCORDcode @@ -1008,7 +1016,7 @@ discord_list_joined_private_archived_threads( int limit, struct discord_ret_thread_response_body *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char query[1024] = ""; int offset = 0; @@ -1025,10 +1033,10 @@ discord_list_joined_private_archived_threads( ASSERT_NOT_OOB(offset, sizeof(query)); } - DISCORD_REQ_INIT(req, discord_thread_response_body, ret); + DISCORD_ATTR_INIT(attr, discord_thread_response_body, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 - "/users/@me/threads/archived/private%s%s", - channel_id, *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 + "/users/@me/threads/archived/private%s%s", + channel_id, *query ? "?" : "", query); } diff --git a/src/concord-once.c b/src/concord-once.c index 09aba9c08..7714ac8e3 100644 --- a/src/concord-once.c +++ b/src/concord-once.c @@ -2,7 +2,7 @@ #include #include "error.h" -#include "work.h" +#include "discord-worker.h" /* if set to 1 then client(s) will be disconnected */ int ccord_has_sigint = 0; @@ -12,7 +12,7 @@ static int once; #ifdef CCORD_SIGINTCATCH /* shutdown gracefully on SIGINT received */ static void -sigint_handler(int signum) +_ccord_sigint_handler(int signum) { (void)signum; fputs("\nSIGINT: Disconnecting running concord client(s) ...\n", stderr); @@ -28,13 +28,13 @@ ccord_global_init() } else { #ifdef CCORD_SIGINTCATCH - signal(SIGINT, &sigint_handler); + signal(SIGINT, &_ccord_sigint_handler); #endif if (0 != curl_global_init(CURL_GLOBAL_DEFAULT)) { fputs("Couldn't start libcurl's globals\n", stderr); return CCORD_GLOBAL_INIT; } - if (work_global_init()) { + if (discord_worker_global_init()) { fputs("Attempt duplicate global initialization\n", stderr); return CCORD_GLOBAL_INIT; } @@ -47,6 +47,7 @@ void ccord_global_cleanup() { curl_global_cleanup(); - work_global_cleanup(); + discord_worker_global_cleanup(); once = 0; + ccord_has_sigint = 0; } diff --git a/src/discord-adapter.c b/src/discord-adapter.c deleted file mode 100644 index acff787e9..000000000 --- a/src/discord-adapter.c +++ /dev/null @@ -1,771 +0,0 @@ -#include -#include -#include -#include - -#include "carray.h" - -#include "discord.h" -#include "discord-internal.h" - -/* No-lock alternative to discord_timestamp() */ -#define NOW(p_adapter) (CLIENT(p_adapter, adapter)->gw.timer->now) - -static void -setopt_cb(struct ua_conn *conn, void *p_token) -{ - struct sized_buffer *token = p_token; - char auth[128]; - int len; - - len = snprintf(auth, sizeof(auth), "Bot %.*s", (int)token->size, - token->start); - ASSERT_NOT_OOB(len, sizeof(auth)); - - ua_conn_add_header(conn, "Authorization", auth); - -#ifdef CCORD_DEBUG_ADAPTER - curl_easy_setopt(ua_conn_get_easy_handle(conn), CURLOPT_VERBOSE, 1L); -#endif /* CCORD_DEBUG_ADAPTER */ -} - -static int -on_io_poller_curl(struct io_poller *io, CURLM *mhandle, void *user_data) -{ - (void)io; - (void)mhandle; - return discord_adapter_perform(user_data); -} - -void -discord_adapter_init(struct discord_adapter *adapter, - struct logconf *conf, - struct sized_buffer *token) -{ - struct ua_attr attr = { 0 }; - - attr.conf = conf; - adapter->ua = ua_init(&attr); - ua_set_url(adapter->ua, DISCORD_API_BASE_URL); - - if (!token->size) { - /* no token means a webhook-only client */ - logconf_branch(&adapter->conf, conf, "DISCORD_WEBHOOK"); - } - else { - /* bot client */ - logconf_branch(&adapter->conf, conf, "DISCORD_HTTP"); - ua_set_opt(adapter->ua, token, &setopt_cb); - } - - adapter->mhandle = curl_multi_init(); - io_poller_curlm_add(CLIENT(adapter, adapter)->io_poller, adapter->mhandle, - on_io_poller_curl, adapter); - - adapter->ratelimiter = discord_ratelimiter_init(&adapter->conf); - adapter->refcounter = discord_refcounter_init(&adapter->conf); - - /* idleq is malloc'd to guarantee a client cloned by discord_clone() will - * share the same queue with the original */ - adapter->idleq = malloc(sizeof(QUEUE)); - QUEUE_INIT(adapter->idleq); - - adapter->retry_limit = 3; /* TODO: shouldn't be a hard limit */ -} - -static void -_discord_context_cleanup(struct discord_context *cxt) -{ - discord_attachments_cleanup(&cxt->req.attachments); - if (cxt->body.buf.start) free(cxt->body.buf.start); - free(cxt); -} - -void -discord_adapter_cleanup(struct discord_adapter *adapter) -{ - QUEUE(struct discord_context) queue, *qelem; - struct discord_context *cxt; - - /* cleanup User-Agent handle */ - ua_cleanup(adapter->ua); - - io_poller_curlm_del(CLIENT(adapter, adapter)->io_poller, adapter->mhandle); - curl_multi_cleanup(adapter->mhandle); - - /* move pending requests to idleq */ - discord_adapter_stop_buckets(adapter); - /* cleanup discovered buckets */ - discord_ratelimiter_cleanup(adapter->ratelimiter); - /* cleanup stored user data */ - discord_refcounter_cleanup(adapter->refcounter); - - /* cleanup idle requests queue */ - QUEUE_MOVE(adapter->idleq, &queue); - while (!QUEUE_EMPTY(&queue)) { - qelem = QUEUE_HEAD(&queue); - cxt = QUEUE_DATA(qelem, struct discord_context, entry); - QUEUE_REMOVE(&cxt->entry); - _discord_context_cleanup(cxt); - } - - free(adapter->idleq); -} - -static CCORDcode _discord_adapter_run_sync(struct discord_adapter *adapter, - struct discord_request *req, - struct sized_buffer *body, - enum http_method method, - char endpoint[DISCORD_ENDPT_LEN], - char key[DISCORD_ROUTE_LEN]); - -static CCORDcode _discord_adapter_run_async(struct discord_adapter *adapter, - struct discord_request *req, - struct sized_buffer *body, - enum http_method method, - char endpoint[DISCORD_ENDPT_LEN], - char key[DISCORD_ROUTE_LEN]); - -/* template function for performing requests */ -CCORDcode -discord_adapter_run(struct discord_adapter *adapter, - struct discord_request *req, - struct sized_buffer *body, - enum http_method method, - char endpoint_fmt[], - ...) -{ - static struct discord_request blank_req = { 0 }; - char endpoint[DISCORD_ENDPT_LEN]; - char key[DISCORD_ROUTE_LEN]; - va_list args; - int len; - - /* have it point somewhere */ - if (!req) req = &blank_req; - - /* build the endpoint string */ - va_start(args, endpoint_fmt); - len = vsnprintf(endpoint, sizeof(endpoint), endpoint_fmt, args); - ASSERT_NOT_OOB(len, sizeof(endpoint)); - va_end(args); - - /* build the bucket's key */ - va_start(args, endpoint_fmt); - discord_ratelimiter_build_key(method, key, endpoint_fmt, args); - va_end(args); - - if (req->ret.sync) { /* perform blocking request */ - if (req->ret.has_type && req->ret.sync != DISCORD_SYNC_FLAG) - req->gnrc.data = req->ret.sync; - - return _discord_adapter_run_sync(adapter, req, body, method, endpoint, - key); - } - - /* enqueue asynchronous request */ - return _discord_adapter_run_async(adapter, req, body, method, endpoint, - key); -} - -static void -_discord_context_to_mime(curl_mime *mime, void *p_cxt) -{ - struct discord_context *cxt = p_cxt; - struct discord_attachments *atchs = &cxt->req.attachments; - struct sized_buffer *body = &cxt->body.buf; - curl_mimepart *part; - char name[64]; - int i; - - /* json part */ - if (body->start && body->size) { - part = curl_mime_addpart(mime); - curl_mime_data(part, body->start, body->size); - curl_mime_type(part, "application/json"); - curl_mime_name(part, "payload_json"); - } - - /* attachment part */ - for (i = 0; i < atchs->size; ++i) { - int len = snprintf(name, sizeof(name), "files[%d]", i); - ASSERT_NOT_OOB(len, sizeof(name)); - - if (atchs->array[i].content) { - part = curl_mime_addpart(mime); - curl_mime_data(part, atchs->array[i].content, - atchs->array[i].size ? atchs->array[i].size - : CURL_ZERO_TERMINATED); - curl_mime_filename(part, !atchs->array[i].filename - ? "a.out" - : atchs->array[i].filename); - curl_mime_type(part, !atchs->array[i].content_type - ? "application/octet-stream" - : atchs->array[i].content_type); - curl_mime_name(part, name); - } - else if (atchs->array[i].filename) { - CURLcode code; - - /* fetch local file by the filename */ - part = curl_mime_addpart(mime); - code = curl_mime_filedata(part, atchs->array[i].filename); - if (code != CURLE_OK) { - char errbuf[256]; - snprintf(errbuf, sizeof(errbuf), "%s (file: %s)", - curl_easy_strerror(code), atchs->array[i].filename); - perror(errbuf); - } - curl_mime_type(part, !atchs->array[i].content_type - ? "application/octet-stream" - : atchs->array[i].content_type); - curl_mime_name(part, name); - } - } -} - -/* return true if there should be a retry attempt */ -static bool -_discord_adapter_get_info(struct discord_adapter *adapter, - struct ua_info *info, - int64_t *wait_ms) -{ - if (info->code != CCORD_HTTP_CODE) { - /** CCORD_OK or internal error */ - return false; - } - - switch (info->httpcode) { - case HTTP_FORBIDDEN: - case HTTP_NOT_FOUND: - case HTTP_BAD_REQUEST: - info->code = CCORD_DISCORD_JSON_CODE; - return false; - case HTTP_UNAUTHORIZED: - logconf_fatal( - &adapter->conf, - "UNAUTHORIZED: Please provide a valid authentication token"); - info->code = CCORD_DISCORD_BAD_AUTH; - return false; - case HTTP_METHOD_NOT_ALLOWED: - logconf_fatal(&adapter->conf, - "METHOD_NOT_ALLOWED: The server couldn't recognize the " - "received HTTP method"); - return false; - case HTTP_TOO_MANY_REQUESTS: { - struct sized_buffer body = ua_info_get_body(info); - struct jsmnftok message = { 0 }; - double retry_after = 1.0; - bool is_global = false; - jsmn_parser parser; - jsmntok_t tokens[16]; - - jsmn_init(&parser); - if (0 < jsmn_parse(&parser, body.start, body.size, tokens, - sizeof(tokens) / sizeof *tokens)) - { - jsmnf_loader loader; - jsmnf_pair pairs[16]; - - jsmnf_init(&loader); - if (0 < jsmnf_load(&loader, body.start, tokens, parser.toknext, - pairs, sizeof(pairs) / sizeof *pairs)) - { - jsmnf_pair *f; - - if ((f = jsmnf_find(pairs, body.start, "global", 6))) - is_global = ('t' == body.start[f->v.pos]); - if ((f = jsmnf_find(pairs, body.start, "message", 7))) - message = f->v; - if ((f = jsmnf_find(pairs, body.start, "retry_after", 11))) - retry_after = strtod(body.start + f->v.pos, NULL); - } - } - - *wait_ms = (int64_t)(1000 * retry_after); - if (*wait_ms < 0) *wait_ms = 0; - - logconf_warn(&adapter->conf, - "429 %s RATELIMITING (wait: %" PRId64 " ms) : %.*s", - is_global ? "GLOBAL" : "", *wait_ms, message.len, - body.start + message.pos); - - return true; - } - default: - if (info->httpcode >= 500) { /* Server Error */ - return true; - } - return false; - } -} - -/* SYNCHRONOUS REQUEST LOGIC */ - -/* perform a blocking request */ -static CCORDcode -_discord_adapter_run_sync(struct discord_adapter *adapter, - struct discord_request *req, - struct sized_buffer *body, - enum http_method method, - char endpoint[DISCORD_ENDPT_LEN], - char key[DISCORD_ROUTE_LEN]) -{ - struct ua_conn_attr conn_attr = { method, body, endpoint, NULL }; - /* throw-away for ua_conn_set_mime() */ - struct discord_context cxt = { 0 }; - struct discord_bucket *b; - struct ua_conn *conn; - int retry_attempt = 0; - bool retry; - CCORDcode code; - - b = discord_bucket_get(adapter->ratelimiter, key); - conn = ua_conn_start(adapter->ua); - - if (HTTP_MIMEPOST == method) { - cxt.req.attachments = req->attachments; - cxt.body.buf = *body; - - ua_conn_add_header(conn, "Content-Type", "multipart/form-data"); - ua_conn_set_mime(conn, &cxt, &_discord_context_to_mime); - } - else { - ua_conn_add_header(conn, "Content-Type", "application/json"); - } - - ua_conn_setup(conn, &conn_attr); - - pthread_mutex_lock(&b->lock); - do { - discord_bucket_try_sleep(adapter->ratelimiter, b); - - /* perform blocking request, and check results */ - switch (code = ua_conn_easy_perform(conn)) { - case CCORD_OK: { - struct discord *client = CLIENT(adapter, adapter); - struct ua_info info = { 0 }; - struct sized_buffer resp; - int64_t wait_ms = 0; - - ua_info_extract(conn, &info); - retry = _discord_adapter_get_info(adapter, &info, &wait_ms); - - resp = ua_info_get_body(&info); - if (info.code != CCORD_OK) { - logconf_error(&client->conf, "%.*s", (int)resp.size, - resp.start); - } - else if (req->gnrc.data) { - /* initialize ret */ - if (req->gnrc.init) req->gnrc.init(req->gnrc.data); - - /* populate ret */ - if (req->gnrc.from_json) - req->gnrc.from_json(resp.start, resp.size, req->gnrc.data); - } - - code = info.code; - - /* in the off-chance of having consecutive blocking calls, update - * timestamp used for ratelimiting - * TODO: redundant for REST-only clients - * TODO: create discord_timestamp_update() */ - ws_timestamp_update(client->gw.ws); - - discord_ratelimiter_build(adapter->ratelimiter, b, key, &info); - cog_sleep_ms(wait_ms); - - ua_info_cleanup(&info); - } break; - case CCORD_CURLE_INTERNAL: - logconf_error(&adapter->conf, - "Curl internal error, will retry again"); - retry = true; - break; - default: - logconf_error(&adapter->conf, "CCORD code: %d", code); - retry = false; - break; - } - - ua_conn_reset(conn); - - } while (retry && retry_attempt++ < adapter->retry_limit); - pthread_mutex_unlock(&b->lock); - - /* reset conn and mark it as free to use */ - ua_conn_stop(conn); - - return code; -} - -/* ASYNCHRONOUS REQUEST LOGIC */ - -/* TODO: make this kind of function gencodecs generated (optional) - * - * Only the fields that are required at _discord_context_to_mime() - * are duplicated*/ -static void -_discord_attachments_dup(struct discord_attachments *dest, - struct discord_attachments *src) -{ - int i; - - if (!src->size) return; - - __carray_init(dest, (size_t)src->size, struct discord_attachment, , ); - for (i = 0; i < src->size; ++i) { - carray_insert(dest, i, src->array[i]); - if (src->array[i].content) { - dest->array[i].size = src->array[i].size - ? src->array[i].size - : strlen(src->array[i].content) + 1; - - dest->array[i].content = malloc(dest->array[i].size); - memcpy(dest->array[i].content, src->array[i].content, - dest->array[i].size); - } - if (src->array[i].filename) - dest->array[i].filename = strdup(src->array[i].filename); - if (src->array[i].content_type) - dest->array[i].content_type = strdup(src->array[i].content_type); - } -} - -static void -_discord_context_reset(struct discord_context *cxt) -{ - ua_conn_stop(cxt->conn); - - cxt->b = NULL; - cxt->body.buf.size = 0; - cxt->method = 0; - *cxt->endpoint = '\0'; - *cxt->key = '\0'; - cxt->conn = NULL; - cxt->retry_attempt = 0; - discord_attachments_cleanup(&cxt->req.attachments); - - memset(&cxt->req, 0, sizeof(struct discord_request)); -} - -static void -_discord_context_populate(struct discord_context *cxt, - struct discord_adapter *adapter, - struct discord_request *req, - struct sized_buffer *body, - enum http_method method, - char endpoint[DISCORD_ENDPT_LEN], - char key[DISCORD_ROUTE_LEN]) -{ - cxt->method = method; - - memcpy(&cxt->req, req, sizeof(struct discord_request)); - _discord_attachments_dup(&cxt->req.attachments, &req->attachments); - - if (body) { - /* copy request body */ - if (body->size > cxt->body.memsize) { - /* needs to increase buffer size */ - void *tmp = realloc(cxt->body.buf.start, body->size); - ASSERT_S(tmp != NULL, "Out of memory"); - - cxt->body.buf.start = tmp; - cxt->body.memsize = body->size; - } - memcpy(cxt->body.buf.start, body->start, body->size); - cxt->body.buf.size = body->size; - } - - /* copy endpoint over to cxt */ - memcpy(cxt->endpoint, endpoint, sizeof(cxt->endpoint)); - /* copy bucket's key */ - memcpy(cxt->key, key, sizeof(cxt->key)); - /* bucket pertaining to the request */ - cxt->b = discord_bucket_get(adapter->ratelimiter, key); -} - -/* enqueue a request to be executed asynchronously */ -static CCORDcode -_discord_adapter_run_async(struct discord_adapter *adapter, - struct discord_request *req, - struct sized_buffer *body, - enum http_method method, - char endpoint[DISCORD_ENDPT_LEN], - char key[DISCORD_ROUTE_LEN]) -{ - struct discord_context *cxt; - - if (QUEUE_EMPTY(adapter->idleq)) { /* create new context struct */ - cxt = calloc(1, sizeof(struct discord_context)); - } - else { /* recycle a context struct from idleq */ - QUEUE(struct discord_context) *qelem = QUEUE_HEAD(adapter->idleq); - QUEUE_REMOVE(qelem); - cxt = QUEUE_DATA(qelem, struct discord_context, entry); - } - QUEUE_INIT(&cxt->entry); - - _discord_context_populate(cxt, adapter, req, body, method, endpoint, key); - - if (req->ret.high_p) - QUEUE_INSERT_HEAD(&cxt->b->waitq, &cxt->entry); - else - QUEUE_INSERT_TAIL(&cxt->b->waitq, &cxt->entry); - - if (req->ret.data) - discord_refcounter_incr(adapter->refcounter, req->ret.data, - req->ret.cleanup); - - io_poller_curlm_enable_perform(CLIENT(adapter, adapter)->io_poller, - adapter->mhandle); - - return CCORD_OK; -} - -/* add a request to libcurl's multi handle */ -static CCORDcode -_discord_adapter_send(struct discord_adapter *adapter, - struct discord_bucket *b) -{ - struct ua_conn_attr conn_attr = { 0 }; - struct discord_context *cxt; - CURLMcode mcode; - CURL *ehandle; - - QUEUE(struct discord_context) *qelem = QUEUE_HEAD(&b->waitq); - QUEUE_REMOVE(qelem); - QUEUE_INIT(qelem); - - cxt = QUEUE_DATA(qelem, struct discord_context, entry); - cxt->conn = ua_conn_start(adapter->ua); - - conn_attr.method = cxt->method; - conn_attr.body = &cxt->body.buf; - conn_attr.endpoint = cxt->endpoint; - - if (HTTP_MIMEPOST == cxt->method) { - ua_conn_add_header(cxt->conn, "Content-Type", "multipart/form-data"); - ua_conn_set_mime(cxt->conn, cxt, &_discord_context_to_mime); - } - else { - ua_conn_add_header(cxt->conn, "Content-Type", "application/json"); - } - ua_conn_setup(cxt->conn, &conn_attr); - - ehandle = ua_conn_get_easy_handle(cxt->conn); - - /* link 'cxt' to 'ehandle' for easy retrieval */ - curl_easy_setopt(ehandle, CURLOPT_PRIVATE, cxt); - - /* initiate libcurl transfer */ - mcode = curl_multi_add_handle(adapter->mhandle, ehandle); - - io_poller_curlm_enable_perform(CLIENT(adapter, adapter)->io_poller, - adapter->mhandle); - - QUEUE_INSERT_TAIL(&cxt->b->busyq, &cxt->entry); - - return mcode ? CCORD_CURLM_INTERNAL : CCORD_OK; -} - -/* send a batch of requests */ -static CCORDcode -_discord_adapter_send_batch(struct discord_adapter *adapter, - struct discord_bucket *b) -{ - CCORDcode code = CCORD_OK; - long i; - - for (i = b->remaining; i > 0; --i) { - if (QUEUE_EMPTY(&b->waitq)) break; - - code = _discord_adapter_send(adapter, b); - if (code != CCORD_OK) break; - } - - return code; -} - -static void -_discord_adapter_try_send(struct discord_adapter *adapter, - struct discord_bucket *b) -{ - /* skip busy and non-pending buckets */ - if (!QUEUE_EMPTY(&b->busyq) || QUEUE_EMPTY(&b->waitq)) { - return; - } - /* if bucket is outdated then its necessary to send a single - * request to fetch updated values */ - if (b->reset_tstamp < NOW(adapter)) { - _discord_adapter_send(adapter, b); - return; - } - /* send remainder or trigger timeout */ - _discord_adapter_send_batch(adapter, b); -} - -/* TODO: redundant constant return value */ -static CCORDcode -_discord_adapter_check_pending(struct discord_adapter *adapter) -{ - discord_ratelimiter_foreach(adapter->ratelimiter, adapter, - &_discord_adapter_try_send); - return CCORD_OK; -} - -static CCORDcode -_discord_adapter_check_action(struct discord_adapter *adapter, - struct CURLMsg *msg) -{ - struct discord *client = CLIENT(adapter, adapter); - struct discord_context *cxt; - int64_t wait_ms = 0LL; - CCORDcode code; - bool retry; - - curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &cxt); - - switch (msg->data.result) { - case CURLE_OK: { - struct ua_info info = { 0 }; - struct sized_buffer body; - - ua_info_extract(cxt->conn, &info); - retry = _discord_adapter_get_info(adapter, &info, &wait_ms); - - body = ua_info_get_body(&info); - if (info.code != CCORD_OK) { - logconf_error(&client->conf, "%.*s", (int)body.size, body.start); - - if (cxt->req.ret.fail) - cxt->req.ret.fail(client, info.code, cxt->req.ret.data); - } - else if (cxt->req.ret.done.typed) { - void *ret = calloc(1, cxt->req.gnrc.size); - - /* initialize ret */ - if (cxt->req.gnrc.init) cxt->req.gnrc.init(ret); - - /* populate ret */ - if (cxt->req.gnrc.from_json) - cxt->req.gnrc.from_json(body.start, body.size, ret); - - if (cxt->req.ret.has_type) - cxt->req.ret.done.typed(client, cxt->req.ret.data, ret); - else - cxt->req.ret.done.typeless(client, cxt->req.ret.data); - - /* cleanup ret */ - if (cxt->req.gnrc.cleanup) cxt->req.gnrc.cleanup(ret); - free(ret); - } - - code = info.code; - - discord_ratelimiter_build(adapter->ratelimiter, cxt->b, cxt->key, - &info); - ua_info_cleanup(&info); - } break; - case CURLE_READ_ERROR: - logconf_warn(&adapter->conf, "Read error, will retry again"); - retry = true; - - code = CCORD_CURLE_INTERNAL; - - break; - default: - logconf_error(&adapter->conf, "(CURLE code: %d)", msg->data.result); - retry = false; - - code = CCORD_CURLE_INTERNAL; - - if (cxt->req.ret.fail) { - cxt->req.ret.fail(client, code, cxt->req.ret.data); - } - - break; - } - - /* enqueue request for retry or recycle */ - QUEUE_REMOVE(&cxt->entry); - if (retry && cxt->retry_attempt++ < adapter->retry_limit) { - ua_conn_reset(cxt->conn); - - if (wait_ms <= 0) { - QUEUE_INSERT_HEAD(&cxt->b->waitq, &cxt->entry); - } - } - else { - discord_refcounter_decr(adapter->refcounter, cxt->req.ret.data); - _discord_context_reset(cxt); - QUEUE_INSERT_TAIL(adapter->idleq, &cxt->entry); - } - - return code; -} - -CCORDcode -discord_adapter_perform(struct discord_adapter *adapter) -{ - CURLMcode mcode; - CCORDcode code; - int alive = 0; - - if (CCORD_OK != (code = _discord_adapter_check_pending(adapter))) - return code; - - if (CURLM_OK != (mcode = curl_multi_socket_all(adapter->mhandle, &alive))) - return CCORD_CURLM_INTERNAL; - - /* ask for any messages/informationals from the individual transfers */ - while (1) { - int msgq = 0; - struct CURLMsg *msg = curl_multi_info_read(adapter->mhandle, &msgq); - - if (!msg) break; - if (CURLMSG_DONE != msg->msg) continue; - - curl_multi_remove_handle(adapter->mhandle, msg->easy_handle); - - /* check for request action */ - _discord_adapter_check_action(adapter, msg); - } - - return CCORD_OK; -} - -static void -_discord_adapter_stop_bucket(struct discord_adapter *adapter, - struct discord_bucket *b) -{ - QUEUE(struct discord_context) * qelem; - struct discord_context *cxt; - CURL *ehandle; - - while (!QUEUE_EMPTY(&b->busyq)) { - qelem = QUEUE_HEAD(&b->busyq); - QUEUE_REMOVE(qelem); - - cxt = QUEUE_DATA(qelem, struct discord_context, entry); - ehandle = ua_conn_get_easy_handle(cxt->conn); - - curl_multi_remove_handle(adapter->mhandle, ehandle); - - /* set for recycling */ - ua_conn_stop(cxt->conn); - QUEUE_INSERT_TAIL(adapter->idleq, qelem); - } - - /* cancel pending tranfers */ - QUEUE_ADD(adapter->idleq, &b->waitq); - QUEUE_INIT(&b->waitq); -} - -void -discord_adapter_stop_buckets(struct discord_adapter *adapter) -{ - discord_ratelimiter_foreach(adapter->ratelimiter, adapter, - &_discord_adapter_stop_bucket); -} diff --git a/src/discord-adapter_ratelimit.c b/src/discord-adapter_ratelimit.c deleted file mode 100644 index 8eb6bbfd1..000000000 --- a/src/discord-adapter_ratelimit.c +++ /dev/null @@ -1,405 +0,0 @@ -#include -#include -#include - -#include "discord.h" -#include "discord-internal.h" - -#include "cog-utils.h" -#include "clock.h" - -#define CHASH_VALUE_FIELD bucket -#define CHASH_BUCKETS_FIELD routes -#include "chash.h" - -/* chash heap-mode (auto-increase hashtable) */ -#define RATELIMITER_TABLE_HEAP 1 -#define RATELIMITER_TABLE_BUCKET struct _discord_route -#define RATELIMITER_TABLE_FREE_KEY(_key) -#define RATELIMITER_TABLE_HASH(_key, _hash) chash_string_hash(_key, _hash) -#define RATELIMITER_TABLE_FREE_VALUE(_value) _discord_bucket_cleanup(_value) -#define RATELIMITER_TABLE_COMPARE(_cmp_a, _cmp_b) \ - chash_string_compare(_cmp_a, _cmp_b) -#define RATELIMITER_TABLE_INIT(route, _key, _value) \ - memcpy(route.key, _key, sizeof(route.key)); \ - route.bucket = _value - -struct _discord_route { - /** key formed from a request's route */ - char key[DISCORD_ROUTE_LEN]; - /** this route's bucket match */ - struct discord_bucket *bucket; - /** the route state in the hashtable (see chash.h 'State enums') */ - int state; -}; - -static void -_discord_bucket_cleanup(struct discord_bucket *b) -{ - pthread_mutex_destroy(&b->lock); - free(b); -} - -#define KEY_PUSH(key, len, ...) \ - do { \ - *len += snprintf(key + *len, DISCORD_ROUTE_LEN - (size_t)*len, \ - ":" __VA_ARGS__); \ - ASSERT_NOT_OOB(*len, DISCORD_ROUTE_LEN); \ - } while (0) - -/* determine which ratelimit group a request belongs to by generating its key. - * see: https://discord.com/developers/docs/topics/rate-limits */ -void -discord_ratelimiter_build_key(enum http_method method, - char key[DISCORD_ROUTE_LEN], - const char endpoint_fmt[], - va_list args) -{ - /* generated key length */ - int keylen = 0; - /* split endpoint sections */ - const char *curr = endpoint_fmt, *prev = ""; - size_t currlen = 0; - - KEY_PUSH(key, &keylen, "%d", method); - do { - u64snowflake id_arg = 0ULL; - size_t i; - - curr += 1 + currlen; - currlen = strcspn(curr, "/"); - - /* reactions and sub-routes share the same bucket */ - if (0 == strncmp(prev, "reactions", 9)) break; - - /* consume variadic arguments */ - for (i = 0; i < currlen; ++i) { - if ('%' == curr[i]) { - const char *type = &curr[i + 1]; - - switch (*type) { - default: - VASSERT_S(0 == strncmp(type, PRIu64, sizeof(PRIu64) - 1), - "Internal error: Missing check for '%%%s'", - type); - - id_arg = va_arg(args, u64snowflake); - break; - case 's': - (void)va_arg(args, char *); - break; - case 'd': - (void)va_arg(args, int); - break; - } - } - } - - /* push section to key's string, in case of a major parameter the - * literal ID will be pushed */ - if (0 == strncmp(curr, "%" PRIu64, currlen) - && (0 == strncmp(prev, "channels", 8) - || 0 == strncmp(prev, "guilds", 6))) - { - KEY_PUSH(key, &keylen, "%" PRIu64, id_arg); - } - else { - KEY_PUSH(key, &keylen, "%.*s", (int)currlen, curr); - } - - prev = curr; - - } while (curr[currlen] != '\0'); -} - -#undef KEY_PUSH - -/* initialize bucket and assign it to ratelimiter hashtable */ -static struct discord_bucket * -_discord_bucket_init(struct discord_ratelimiter *rl, - const char key[DISCORD_ROUTE_LEN], - const struct sized_buffer *hash, - const long limit) -{ - struct discord_bucket *b = calloc(1, sizeof *b); - int len = snprintf(b->hash, sizeof(b->hash), "%.*s", (int)hash->size, - hash->start); - - ASSERT_NOT_OOB(len, sizeof(b->hash)); - - b->remaining = 1; - b->limit = limit; - - if (pthread_mutex_init(&b->lock, NULL)) - ERR("Couldn't initialize pthread mutex"); - - QUEUE_INIT(&b->waitq); - QUEUE_INIT(&b->busyq); - - pthread_mutex_lock(&rl->global.lock); - chash_assign(rl, key, b, RATELIMITER_TABLE); - pthread_mutex_unlock(&rl->global.lock); - - return b; -} - -struct discord_ratelimiter * -discord_ratelimiter_init(struct logconf *conf) -{ - const struct sized_buffer keynull = { "null", 4 }, keymiss = { "miss", 4 }; - struct discord_ratelimiter *rl = chash_init(rl, RATELIMITER_TABLE); - - logconf_branch(&rl->conf, conf, "DISCORD_RATELIMIT"); - - /* global ratelimiting resources */ - rl->global.wait_ms = 0; - if (pthread_rwlock_init(&rl->global.rwlock, NULL)) - ERR("Couldn't initialize pthread rwlock"); - if (pthread_mutex_init(&rl->global.lock, NULL)) - ERR("Couldn't initialize pthread mutex"); - - /* initialize 'singleton' buckets */ - rl->null = _discord_bucket_init(rl, "null", &keynull, 1L); - rl->miss = _discord_bucket_init(rl, "miss", &keymiss, LONG_MAX); - - return rl; -} - -void -discord_ratelimiter_cleanup(struct discord_ratelimiter *rl) -{ - pthread_rwlock_destroy(&rl->global.rwlock); - pthread_mutex_destroy(&rl->global.lock); - chash_free(rl, RATELIMITER_TABLE); -} - -void -discord_ratelimiter_foreach(struct discord_ratelimiter *rl, - struct discord_adapter *adapter, - void (*iter)(struct discord_adapter *adapter, - struct discord_bucket *b)) -{ - struct _discord_route *r; - int i; - - pthread_mutex_lock(&rl->global.lock); - for (i = 0; i < rl->capacity; ++i) { - r = rl->routes + i; - if (CHASH_FILLED == r->state) (*iter)(adapter, r->bucket); - } - pthread_mutex_unlock(&rl->global.lock); -} - -static struct discord_bucket * -_discord_bucket_find(struct discord_ratelimiter *rl, - const char key[DISCORD_ROUTE_LEN]) -{ - struct discord_bucket *b = NULL; - int ret; - - pthread_mutex_lock(&rl->global.lock); - ret = chash_contains(rl, key, ret, RATELIMITER_TABLE); - if (ret) { - b = chash_lookup(rl, key, b, RATELIMITER_TABLE); - } - pthread_mutex_unlock(&rl->global.lock); - - return b; -} - -u64unix_ms -discord_ratelimiter_get_global_wait(struct discord_ratelimiter *rl) -{ - u64unix_ms global; - - pthread_rwlock_rdlock(&rl->global.rwlock); - global = rl->global.wait_ms; - pthread_rwlock_unlock(&rl->global.rwlock); - - return global; -} - -/* return ratelimit timeout timestamp for this bucket */ -u64unix_ms -discord_bucket_get_timeout(struct discord_ratelimiter *rl, - struct discord_bucket *b) -{ - u64unix_ms global = discord_ratelimiter_get_global_wait(rl), - reset = (b->remaining < 1) ? b->reset_tstamp : 0ULL; - - return (global > reset) ? global : reset; -} - -void -discord_bucket_try_sleep(struct discord_ratelimiter *rl, - struct discord_bucket *b) -{ - /* sleep_ms := reset timestamp - current timestamp */ - const int64_t sleep_ms = - (int64_t)(discord_bucket_get_timeout(rl, b) - cog_timestamp_ms()); - - if (sleep_ms > 0) { - /* block thread's runtime for delay amount */ - logconf_info(&rl->conf, "[%.4s] RATELIMITING (wait %" PRId64 " ms)", - b->hash, sleep_ms); - cog_sleep_ms(sleep_ms); - } -} - -/* attempt to find a bucket associated key */ -struct discord_bucket * -discord_bucket_get(struct discord_ratelimiter *rl, - const char key[DISCORD_ROUTE_LEN]) -{ - struct discord_bucket *b; - - if (NULL != (b = _discord_bucket_find(rl, key))) { - logconf_trace(&rl->conf, "[%.4s] Found a bucket match for '%s'!", - b->hash, key); - - return b; - } - - logconf_trace(&rl->conf, "[null] Couldn't match known buckets to '%s'", - key); - - return rl->null; -} - -static struct discord_bucket * -_discord_ratelimiter_get_match(struct discord_ratelimiter *rl, - const char key[DISCORD_ROUTE_LEN], - struct ua_info *info) -{ - struct discord_bucket *b; - - /* create bucket if it doesn't exist yet */ - if (NULL == (b = _discord_bucket_find(rl, key))) { - struct sized_buffer hash = - ua_info_get_header(info, "x-ratelimit-bucket"); - - if (!hash.size) { /* bucket is not part of a ratelimiting group */ - b = rl->miss; - } - else { - struct sized_buffer limit = - ua_info_get_header(info, "x-ratelimit-limit"); - long _limit = - limit.size ? strtol(limit.start, NULL, 10) : LONG_MAX; - - b = _discord_bucket_init(rl, key, &hash, _limit); - } - } - - logconf_debug(&rl->conf, "[%.4s] Match '%s' to bucket", b->hash, key); - - return b; -} - -/* attempt to fill bucket's values with response header fields */ -static void -_discord_bucket_populate(struct discord_ratelimiter *rl, - struct discord_bucket *b, - struct ua_info *info) -{ - struct sized_buffer remaining, reset, reset_after; - u64unix_ms now = cog_timestamp_ms(); - long _remaining; - - remaining = ua_info_get_header(info, "x-ratelimit-remaining"); - _remaining = remaining.size ? strtol(remaining.start, NULL, 10) : 1L; - - /* skip out of order responses */ - if (_remaining > b->remaining && now < b->reset_tstamp) return; - - b->remaining = _remaining; - - reset = ua_info_get_header(info, "x-ratelimit-reset"); - reset_after = ua_info_get_header(info, "x-ratelimit-reset-after"); - - /* use X-Ratelimit-Reset-After if available, X-Ratelimit-Reset otherwise */ - if (reset_after.size) { - struct sized_buffer global = - ua_info_get_header(info, "x-ratelimit-global"); - u64unix_ms reset_tstamp = - now + (u64unix_ms)(1000 * strtod(reset_after.start, NULL)); - - if (global.size) { - /* lock all buckets */ - pthread_rwlock_wrlock(&rl->global.rwlock); - rl->global.wait_ms = reset_tstamp; - pthread_rwlock_unlock(&rl->global.rwlock); - } - else { - /* lock single bucket, timeout at discord_adapter_run() */ - b->reset_tstamp = reset_tstamp; - } - } - else if (reset.size) { - struct sized_buffer date = ua_info_get_header(info, "date"); - /* get approximate elapsed time since request */ - struct PsnipClockTimespec ts; - /* the Discord time in milliseconds */ - u64unix_ms server; - /* the Discord time + request's elapsed time */ - u64unix_ms offset; - - server = (u64unix_ms)(1000 * curl_getdate(date.start, NULL)); - psnip_clock_wall_get_time(&ts); - offset = server + ts.nanoseconds / 1000000; - - /* reset timestamp = - * (system time) - * + (diff between Discord's reset timestamp and offset) - */ - b->reset_tstamp = - now + ((u64unix_ms)(1000 * strtod(reset.start, NULL)) - offset); - } - - logconf_debug(&rl->conf, "[%.4s] Remaining = %ld | Reset = %" PRIu64, - b->hash, b->remaining, b->reset_tstamp); -} - -/* in case of asynchronous requests, check if successive requests made from a - * `null` singleton bucket can be matched to another bucket */ -static void -_discord_ratelimiter_null_filter(struct discord_ratelimiter *rl, - struct discord_bucket *b, - const char key[DISCORD_ROUTE_LEN]) -{ - QUEUE(struct discord_context) queue, *qelem; - struct discord_context *cxt; - - QUEUE_MOVE(&rl->null->waitq, &queue); - QUEUE_INIT(&rl->null->waitq); - - while (!QUEUE_EMPTY(&queue)) { - qelem = QUEUE_HEAD(&queue); - QUEUE_REMOVE(qelem); - - cxt = QUEUE_DATA(qelem, struct discord_context, entry); - if (0 == strcmp(cxt->key, key)) { - QUEUE_INSERT_TAIL(&b->waitq, qelem); - cxt->b = b; - } - else { - QUEUE_INSERT_TAIL(&rl->null->waitq, qelem); - } - } -} - -/* attempt to create and/or update bucket's values */ -void -discord_ratelimiter_build(struct discord_ratelimiter *rl, - struct discord_bucket *b, - const char key[DISCORD_ROUTE_LEN], - struct ua_info *info) -{ - /* try to match to existing, or create new bucket */ - if (b == rl->null) { - b = _discord_ratelimiter_get_match(rl, key, info); - _discord_ratelimiter_null_filter(rl, b, key); - } - /* populate bucket with response header values */ - _discord_bucket_populate(rl, b, info); -} diff --git a/src/discord-adapter_refcount.c b/src/discord-adapter_refcount.c deleted file mode 100644 index 6cf20bced..000000000 --- a/src/discord-adapter_refcount.c +++ /dev/null @@ -1,122 +0,0 @@ -#include -#include -#include - -#include "discord.h" -#include "discord-internal.h" - -#define CHASH_BUCKETS_FIELD refs -#include "chash.h" - -/* chash heap-mode (auto-increase hashtable) */ -#define REFCOUNTER_TABLE_HEAP 1 -#define REFCOUNTER_TABLE_BUCKET struct _discord_ref -#define REFCOUNTER_TABLE_FREE_KEY(_key) -#define REFCOUNTER_TABLE_HASH(_key, _hash) ((intptr_t)(_key)) -#define REFCOUNTER_TABLE_FREE_VALUE(_value) _discord_refvalue_cleanup(&_value) -#define REFCOUNTER_TABLE_COMPARE(_cmp_a, _cmp_b) (_cmp_a == _cmp_b) -#define REFCOUNTER_TABLE_INIT(ref, _key, _value) \ - memset(&ref, 0, sizeof(ref)); \ - chash_default_init(ref, _key, _value) - -struct _discord_refvalue { - /** user arbitrary data to be retrieved at `done` or `fail` callbacks */ - void *data; - /** - * cleanup for when `data` is no longer needed - * @note this only has to be assigned once, it is automatically called once - * `data` is no longer referenced by any callback */ - void (*cleanup)(void *data); - /** `data` references count */ - int visits; -}; - -struct _discord_ref { - /** key is the user data's address */ - intptr_t key; - /** holds the user data and information for automatic cleanup */ - struct _discord_refvalue value; - /** the route state in the hashtable (see chash.h 'State enums') */ - int state; -}; - -static void -_discord_refvalue_cleanup(struct _discord_refvalue *value) -{ - if (value->cleanup) value->cleanup(value->data); -} - -static struct _discord_refvalue * -_discord_refvalue_find(struct discord_refcounter *rc, intptr_t key) -{ - struct _discord_ref *ref = NULL; - - ref = chash_lookup_bucket(rc, key, ref, REFCOUNTER_TABLE); - - return &ref->value; -} - -static struct _discord_refvalue * -_discord_refvalue_init(struct discord_refcounter *rc, - intptr_t key, - void *data, - void (*cleanup)(void *data)) -{ - struct _discord_refvalue value; - - value.data = data; - value.cleanup = cleanup; - value.visits = 0; - chash_assign(rc, key, value, REFCOUNTER_TABLE); - - return _discord_refvalue_find(rc, key); -} - -struct discord_refcounter * -discord_refcounter_init(struct logconf *conf) -{ - struct discord_refcounter *rc = chash_init(rc, REFCOUNTER_TABLE); - - logconf_branch(&rc->conf, conf, "DISCORD_REFCOUNT"); - - return rc; -} - -void -discord_refcounter_cleanup(struct discord_refcounter *rc) -{ - chash_free(rc, REFCOUNTER_TABLE); -} - -void -discord_refcounter_incr(struct discord_refcounter *rc, - void *data, - void (*cleanup)(void *data)) -{ - struct _discord_refvalue *value = NULL; - intptr_t key = (intptr_t)data; - int ret; - - ret = chash_contains(rc, key, ret, REFCOUNTER_TABLE); - if (ret) - value = _discord_refvalue_find(rc, key); - else - value = _discord_refvalue_init(rc, key, data, cleanup); - ++value->visits; -} - -void -discord_refcounter_decr(struct discord_refcounter *rc, void *data) -{ - struct _discord_refvalue *value = NULL; - intptr_t key = (intptr_t)data; - int ret; - - ret = chash_contains(rc, key, ret, REFCOUNTER_TABLE); - if (ret) { - value = _discord_refvalue_find(rc, key); - if (0 == --value->visits) { - chash_delete(rc, key, REFCOUNTER_TABLE); - } - } -} diff --git a/src/discord-client.c b/src/discord-client.c index a3c27781d..60020dd20 100644 --- a/src/discord-client.c +++ b/src/discord-client.c @@ -1,37 +1,43 @@ #include #include #include -#include /* isgraph() */ #include #include "discord.h" #include "discord-internal.h" +#include "discord-worker.h" #include "cog-utils.h" static void _discord_init(struct discord *new_client) { ccord_global_init(); - discord_timers_init(new_client); + discord_timers_init(&new_client->timers.internal); + discord_timers_init(&new_client->timers.user); new_client->io_poller = io_poller_create(); - discord_adapter_init(&new_client->adapter, &new_client->conf, - &new_client->token); + + new_client->workers = calloc(1, sizeof *new_client->workers); + ASSERT_S(!pthread_mutex_init(&new_client->workers->lock, NULL), + "Couldn't initialize Client's mutex"); + ASSERT_S(!pthread_cond_init(&new_client->workers->cond, NULL), + "Couldn't initialize Client's cond"); + + discord_refcounter_init(&new_client->refcounter, &new_client->conf); + discord_message_commands_init(&new_client->commands, &new_client->conf); + discord_rest_init(&new_client->rest, &new_client->conf, new_client->token); discord_gateway_init(&new_client->gw, &new_client->conf, - &new_client->token); -#ifdef HAS_DISCORD_VOICE + new_client->token); +#ifdef CCORD_VOICE discord_voice_connections_init(new_client); -#endif /* HAS_DISCORD_VOICE */ +#endif - /* fetch the client user structure */ - if (new_client->token.size) { - struct discord_ret_user ret = { 0 }; - CCORDcode code; - - ret.sync = &new_client->self; - code = discord_get_current_user(new_client, &ret); + if (new_client->token) { /* fetch client's user structure */ + CCORDcode code = + discord_get_current_user(new_client, &(struct discord_ret_user){ + .sync = &new_client->self, + }); ASSERT_S(CCORD_OK == code, "Couldn't fetch client's user object"); } - new_client->is_original = true; } @@ -45,8 +51,7 @@ discord_init(const char token[]) /* silence terminal input by default */ logconf_set_quiet(&new_client->conf, true); - new_client->token.start = (char *)token; - new_client->token.size = token ? cog_str_bounds_check(token, 128) : 0; + if (token && *token) cog_strndup(token, strlen(token), &new_client->token); _discord_init(new_client); @@ -56,8 +61,8 @@ discord_init(const char token[]) struct discord * discord_config_init(const char config_file[]) { + struct ccord_szbuf_readonly field; struct discord *new_client; - char *path[] = { "discord", "token" }; FILE *fp; fp = fopen(config_file, "rb"); @@ -69,16 +74,47 @@ discord_config_init(const char config_file[]) fclose(fp); - new_client->token = logconf_get_field(&new_client->conf, path, - sizeof(path) / sizeof *path); - if (!strncmp("YOUR-BOT-TOKEN", new_client->token.start, - new_client->token.size)) - { - memset(&new_client->token, 0, sizeof(new_client->token)); - } + field = discord_config_get_field(new_client, + (char *[2]){ "discord", "token" }, 2); + if (field.size && 0 != strncmp("YOUR-BOT-TOKEN", field.start, field.size)) + cog_strndup(field.start, field.size, &new_client->token); _discord_init(new_client); + /* check for default prefix in config file */ + field = discord_config_get_field( + new_client, (char *[2]){ "discord", "default_prefix" }, 2); + if (field.size) { + jsmn_parser parser; + jsmntok_t tokens[16]; + + jsmn_init(&parser); + if (0 < jsmn_parse(&parser, field.start, field.size, tokens, + sizeof(tokens) / sizeof *tokens)) + { + jsmnf_loader loader; + jsmnf_pair pairs[16]; + + jsmnf_init(&loader); + if (0 < jsmnf_load(&loader, field.start, tokens, parser.toknext, + pairs, sizeof(pairs) / sizeof *pairs)) + { + bool enable_prefix = false; + jsmnf_pair *f; + + if ((f = jsmnf_find(pairs, field.start, "enable", 6))) + enable_prefix = ('t' == field.start[f->v.pos]); + + if (enable_prefix + && (f = jsmnf_find(pairs, field.start, "prefix", 6))) { + discord_message_commands_set_prefix(&new_client->commands, + field.start + f->v.pos, + f->v.len); + } + } + } + } + return new_client; } @@ -86,14 +122,16 @@ static void _discord_clone_gateway(struct discord_gateway *clone, const struct discord_gateway *orig) { - const size_t n = - orig->parse.npairs - (size_t)(orig->payload.data - orig->parse.pairs); + const size_t n = orig->payload.json.npairs + - (size_t)(orig->payload.data - orig->payload.json.pairs); - clone->payload.data = malloc(n * sizeof *orig->parse.pairs); + clone->payload.data = malloc(n * sizeof *orig->payload.json.pairs); memcpy(clone->payload.data, orig->payload.data, - n * sizeof *orig->parse.pairs); + n * sizeof *orig->payload.json.pairs); - clone->length = cog_strndup(orig->json, orig->length, &clone->json); + clone->payload.json.size = + cog_strndup(orig->payload.json.start, orig->payload.json.size, + &clone->payload.json.start); } struct discord * @@ -113,7 +151,7 @@ static void _discord_clone_gateway_cleanup(struct discord_gateway *clone) { free(clone->payload.data); - free(clone->json); + free(clone->payload.json.start); } static void @@ -125,19 +163,27 @@ _discord_clone_cleanup(struct discord *client) void discord_cleanup(struct discord *client) { - if (client->is_original) { - discord_timers_cleanup(client); - logconf_cleanup(&client->conf); - discord_adapter_cleanup(&client->adapter); + if (!client->is_original) { + _discord_clone_cleanup(client); + } + else { + discord_worker_join(client); + discord_rest_cleanup(&client->rest); discord_gateway_cleanup(&client->gw); + discord_message_commands_cleanup(&client->commands); +#ifdef CCORD_VOICE + discord_voice_connections_cleanup(client); +#endif discord_user_cleanup(&client->self); io_poller_destroy(client->io_poller); -#ifdef HAS_DISCORD_VOICE - discord_voice_connections_cleanup(client); -#endif /* HAS_DISCORD_VOICE */ - } - else { - _discord_clone_cleanup(client); + discord_refcounter_cleanup(&client->refcounter); + discord_timers_cleanup(client, &client->timers.user); + discord_timers_cleanup(client, &client->timers.internal); + logconf_cleanup(&client->conf); + if (client->token) free(client->token); + pthread_mutex_destroy(&client->workers->lock); + pthread_cond_destroy(&client->workers->cond); + free(client->workers); } free(client); } @@ -229,14 +275,12 @@ discord_remove_intents(struct discord *client, uint64_t code) } void -discord_set_prefix(struct discord *client, char *prefix) +discord_set_prefix(struct discord *client, const char prefix[]) { if (!prefix || !*prefix) return; - if (client->gw.cmds.prefix.start) free(client->gw.cmds.prefix.start); - - client->gw.cmds.prefix.size = - cog_strndup(prefix, strlen(prefix), &client->gw.cmds.prefix.start); + discord_message_commands_set_prefix(&client->commands, prefix, + strlen(prefix)); } const struct discord_user * @@ -250,74 +294,29 @@ discord_set_on_command(struct discord *client, char command[], discord_ev_message callback) { - const size_t cmd_len = command ? strlen(command) : 0; - size_t i; - - /* fallback callback if prefix is detected, but command isn't specified */ - if (client->gw.cmds.prefix.size && !cmd_len) { - client->gw.cmds.fallback.cb = callback; - return; - } - - /* if command is already set then modify it */ - for (i = 0; i < client->gw.cmds.amt; i++) { - if (cmd_len == client->gw.cmds.pool[i].size - && 0 == strcmp(command, client->gw.cmds.pool[i].start)) - { - goto _modify; - } - } - - if (i == client->gw.cmds.cap) { - size_t cap = 8; - void *tmp; - - while (cap <= i) - cap <<= 1; - - tmp = - realloc(client->gw.cmds.pool, cap * sizeof(*client->gw.cmds.pool)); - if (!tmp) return; - - client->gw.cmds.pool = tmp; - client->gw.cmds.cap = cap; - } - - ++client->gw.cmds.amt; - client->gw.cmds.pool[i].size = - cog_strndup(command, cmd_len, &client->gw.cmds.pool[i].start); - -_modify: - client->gw.cmds.pool[i].cb = callback; + size_t length = (!command || !*command) ? 0 : strlen(command); + discord_message_commands_append(&client->commands, command, length, + callback); discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGES | DISCORD_GATEWAY_DIRECT_MESSAGES); } void discord_set_on_commands(struct discord *client, - discord_ev_message callback, - ...) + char *const commands[], + int amount, + discord_ev_message callback) { - char *command = NULL; - va_list commands; - - va_start(commands, callback); - - command = va_arg(commands, char *); - while (command != NULL) { - discord_set_on_command(client, command, callback); - command = va_arg(commands, char *); - } - - va_end(commands); + for (int i = 0; i < amount; ++i) + discord_set_on_command(client, commands[i], callback); } void discord_set_event_scheduler(struct discord *client, discord_ev_scheduler callback) { - client->gw.cmds.scheduler = callback; + client->gw.scheduler = callback; } void @@ -334,24 +333,24 @@ discord_reconnect(struct discord *client, bool resume) } void -discord_set_on_ready(struct discord *client, discord_ev_idle callback) +discord_set_on_ready(struct discord *client, discord_ev_ready callback) { - client->gw.cmds.cbs.on_ready = callback; + client->gw.cbs[DISCORD_EV_READY] = (discord_ev)callback; } void discord_set_on_guild_role_create(struct discord *client, - discord_ev_guild_role callback) + discord_ev_guild_role_create callback) { - client->gw.cmds.cbs.on_guild_role_create = callback; + client->gw.cbs[DISCORD_EV_GUILD_ROLE_CREATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } void discord_set_on_guild_role_update(struct discord *client, - discord_ev_guild_role callback) + discord_ev_guild_role_update callback) { - client->gw.cmds.cbs.on_guild_role_update = callback; + client->gw.cbs[DISCORD_EV_GUILD_ROLE_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -359,7 +358,7 @@ void discord_set_on_guild_role_delete(struct discord *client, discord_ev_guild_role_delete callback) { - client->gw.cmds.cbs.on_guild_role_delete = callback; + client->gw.cbs[DISCORD_EV_GUILD_ROLE_DELETE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -367,15 +366,15 @@ void discord_set_on_guild_member_add(struct discord *client, discord_ev_guild_member callback) { - client->gw.cmds.cbs.on_guild_member_add = callback; + client->gw.cbs[DISCORD_EV_GUILD_MEMBER_ADD] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MEMBERS); } void discord_set_on_guild_member_update(struct discord *client, - discord_ev_guild_member callback) + discord_ev_guild_member_update callback) { - client->gw.cmds.cbs.on_guild_member_update = callback; + client->gw.cbs[DISCORD_EV_GUILD_MEMBER_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MEMBERS); } @@ -383,23 +382,23 @@ void discord_set_on_guild_member_remove(struct discord *client, discord_ev_guild_member_remove callback) { - client->gw.cmds.cbs.on_guild_member_remove = callback; + client->gw.cbs[DISCORD_EV_GUILD_MEMBER_REMOVE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MEMBERS); } void discord_set_on_guild_ban_add(struct discord *client, - discord_ev_guild_ban callback) + discord_ev_guild_ban_add callback) { - client->gw.cmds.cbs.on_guild_ban_add = callback; + client->gw.cbs[DISCORD_EV_GUILD_BAN_ADD] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_BANS); } void discord_set_on_guild_ban_remove(struct discord *client, - discord_ev_guild_ban callback) + discord_ev_guild_ban_remove callback) { - client->gw.cmds.cbs.on_guild_ban_remove = callback; + client->gw.cbs[DISCORD_EV_GUILD_BAN_REMOVE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_BANS); } @@ -407,28 +406,31 @@ void discord_set_on_application_command_create( struct discord *client, discord_ev_application_command callback) { - client->gw.cmds.cbs.on_application_command_create = callback; + client->gw.cbs[DISCORD_EV_APPLICATION_COMMAND_CREATE] = + (discord_ev)callback; } void discord_set_on_application_command_update( struct discord *client, discord_ev_application_command callback) { - client->gw.cmds.cbs.on_application_command_update = callback; + client->gw.cbs[DISCORD_EV_APPLICATION_COMMAND_UPDATE] = + (discord_ev)callback; } void discord_set_on_application_command_delete( struct discord *client, discord_ev_application_command callback) { - client->gw.cmds.cbs.on_application_command_delete = callback; + client->gw.cbs[DISCORD_EV_APPLICATION_COMMAND_DELETE] = + (discord_ev)callback; } void discord_set_on_channel_create(struct discord *client, discord_ev_channel callback) { - client->gw.cmds.cbs.on_channel_create = callback; + client->gw.cbs[DISCORD_EV_CHANNEL_CREATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -436,7 +438,7 @@ void discord_set_on_channel_update(struct discord *client, discord_ev_channel callback) { - client->gw.cmds.cbs.on_channel_update = callback; + client->gw.cbs[DISCORD_EV_CHANNEL_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -444,7 +446,7 @@ void discord_set_on_channel_delete(struct discord *client, discord_ev_channel callback) { - client->gw.cmds.cbs.on_channel_delete = callback; + client->gw.cbs[DISCORD_EV_CHANNEL_DELETE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -452,7 +454,7 @@ void discord_set_on_channel_pins_update(struct discord *client, discord_ev_channel_pins_update callback) { - client->gw.cmds.cbs.on_channel_pins_update = callback; + client->gw.cbs[DISCORD_EV_CHANNEL_PINS_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -460,7 +462,7 @@ void discord_set_on_thread_create(struct discord *client, discord_ev_channel callback) { - client->gw.cmds.cbs.on_thread_create = callback; + client->gw.cbs[DISCORD_EV_THREAD_CREATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -468,7 +470,7 @@ void discord_set_on_thread_update(struct discord *client, discord_ev_channel callback) { - client->gw.cmds.cbs.on_thread_update = callback; + client->gw.cbs[DISCORD_EV_THREAD_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -476,29 +478,28 @@ void discord_set_on_thread_delete(struct discord *client, discord_ev_channel callback) { - client->gw.cmds.cbs.on_thread_delete = callback; + client->gw.cbs[DISCORD_EV_THREAD_DELETE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } void discord_set_on_guild_create(struct discord *client, discord_ev_guild callback) { - client->gw.cmds.cbs.on_guild_create = callback; + client->gw.cbs[DISCORD_EV_GUILD_CREATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } void discord_set_on_guild_update(struct discord *client, discord_ev_guild callback) { - client->gw.cmds.cbs.on_guild_update = callback; + client->gw.cbs[DISCORD_EV_GUILD_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } void -discord_set_on_guild_delete(struct discord *client, - discord_ev_guild_delete callback) +discord_set_on_guild_delete(struct discord *client, discord_ev_guild callback) { - client->gw.cmds.cbs.on_guild_delete = callback; + client->gw.cbs[DISCORD_EV_GUILD_DELETE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILDS); } @@ -506,7 +507,7 @@ void discord_set_on_message_create(struct discord *client, discord_ev_message callback) { - client->gw.cmds.cbs.on_message_create = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_CREATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGES | DISCORD_GATEWAY_DIRECT_MESSAGES); } @@ -515,7 +516,7 @@ void discord_set_on_message_update(struct discord *client, discord_ev_message callback) { - client->gw.cmds.cbs.on_message_update = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGES | DISCORD_GATEWAY_DIRECT_MESSAGES); } @@ -524,7 +525,7 @@ void discord_set_on_message_delete(struct discord *client, discord_ev_message_delete callback) { - client->gw.cmds.cbs.on_message_delete = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_DELETE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGES | DISCORD_GATEWAY_DIRECT_MESSAGES); } @@ -533,7 +534,7 @@ void discord_set_on_message_delete_bulk(struct discord *client, discord_ev_message_delete_bulk callback) { - client->gw.cmds.cbs.on_message_delete_bulk = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_DELETE_BULK] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGES | DISCORD_GATEWAY_DIRECT_MESSAGES); } @@ -542,7 +543,7 @@ void discord_set_on_message_reaction_add(struct discord *client, discord_ev_message_reaction_add callback) { - client->gw.cmds.cbs.on_message_reaction_add = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_REACTION_ADD] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGE_REACTIONS | DISCORD_GATEWAY_DIRECT_MESSAGE_REACTIONS); @@ -552,7 +553,7 @@ void discord_set_on_message_reaction_remove( struct discord *client, discord_ev_message_reaction_remove callback) { - client->gw.cmds.cbs.on_message_reaction_remove = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_REACTION_REMOVE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGE_REACTIONS | DISCORD_GATEWAY_DIRECT_MESSAGE_REACTIONS); @@ -562,7 +563,8 @@ void discord_set_on_message_reaction_remove_all( struct discord *client, discord_ev_message_reaction_remove_all callback) { - client->gw.cmds.cbs.on_message_reaction_remove_all = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_REACTION_REMOVE_ALL] = + (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGE_REACTIONS | DISCORD_GATEWAY_DIRECT_MESSAGE_REACTIONS); @@ -572,7 +574,8 @@ void discord_set_on_message_reaction_remove_emoji( struct discord *client, discord_ev_message_reaction_remove_emoji callback) { - client->gw.cmds.cbs.on_message_reaction_remove_emoji = callback; + client->gw.cbs[DISCORD_EV_MESSAGE_REACTION_REMOVE_EMOJI] = + (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_MESSAGE_REACTIONS | DISCORD_GATEWAY_DIRECT_MESSAGE_REACTIONS); @@ -582,14 +585,14 @@ void discord_set_on_interaction_create(struct discord *client, discord_ev_interaction callback) { - client->gw.cmds.cbs.on_interaction_create = callback; + client->gw.cbs[DISCORD_EV_INTERACTION_CREATE] = (discord_ev)callback; } void discord_set_on_voice_state_update(struct discord *client, discord_ev_voice_state_update callback) { - client->gw.cmds.cbs.on_voice_state_update = callback; + client->gw.cbs[DISCORD_EV_VOICE_STATE_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_VOICE_STATES); } @@ -597,16 +600,37 @@ void discord_set_on_voice_server_update(struct discord *client, discord_ev_voice_server_update callback) { - client->gw.cmds.cbs.on_voice_server_update = callback; + client->gw.cbs[DISCORD_EV_VOICE_SERVER_UPDATE] = (discord_ev)callback; discord_add_intents(client, DISCORD_GATEWAY_GUILD_VOICE_STATES); } +/* deprecated, use discord_update_presence() instead */ void discord_set_presence(struct discord *client, struct discord_presence_update *presence) { - memcpy(client->gw.id.presence, presence, sizeof *presence); - discord_gateway_send_presence_update(&client->gw); + discord_gateway_send_presence_update(&client->gw, presence); +} + +void +discord_request_guild_members(struct discord *client, + struct discord_request_guild_members *request) +{ + discord_gateway_send_request_guild_members(&client->gw, request); +} + +void +discord_update_voice_state(struct discord *client, + struct discord_update_voice_state *update) +{ + discord_gateway_send_update_voice_state(&client->gw, update); +} + +void +discord_update_presence(struct discord *client, + struct discord_presence_update *presence) +{ + discord_gateway_send_presence_update(&client->gw, presence); } int @@ -645,3 +669,27 @@ discord_get_io_poller(struct discord *client) { return client->io_poller; } + +struct ccord_szbuf_readonly +discord_config_get_field(struct discord *client, + char *const path[], + unsigned depth) +{ + struct logconf_field field = logconf_get_field(&client->conf, path, depth); + + return (struct ccord_szbuf_readonly){ field.start, field.size }; +} + +void +__discord_claim(struct discord *client, const void *param) +{ + ASSERT_S(discord_refcounter_claim(&client->refcounter, (void *)param), + "Failed attempt to claim non-Concord function parameter"); +} + +void +discord_unclaim(struct discord *client, const void *param) +{ + ASSERT_S(discord_refcounter_unclaim(&client->refcounter, (void *)param), + "Failed attempt to unclaim non-Concord function parameter"); +} diff --git a/src/discord-gateway.c b/src/discord-gateway.c index aad9da0cc..071c83410 100644 --- a/src/discord-gateway.c +++ b/src/discord-gateway.c @@ -1,58 +1,19 @@ #include #include #include -#include /* offsetof() */ -#include /* isspace() */ #include "discord.h" #include "discord-internal.h" +#include "discord-worker.h" #include "osname.h" -/* shorten event callback for maintainability purposes */ -#define ON(event, ...) gw->cmds.cbs.on_##event(CLIENT(gw, gw), __VA_ARGS__) - /* return enumerator as string in case of a match */ #define CASE_RETURN_STR(code) \ case code: \ return #code -/** - * @brief Context in case event is scheduled to be triggered - * from Concord's worker threads - */ -struct _discord_event_context { - /** the discord gateway client */ - struct discord_gateway *gw; - /** the event unique id value */ - enum discord_gateway_events event; - /** the event callback */ - void (*on_event)(struct discord_gateway *gw); -}; - -static struct _discord_event_context * -_discord_event_context_init(const struct discord_gateway *gw, - enum discord_gateway_events event, - void (*on_event)(struct discord_gateway *gw)) -{ - struct _discord_event_context *cxt = malloc(sizeof *cxt); - struct discord *clone = discord_clone(CLIENT(gw, gw)); - - cxt->gw = &clone->gw; - cxt->event = event; - cxt->on_event = on_event; - - return cxt; -} - -static void -_discord_event_context_cleanup(struct _discord_event_context *cxt) -{ - discord_cleanup(CLIENT(cxt->gw, gw)); - free(cxt); -} - static const char * -opcode_print(enum discord_gateway_opcodes opcode) +_discord_gateway_opcode_print(enum discord_gateway_opcodes opcode) { switch (opcode) { CASE_RETURN_STR(DISCORD_GATEWAY_DISPATCH); @@ -72,7 +33,7 @@ opcode_print(enum discord_gateway_opcodes opcode) } static const char * -close_opcode_print(enum discord_gateway_close_opcodes opcode) +_discord_gateway_close_opcode_print(enum discord_gateway_close_opcodes opcode) { switch (opcode) { CASE_RETURN_STR(DISCORD_GATEWAY_CLOSE_REASON_UNKNOWN_ERROR); @@ -91,9 +52,7 @@ close_opcode_print(enum discord_gateway_close_opcodes opcode) CASE_RETURN_STR(DISCORD_GATEWAY_CLOSE_REASON_DISALLOWED_INTENTS); CASE_RETURN_STR(DISCORD_GATEWAY_CLOSE_REASON_RECONNECT); default: { - const char *str; - - str = ws_close_opcode_print((enum ws_close_reason)opcode); + const char *str = ws_close_opcode_print((enum ws_close_reason)opcode); if (str) return str; log_warn("Unknown WebSockets close opcode (code: %d)", opcode); @@ -102,201 +61,32 @@ close_opcode_print(enum discord_gateway_close_opcodes opcode) } } -void -discord_gateway_send_presence_update(struct discord_gateway *gw) -{ - struct ws_info info = { 0 }; - char buf[2048]; - jsonb b; - - if (!gw->session->is_ready) return; - - jsonb_init(&b); - jsonb_object(&b, buf, sizeof(buf)); - { - jsonb_key(&b, buf, sizeof(buf), "op", 2); - jsonb_number(&b, buf, sizeof(buf), 3); - jsonb_key(&b, buf, sizeof(buf), "d", 1); - discord_presence_update_to_jsonb(&b, buf, sizeof(buf), - gw->id.presence); - jsonb_object_pop(&b, buf, sizeof(buf)); - } - - if (ws_send_text(gw->ws, &info, buf, b.pos)) { - io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); - logconf_info( - &gw->conf, - ANSICOLOR("SEND", ANSI_FG_BRIGHT_GREEN) " PRESENCE UPDATE (%d " - "bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - } - else { - logconf_error( - &gw->conf, - ANSICOLOR("FAIL SEND", ANSI_FG_RED) " PRESENCE UPDATE (%d " - "bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - } -} - static void -send_resume(struct discord_gateway *gw) -{ - struct ws_info info = { 0 }; - char buf[1024]; - jsonb b; - - /* reset */ - gw->session->status ^= DISCORD_SESSION_RESUMABLE; - - jsonb_init(&b); - jsonb_object(&b, buf, sizeof(buf)); - { - jsonb_key(&b, buf, sizeof(buf), "op", 2); - jsonb_number(&b, buf, sizeof(buf), 6); - jsonb_key(&b, buf, sizeof(buf), "d", 1); - jsonb_object(&b, buf, sizeof(buf)); - { - jsonb_key(&b, buf, sizeof(buf), "token", 5); - jsonb_string(&b, buf, sizeof(buf), gw->id.token, - strlen(gw->id.token)); - jsonb_key(&b, buf, sizeof(buf), "session_id", 10); - jsonb_string(&b, buf, sizeof(buf), gw->session->id, - strlen(gw->session->id)); - jsonb_key(&b, buf, sizeof(buf), "seq", 3); - jsonb_number(&b, buf, sizeof(buf), gw->payload.seq); - jsonb_object_pop(&b, buf, sizeof(buf)); - } - jsonb_object_pop(&b, buf, sizeof(buf)); - } - - if (ws_send_text(gw->ws, &info, buf, b.pos)) { - io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); - logconf_info( - &gw->conf, - ANSICOLOR("SEND", - ANSI_FG_BRIGHT_GREEN) " RESUME (%d bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - } - else { - logconf_info(&gw->conf, - ANSICOLOR("FAIL SEND", - ANSI_FG_RED) " RESUME (%d bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - } -} - -static void -send_identify(struct discord_gateway *gw) -{ - struct ws_info info = { 0 }; - char buf[1024]; - jsonb b; - - /* Ratelimit check */ - if (gw->timer->now - gw->timer->identify < 5) { - ++gw->session->concurrent; - VASSERT_S(gw->session->concurrent - < gw->session->start_limit.max_concurrency, - "Reach identify request threshold (%d every 5 seconds)", - gw->session->start_limit.max_concurrency); - } - else { - gw->session->concurrent = 0; - } - - jsonb_init(&b); - jsonb_object(&b, buf, sizeof(buf)); - { - jsonb_key(&b, buf, sizeof(buf), "op", 2); - jsonb_number(&b, buf, sizeof(buf), 2); - jsonb_key(&b, buf, sizeof(buf), "d", 1); - discord_identify_to_jsonb(&b, buf, sizeof(buf), &gw->id); - jsonb_object_pop(&b, buf, sizeof(buf)); - } - - if (ws_send_text(gw->ws, &info, buf, b.pos)) { - io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); - logconf_info( - &gw->conf, - ANSICOLOR( - "SEND", - ANSI_FG_BRIGHT_GREEN) " IDENTIFY (%d bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - /* get timestamp for this identify */ - gw->timer->identify = gw->timer->now; - } - else { - logconf_info( - &gw->conf, - ANSICOLOR("FAIL SEND", - ANSI_FG_RED) " IDENTIFY (%d bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - } -} - -/* send heartbeat pulse to websockets server in order - * to maintain connection alive */ -static void -send_heartbeat(struct discord_gateway *gw) -{ - struct ws_info info = { 0 }; - char buf[64]; - jsonb b; - - jsonb_init(&b); - jsonb_object(&b, buf, sizeof(buf)); - { - jsonb_key(&b, buf, sizeof(buf), "op", 2); - jsonb_number(&b, buf, sizeof(buf), 1); - jsonb_key(&b, buf, sizeof(buf), "d", 1); - jsonb_number(&b, buf, sizeof(buf), gw->payload.seq); - jsonb_object_pop(&b, buf, sizeof(buf)); - } - - if (ws_send_text(gw->ws, &info, buf, b.pos)) { - io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); - logconf_info( - &gw->conf, - ANSICOLOR( - "SEND", - ANSI_FG_BRIGHT_GREEN) " HEARTBEAT (%d bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - /* update heartbeat timestamp */ - gw->timer->hbeat = gw->timer->now; - } - else { - logconf_info( - &gw->conf, - ANSICOLOR("FAIL SEND", - ANSI_FG_RED) " HEARTBEAT (%d bytes) [@@@_%zu_@@@]", - b.pos, info.loginfo.counter + 1); - } -} - -static void -on_hello(struct discord_gateway *gw) +_discord_on_hello(struct discord_gateway *gw) { jsmnf_pair *f; - gw->timer->interval = 0; - gw->timer->hbeat = gw->timer->now; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "heartbeat_interval", 18))) - gw->timer->interval = strtoull(gw->json + f->v.pos, NULL, 10); + if ((f = jsmnf_find(gw->payload.data, gw->payload.json.start, + "heartbeat_interval", 18))) + gw->timer->hbeat_interval = + strtoll(gw->payload.json.start + f->v.pos, NULL, 10); if (gw->session->status & DISCORD_SESSION_RESUMABLE) - send_resume(gw); + discord_gateway_send_resume(gw, &(struct discord_resume){ + .token = gw->id.token, + .session_id = gw->session->id, + .seq = gw->payload.seq, + }); else - send_identify(gw); + discord_gateway_send_identify(gw, &gw->id); } -static enum discord_gateway_events -get_dispatch_event(char name[]) -{ #define RETURN_IF_MATCH(event, str) \ - if (!strcmp(#event, str)) return DISCORD_GATEWAY_EVENTS_##event + if (!strcmp(#event, str)) return DISCORD_EV_##event +static enum discord_gateway_events +_discord_gateway_event_eval(char name[]) +{ RETURN_IF_MATCH(READY, name); RETURN_IF_MATCH(RESUMED, name); RETURN_IF_MATCH(APPLICATION_COMMAND_CREATE, name); @@ -350,819 +140,109 @@ get_dispatch_event(char name[]) RETURN_IF_MATCH(VOICE_STATE_UPDATE, name); RETURN_IF_MATCH(VOICE_SERVER_UPDATE, name); RETURN_IF_MATCH(WEBHOOKS_UPDATE, name); - return DISCORD_GATEWAY_EVENTS_NONE; - -#undef RETURN_IF_MATCH -} - -static void -on_guild_create(struct discord_gateway *gw) -{ - struct discord_guild guild = { 0 }; - - discord_guild_from_jsmnf(gw->payload.data, gw->json, &guild); - - ON(guild_create, &guild); - - discord_guild_cleanup(&guild); -} - -static void -on_guild_update(struct discord_gateway *gw) -{ - struct discord_guild guild = { 0 }; - - discord_guild_from_jsmnf(gw->payload.data, gw->json, &guild); - - ON(guild_update, &guild); - - discord_guild_cleanup(&guild); -} - -static void -on_guild_delete(struct discord_gateway *gw) -{ - u64snowflake guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "id", 2))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - - ON(guild_delete, guild_id); -} - -static void -on_guild_role_create(struct discord_gateway *gw) -{ - struct discord_role role = { 0 }; - u64snowflake guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "role", 4))) - discord_role_from_jsmnf(f, gw->json, &role); - - ON(guild_role_create, guild_id, &role); - - discord_role_cleanup(&role); -} - -static void -on_guild_role_update(struct discord_gateway *gw) -{ - struct discord_role role = { 0 }; - u64snowflake guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "role", 4))) - discord_role_from_jsmnf(f, gw->json, &role); - - ON(guild_role_update, guild_id, &role); - - discord_role_cleanup(&role); -} - -static void -on_guild_role_delete(struct discord_gateway *gw) -{ - u64snowflake guild_id = 0, role_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "role_id", 7))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &role_id); - - ON(guild_role_delete, guild_id, role_id); + return DISCORD_EV_NONE; } -static void -on_guild_member_add(struct discord_gateway *gw) -{ - struct discord_guild_member member = { 0 }; - u64snowflake guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - discord_guild_member_from_jsmnf(gw->payload.data, gw->json, &member); - - ON(guild_member_add, guild_id, &member); - - discord_guild_member_cleanup(&member); -} - -static void -on_guild_member_update(struct discord_gateway *gw) -{ - struct discord_guild_member member = { 0 }; - u64snowflake guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - discord_guild_member_from_jsmnf(gw->payload.data, gw->json, &member); - - ON(guild_member_update, guild_id, &member); - - discord_guild_member_cleanup(&member); -} - -static void -on_guild_member_remove(struct discord_gateway *gw) -{ - u64snowflake guild_id = 0; - struct discord_user user = { 0 }; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "user", 4))) - discord_user_from_jsmnf(f, gw->json, &user); - - ON(guild_member_remove, guild_id, &user); - - discord_user_cleanup(&user); -} - -static void -on_guild_ban_add(struct discord_gateway *gw) -{ - u64snowflake guild_id = 0; - struct discord_user user = { 0 }; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "user", 4))) - discord_user_from_jsmnf(f, gw->json, &user); - - ON(guild_ban_add, guild_id, &user); - - discord_user_cleanup(&user); -} - -static void -on_guild_ban_remove(struct discord_gateway *gw) -{ - u64snowflake guild_id = 0; - struct discord_user user = { 0 }; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "user", 4))) - discord_user_from_jsmnf(f, gw->json, &user); - - ON(guild_ban_remove, guild_id, &user); - - discord_user_cleanup(&user); -} - -static void -on_application_command_create(struct discord_gateway *gw) -{ - struct discord_application_command cmd = { 0 }; - - discord_application_command_from_jsmnf(gw->payload.data, gw->json, &cmd); - - ON(application_command_create, &cmd); - - discord_application_command_cleanup(&cmd); -} - -static void -on_application_command_update(struct discord_gateway *gw) -{ - struct discord_application_command cmd = { 0 }; - - discord_application_command_from_jsmnf(gw->payload.data, gw->json, &cmd); - - ON(application_command_update, &cmd); - - discord_application_command_cleanup(&cmd); -} - -static void -on_application_command_delete(struct discord_gateway *gw) -{ - struct discord_application_command cmd = { 0 }; - - discord_application_command_from_jsmnf(gw->payload.data, gw->json, &cmd); - - ON(application_command_delete, &cmd); - - discord_application_command_cleanup(&cmd); -} - -static void -on_channel_create(struct discord_gateway *gw) -{ - struct discord_channel channel = { 0 }; - - discord_channel_from_jsmnf(gw->payload.data, gw->json, &channel); - - ON(channel_create, &channel); - - discord_channel_cleanup(&channel); -} - -static void -on_channel_update(struct discord_gateway *gw) -{ - struct discord_channel channel = { 0 }; - - discord_channel_from_jsmnf(gw->payload.data, gw->json, &channel); - - ON(channel_update, &channel); - - discord_channel_cleanup(&channel); -} - -static void -on_channel_delete(struct discord_gateway *gw) -{ - struct discord_channel channel = { 0 }; - - discord_channel_from_jsmnf(gw->payload.data, gw->json, &channel); - - ON(channel_delete, &channel); - - discord_channel_cleanup(&channel); -} - -static void -on_channel_pins_update(struct discord_gateway *gw) -{ - u64snowflake guild_id = 0, channel_id = 0; - u64unix_ms last_pin_timestamp = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "channel_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &channel_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "last_pin_timestamp", 18))) - cog_iso8601_to_unix_ms(gw->json + f->v.pos, (size_t)(f->v.len), - &last_pin_timestamp); - - ON(channel_pins_update, guild_id, channel_id, last_pin_timestamp); -} - -static void -on_thread_create(struct discord_gateway *gw) -{ - struct discord_channel thread = { 0 }; - - discord_channel_from_jsmnf(gw->payload.data, gw->json, &thread); - - ON(thread_create, &thread); - - discord_channel_cleanup(&thread); -} - -static void -on_thread_update(struct discord_gateway *gw) -{ - struct discord_channel thread = { 0 }; - - discord_channel_from_jsmnf(gw->payload.data, gw->json, &thread); - - ON(thread_update, &thread); - - discord_channel_cleanup(&thread); -} - -static void -on_thread_delete(struct discord_gateway *gw) -{ - struct discord_channel thread = { 0 }; - - discord_channel_from_jsmnf(gw->payload.data, gw->json, &thread); - - ON(thread_delete, &thread); - - discord_channel_cleanup(&thread); -} - -static void -on_interaction_create(struct discord_gateway *gw) -{ - struct discord_interaction interaction = { 0 }; - - discord_interaction_from_jsmnf(gw->payload.data, gw->json, &interaction); - - ON(interaction_create, &interaction); - - discord_interaction_cleanup(&interaction); -} - -static void -on_message_create(struct discord_gateway *gw) -{ - struct discord_message msg = { 0 }; - - discord_message_from_jsmnf(gw->payload.data, gw->json, &msg); - - if (gw->cmds.pool - && !strncmp(gw->cmds.prefix.start, msg.content, gw->cmds.prefix.size)) - { - char *cmd_start = msg.content + gw->cmds.prefix.size; - size_t cmd_len = strcspn(cmd_start, " \n\t\r"); - discord_ev_message cmd_cb = NULL; - - char *tmp = msg.content; /* hold original ptr */ - size_t i; - - /* match command to its callback */ - for (i = 0; i < gw->cmds.amt; ++i) { - if (cmd_len == gw->cmds.pool[i].size - && 0 == strncmp(gw->cmds.pool[i].start, cmd_start, cmd_len)) - { - - cmd_cb = gw->cmds.pool[i].cb; - break; - } - } - - /* couldn't match command to callback, get fallback if available */ - if (!cmd_cb && gw->cmds.prefix.size) { - cmd_len = 0; /* no command specified */ - cmd_cb = gw->cmds.fallback.cb ? gw->cmds.fallback.cb - : gw->cmds.cbs.on_message_create; - } - - if (cmd_cb) { - /* skip blank characters after command */ - if (msg.content) { - msg.content = cmd_start + cmd_len; - while (*msg.content && isspace((int)msg.content[0])) - ++msg.content; - } - - cmd_cb(CLIENT(gw, gw), &msg); - } - - msg.content = tmp; /* retrieve original ptr */ - } - else if (gw->cmds.cbs.on_message_create) { - ON(message_create, &msg); - } - - discord_message_cleanup(&msg); -} - -static void -on_message_update(struct discord_gateway *gw) -{ - struct discord_message msg = { 0 }; - - discord_message_from_jsmnf(gw->payload.data, gw->json, &msg); - - ON(message_update, &msg); - - discord_message_cleanup(&msg); -} - -static void -on_message_delete(struct discord_gateway *gw) -{ - u64snowflake message_id = 0, channel_id = 0, guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "id", 2))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &message_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "channel_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &channel_id); - - ON(message_delete, message_id, channel_id, guild_id); -} - -static void -on_message_delete_bulk(struct discord_gateway *gw) -{ - struct snowflakes ids = { 0 }; - u64snowflake channel_id = 0, guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "ids", 3))) - snowflakes_from_jsmnf(f, gw->json, &ids); - if ((f = jsmnf_find(gw->payload.data, gw->json, "channel_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &channel_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - - ON(message_delete_bulk, &ids, channel_id, guild_id); - - snowflakes_cleanup(&ids); -} - -static void -on_message_reaction_add(struct discord_gateway *gw) -{ - u64snowflake user_id = 0, message_id = 0, channel_id = 0, guild_id = 0; - struct discord_guild_member member = { 0 }; - struct discord_emoji emoji = { 0 }; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "user_id", 7))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &user_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "message_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &message_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "member", 6))) - discord_guild_member_from_jsmnf(f, gw->json, &member); - if ((f = jsmnf_find(gw->payload.data, gw->json, "emoji", 5))) - discord_emoji_from_jsmnf(f, gw->json, &emoji); - if ((f = jsmnf_find(gw->payload.data, gw->json, "channel_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &channel_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - - ON(message_reaction_add, user_id, channel_id, message_id, guild_id, - &member, &emoji); - - discord_guild_member_cleanup(&member); - discord_emoji_cleanup(&emoji); -} - -static void -on_message_reaction_remove(struct discord_gateway *gw) -{ - u64snowflake user_id = 0, message_id = 0, channel_id = 0, guild_id = 0; - struct discord_emoji emoji = { 0 }; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "user_id", 7))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &user_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "message_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &message_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "emoji", 5))) - discord_emoji_from_jsmnf(f, gw->json, &emoji); - if ((f = jsmnf_find(gw->payload.data, gw->json, "channel_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &channel_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - - ON(message_reaction_remove, user_id, channel_id, message_id, guild_id, - &emoji); - - discord_emoji_cleanup(&emoji); -} - -static void -on_message_reaction_remove_all(struct discord_gateway *gw) -{ - u64snowflake channel_id = 0, message_id = 0, guild_id = 0; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "channel_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &channel_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "message_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &message_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - - ON(message_reaction_remove_all, channel_id, message_id, guild_id); -} - -static void -on_message_reaction_remove_emoji(struct discord_gateway *gw) -{ - u64snowflake channel_id = 0, guild_id = 0, message_id = 0; - struct discord_emoji emoji = { 0 }; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "channel_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &channel_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "message_id", 10))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &message_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "emoji", 5))) - discord_emoji_from_jsmnf(f, gw->json, &emoji); - - ON(message_reaction_remove_emoji, channel_id, guild_id, message_id, - &emoji); - - discord_emoji_cleanup(&emoji); -} - -static void -on_voice_state_update(struct discord_gateway *gw) -{ - struct discord_voice_state vs = { 0 }; - - discord_voice_state_from_jsmnf(gw->payload.data, gw->json, &vs); - -#ifdef HAS_DISCORD_VOICE - if (vs.user_id == CLIENT(gw, gw)->self.id) { - /* we only care about the voice_state_update of bot */ - _discord_on_voice_state_update(CLIENT(gw, gw), &vs); - } -#endif /* HAS_DISCORD_VOICE */ - - if (gw->cmds.cbs.on_voice_state_update) ON(voice_state_update, &vs); - - discord_voice_state_cleanup(&vs); -} +#undef RETURN_IF_MATCH -static void -on_voice_server_update(struct discord_gateway *gw) +static struct discord_gateway * +_discord_gateway_clone(const struct discord_gateway *gw) { - u64snowflake guild_id = 0; - char token[512], endpoint[1024]; - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->payload.data, gw->json, "token", 5))) - snprintf(token, sizeof(token), "%.*s", (int)f->v.len, - gw->json + f->v.pos); - if ((f = jsmnf_find(gw->payload.data, gw->json, "guild_id", 8))) - sscanf(gw->json + f->v.pos, "%" SCNu64, &guild_id); - if ((f = jsmnf_find(gw->payload.data, gw->json, "endpoint", 8))) - snprintf(endpoint, sizeof(endpoint), "%.*s", (int)f->v.len, - gw->json + f->v.pos); - -#ifdef HAS_DISCORD_VOICE - /* this happens for everyone */ - _discord_on_voice_server_update(CLIENT(gw, gw), guild_id, token, endpoint); -#endif /* HAS_DISCORD_VOICE */ - - if (gw->cmds.cbs.on_voice_server_update) - ON(voice_server_update, token, guild_id, endpoint); + return &discord_clone(CLIENT(gw, gw))->gw; } static void -on_ready(struct discord_gateway *gw) +_discord_gateway_clone_cleanup(struct discord_gateway *clone) { - gw->cmds.cbs.on_ready(CLIENT(gw, gw)); + discord_cleanup(CLIENT(clone, gw)); } static void -dispatch_run(void *p_cxt) +_discord_gateway_dispatch_thread(void *p_gw) { - struct _discord_event_context *cxt = p_cxt; + struct discord_gateway *gw = p_gw; - logconf_info(&cxt->gw->conf, + logconf_info(&gw->conf, "Thread " ANSICOLOR("starts", ANSI_FG_RED) " to serve %s", - cxt->gw->payload.name); + gw->payload.name); - cxt->on_event(cxt->gw); + discord_gateway_dispatch(gw); - logconf_info(&cxt->gw->conf, + logconf_info(&gw->conf, "Thread " ANSICOLOR("exits", ANSI_FG_RED) " from serving %s", - cxt->gw->payload.name); + gw->payload.name); - _discord_event_context_cleanup(cxt); + _discord_gateway_clone_cleanup(gw); } static void -on_dispatch(struct discord_gateway *gw) +_discord_on_dispatch(struct discord_gateway *gw) { - /* event-callback selector */ - void (*on_event)(struct discord_gateway *) = NULL; - /* get dispatch event opcode */ - enum discord_gateway_events event; - enum discord_event_scheduler mode; + struct discord *client = CLIENT(gw, gw); - /* XXX: this should only apply for user dispatched payloads? */ + /* TODO: this should only apply for user dispatched payloads? */ #if 0 /* Ratelimit check */ - if (gw->timer->now - gw->timer->event < 60000) { + if (gw->timer->now - gw->timer->event_last < 60000) { ++gw->session->event_count; ASSERT_S(gw->session->event_count < 120, "Reach event dispatch threshold (120 every 60 seconds)"); } else { - gw->timer->event = gw->timer->now; + gw->timer->event_last = gw->timer->now; gw->session->event_count = 0; } #endif - switch (event = get_dispatch_event(gw->payload.name)) { - case DISCORD_GATEWAY_EVENTS_READY: { + switch (gw->payload.event) { + case DISCORD_EV_READY: { jsmnf_pair *f; logconf_info(&gw->conf, "Succesfully started a Discord session!"); - if ((f = jsmnf_find(gw->payload.data, gw->json, "session_id", 10))) + if ((f = jsmnf_find(gw->payload.data, gw->payload.json.start, + "session_id", 10))) snprintf(gw->session->id, sizeof(gw->session->id), "%.*s", - (int)f->v.len, gw->json + f->v.pos); + (int)f->v.len, gw->payload.json.start + f->v.pos); ASSERT_S(*gw->session->id, "Missing session_id from READY event"); gw->session->is_ready = true; gw->session->retry.attempt = 0; - if (gw->cmds.cbs.on_ready) on_event = &on_ready; - - send_heartbeat(gw); + discord_gateway_send_heartbeat(gw, gw->payload.seq); } break; - case DISCORD_GATEWAY_EVENTS_RESUMED: + case DISCORD_EV_RESUMED: logconf_info(&gw->conf, "Succesfully resumed a Discord session!"); gw->session->is_ready = true; gw->session->retry.attempt = 0; - send_heartbeat(gw); - - break; - case DISCORD_GATEWAY_EVENTS_APPLICATION_COMMAND_CREATE: - if (gw->cmds.cbs.on_application_command_create) - on_event = on_application_command_create; - break; - case DISCORD_GATEWAY_EVENTS_APPLICATION_COMMAND_UPDATE: - if (gw->cmds.cbs.on_application_command_update) - on_event = on_application_command_update; - break; - case DISCORD_GATEWAY_EVENTS_APPLICATION_COMMAND_DELETE: - if (gw->cmds.cbs.on_application_command_delete) - on_event = on_application_command_delete; - break; - case DISCORD_GATEWAY_EVENTS_CHANNEL_CREATE: - if (gw->cmds.cbs.on_channel_create) on_event = on_channel_create; - break; - case DISCORD_GATEWAY_EVENTS_CHANNEL_UPDATE: - if (gw->cmds.cbs.on_channel_update) on_event = on_channel_update; - break; - case DISCORD_GATEWAY_EVENTS_CHANNEL_DELETE: - if (gw->cmds.cbs.on_channel_delete) on_event = on_channel_delete; - break; - case DISCORD_GATEWAY_EVENTS_CHANNEL_PINS_UPDATE: - if (gw->cmds.cbs.on_channel_pins_update) - on_event = on_channel_pins_update; - break; - case DISCORD_GATEWAY_EVENTS_THREAD_CREATE: - if (gw->cmds.cbs.on_thread_create) on_event = on_thread_create; - break; - case DISCORD_GATEWAY_EVENTS_THREAD_UPDATE: - if (gw->cmds.cbs.on_thread_update) on_event = on_thread_update; - break; - case DISCORD_GATEWAY_EVENTS_THREAD_DELETE: - if (gw->cmds.cbs.on_thread_delete) on_event = on_thread_delete; - break; - case DISCORD_GATEWAY_EVENTS_THREAD_LIST_SYNC: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_THREAD_MEMBER_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_THREAD_MEMBERS_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_GUILD_CREATE: - if (gw->cmds.cbs.on_guild_create) on_event = on_guild_create; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_UPDATE: - if (gw->cmds.cbs.on_guild_update) on_event = on_guild_update; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_DELETE: - if (gw->cmds.cbs.on_guild_delete) on_event = on_guild_delete; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_BAN_ADD: - if (gw->cmds.cbs.on_guild_ban_add) on_event = on_guild_ban_add; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_BAN_REMOVE: - if (gw->cmds.cbs.on_guild_ban_remove) on_event = on_guild_ban_remove; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_EMOJIS_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_GUILD_STICKERS_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_GUILD_INTEGRATIONS_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_GUILD_MEMBER_ADD: - if (gw->cmds.cbs.on_guild_member_add) on_event = on_guild_member_add; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_MEMBER_UPDATE: - if (gw->cmds.cbs.on_guild_member_update) - on_event = on_guild_member_update; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_MEMBER_REMOVE: - if (gw->cmds.cbs.on_guild_member_remove) - on_event = on_guild_member_remove; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_ROLE_CREATE: - if (gw->cmds.cbs.on_guild_role_create) on_event = on_guild_role_create; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_ROLE_UPDATE: - if (gw->cmds.cbs.on_guild_role_update) on_event = on_guild_role_update; - break; - case DISCORD_GATEWAY_EVENTS_GUILD_ROLE_DELETE: - if (gw->cmds.cbs.on_guild_role_delete) on_event = on_guild_role_delete; - break; - case DISCORD_GATEWAY_EVENTS_INTEGRATION_CREATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_INTEGRATION_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_INTEGRATION_DELETE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_INTERACTION_CREATE: - if (gw->cmds.cbs.on_interaction_create) - on_event = on_interaction_create; - break; - case DISCORD_GATEWAY_EVENTS_INVITE_CREATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_INVITE_DELETE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_CREATE: - if (gw->cmds.pool || gw->cmds.cbs.on_message_create) - on_event = &on_message_create; - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_UPDATE: - if (gw->cmds.cbs.on_message_update) on_event = on_message_update; - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_DELETE: - if (gw->cmds.cbs.on_message_delete) on_event = on_message_delete; - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_DELETE_BULK: - if (gw->cmds.cbs.on_message_delete_bulk) - on_event = on_message_delete_bulk; - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_ADD: - if (gw->cmds.cbs.on_message_reaction_add) - on_event = on_message_reaction_add; - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_REMOVE: - if (gw->cmds.cbs.on_message_reaction_remove) - on_event = on_message_reaction_remove; - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_REMOVE_ALL: - if (gw->cmds.cbs.on_message_reaction_remove_all) - on_event = on_message_reaction_remove_all; - break; - case DISCORD_GATEWAY_EVENTS_MESSAGE_REACTION_REMOVE_EMOJI: - if (gw->cmds.cbs.on_message_reaction_remove_emoji) - on_event = on_message_reaction_remove_emoji; - break; - case DISCORD_GATEWAY_EVENTS_PRESENCE_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_STAGE_INSTANCE_CREATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_STAGE_INSTANCE_DELETE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_STAGE_INSTANCE_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_TYPING_START: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_USER_UPDATE: - /** @todo implement */ - break; - case DISCORD_GATEWAY_EVENTS_VOICE_STATE_UPDATE: - if (gw->cmds.cbs.on_voice_state_update) - on_event = on_voice_state_update; - break; - case DISCORD_GATEWAY_EVENTS_VOICE_SERVER_UPDATE: - if (gw->cmds.cbs.on_voice_server_update) - on_event = on_voice_server_update; - break; - case DISCORD_GATEWAY_EVENTS_WEBHOOKS_UPDATE: - /** @todo implement */ + discord_gateway_send_heartbeat(gw, gw->payload.seq); break; default: - logconf_warn( - &gw->conf, - "Expected unimplemented GATEWAY_DISPATCH event (code: %d)", event); break; } - mode = - gw->cmds.scheduler(CLIENT(gw, gw), gw->json + gw->payload.data->v.pos, - gw->payload.data->v.len, event); - if (!on_event) return; + /* get dispatch event opcode */ + enum discord_event_scheduler mode = + gw->scheduler(client, gw->payload.json.start + gw->payload.data->v.pos, + gw->payload.data->v.len, gw->payload.event); - /* user subscribed to event */ switch (mode) { case DISCORD_EVENT_IGNORE: break; case DISCORD_EVENT_MAIN_THREAD: - on_event(gw); + discord_gateway_dispatch(gw); break; case DISCORD_EVENT_WORKER_THREAD: { - struct _discord_event_context *cxt = - _discord_event_context_init(gw, event, on_event); - int ret = work_run(&dispatch_run, cxt); - if (ret != 0) { - log_error("Couldn't execute worker-thread (code %d)", ret); - _discord_event_context_cleanup(cxt); + struct discord_gateway *clone = _discord_gateway_clone(gw); + CCORDcode code = discord_worker_add( + client, &_discord_gateway_dispatch_thread, clone); + + if (code != CCORD_OK) { + log_error("Couldn't start worker-thread (code %d)", code); + _discord_gateway_clone_cleanup(clone); } - break; } break; default: ERR("Unknown event handling mode (code: %d)", mode); @@ -1170,16 +250,17 @@ on_dispatch(struct discord_gateway *gw) } static void -on_invalid_session(struct discord_gateway *gw) +_discord_on_invalid_session(struct discord_gateway *gw) { enum ws_close_reason opcode; const char *reason; - gw->session->status = DISCORD_SESSION_SHUTDOWN; - /* attempt to resume if session isn't invalid */ + gw->session->retry.enable = true; + gw->session->status = DISCORD_SESSION_SHUTDOWN; if (gw->payload.data->v.len != 5 - || strncmp("false", gw->json + gw->payload.data->v.pos, 5)) + || strncmp("false", gw->payload.json.start + gw->payload.data->v.pos, + 5)) { gw->session->status |= DISCORD_SESSION_RESUMABLE; reason = "Invalid session, will attempt to resume"; @@ -1189,13 +270,13 @@ on_invalid_session(struct discord_gateway *gw) reason = "Invalid session, can't resume"; opcode = WS_CLOSE_REASON_NORMAL; } - gw->session->retry.enable = true; ws_close(gw->ws, opcode, reason, SIZE_MAX); + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); } static void -on_reconnect(struct discord_gateway *gw) +_discord_on_reconnect(struct discord_gateway *gw) { const char reason[] = "Discord expects client to reconnect"; @@ -1205,24 +286,25 @@ on_reconnect(struct discord_gateway *gw) ws_close(gw->ws, (enum ws_close_reason)DISCORD_GATEWAY_CLOSE_REASON_RECONNECT, reason, sizeof(reason)); + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); } static void -on_heartbeat_ack(struct discord_gateway *gw) +_discord_on_heartbeat_ack(struct discord_gateway *gw) { /* get request / response interval in milliseconds */ pthread_rwlock_wrlock(&gw->timer->rwlock); - gw->timer->ping_ms = (int)(gw->timer->now - gw->timer->hbeat); + gw->timer->ping_ms = (int)(gw->timer->now - gw->timer->hbeat_last); pthread_rwlock_unlock(&gw->timer->rwlock); logconf_trace(&gw->conf, "PING: %d ms", gw->timer->ping_ms); } static void -on_connect_cb(void *p_gw, - struct websockets *ws, - struct ws_info *info, - const char *ws_protocols) +_ws_on_connect(void *p_gw, + struct websockets *ws, + struct ws_info *info, + const char *ws_protocols) { (void)ws; (void)info; @@ -1232,12 +314,12 @@ on_connect_cb(void *p_gw, } static void -on_close_cb(void *p_gw, - struct websockets *ws, - struct ws_info *info, - enum ws_close_reason wscode, - const char *reason, - size_t len) +_ws_on_close(void *p_gw, + struct websockets *ws, + struct ws_info *info, + enum ws_close_reason wscode, + const char *reason, + size_t len) { (void)ws; (void)info; @@ -1248,7 +330,8 @@ on_close_cb(void *p_gw, logconf_warn( &gw->conf, ANSICOLOR("CLOSE %s", ANSI_FG_RED) " (code: %4d, %zu bytes): '%.*s'", - close_opcode_print(opcode), opcode, len, (int)len, reason); + _discord_gateway_close_opcode_print(opcode), opcode, len, (int)len, + reason); /* user-triggered shutdown */ if (gw->session->status & DISCORD_SESSION_SHUTDOWN) return; @@ -1297,73 +380,88 @@ on_close_cb(void *p_gw, } } +static bool +_discord_gateway_payload_from_json(struct discord_gateway_payload *payload, + const char text[], + size_t len) +{ + payload->json.start = (char *)text; + payload->json.size = len; + + jsmn_parser parser; + jsmn_init(&parser); + if (jsmn_parse_auto(&parser, text, len, &payload->json.tokens, + &payload->json.ntokens) + <= 0) + return false; + + jsmnf_loader loader; + jsmnf_init(&loader); + if (jsmnf_load_auto(&loader, text, payload->json.tokens, parser.toknext, + &payload->json.pairs, &payload->json.npairs) + <= 0) + return false; + + jsmnf_pair *f; + if ((f = jsmnf_find(payload->json.pairs, text, "t", 1))) { + if (JSMN_STRING == f->type) + snprintf(payload->name, sizeof(payload->name), "%.*s", + (int)f->v.len, text + f->v.pos); + else + *payload->name = '\0'; + + payload->event = _discord_gateway_event_eval(payload->name); + } + if ((f = jsmnf_find(payload->json.pairs, text, "s", 1))) { + int seq = (int)strtol(text + f->v.pos, NULL, 10); + if (seq) payload->seq = seq; + } + if ((f = jsmnf_find(payload->json.pairs, text, "op", 2))) + payload->opcode = + (enum discord_gateway_opcodes)strtol(text + f->v.pos, NULL, 10); + payload->data = jsmnf_find(payload->json.pairs, text, "d", 1); + + return true; +} + static void -on_text_cb(void *p_gw, - struct websockets *ws, - struct ws_info *info, - const char *text, - size_t len) +_ws_on_text(void *p_gw, + struct websockets *ws, + struct ws_info *info, + const char *text, + size_t len) { (void)ws; struct discord_gateway *gw = p_gw; - jsmn_parser parser; - gw->json = (char *)text; - gw->length = len; - - jsmn_init(&parser); - if (0 < jsmn_parse_auto(&parser, text, len, &gw->parse.tokens, - &gw->parse.ntokens)) - { - jsmnf_loader loader; - - jsmnf_init(&loader); - if (0 < jsmnf_load_auto(&loader, text, gw->parse.tokens, - parser.toknext, &gw->parse.pairs, - &gw->parse.npairs)) - { - jsmnf_pair *f; - - if ((f = jsmnf_find(gw->parse.pairs, text, "t", 1))) { - if (JSMN_STRING == f->type) - snprintf(gw->payload.name, sizeof(gw->payload.name), - "%.*s", (int)f->v.len, gw->json + f->v.pos); - else - *gw->payload.name = '\0'; - } - if ((f = jsmnf_find(gw->parse.pairs, text, "s", 1))) { - int seq = (int)strtol(gw->json + f->v.pos, NULL, 10); - if (seq) gw->payload.seq = seq; - } - if ((f = jsmnf_find(gw->parse.pairs, text, "op", 2))) - gw->payload.opcode = - (int)strtol(gw->json + f->v.pos, NULL, 10); - gw->payload.data = jsmnf_find(gw->parse.pairs, text, "d", 1); - } + if (!_discord_gateway_payload_from_json(&gw->payload, text, len)) { + logconf_fatal(&gw->conf, "Couldn't parse Gateway Payload"); + return; } logconf_trace( &gw->conf, ANSICOLOR("RCV", ANSI_FG_BRIGHT_YELLOW) " %s%s%s (%zu bytes) [@@@_%zu_@@@]", - opcode_print(gw->payload.opcode), *gw->payload.name ? " -> " : "", - gw->payload.name, len, info->loginfo.counter); + _discord_gateway_opcode_print(gw->payload.opcode), + *gw->payload.name ? " -> " : "", gw->payload.name, len, + info->loginfo.counter); switch (gw->payload.opcode) { case DISCORD_GATEWAY_DISPATCH: - on_dispatch(gw); + _discord_on_dispatch(gw); break; case DISCORD_GATEWAY_INVALID_SESSION: - on_invalid_session(gw); + _discord_on_invalid_session(gw); break; case DISCORD_GATEWAY_RECONNECT: - on_reconnect(gw); + _discord_on_reconnect(gw); break; case DISCORD_GATEWAY_HELLO: - on_hello(gw); + _discord_on_hello(gw); break; case DISCORD_GATEWAY_HEARTBEAT_ACK: - on_heartbeat_ack(gw); + _discord_on_heartbeat_ack(gw); break; default: logconf_error(&gw->conf, @@ -1374,10 +472,10 @@ on_text_cb(void *p_gw, } static discord_event_scheduler_t -default_scheduler_cb(struct discord *a, - const char b[], - size_t c, - enum discord_gateway_events d) +_discord_on_scheduler_default(struct discord *a, + const char b[], + size_t c, + enum discord_gateway_events d) { (void)a; (void)b; @@ -1387,108 +485,70 @@ default_scheduler_cb(struct discord *a, } static int -on_io_poller_curl(struct io_poller *io, CURLM *mhandle, void *user_data) +_discord_on_gateway_perform(struct io_poller *io, CURLM *mhandle, void *p_gw) { (void)io; (void)mhandle; - return discord_gateway_perform(user_data); + return discord_gateway_perform(p_gw); } void discord_gateway_init(struct discord_gateway *gw, struct logconf *conf, - struct sized_buffer *token) + const char token[]) { struct discord *client = CLIENT(gw, gw); /* Web-Sockets callbacks */ - struct ws_callbacks cbs = { 0 }; + struct ws_callbacks cbs = { .data = gw, + .on_connect = &_ws_on_connect, + .on_text = &_ws_on_text, + .on_close = &_ws_on_close }; /* Web-Sockets custom attributes */ - struct ws_attr attr = { 0 }; - /* Bot default presence update */ - struct discord_presence_update presence = { 0 }; - struct sized_buffer buf; - /* prefix directive */ - char *path[] = { "discord", "default_prefix" }; - - cbs.data = gw; - cbs.on_connect = &on_connect_cb; - cbs.on_text = &on_text_cb; - cbs.on_close = &on_close_cb; - - attr.conf = conf; + struct ws_attr attr = { .conf = conf }; /* Web-Sockets handler */ gw->mhandle = curl_multi_init(); - io_poller_curlm_add(client->io_poller, gw->mhandle, on_io_poller_curl, gw); + io_poller_curlm_add(client->io_poller, gw->mhandle, + _discord_on_gateway_perform, gw); gw->ws = ws_init(&cbs, gw->mhandle, &attr); logconf_branch(&gw->conf, conf, "DISCORD_GATEWAY"); gw->timer = calloc(1, sizeof *gw->timer); - if (pthread_rwlock_init(&gw->timer->rwlock, NULL)) - ERR("Couldn't initialize pthread rwlock"); + ASSERT_S(!pthread_rwlock_init(&gw->timer->rwlock, NULL), + "Couldn't initialize Gateway's rwlock"); /* client connection status */ gw->session = calloc(1, sizeof *gw->session); gw->session->retry.enable = true; - gw->session->retry.limit = 5; /* TODO: shouldn't be a hard limit */ + gw->session->retry.limit = 5; /* FIXME: shouldn't be a hard limit */ - /* connection identify token */ - cog_strndup(token->start, token->size, &gw->id.token); + /* default callbacks */ + gw->scheduler = _discord_on_scheduler_default; + /* connection identify token */ + gw->id.token = (char *)token; /* connection identify properties */ gw->id.properties = calloc(1, sizeof *gw->id.properties); gw->id.properties->os = OSNAME; gw->id.properties->browser = "concord"; gw->id.properties->device = "concord"; - /* the bot initial presence */ gw->id.presence = calloc(1, sizeof *gw->id.presence); - presence.status = "online"; - presence.since = cog_timestamp_ms(); - discord_set_presence(client, &presence); + gw->id.presence->status = "online"; + gw->id.presence->since = cog_timestamp_ms(); - /* default callbacks */ - gw->cmds.scheduler = default_scheduler_cb; - - /* check for default prefix in config file */ - buf = logconf_get_field(conf, path, sizeof(path) / sizeof *path); - if (buf.size) { - jsmn_parser parser; - jsmntok_t tokens[16]; - - jsmn_init(&parser); - if (0 < jsmn_parse(&parser, buf.start, buf.size, tokens, - sizeof(tokens) / sizeof *tokens)) - { - jsmnf_loader loader; - jsmnf_pair pairs[16]; - - jsmnf_init(&loader); - if (0 < jsmnf_load(&loader, buf.start, tokens, parser.toknext, - pairs, sizeof(pairs) / sizeof *pairs)) - { - bool enable_prefix = false; - jsmnf_pair *f; - - if ((f = jsmnf_find(pairs, buf.start, "enable", 6))) - enable_prefix = ('t' == buf.start[f->v.pos]); - - if (enable_prefix - && (f = jsmnf_find(pairs, buf.start, "prefix", 6))) { - char prefix[64] = ""; - - snprintf(prefix, sizeof(prefix), "%.*s", (int)f->v.len, - buf.start + f->v.pos); - discord_set_prefix(CLIENT(gw, gw), prefix); - } - } - } - } + discord_gateway_send_presence_update(gw, gw->id.presence); } void discord_gateway_cleanup(struct discord_gateway *gw) { + if (gw->timer->hbeat_timer) + discord_internal_timer_ctl(CLIENT(gw, gw), + &(struct discord_timer){ + .id = gw->timer->hbeat_timer, + .flags = DISCORD_TIMER_DELETE, + }); /* cleanup WebSockets handle */ io_poller_curlm_del(CLIENT(gw, gw)->io_poller, gw->mhandle); curl_multi_cleanup(gw->mhandle); @@ -1497,20 +557,12 @@ discord_gateway_cleanup(struct discord_gateway *gw) pthread_rwlock_destroy(&gw->timer->rwlock); free(gw->timer); /* cleanup bot identification */ - if (gw->id.token) free(gw->id.token); free(gw->id.properties); free(gw->id.presence); /* cleanup client session */ free(gw->session); - /* cleanup user commands */ - if (gw->cmds.pool) { - for (size_t i = 0; i < gw->cmds.amt; i++) - free(gw->cmds.pool[i].start); - free(gw->cmds.pool); - } - if (gw->cmds.prefix.start) free(gw->cmds.prefix.start); - if (gw->parse.pairs) free(gw->parse.pairs); - if (gw->parse.tokens) free(gw->parse.tokens); + if (gw->payload.json.pairs) free(gw->payload.json.pairs); + if (gw->payload.json.tokens) free(gw->payload.json.tokens); } #ifdef CCORD_DEBUG_WEBSOCKETS @@ -1564,9 +616,9 @@ static int _ws_curl_debug_trace( CURL *handle, curl_infotype type, char *data, size_t size, void *userp) { - const char *text; (void)handle; (void)userp; + const char *text; switch (type) { case CURLINFO_TEXT: @@ -1600,13 +652,47 @@ _ws_curl_debug_trace( } #endif /* CCORD_DEBUG_WEBSOCKETS */ +static bool +_discord_gateway_session_from_json(struct discord_gateway_session *session, + const char text[], + size_t len) +{ + jsmn_parser parser; + jsmntok_t tokens[32]; + jsmn_init(&parser); + if (jsmn_parse(&parser, text, len, tokens, sizeof(tokens) / sizeof *tokens) + <= 0) + return false; + + jsmnf_loader loader; + jsmnf_pair pairs[32]; + jsmnf_init(&loader); + if (jsmnf_load(&loader, text, tokens, parser.toknext, pairs, + sizeof(pairs) / sizeof *pairs) + <= 0) + return false; + + jsmnf_pair *f; + if ((f = jsmnf_find(pairs, text, "url", 3))) { + const char *url = text + f->v.pos; + int url_len = (int)f->v.len; + + url_len = snprintf(session->base_url, sizeof(session->base_url), + "%.*s%c" DISCORD_GATEWAY_URL_SUFFIX, url_len, url, + ('/' == url[url_len - 1]) ? '\0' : '/'); + ASSERT_NOT_OOB(url_len, sizeof(session->base_url)); + } + if ((f = jsmnf_find(pairs, text, "shards", 6))) + session->shards = (int)strtol(text + f->v.pos, NULL, 10); + if ((f = jsmnf_find(pairs, text, "session_start_limit", 19))) + discord_session_start_limit_from_jsmnf(f, text, &session->start_limit); + return true; +} + CCORDcode discord_gateway_start(struct discord_gateway *gw) { - struct discord *client = CLIENT(gw, gw); - struct sized_buffer json = { 0 }; - char url[1024]; - CURL *ehandle; + struct ccord_szbuf json = { 0 }; if (gw->session->retry.attempt >= gw->session->retry.limit) { logconf_fatal(&gw->conf, @@ -1615,50 +701,16 @@ discord_gateway_start(struct discord_gateway *gw) return CCORD_DISCORD_CONNECTION; } - else if (CCORD_OK != discord_get_gateway_bot(client, &json)) { + + if (discord_get_gateway_bot(CLIENT(gw, gw), &json) != CCORD_OK + || !_discord_gateway_session_from_json(gw->session, json.start, + json.size)) + { logconf_fatal(&gw->conf, "Couldn't retrieve Gateway Bot information"); + free(json.start); return CCORD_DISCORD_BAD_AUTH; } - else { - jsmn_parser parser; - jsmntok_t tokens[32]; - - jsmn_init(&parser); - if (0 < jsmn_parse(&parser, json.start, json.size, tokens, - sizeof(tokens) / sizeof *tokens)) - { - jsmnf_loader loader; - jsmnf_pair pairs[32]; - - jsmnf_init(&loader); - if (0 < jsmnf_load(&loader, json.start, tokens, parser.toknext, - pairs, sizeof(pairs) / sizeof *pairs)) - { - jsmnf_pair *f; - - if ((f = jsmnf_find(pairs, json.start, "url", 3))) { - const char *base_url = json.start + f->v.pos; - const int base_url_len = (int)f->v.len; - int len; - - len = snprintf( - url, sizeof(url), "%.*s%s" DISCORD_GATEWAY_URL_SUFFIX, - base_url_len, base_url, - ('/' == base_url[base_url_len - 1]) ? "" : "/"); - ASSERT_NOT_OOB(len, sizeof(url)); - } - if ((f = jsmnf_find(pairs, json.start, "shards", 6))) - gw->session->shards = - (int)strtol(json.start + f->v.pos, NULL, 10); - if ((f = jsmnf_find(pairs, json.start, "session_start_limit", - 19))) - discord_session_start_limit_from_jsmnf( - f, json.start, &gw->session->start_limit); - } - } - } - free(json.start); if (!gw->session->start_limit.remaining) { @@ -1671,14 +723,13 @@ discord_gateway_start(struct discord_gateway *gw) return CCORD_DISCORD_RATELIMIT; } - ws_set_url(gw->ws, url, NULL); - ehandle = ws_start(gw->ws); - -#ifdef CCORD_DEBUG_WEBSOCKETS + ws_set_url(gw->ws, gw->session->base_url, NULL); +#ifndef CCORD_DEBUG_WEBSOCKETS + ws_start(gw->ws); +#else + CURL *ehandle = ws_start(gw->ws); curl_easy_setopt(ehandle, CURLOPT_DEBUGFUNCTION, _ws_curl_debug_trace); curl_easy_setopt(ehandle, CURLOPT_VERBOSE, 1L); -#else - (void)ehandle; #endif /* CCORD_DEBUG_WEBSOCKETS */ return CCORD_OK; @@ -1716,23 +767,10 @@ discord_gateway_end(struct discord_gateway *gw) CCORDcode discord_gateway_perform(struct discord_gateway *gw) { - /* check for pending transfer, exit on failure */ - if (!ws_multi_socket_run(gw->ws, &gw->timer->now)) - return CCORD_DISCORD_CONNECTION; - - /* client is in the process of shutting down */ - if (gw->session->status & DISCORD_SESSION_SHUTDOWN) return CCORD_OK; - - /* client is in the process of connecting */ - if (!gw->session->is_ready) return CCORD_OK; - - /* check if timespan since first pulse is greater than - * minimum heartbeat interval required */ - if (gw->timer->interval < gw->timer->now - gw->timer->hbeat) { - send_heartbeat(gw); - } - - return CCORD_OK; + /* check for pending transfer, exit if not running */ + return !ws_multi_socket_run(gw->ws, &gw->timer->now) + ? CCORD_DISCORD_CONNECTION + : CCORD_OK; } void @@ -1745,6 +783,7 @@ discord_gateway_shutdown(struct discord_gateway *gw) gw->session->status = DISCORD_SESSION_SHUTDOWN; ws_close(gw->ws, WS_CLOSE_REASON_NORMAL, reason, sizeof(reason)); + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); } void @@ -1765,4 +804,5 @@ discord_gateway_reconnect(struct discord_gateway *gw, bool resume) } ws_close(gw->ws, opcode, reason, sizeof(reason)); + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); } diff --git a/src/discord-gateway_dispatch.c b/src/discord-gateway_dispatch.c new file mode 100644 index 000000000..9b0bbd1a0 --- /dev/null +++ b/src/discord-gateway_dispatch.c @@ -0,0 +1,415 @@ +#include +#include +#include + +#include "discord.h" +#include "discord-internal.h" + +#define INIT(type, event_name) \ + { \ + sizeof(struct type), \ + (size_t(*)(jsmnf_pair *, const char *, void *))type##_from_jsmnf, \ + (void (*)(void *))type##_cleanup \ + } + +/** @brief Information for deserializing a Discord event */ +static const struct { + /** size of event's datatype */ + size_t size; + /** event's payload deserializer */ + size_t (*from_jsmnf)(jsmnf_pair *, const char *, void *); + /** event's cleanup */ + void (*cleanup)(void *); +} dispatch[] = { + [DISCORD_EV_READY] = INIT(discord_ready, ready), + [DISCORD_EV_APPLICATION_COMMAND_CREATE] = + INIT(discord_application_command, application_command_create), + [DISCORD_EV_APPLICATION_COMMAND_UPDATE] = + INIT(discord_application_command, application_command_update), + [DISCORD_EV_APPLICATION_COMMAND_DELETE] = + INIT(discord_application_command, application_command_delete), + [DISCORD_EV_CHANNEL_CREATE] = INIT(discord_channel, channel_create), + [DISCORD_EV_CHANNEL_UPDATE] = INIT(discord_channel, channel_update), + [DISCORD_EV_CHANNEL_DELETE] = INIT(discord_channel, channel_delete), + [DISCORD_EV_CHANNEL_PINS_UPDATE] = + INIT(discord_channel_pins_update, channel_pins_update), + [DISCORD_EV_THREAD_CREATE] = INIT(discord_channel, thread_create), + [DISCORD_EV_THREAD_UPDATE] = INIT(discord_channel, thread_update), + [DISCORD_EV_THREAD_DELETE] = INIT(discord_channel, thread_delete), + [DISCORD_EV_THREAD_LIST_SYNC] = + INIT(discord_thread_list_sync, thread_list_sync), + [DISCORD_EV_THREAD_MEMBER_UPDATE] = + INIT(discord_thread_member, thread_member_update), + [DISCORD_EV_THREAD_MEMBERS_UPDATE] = + INIT(discord_thread_members_update, thread_members_update), + [DISCORD_EV_GUILD_CREATE] = INIT(discord_guild, guild_create), + [DISCORD_EV_GUILD_UPDATE] = INIT(discord_guild, guild_update), + [DISCORD_EV_GUILD_DELETE] = INIT(discord_guild, guild_delete), + [DISCORD_EV_GUILD_BAN_ADD] = INIT(discord_guild_ban_add, guild_ban_add), + [DISCORD_EV_GUILD_BAN_REMOVE] = + INIT(discord_guild_ban_remove, guild_ban_remove), + [DISCORD_EV_GUILD_EMOJIS_UPDATE] = + INIT(discord_guild_emojis_update, guild_emojis_update), + [DISCORD_EV_GUILD_STICKERS_UPDATE] = + INIT(discord_guild_stickers_update, guild_stickers_update), + [DISCORD_EV_GUILD_INTEGRATIONS_UPDATE] = + INIT(discord_guild_integrations_update, guild_integrations_update), + [DISCORD_EV_GUILD_MEMBER_ADD] = + INIT(discord_guild_member, guild_member_add), + [DISCORD_EV_GUILD_MEMBER_UPDATE] = + INIT(discord_guild_member_update, guild_member_update), + [DISCORD_EV_GUILD_MEMBER_REMOVE] = + INIT(discord_guild_member_remove, guild_member_remove), + [DISCORD_EV_GUILD_ROLE_CREATE] = + INIT(discord_guild_role_create, guild_role_create), + [DISCORD_EV_GUILD_ROLE_UPDATE] = + INIT(discord_guild_role_update, guild_role_update), + [DISCORD_EV_GUILD_ROLE_DELETE] = + INIT(discord_guild_role_delete, guild_role_delete), + [DISCORD_EV_INTEGRATION_CREATE] = + INIT(discord_integration, integration_create), + [DISCORD_EV_INTEGRATION_UPDATE] = + INIT(discord_integration, integration_update), + [DISCORD_EV_INTEGRATION_DELETE] = + INIT(discord_integration_delete, integration_delete), + [DISCORD_EV_INTERACTION_CREATE] = + INIT(discord_interaction, interaction_create), + [DISCORD_EV_INVITE_CREATE] = INIT(discord_invite_create, invite_create), + [DISCORD_EV_INVITE_DELETE] = INIT(discord_invite_delete, invite_delete), + [DISCORD_EV_MESSAGE_CREATE] = INIT(discord_message, message_create), + [DISCORD_EV_MESSAGE_UPDATE] = INIT(discord_message, message_update), + [DISCORD_EV_MESSAGE_DELETE] = INIT(discord_message_delete, message_delete), + [DISCORD_EV_MESSAGE_DELETE_BULK] = + INIT(discord_message_delete_bulk, message_delete_bulk), + [DISCORD_EV_MESSAGE_REACTION_ADD] = + INIT(discord_message_reaction_add, message_reaction_add), + [DISCORD_EV_MESSAGE_REACTION_REMOVE] = + INIT(discord_message_reaction_remove, message_reaction_remove), + [DISCORD_EV_MESSAGE_REACTION_REMOVE_ALL] = + INIT(discord_message_reaction_remove_all, message_reaction_remove_all), + [DISCORD_EV_MESSAGE_REACTION_REMOVE_EMOJI] = INIT( + discord_message_reaction_remove_emoji, message_reaction_remove_emoji), + [DISCORD_EV_PRESENCE_UPDATE] = + INIT(discord_presence_update, presence_update), + [DISCORD_EV_STAGE_INSTANCE_CREATE] = + INIT(discord_stage_instance, stage_instance_create), + [DISCORD_EV_STAGE_INSTANCE_UPDATE] = + INIT(discord_stage_instance, stage_instance_update), + [DISCORD_EV_STAGE_INSTANCE_DELETE] = + INIT(discord_stage_instance, stage_instance_delete), + [DISCORD_EV_TYPING_START] = INIT(discord_typing_start, typing_start), + [DISCORD_EV_USER_UPDATE] = INIT(discord_user, user_update), + [DISCORD_EV_VOICE_STATE_UPDATE] = + INIT(discord_voice_state, voice_state_update), + [DISCORD_EV_VOICE_SERVER_UPDATE] = + INIT(discord_voice_server_update, voice_server_update), + [DISCORD_EV_WEBHOOKS_UPDATE] = + INIT(discord_webhooks_update, webhooks_update), +}; + +void +discord_gateway_dispatch(struct discord_gateway *gw) +{ + const enum discord_gateway_events event = gw->payload.event; + struct discord *client = CLIENT(gw, gw); + + switch (event) { + case DISCORD_EV_MESSAGE_CREATE: + if (discord_message_commands_try_perform(&client->commands, + &gw->payload)) { + return; + } + /* fall-through */ + default: + if (gw->cbs[event]) { + void *event_data = calloc(1, dispatch[event].size); + + dispatch[event].from_jsmnf(gw->payload.data, + gw->payload.json.start, event_data); + + if (CCORD_UNAVAILABLE + == discord_refcounter_incr(&client->refcounter, event_data)) + { + discord_refcounter_add_internal(&client->refcounter, + event_data, + dispatch[event].cleanup, true); + } + gw->cbs[event](client, event_data); + discord_refcounter_decr(&client->refcounter, event_data); + } + break; + case DISCORD_EV_NONE: + logconf_warn( + &gw->conf, + "Expected unimplemented GATEWAY_DISPATCH event (code: %d)", event); + break; + } +} + +void +discord_gateway_send_identify(struct discord_gateway *gw, + struct discord_identify *identify) +{ + struct ws_info info = { 0 }; + char buf[1024]; + jsonb b; + + /* Ratelimit check */ + if (gw->timer->now - gw->timer->identify_last < 5) { + ++gw->session->concurrent; + VASSERT_S(gw->session->concurrent + < gw->session->start_limit.max_concurrency, + "Reach identify request threshold (%d every 5 seconds)", + gw->session->start_limit.max_concurrency); + } + else { + gw->session->concurrent = 0; + } + + jsonb_init(&b); + jsonb_object(&b, buf, sizeof(buf)); + { + jsonb_key(&b, buf, sizeof(buf), "op", 2); + jsonb_number(&b, buf, sizeof(buf), 2); + jsonb_key(&b, buf, sizeof(buf), "d", 1); + discord_identify_to_jsonb(&b, buf, sizeof(buf), identify); + jsonb_object_pop(&b, buf, sizeof(buf)); + } + + if (ws_send_text(gw->ws, &info, buf, b.pos)) { + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); + logconf_info( + &gw->conf, + ANSICOLOR( + "SEND", + ANSI_FG_BRIGHT_GREEN) " IDENTIFY (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + + /* get timestamp for this identify */ + gw->timer->identify_last = gw->timer->now; + } + else { + logconf_info( + &gw->conf, + ANSICOLOR("FAIL SEND", + ANSI_FG_RED) " IDENTIFY (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } +} + +void +discord_gateway_send_resume(struct discord_gateway *gw, + struct discord_resume *event) +{ + struct ws_info info = { 0 }; + char buf[1024]; + jsonb b; + + /* reset */ + gw->session->status ^= DISCORD_SESSION_RESUMABLE; + + jsonb_init(&b); + jsonb_object(&b, buf, sizeof(buf)); + { + jsonb_key(&b, buf, sizeof(buf), "op", 2); + jsonb_number(&b, buf, sizeof(buf), 6); + jsonb_key(&b, buf, sizeof(buf), "d", 1); + discord_resume_to_jsonb(&b, buf, sizeof(buf), event); + jsonb_object_pop(&b, buf, sizeof(buf)); + } + + if (ws_send_text(gw->ws, &info, buf, b.pos)) { + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); + logconf_info( + &gw->conf, + ANSICOLOR("SEND", + ANSI_FG_BRIGHT_GREEN) " RESUME (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } + else { + logconf_info(&gw->conf, + ANSICOLOR("FAIL SEND", + ANSI_FG_RED) " RESUME (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } +} + +static void +_discord_on_heartbeat_timeout(struct discord *client, + struct discord_timer *timer) +{ + (void)client; + struct discord_gateway *gw = timer->data; + + if (~timer->flags & DISCORD_TIMER_CANCELED) { + if (CCORD_OK == discord_gateway_perform(gw) + && ~gw->session->status & DISCORD_SESSION_SHUTDOWN + && gw->session->is_ready) + { + discord_gateway_send_heartbeat(gw, gw->payload.seq); + } + const u64unix_ms next_hb = + gw->timer->hbeat_last + (u64unix_ms)gw->timer->hbeat_interval; + + timer->interval = + (int64_t)(next_hb) - (int64_t)discord_timestamp(client); + if (timer->interval < 1) timer->interval = 1; + timer->repeat = 1; + } +} + +/* send heartbeat pulse to websockets server in order + * to maintain connection alive */ +void +discord_gateway_send_heartbeat(struct discord_gateway *gw, int seq) +{ + struct ws_info info = { 0 }; + char buf[64]; + jsonb b; + + jsonb_init(&b); + jsonb_object(&b, buf, sizeof(buf)); + { + jsonb_key(&b, buf, sizeof(buf), "op", 2); + jsonb_number(&b, buf, sizeof(buf), 1); + jsonb_key(&b, buf, sizeof(buf), "d", 1); + jsonb_number(&b, buf, sizeof(buf), seq); + jsonb_object_pop(&b, buf, sizeof(buf)); + } + + if (ws_send_text(gw->ws, &info, buf, b.pos)) { + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); + logconf_info( + &gw->conf, + ANSICOLOR( + "SEND", + ANSI_FG_BRIGHT_GREEN) " HEARTBEAT (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + + /* update heartbeat timestamp */ + gw->timer->hbeat_last = gw->timer->now; + if (!gw->timer->hbeat_timer) + gw->timer->hbeat_timer = discord_internal_timer( + CLIENT(gw, gw), _discord_on_heartbeat_timeout, gw, + gw->timer->hbeat_interval); + } + else { + logconf_info( + &gw->conf, + ANSICOLOR("FAIL SEND", + ANSI_FG_RED) " HEARTBEAT (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } +} + +void +discord_gateway_send_request_guild_members( + struct discord_gateway *gw, struct discord_request_guild_members *event) +{ + struct ws_info info = { 0 }; + char buf[1024]; + jsonb b; + + jsonb_init(&b); + jsonb_object(&b, buf, sizeof(buf)); + { + jsonb_key(&b, buf, sizeof(buf), "op", 2); + jsonb_number(&b, buf, sizeof(buf), 8); + jsonb_key(&b, buf, sizeof(buf), "d", 1); + discord_request_guild_members_to_jsonb(&b, buf, sizeof(buf), event); + jsonb_object_pop(&b, buf, sizeof(buf)); + } + + if (ws_send_text(gw->ws, &info, buf, b.pos)) { + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); + logconf_info( + &gw->conf, + ANSICOLOR("SEND", ANSI_FG_BRIGHT_GREEN) " REQUEST_GUILD_MEMBERS " + "(%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } + else { + logconf_info( + &gw->conf, + ANSICOLOR( + "FAIL SEND", + ANSI_FG_RED) " REQUEST_GUILD_MEMBERS (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } +} + +void +discord_gateway_send_update_voice_state( + struct discord_gateway *gw, struct discord_update_voice_state *event) +{ + struct ws_info info = { 0 }; + char buf[256]; + jsonb b; + + jsonb_init(&b); + jsonb_object(&b, buf, sizeof(buf)); + { + jsonb_key(&b, buf, sizeof(buf), "op", 2); + jsonb_number(&b, buf, sizeof(buf), 4); + jsonb_key(&b, buf, sizeof(buf), "d", 1); + discord_update_voice_state_to_jsonb(&b, buf, sizeof(buf), event); + jsonb_object_pop(&b, buf, sizeof(buf)); + } + + if (ws_send_text(gw->ws, &info, buf, b.pos)) { + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); + logconf_info( + &gw->conf, + ANSICOLOR( + "SEND", + ANSI_FG_BRIGHT_GREEN) " UPDATE_VOICE_STATE " + "(%d bytes): %s channels [@@@_%zu_@@@]", + b.pos, event->channel_id ? "join" : "leave", + info.loginfo.counter + 1); + } + else { + logconf_info( + &gw->conf, + ANSICOLOR( + "FAIL SEND", + ANSI_FG_RED) " UPDATE_VOICE_STATE (%d bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } +} + +void +discord_gateway_send_presence_update(struct discord_gateway *gw, + struct discord_presence_update *presence) +{ + struct ws_info info = { 0 }; + char buf[2048]; + jsonb b; + + if (!gw->session->is_ready) return; + + jsonb_init(&b); + jsonb_object(&b, buf, sizeof(buf)); + { + jsonb_key(&b, buf, sizeof(buf), "op", 2); + jsonb_number(&b, buf, sizeof(buf), 3); + jsonb_key(&b, buf, sizeof(buf), "d", 1); + discord_presence_update_to_jsonb(&b, buf, sizeof(buf), presence); + jsonb_object_pop(&b, buf, sizeof(buf)); + } + + if (ws_send_text(gw->ws, &info, buf, b.pos)) { + io_poller_curlm_enable_perform(CLIENT(gw, gw)->io_poller, gw->mhandle); + logconf_info( + &gw->conf, + ANSICOLOR("SEND", ANSI_FG_BRIGHT_GREEN) " PRESENCE UPDATE (%d " + "bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } + else { + logconf_error( + &gw->conf, + ANSICOLOR("FAIL SEND", ANSI_FG_RED) " PRESENCE UPDATE (%d " + "bytes) [@@@_%zu_@@@]", + b.pos, info.loginfo.counter + 1); + } +} diff --git a/src/discord-loop.c b/src/discord-loop.c index e67a27891..efbcb7409 100644 --- a/src/discord-loop.c +++ b/src/discord-loop.c @@ -49,28 +49,6 @@ discord_set_on_cycle(struct discord *client, discord_ev_idle callback) client->on_cycle = callback; } -static inline int64_t -discord_timer_get_next_trigger(struct discord_timers *const timers[], - size_t n, - int64_t now, - int64_t max_time) -{ - if (max_time == 0) return 0; - - for (unsigned i = 0; i < n; i++) { - int64_t trigger; - if (priority_queue_peek(timers[i]->q, &trigger, NULL)) { - if (trigger < 0) continue; - - if (trigger <= now) - max_time = 0; - else if (max_time > trigger - now) - max_time = trigger - now; - } - } - return max_time; -} - #define BREAK_ON_FAIL(code, function) \ if (CCORD_OK != (code = function)) break @@ -83,42 +61,40 @@ discord_timer_get_next_trigger(struct discord_timers *const timers[], CCORDcode discord_run(struct discord *client) { - int64_t next_run, now; - CCORDcode code; struct discord_timers *const timers[] = { &client->timers.internal, &client->timers.user }; + int64_t now; + CCORDcode code; while (1) { BREAK_ON_FAIL(code, discord_gateway_start(&client->gw)); - next_run = (int64_t)discord_timestamp_us(client); while (1) { - int64_t poll_time = 0; int poll_result, poll_errno = 0; + int64_t poll_time = 0; now = (int64_t)discord_timestamp_us(client); if (!client->on_idle) { - poll_time = discord_timer_get_next_trigger( - timers, sizeof timers / sizeof *timers, now, - now < next_run ? ((next_run - now)) : 0); + poll_time = discord_timers_get_next_trigger( + timers, sizeof timers / sizeof *timers, now, 60000000); } CALL_IO_POLLER_POLL(poll_errno, poll_result, client->io_poller, poll_time / 1000); now = (int64_t)discord_timestamp_us(client); - + if (0 == poll_result) { - if (ccord_has_sigint != 0) discord_shutdown(client); + if (client->on_idle) { client->on_idle(client); } else { - poll_time = discord_timer_get_next_trigger( + int64_t sleep_time = discord_timers_get_next_trigger( timers, sizeof timers / sizeof *timers, now, 1000); - if (poll_time > 0 && poll_time < 1000) - cog_sleep_us(poll_time); + if (sleep_time > 0 && sleep_time < 1000) + cog_sleep_us(sleep_time); } } @@ -131,28 +107,20 @@ discord_run(struct discord *client) CALL_IO_POLLER_POLL(poll_errno, poll_result, client->io_poller, 0); + if (ccord_has_sigint != 0) discord_shutdown(client); if (-1 == poll_result) { /* TODO: handle poll error here */ - // use poll_errno instead of errno + /* use poll_errno instead of errno */ (void)poll_errno; } BREAK_ON_FAIL(code, io_poller_perform(client->io_poller)); - if (next_run <= now) { - BREAK_ON_FAIL(code, discord_gateway_perform(&client->gw)); - BREAK_ON_FAIL(code, discord_adapter_perform(&client->adapter)); - - /* enforce a min 1 sec delay between runs */ - next_run = now + 1000000; - } + discord_requestor_dispatch_responses(&client->rest.requestor); } /* stop all pending requests in case of connection shutdown */ - if (true == discord_gateway_end(&client->gw)) { - discord_adapter_stop_buckets(&client->adapter); - break; - } + if (true == discord_gateway_end(&client->gw)) break; } return code; diff --git a/src/discord-messagecommands.c b/src/discord-messagecommands.c new file mode 100644 index 000000000..d3730c17e --- /dev/null +++ b/src/discord-messagecommands.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include /* isspace() */ + +#include "discord.h" +#include "discord-internal.h" + +#define CHASH_KEY_FIELD command +#define CHASH_VALUE_FIELD callback +#define CHASH_BUCKETS_FIELD entries +#include "chash.h" + +#define _key_hash(key, hash) \ + 5031; \ + do { \ + unsigned __CHASH_HINDEX; \ + for (__CHASH_HINDEX = 0; __CHASH_HINDEX < (key).size; \ + ++__CHASH_HINDEX) { \ + (hash) = (((hash) << 1) + (hash)) + (key).start[__CHASH_HINDEX]; \ + } \ + } while (0) + +/* compare jsmnf keys */ +#define _key_compare(cmp_a, cmp_b) \ + ((cmp_a).size == (cmp_b).size \ + && !strncmp((cmp_a).start, (cmp_b).start, (cmp_a).size)) + +/* chash heap-mode (auto-increase hashtable) */ +#define COMMANDS_TABLE_HEAP 1 +#define COMMANDS_TABLE_BUCKET struct _discord_message_commands_entry +#define COMMANDS_TABLE_FREE_KEY(_key) free((_key).start) +#define COMMANDS_TABLE_HASH(_key, _hash) _key_hash(_key, _hash) +#define COMMANDS_TABLE_FREE_VALUE(_value) +#define COMMANDS_TABLE_COMPARE(_cmp_a, _cmp_b) _key_compare(_cmp_a, _cmp_b) +#define COMMANDS_TABLE_INIT(entry, _key, _value) \ + chash_default_init(entry, _key, _value) + +struct _discord_message_commands_entry { + /** message command */ + struct ccord_szbuf command; + /** the callback assigned to the command */ + discord_ev_message callback; + /** the route state in the hashtable (see chash.h 'State enums') */ + int state; +}; + +void +discord_message_commands_init(struct discord_message_commands *cmds, + struct logconf *conf) +{ + __chash_init(cmds, COMMANDS_TABLE); + + logconf_branch(&cmds->conf, conf, "DISCORD_MESSAGE_COMMANDS"); + + cmds->fallback = NULL; + memset(&cmds->prefix, 0, sizeof(cmds->prefix)); +} + +void +discord_message_commands_cleanup(struct discord_message_commands *cmds) +{ + if (cmds->prefix.start) free(cmds->prefix.start); + __chash_free(cmds, COMMANDS_TABLE); +} + +discord_ev_message +discord_message_commands_find(struct discord_message_commands *cmds, + const char command[], + size_t length) +{ + struct ccord_szbuf key = { (char *)command, length }; + discord_ev_message callback = NULL; + int ret; + + ret = chash_contains(cmds, key, ret, COMMANDS_TABLE); + if (ret) { + callback = chash_lookup(cmds, key, callback, COMMANDS_TABLE); + } + + return callback; +} + +void +discord_message_commands_append(struct discord_message_commands *cmds, + const char command[], + size_t length, + discord_ev_message callback) +{ + /* define callback as a fallback callback if prefix is detected, but + * command isn't specified */ + if (cmds->prefix.size && !length) { + cmds->fallback = callback; + } + else { + struct ccord_szbuf key; + + key.size = cog_strndup(command, length, &key.start); + chash_assign(cmds, key, callback, COMMANDS_TABLE); + } +} + +void +discord_message_commands_set_prefix(struct discord_message_commands *cmds, + const char prefix[], + size_t length) +{ + if (cmds->prefix.start) free(cmds->prefix.start); + + cmds->prefix.size = cog_strndup(prefix, length, &cmds->prefix.start); +} + +static void +_discord_message_cleanup_v(void *p_message) +{ + discord_message_cleanup(p_message); + free(p_message); +} + +/** return true in case user command has been triggered */ +bool +discord_message_commands_try_perform(struct discord_message_commands *cmds, + struct discord_gateway_payload *payload) +{ + jsmnf_pair *f; + + if (!(f = jsmnf_find(payload->data, payload->json.start, "content", 7))) + return false; + + if (cmds->length + && !strncmp(cmds->prefix.start, payload->json.start + f->v.pos, + cmds->prefix.size)) + { + struct discord *client = CLIENT(cmds, commands); + struct discord_message *event_data = calloc(1, sizeof *event_data); + discord_ev_message callback = NULL; + struct ccord_szbuf command; + char *tmp; + + discord_message_from_jsmnf(payload->data, payload->json.start, + event_data); + + command.start = event_data->content + cmds->prefix.size; + command.size = strcspn(command.start, " \n\t\r"); + + tmp = event_data->content; + + /* match command to its callback */ + if (!(callback = discord_message_commands_find(cmds, command.start, + command.size))) + { + /* couldn't match command to callback, get fallback if available */ + if (!cmds->prefix.size || !cmds->fallback) { + _discord_message_cleanup_v(event_data); + return false; + } + command.size = 0; + callback = cmds->fallback; + } + + /* skip blank characters after command */ + if (event_data->content) { + event_data->content = command.start + command.size; + while (*event_data->content + && isspace((int)event_data->content[0])) + ++event_data->content; + } + + if (CCORD_UNAVAILABLE + == discord_refcounter_incr(&client->refcounter, event_data)) + { + discord_refcounter_add_internal(&client->refcounter, event_data, + _discord_message_cleanup_v, false); + } + callback(client, event_data); + event_data->content = tmp; /* retrieve original ptr */ + discord_refcounter_decr(&client->refcounter, event_data); + + return true; + } + + return false; +} diff --git a/src/discord-refcount.c b/src/discord-refcount.c new file mode 100644 index 000000000..c52203e0b --- /dev/null +++ b/src/discord-refcount.c @@ -0,0 +1,230 @@ +#include +#include +#include + +#include "discord.h" +#include "discord-internal.h" + +#define CHASH_BUCKETS_FIELD refs +#include "chash.h" + +/* chash heap-mode (auto-increase hashtable) */ +#define REFCOUNTER_TABLE_HEAP 1 +#define REFCOUNTER_TABLE_BUCKET struct _discord_ref +#define REFCOUNTER_TABLE_FREE_KEY(_key) +#define REFCOUNTER_TABLE_HASH(_key, _hash) ((intptr_t)(_key)) +#define REFCOUNTER_TABLE_FREE_VALUE(_value) \ + _discord_refvalue_cleanup(rc, &_value) +#define REFCOUNTER_TABLE_COMPARE(_cmp_a, _cmp_b) (_cmp_a == _cmp_b) +#define REFCOUNTER_TABLE_INIT(ref, _key, _value) \ + memset(&ref, 0, sizeof(ref)); \ + chash_default_init(ref, _key, _value) + +struct _discord_refvalue { + /** user arbitrary data to be retrieved at `done` or `fail` callbacks */ + void *data; + /** whether cleanup expects a client parameter */ + bool expects_client; + /** + * cleanup for when `data` is no longer needed + * @note this only has to be assigned once, it is automatically called once + * `data` is no longer referenced by any callback */ + union { + void (*client)(struct discord *client, void *data); + void (*internal)(void *data); + } cleanup; + /** + * `data` references count + * @note if `-1` then `data` has been claimed with + * discord_refcounter_claim() and will be cleaned up once + * discord_refcount_unclaim() is called + */ + int visits; + /** whether `data` cleanup should also be followed by a free() */ + bool should_free; +}; + +struct _discord_ref { + /** key is the user data's address */ + intptr_t key; + /** holds the user data and information for automatic cleanup */ + struct _discord_refvalue value; + /** the route state in the hashtable (see chash.h 'State enums') */ + int state; +}; + +static void +_discord_refvalue_cleanup(struct discord_refcounter *rc, + struct _discord_refvalue *value) +{ + if (value->cleanup.client) { + if (value->expects_client) + value->cleanup.client(CLIENT(rc, refcounter), value->data); + else + value->cleanup.internal(value->data); + } + if (value->should_free) free(value->data); +} + +static struct _discord_refvalue * +_discord_refvalue_find(struct discord_refcounter *rc, const void *data) +{ + struct _discord_ref *ref = + chash_lookup_bucket(rc, (intptr_t)data, ref, REFCOUNTER_TABLE); + return &ref->value; +} + +static void +_discord_refvalue_init(struct discord_refcounter *rc, + void *data, + struct _discord_refvalue *init_fields) +{ + init_fields->data = data; + init_fields->visits = 1; + chash_assign(rc, (intptr_t)data, *init_fields, REFCOUNTER_TABLE); +} + +static void +_discord_refvalue_delete(struct discord_refcounter *rc, void *data) +{ + chash_delete(rc, (intptr_t)data, REFCOUNTER_TABLE); +} + +void +discord_refcounter_init(struct discord_refcounter *rc, struct logconf *conf) +{ + logconf_branch(&rc->conf, conf, "DISCORD_REFCOUNT"); + + __chash_init(rc, REFCOUNTER_TABLE); + + rc->g_lock = malloc(sizeof *rc->g_lock); + ASSERT_S(!pthread_mutex_init(rc->g_lock, NULL), + "Couldn't initialize refcounter mutex"); +} + +void +discord_refcounter_cleanup(struct discord_refcounter *rc) +{ + __chash_free(rc, REFCOUNTER_TABLE); + pthread_mutex_destroy(rc->g_lock); + free(rc->g_lock); +} + +static bool +_discord_refcounter_contains(struct discord_refcounter *rc, const void *data) +{ + bool ret = chash_contains(rc, (intptr_t)data, ret, REFCOUNTER_TABLE); + return ret; +} + +bool +discord_refcounter_claim(struct discord_refcounter *rc, const void *data) +{ + bool ret = false; + + pthread_mutex_lock(rc->g_lock); + if (_discord_refcounter_contains(rc, data)) { + struct _discord_refvalue *value = _discord_refvalue_find(rc, data); + + value->visits = -1; + ret = true; + } + pthread_mutex_unlock(rc->g_lock); + + return ret; +} + +bool +discord_refcounter_unclaim(struct discord_refcounter *rc, void *data) +{ + bool ret = false; + + pthread_mutex_lock(rc->g_lock); + if (_discord_refcounter_contains(rc, data)) { + struct _discord_refvalue *value = _discord_refvalue_find(rc, data); + + if (value->visits == -1) { + _discord_refvalue_delete(rc, data); + ret = true; + } + } + pthread_mutex_unlock(rc->g_lock); + + return ret; +} + +void +discord_refcounter_add_internal(struct discord_refcounter *rc, + void *data, + void (*cleanup)(void *data), + bool should_free) +{ + pthread_mutex_lock(rc->g_lock); + _discord_refvalue_init(rc, data, + &(struct _discord_refvalue){ + .expects_client = false, + .cleanup.internal = cleanup, + .should_free = should_free, + }); + pthread_mutex_unlock(rc->g_lock); +} + +void +discord_refcounter_add_client(struct discord_refcounter *rc, + void *data, + void (*cleanup)(struct discord *client, + void *data), + bool should_free) +{ + pthread_mutex_lock(rc->g_lock); + _discord_refvalue_init(rc, data, + &(struct _discord_refvalue){ + .expects_client = true, + .cleanup.client = cleanup, + .should_free = should_free, + }); + pthread_mutex_unlock(rc->g_lock); +} + +CCORDcode +discord_refcounter_incr(struct discord_refcounter *rc, void *data) +{ + CCORDcode code = CCORD_OWNERSHIP; + + pthread_mutex_lock(rc->g_lock); + if (!_discord_refcounter_contains(rc, data)) { + code = CCORD_UNAVAILABLE; + } + else { + struct _discord_refvalue *value = _discord_refvalue_find(rc, data); + + if (value->visits != -1) { + ++value->visits; + code = CCORD_OK; + } + } + pthread_mutex_unlock(rc->g_lock); + return code; +} + +CCORDcode +discord_refcounter_decr(struct discord_refcounter *rc, void *data) +{ + CCORDcode code = CCORD_OWNERSHIP; + + pthread_mutex_lock(rc->g_lock); + if (!_discord_refcounter_contains(rc, data)) { + code = CCORD_UNAVAILABLE; + } + else { + struct _discord_refvalue *value = _discord_refvalue_find(rc, data); + if (value->visits != -1) { + if (0 == --value->visits) { + _discord_refvalue_delete(rc, data); + } + code = CCORD_OK; + } + } + pthread_mutex_unlock(rc->g_lock); + return code; +} diff --git a/src/discord-rest.c b/src/discord-rest.c new file mode 100644 index 000000000..5ab05c4c7 --- /dev/null +++ b/src/discord-rest.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include + +#include "carray.h" +#include "threadpool.h" + +#include "discord.h" +#include "discord-internal.h" + +static CCORDcode +_discord_rest_perform(struct discord_rest *rest) +{ + CCORDcode code; + + discord_requestor_info_read(&rest->requestor); + code = discord_requestor_start_pending(&rest->requestor); + io_poller_wakeup(CLIENT(rest, rest)->io_poller); + + return code; +} + +static void +_discord_rest_manager(void *p_rest) +{ + struct discord *client = CLIENT(p_rest, rest); + struct discord_rest *rest = p_rest; + + struct discord_timers *const timers[] = { &rest->timers }; + int64_t now, trigger; + int poll_result; + + _discord_rest_perform(rest); + + now = (int64_t)discord_timestamp_us(client); + + trigger = discord_timers_get_next_trigger(timers, 1, now, 60000000); + poll_result = io_poller_poll(rest->io_poller, (int)(trigger / 1000)); + + now = (int64_t)discord_timestamp_us(client); + if (0 == poll_result) { + trigger = discord_timers_get_next_trigger(timers, 1, now, 1000); + if (trigger > 0 && trigger < 1000) cog_sleep_us((long)trigger); + } + discord_timers_run(client, &rest->timers); + io_poller_perform(rest->io_poller); + + threadpool_add(rest->tpool, _discord_rest_manager, rest, 0); +} + +static int +_discord_on_rest_perform(struct io_poller *io, CURLM *mhandle, void *p_rest) +{ + (void)io; + (void)mhandle; + return _discord_rest_perform(p_rest); +} + +void +discord_rest_init(struct discord_rest *rest, + struct logconf *conf, + const char token[]) +{ + if (!token || !*token) + logconf_branch(&rest->conf, conf, "DISCORD_WEBHOOK"); + else + logconf_branch(&rest->conf, conf, "DISCORD_HTTP"); + + discord_timers_init(&rest->timers); + + rest->io_poller = io_poller_create(); + discord_requestor_init(&rest->requestor, &rest->conf, token); + io_poller_curlm_add(rest->io_poller, rest->requestor.mhandle, + &_discord_on_rest_perform, rest); + + rest->tpool = threadpool_create(1, 1024, 0); + ASSERT_S(!threadpool_add(rest->tpool, &_discord_rest_manager, rest, 0), + "Couldn't initialize REST managagement thread"); +} + +void +discord_rest_cleanup(struct discord_rest *rest) +{ + /* cleanup REST managing thread */ + io_poller_wakeup(rest->io_poller); + threadpool_destroy(rest->tpool, threadpool_graceful); + /* cleanup discovered buckets */ + discord_timers_cleanup(CLIENT(rest, rest), &rest->timers); + /* cleanup requests */ + discord_requestor_cleanup(&rest->requestor); + /* cleanup REST poller */ + io_poller_destroy(rest->io_poller); +} + +/* template function for performing requests */ +CCORDcode +discord_rest_run(struct discord_rest *rest, + struct discord_attributes *attr, + struct ccord_szbuf *body, + enum http_method method, + char endpoint_fmt[], + ...) +{ + char endpoint[DISCORD_ENDPT_LEN], key[DISCORD_ROUTE_LEN]; + va_list args; + int len; + + /* have it point somewhere */ + if (!attr) { + static struct discord_attributes blank = { 0 }; + attr = ␣ + } + if (!body) { + static struct ccord_szbuf blank = { 0 }; + body = ␣ + } + + /* build the endpoint string */ + va_start(args, endpoint_fmt); + len = vsnprintf(endpoint, sizeof(endpoint), endpoint_fmt, args); + ASSERT_NOT_OOB(len, sizeof(endpoint)); + va_end(args); + + /* build the bucket's key */ + va_start(args, endpoint_fmt); + discord_ratelimiter_build_key(method, key, endpoint_fmt, args); + va_end(args); + + return discord_request_begin(&rest->requestor, attr, body, method, + endpoint, key); +} diff --git a/src/discord-rest_ratelimit.c b/src/discord-rest_ratelimit.c new file mode 100644 index 000000000..be487b241 --- /dev/null +++ b/src/discord-rest_ratelimit.c @@ -0,0 +1,447 @@ +#include +#include +#include + +#include "discord.h" +#include "discord-internal.h" + +#include "cog-utils.h" +#include "clock.h" + +#define CHASH_VALUE_FIELD bucket +#define CHASH_BUCKETS_FIELD routes +#include "chash.h" + +/* chash heap-mode (auto-increase hashtable) */ +#define RATELIMITER_TABLE_HEAP 1 +#define RATELIMITER_TABLE_BUCKET struct _discord_route +#define RATELIMITER_TABLE_FREE_KEY(_key) +#define RATELIMITER_TABLE_HASH(_key, _hash) chash_string_hash(_key, _hash) +#define RATELIMITER_TABLE_FREE_VALUE(_value) free(_value) +#define RATELIMITER_TABLE_COMPARE(_cmp_a, _cmp_b) \ + chash_string_compare(_cmp_a, _cmp_b) +#define RATELIMITER_TABLE_INIT(route, _key, _value) \ + memcpy(route.key, _key, sizeof(route.key)); \ + route.bucket = _value + +struct _discord_route { + /** key formed from a request's route */ + char key[DISCORD_ROUTE_LEN]; + /** this route's bucket match */ + struct discord_bucket *bucket; + /** the route state in the hashtable (see chash.h 'State enums') */ + int state; +}; + +#define KEY_PUSH(key, len, ...) \ + do { \ + *len += snprintf(key + *len, DISCORD_ROUTE_LEN - (size_t)*len, \ + ":" __VA_ARGS__); \ + ASSERT_NOT_OOB(*len, DISCORD_ROUTE_LEN); \ + } while (0) + +/* determine which ratelimit group a request belongs to by generating its key. + * see: https://discord.com/developers/docs/topics/rate-limits */ +void +discord_ratelimiter_build_key(enum http_method method, + char key[DISCORD_ROUTE_LEN], + const char endpoint_fmt[], + va_list args) +{ + /* generated key length */ + int keylen = 0; + /* split endpoint sections */ + const char *curr = endpoint_fmt, *prev = ""; + size_t currlen = 0; + + KEY_PUSH(key, &keylen, "%d", method); + do { + u64snowflake id_arg = 0ULL; + + curr += 1 + currlen; + currlen = strcspn(curr, "/"); + + /* reactions and sub-routes share the same bucket */ + if (0 == strncmp(prev, "reactions", 9)) break; + + /* consume variadic arguments */ + for (size_t i = 0; i < currlen; ++i) { + if (curr[i] != '%') continue; + + const char *type = &curr[i + 1]; + switch (*type) { + default: + VASSERT_S(0 == strncmp(type, PRIu64, sizeof(PRIu64) - 1), + "Internal error: Missing check for '%%%s'", type); + + id_arg = va_arg(args, u64snowflake); + break; + case 's': + (void)va_arg(args, char *); + break; + case 'd': + (void)va_arg(args, int); + break; + } + } + + /* push section to key's string, in case of a major parameter the + * literal ID will be pushed */ + if (0 == strncmp(curr, "%" PRIu64, currlen) + && (0 == strncmp(prev, "channels", 8) + || 0 == strncmp(prev, "guilds", 6))) + KEY_PUSH(key, &keylen, "%" PRIu64, id_arg); + else + KEY_PUSH(key, &keylen, "%.*s", (int)currlen, curr); + + prev = curr; + + } while (curr[currlen] != '\0'); +} + +#undef KEY_PUSH + +/* initialize bucket and assign it to ratelimiter hashtable */ +static struct discord_bucket * +_discord_bucket_init(struct discord_ratelimiter *rl, + const char key[], + const struct ua_szbuf_readonly *hash, + const long limit) +{ + struct discord_bucket *b = calloc(1, sizeof *b); + int len = snprintf(b->hash, sizeof(b->hash), "%.*s", (int)hash->size, + hash->start); + ASSERT_NOT_OOB(len, sizeof(b->hash)); + + b->remaining = 1; + b->limit = limit; + + QUEUE_INIT(&b->queues.next); + QUEUE_INIT(&b->entry); + + chash_assign(rl, key, b, RATELIMITER_TABLE); + + return b; +} + +void +discord_ratelimiter_init(struct discord_ratelimiter *rl, struct logconf *conf) +{ + struct ua_szbuf_readonly keynull = { "null", 4 }, keymiss = { "miss", 4 }; + + __chash_init(rl, RATELIMITER_TABLE); + + logconf_branch(&rl->conf, conf, "DISCORD_RATELIMIT"); + + /* global ratelimiting */ + rl->global_wait_ms = calloc(1, sizeof *rl->global_wait_ms); + + /* initialize 'singleton' buckets */ + rl->null = _discord_bucket_init(rl, "null", &keynull, 1L); + rl->miss = _discord_bucket_init(rl, "miss", &keymiss, LONG_MAX); + + /* initialize bucket queues */ + QUEUE_INIT(&rl->queues.pending); +} + +/* cancel all pending and busy requests from a bucket */ +static void +_discord_bucket_cancel_all(struct discord_ratelimiter *rl, + struct discord_bucket *b) +{ + struct discord_requestor *rqtor = + CONTAINEROF(rl, struct discord_requestor, ratelimiter); + + /* cancel busy transfer */ + if (b->busy_req) discord_request_cancel(rqtor, b->busy_req); + + /* move pending tranfers to recycling */ + pthread_mutex_lock(&rqtor->qlocks->recycling); + QUEUE_ADD(&rqtor->queues->recycling, &b->queues.next); + pthread_mutex_unlock(&rqtor->qlocks->recycling); + QUEUE_INIT(&b->queues.next); +} + +void +discord_ratelimiter_cleanup(struct discord_ratelimiter *rl) +{ + /* iterate and cleanup known buckets */ + for (int i = 0; i < rl->capacity; ++i) { + struct _discord_route *r = rl->routes + i; + if (CHASH_FILLED == r->state) + _discord_bucket_cancel_all(rl, r->bucket); + } + free(rl->global_wait_ms); + __chash_free(rl, RATELIMITER_TABLE); +} + +static struct discord_bucket * +_discord_bucket_find(struct discord_ratelimiter *rl, const char key[]) +{ + struct discord_bucket *b = NULL; + int ret = chash_contains(rl, key, ret, RATELIMITER_TABLE); + + if (ret) { + b = chash_lookup(rl, key, b, RATELIMITER_TABLE); + } + return b; +} + +/* return ratelimit timeout timestamp for this bucket */ +u64unix_ms +discord_bucket_get_timeout(struct discord_ratelimiter *rl, + struct discord_bucket *b) +{ + u64unix_ms reset = (b->remaining < 1) ? b->reset_tstamp : 0ULL; + return (*rl->global_wait_ms > reset) ? *rl->global_wait_ms : reset; +} + +static void +_discord_bucket_wake_cb(struct discord *client, struct discord_timer *timer) +{ + (void)client; + struct discord_bucket *b = timer->data; + + b->busy_req = NULL; + b->remaining = 1; +} + +static void +_discord_bucket_try_timeout(struct discord_ratelimiter *rl, + struct discord_bucket *b) +{ + struct discord *client = CLIENT(rl, rest.requestor.ratelimiter); + int64_t delay_ms = (int64_t)(b->reset_tstamp - cog_timestamp_ms()); + + if (delay_ms < 0) delay_ms = 0; + b->busy_req = DISCORD_BUCKET_TIMEOUT; + + _discord_timer_ctl(client, &client->rest.timers, + &(struct discord_timer){ + .cb = &_discord_bucket_wake_cb, + .data = b, + .delay = delay_ms, + .flags = DISCORD_TIMER_DELETE_AUTO, + }); + + logconf_info(&rl->conf, "[%.4s] RATELIMITING (wait %" PRId64 " ms)", + b->hash, delay_ms); +} + +/* attempt to find a bucket associated key */ +struct discord_bucket * +discord_bucket_get(struct discord_ratelimiter *rl, const char key[]) +{ + struct discord_bucket *b; + + if (NULL != (b = _discord_bucket_find(rl, key))) { + logconf_trace(&rl->conf, "[%.4s] Found a bucket match for '%s'!", + b->hash, key); + } + else { + b = rl->null; + logconf_trace(&rl->conf, "[null] Couldn't match known buckets to '%s'", + key); + } + return b; +} + +/* check if successive requests made from a `null` singleton bucket can be + * matched to another bucket */ +static void +_discord_ratelimiter_null_filter(struct discord_ratelimiter *rl, + struct discord_bucket *b, + const char key[]) +{ + QUEUE(struct discord_request) queue, *qelem; + struct discord_request *req; + + QUEUE_MOVE(&rl->null->queues.next, &queue); + while (!QUEUE_EMPTY(&queue)) { + qelem = QUEUE_HEAD(&queue); + req = QUEUE_DATA(qelem, struct discord_request, entry); + if (strcmp(req->key, key) != 0) b = rl->null; + discord_bucket_insert(rl, b, req, false); + } +} + +static struct discord_bucket * +_discord_ratelimiter_get_match(struct discord_ratelimiter *rl, + const char key[], + struct ua_info *info) +{ + struct discord_bucket *b; + + if (NULL == (b = _discord_bucket_find(rl, key))) { + struct ua_szbuf_readonly hash = + ua_info_get_header(info, "x-ratelimit-bucket"); + + if (!hash.size) { /* bucket is not part of a ratelimiting group */ + b = rl->miss; + } + else { /* create bucket if it doesn't exist yet */ + struct ua_szbuf_readonly limit = + ua_info_get_header(info, "x-ratelimit-limit"); + long _limit = + limit.size ? strtol(limit.start, NULL, 10) : LONG_MAX; + + b = _discord_bucket_init(rl, key, &hash, _limit); + } + } + + logconf_debug(&rl->conf, "[%.4s] Match '%s' to bucket", b->hash, key); + + _discord_ratelimiter_null_filter(rl, b, key); + + return b; +} + +/* attempt to fill bucket's values with response header fields */ +static void +_discord_bucket_populate(struct discord_ratelimiter *rl, + struct discord_bucket *b, + struct ua_info *info) +{ + struct ua_szbuf_readonly remaining = ua_info_get_header( + info, "x-ratelimit-remaining"), + reset = + ua_info_get_header(info, "x-ratelimit-reset"), + reset_after = ua_info_get_header( + info, "x-ratelimit-reset-after"); + u64unix_ms now = cog_timestamp_ms(); + + b->remaining = remaining.size ? strtol(remaining.start, NULL, 10) : 1L; + + /* use X-Ratelimit-Reset-After if available, X-Ratelimit-Reset otherwise */ + if (reset_after.size) { + struct ua_szbuf_readonly global = + ua_info_get_header(info, "x-ratelimit-global"); + u64unix_ms reset_tstamp = + now + (u64unix_ms)(1000 * strtod(reset_after.start, NULL)); + + if (global.size) /* lock all buckets */ + *rl->global_wait_ms = reset_tstamp; + else /* lock single bucket, timeout at discord_rest_run() */ + b->reset_tstamp = reset_tstamp; + } + else if (reset.size) { + struct ua_szbuf_readonly date = ua_info_get_header(info, "date"); + /* get approximate elapsed time since request */ + struct PsnipClockTimespec ts; + /* the Discord time in milliseconds */ + u64unix_ms server; + /* the Discord time + request's elapsed time */ + u64unix_ms offset; + + server = (u64unix_ms)(1000 * curl_getdate(date.start, NULL)); + psnip_clock_wall_get_time(&ts); + offset = server + ts.nanoseconds / 1000000; + + /* reset timestamp = + * (system time) + * + (diff between Discord's reset timestamp and offset) + */ + b->reset_tstamp = + now + ((u64unix_ms)(1000 * strtod(reset.start, NULL)) - offset); + } + + logconf_debug(&rl->conf, "[%.4s] Remaining = %ld | Reset = %" PRIu64, + b->hash, b->remaining, b->reset_tstamp); +} + +/* attempt to create and/or update bucket's values */ +void +discord_ratelimiter_build(struct discord_ratelimiter *rl, + struct discord_bucket *b, + const char key[], + struct ua_info *info) +{ + /* try to match to existing, or create new bucket */ + if (b == rl->null) b = _discord_ratelimiter_get_match(rl, key, info); + /* populate bucket with response header values */ + _discord_bucket_populate(rl, b, info); +} + +void +discord_bucket_insert(struct discord_ratelimiter *rl, + struct discord_bucket *b, + struct discord_request *req, + bool high_priority) +{ + QUEUE_REMOVE(&req->entry); + if (high_priority) + QUEUE_INSERT_HEAD(&b->queues.next, &req->entry); + else + QUEUE_INSERT_TAIL(&b->queues.next, &req->entry); + + /* add bucket to ratelimiter pending buckets queue (if not already in) */ + if (QUEUE_EMPTY(&b->entry)) + QUEUE_INSERT_HEAD(&rl->queues.pending, &b->entry); + + req->b = b; +} + +static void +_discord_bucket_request_select(struct discord_bucket *b) +{ + QUEUE(struct discord_request) *qelem = QUEUE_HEAD(&b->queues.next); + QUEUE_REMOVE(qelem); + QUEUE_INIT(qelem); + + b->busy_req = QUEUE_DATA(qelem, struct discord_request, entry); +} + +void +discord_bucket_request_selector(struct discord_ratelimiter *rl, + void *data, + void (*iter)(void *data, + struct discord_request *req)) +{ + QUEUE(struct discord_bucket) queue, *qelem; + struct discord_bucket *b; + + /* loop through each pending buckets and enqueue next requests */ + QUEUE_MOVE(&rl->queues.pending, &queue); + while (!QUEUE_EMPTY(&queue)) { + qelem = QUEUE_HEAD(&queue); + b = QUEUE_DATA(qelem, struct discord_bucket, entry); + + QUEUE_REMOVE(qelem); + if (b->busy_req) { + QUEUE_INSERT_TAIL(&rl->queues.pending, qelem); + continue; + } + if (!b->remaining) { + _discord_bucket_try_timeout(rl, b); + QUEUE_INSERT_TAIL(&rl->queues.pending, qelem); + continue; + } + + _discord_bucket_request_select(b); + (*iter)(data, b->busy_req); + + /* if bucket has no pending requests then remove it from + * ratelimiter pending buckets queue */ + if (QUEUE_EMPTY(&b->queues.next)) + QUEUE_INIT(qelem); + else /* otherwise move it back to pending buckets queue */ + QUEUE_INSERT_TAIL(&rl->queues.pending, qelem); + } +} + +void +discord_bucket_request_unselect(struct discord_ratelimiter *rl, + struct discord_bucket *b, + struct discord_request *req) +{ + (void)rl; + ASSERT_S(req == b->busy_req, + "Attempt to unlock a bucket with a non-busy request"); + + if (QUEUE_EMPTY(&b->queues.next)) { + QUEUE_REMOVE(&b->entry); + QUEUE_INIT(&b->entry); + } + b->busy_req = NULL; + req->b = NULL; +} diff --git a/src/discord-rest_request.c b/src/discord-rest_request.c new file mode 100644 index 000000000..379cdce3b --- /dev/null +++ b/src/discord-rest_request.c @@ -0,0 +1,622 @@ +#include +#include +#include + +#include "discord.h" +#include "discord-internal.h" + +static struct discord_request * +_discord_request_init(void) +{ + return calloc(1, sizeof(struct discord_request)); +} + +static void +_discord_request_cleanup(struct discord_request *req) +{ + discord_attachments_cleanup(&req->attachments); + if (req->body.start) free(req->body.start); + free(req); +} + +static void +_discord_on_curl_setopt(struct ua_conn *conn, void *p_token) +{ + char auth[128]; + int len = snprintf(auth, sizeof(auth), "Bot %s", (char *)p_token); + ASSERT_NOT_OOB(len, sizeof(auth)); + + ua_conn_add_header(conn, "Authorization", auth); +#ifdef CCORD_DEBUG_HTTP + curl_easy_setopt(ua_conn_get_easy_handle(conn), CURLOPT_VERBOSE, 1L); +#endif +} + +void +discord_requestor_init(struct discord_requestor *rqtor, + struct logconf *conf, + const char token[]) +{ + logconf_branch(&rqtor->conf, conf, "DISCORD_REQUEST"); + + rqtor->ua = ua_init(&(struct ua_attr){ .conf = conf }); + ua_set_url(rqtor->ua, DISCORD_API_BASE_URL); + ua_set_opt(rqtor->ua, (char *)token, &_discord_on_curl_setopt); + + /* queues are malloc'd to guarantee a client cloned by + * discord_clone() will share the same queue with the original */ + rqtor->queues = malloc(sizeof *rqtor->queues); + QUEUE_INIT(&rqtor->queues->recycling); + QUEUE_INIT(&rqtor->queues->pending); + QUEUE_INIT(&rqtor->queues->finished); + + rqtor->qlocks = malloc(sizeof *rqtor->qlocks); + ASSERT_S(!pthread_mutex_init(&rqtor->qlocks->recycling, NULL), + "Couldn't initialize requestor's recycling queue mutex"); + ASSERT_S(!pthread_mutex_init(&rqtor->qlocks->pending, NULL), + "Couldn't initialize requestor's pending queue mutex"); + ASSERT_S(!pthread_mutex_init(&rqtor->qlocks->finished, NULL), + "Couldn't initialize requestor's finished queue mutex"); + + rqtor->mhandle = curl_multi_init(); + rqtor->retry_limit = 3; /* FIXME: shouldn't be a hard limit */ + + discord_ratelimiter_init(&rqtor->ratelimiter, &rqtor->conf); +} + +void +discord_requestor_cleanup(struct discord_requestor *rqtor) +{ + struct discord_rest *rest = + CONTAINEROF(rqtor, struct discord_rest, requestor); + QUEUE *const req_queues[] = { &rqtor->queues->recycling, + &rqtor->queues->pending, + &rqtor->queues->finished }; + + /* cleanup ratelimiting handle */ + discord_ratelimiter_cleanup(&rqtor->ratelimiter); + + /* cleanup queues */ + for (size_t i = 0; i < sizeof(req_queues) / sizeof *req_queues; ++i) { + QUEUE(struct discord_request) queue, *qelem; + struct discord_request *req; + + QUEUE_MOVE(req_queues[i], &queue); + while (!QUEUE_EMPTY(&queue)) { + qelem = QUEUE_HEAD(&queue); + QUEUE_REMOVE(qelem); + + req = QUEUE_DATA(qelem, struct discord_request, entry); + _discord_request_cleanup(req); + } + } + free(rqtor->queues); + + /* cleanup queue locks */ + pthread_mutex_destroy(&rqtor->qlocks->recycling); + pthread_mutex_destroy(&rqtor->qlocks->pending); + pthread_mutex_destroy(&rqtor->qlocks->finished); + free(rqtor->qlocks); + + /* cleanup curl's multi handle */ + io_poller_curlm_del(rest->io_poller, rqtor->mhandle); + curl_multi_cleanup(rqtor->mhandle); + /* cleanup User-Agent handle */ + ua_cleanup(rqtor->ua); +} + +static void +_discord_request_to_multipart(curl_mime *mime, void *p_req) +{ + struct discord_request *req = p_req; + curl_mimepart *part; + char name[64]; + + /* json part */ + if (req->body.start && req->body.size) { + part = curl_mime_addpart(mime); + curl_mime_data(part, req->body.start, req->body.size); + curl_mime_type(part, "application/json"); + curl_mime_name(part, "payload_json"); + } + + /* attachment part */ + for (int i = 0; i < req->attachments.size; ++i) { + int len = snprintf(name, sizeof(name), "files[%d]", i); + ASSERT_NOT_OOB(len, sizeof(name)); + + if (req->attachments.array[i].content) { + part = curl_mime_addpart(mime); + curl_mime_data(part, req->attachments.array[i].content, + req->attachments.array[i].size + ? req->attachments.array[i].size + : CURL_ZERO_TERMINATED); + curl_mime_filename(part, !req->attachments.array[i].filename + ? "a.out" + : req->attachments.array[i].filename); + curl_mime_type(part, !req->attachments.array[i].content_type + ? "application/octet-stream" + : req->attachments.array[i].content_type); + curl_mime_name(part, name); + } + else if (req->attachments.array[i].filename) { + CURLcode ecode; + + /* fetch local file by the filename */ + part = curl_mime_addpart(mime); + ecode = + curl_mime_filedata(part, req->attachments.array[i].filename); + if (ecode != CURLE_OK) { + char errbuf[256]; + snprintf(errbuf, sizeof(errbuf), "%s (file: %s)", + curl_easy_strerror(ecode), + req->attachments.array[i].filename); + perror(errbuf); + } + curl_mime_type(part, !req->attachments.array[i].content_type + ? "application/octet-stream" + : req->attachments.array[i].content_type); + curl_mime_name(part, name); + } + } +} + +static bool +_discord_request_info_extract(struct discord_requestor *rqtor, + struct discord_request *req, + struct ua_info *info) +{ + ua_info_extract(req->conn, info); + + if (info->code != CCORD_HTTP_CODE) { /* CCORD_OK or internal error */ + req->code = info->code; + return false; + } + + switch (info->httpcode) { + case HTTP_FORBIDDEN: + case HTTP_NOT_FOUND: + case HTTP_BAD_REQUEST: + req->code = CCORD_DISCORD_JSON_CODE; + return false; + case HTTP_UNAUTHORIZED: + logconf_fatal( + &rqtor->conf, + "UNAUTHORIZED: Please provide a valid authentication token"); + req->code = CCORD_DISCORD_BAD_AUTH; + return false; + case HTTP_METHOD_NOT_ALLOWED: + logconf_fatal(&rqtor->conf, + "METHOD_NOT_ALLOWED: The server couldn't recognize the " + "received HTTP method"); + + req->code = info->code; + return false; + case HTTP_TOO_MANY_REQUESTS: { + struct ua_szbuf_readonly body = ua_info_get_body(info); + struct jsmnftok message = { 0 }; + double retry_after = 1.0; + bool is_global = false; + jsmn_parser parser; + jsmntok_t tokens[16]; + + jsmn_init(&parser); + if (0 < jsmn_parse(&parser, body.start, body.size, tokens, + sizeof(tokens) / sizeof *tokens)) + { + jsmnf_loader loader; + jsmnf_pair pairs[16]; + + jsmnf_init(&loader); + if (0 < jsmnf_load(&loader, body.start, tokens, parser.toknext, + pairs, sizeof(pairs) / sizeof *pairs)) + { + jsmnf_pair *f; + + if ((f = jsmnf_find(pairs, body.start, "global", 6))) + is_global = ('t' == body.start[f->v.pos]); + if ((f = jsmnf_find(pairs, body.start, "message", 7))) + message = f->v; + if ((f = jsmnf_find(pairs, body.start, "retry_after", 11))) + retry_after = strtod(body.start + f->v.pos, NULL); + } + } + + req->wait_ms = (int64_t)(1000 * retry_after); + if (req->wait_ms < 0) req->wait_ms = 0; + + logconf_warn(&rqtor->conf, + "429 %sRATELIMITING (wait: %" PRId64 " ms) : %.*s", + is_global ? "GLOBAL " : "", req->wait_ms, message.len, + body.start + message.pos); + + req->code = info->code; + return true; + } + default: + req->code = info->code; + return (info->httpcode >= 500); /* retry if Server Error */ + } +} + +void +discord_request_cancel(struct discord_requestor *rqtor, + struct discord_request *req) +{ + struct discord_refcounter *rc = &CLIENT(rqtor, rest.requestor)->refcounter; + + if (req->conn) { + ua_conn_stop(req->conn); + } + if (req->dispatch.keep) { + discord_refcounter_decr(rc, (void *)req->dispatch.keep); + } + if (req->dispatch.data) { + discord_refcounter_decr(rc, req->dispatch.data); + } + + req->body.size = 0; + req->method = 0; + *req->endpoint = '\0'; + *req->key = '\0'; + req->conn = NULL; + req->retry_attempt = 0; + discord_attachments_cleanup(&req->attachments); + memset(req, 0, sizeof(struct discord_attributes)); + + QUEUE_REMOVE(&req->entry); + pthread_mutex_lock(&rqtor->qlocks->recycling); + QUEUE_INSERT_TAIL(&rqtor->queues->recycling, &req->entry); + pthread_mutex_unlock(&rqtor->qlocks->recycling); +} + +static CCORDcode +_discord_request_dispatch_response(struct discord_requestor *rqtor, + struct discord_request *req) +{ + struct discord *client = CLIENT(rqtor, rest.requestor); + struct discord_response resp = { .data = req->dispatch.data, + .keep = req->dispatch.keep, + .code = req->code }; + + if (req->code != CCORD_OK) { + if (req->dispatch.fail) req->dispatch.fail(client, &resp); + } + else if (req->dispatch.done.typed) { + if (!req->dispatch.has_type) { + req->dispatch.done.typeless(client, &resp); + } + else { + req->dispatch.done.typed(client, &resp, req->response.data); + discord_refcounter_decr(&client->refcounter, req->response.data); + } + } + /* enqueue request for recycle */ + discord_request_cancel(rqtor, req); + + return resp.code; +} + +void +discord_requestor_dispatch_responses(struct discord_requestor *rqtor) +{ + if (0 == pthread_mutex_trylock(&rqtor->qlocks->finished)) { + QUEUE(struct discord_request) queue; + QUEUE_MOVE(&rqtor->queues->finished, &queue); + pthread_mutex_unlock(&rqtor->qlocks->finished); + + if (!QUEUE_EMPTY(&queue)) { + struct discord_rest *rest = + CONTAINEROF(rqtor, struct discord_rest, requestor); + QUEUE(struct discord_request) * qelem; + struct discord_request *req; + + do { + qelem = QUEUE_HEAD(&queue); + req = QUEUE_DATA(qelem, struct discord_request, entry); + _discord_request_dispatch_response(rqtor, req); + } while (!QUEUE_EMPTY(&queue)); + io_poller_wakeup(rest->io_poller); + } + } +} + +/** + * @brief If request can be retried then it will be moved back to its + * bucket's queue + * @note this **MUST** be called only after discord_request_info_extract() + * + * @param rqtor the requestor handle initialized with discord_requestor_init() + * @param req the request to be checked for retry + * @return `true` if request has been enqueued for retry + */ +static bool +_discord_request_retry(struct discord_requestor *rqtor, + struct discord_request *req) +{ + if (req->retry_attempt++ >= rqtor->retry_limit) return false; + + ua_conn_reset(req->conn); + + /* FIXME: wait_ms > 0 should be dealt with aswell */ + if (req->wait_ms <= 0) + discord_bucket_insert(&rqtor->ratelimiter, req->b, req, true); + + return true; +} + +/* parse request response and prepare callback that should be triggered + * at _discord_rest_run_request_callback() */ +CCORDcode +discord_requestor_info_read(struct discord_requestor *rqtor) +{ + int alive = 0; + + if (CURLM_OK != curl_multi_socket_all(rqtor->mhandle, &alive)) + return CCORD_CURLM_INTERNAL; + + /* ask for any messages/informationals from the individual transfers */ + while (1) { + int msgq = 0; + struct CURLMsg *msg = curl_multi_info_read(rqtor->mhandle, &msgq); + + if (!msg) break; + + if (CURLMSG_DONE == msg->msg) { + const CURLcode ecode = msg->data.result; + struct discord_request *req; + bool retry = false; + + curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &req); + curl_multi_remove_handle(rqtor->mhandle, msg->easy_handle); + + switch (ecode) { + case CURLE_OK: { + struct ua_szbuf_readonly body; + struct ua_info info; + + retry = _discord_request_info_extract(rqtor, req, &info); + body = ua_info_get_body(&info); + + if (info.code != CCORD_OK) { + logconf_error(&rqtor->conf, "%.*s", (int)body.size, + body.start); + } + else if (req->dispatch.has_type + && req->dispatch.sync != DISCORD_SYNC_FLAG) { + if (req->dispatch.sync) { + req->response.data = req->dispatch.sync; + } + else { + req->response.data = calloc(1, req->response.size); + discord_refcounter_add_internal( + &CLIENT(rqtor, rest.requestor)->refcounter, + req->response.data, req->response.cleanup, true); + } + + /* initialize ret */ + if (req->response.init) + req->response.init(req->response.data); + /* populate ret */ + if (req->response.from_json) + req->response.from_json(body.start, body.size, + req->response.data); + } + + /** FIXME: bucket should be recycled if it was matched with an + * invalid endpoint */ + discord_ratelimiter_build(&rqtor->ratelimiter, req->b, + req->key, &info); + + ua_info_cleanup(&info); + } break; + default: + logconf_warn(&rqtor->conf, "%s (CURLE code: %d)", + curl_easy_strerror(ecode), ecode); + + retry = (ecode == CURLE_READ_ERROR); + req->code = CCORD_CURLE_INTERNAL; + break; + } + + if (!retry || !_discord_request_retry(rqtor, req)) { + discord_bucket_request_unselect(&rqtor->ratelimiter, req->b, + req); + + if (req->dispatch.sync) { + pthread_mutex_lock(&rqtor->qlocks->pending); + pthread_cond_signal(req->cond); + pthread_mutex_unlock(&rqtor->qlocks->pending); + } + else { + pthread_mutex_lock(&rqtor->qlocks->finished); + QUEUE_INSERT_TAIL(&rqtor->queues->finished, &req->entry); + pthread_mutex_unlock(&rqtor->qlocks->finished); + } + } + } + } + + return CCORD_OK; +} + +static void +_discord_request_send(void *p_rqtor, struct discord_request *req) +{ + struct discord_requestor *rqtor = p_rqtor; + CURL *ehandle; + + req->conn = ua_conn_start(rqtor->ua); + ehandle = ua_conn_get_easy_handle(req->conn); + + if (HTTP_MIMEPOST == req->method) { + ua_conn_add_header(req->conn, "Content-Type", "multipart/form-data"); + ua_conn_set_mime(req->conn, req, &_discord_request_to_multipart); + } + else { + ua_conn_add_header(req->conn, "Content-Type", "application/json"); + } + + ua_conn_setup(req->conn, &(struct ua_conn_attr){ + .method = req->method, + .body = req->body.start, + .body_size = req->body.size, + .endpoint = req->endpoint, + .base_url = NULL, + }); + + /* link 'req' to 'ehandle' for easy retrieval */ + curl_easy_setopt(ehandle, CURLOPT_PRIVATE, req); + + /* initiate libcurl transfer */ + curl_multi_add_handle(rqtor->mhandle, ehandle); +} + +CCORDcode +discord_requestor_start_pending(struct discord_requestor *rqtor) +{ + QUEUE(struct discord_request) queue, *qelem; + struct discord_request *req; + struct discord_bucket *b; + + pthread_mutex_lock(&rqtor->qlocks->pending); + QUEUE_MOVE(&rqtor->queues->pending, &queue); + pthread_mutex_unlock(&rqtor->qlocks->pending); + + /* match pending requests to their buckets */ + while (!QUEUE_EMPTY(&queue)) { + qelem = QUEUE_HEAD(&queue); + QUEUE_REMOVE(qelem); + + req = QUEUE_DATA(qelem, struct discord_request, entry); + b = discord_bucket_get(&rqtor->ratelimiter, req->key); + discord_bucket_insert(&rqtor->ratelimiter, b, req, + req->dispatch.high_priority); + } + + discord_bucket_request_selector(&rqtor->ratelimiter, rqtor, + &_discord_request_send); + + /* FIXME: redundant return value (constant) */ + return CCORD_OK; +} + +/* Only fields required at _discord_request_to_multipart() are duplicated */ +static void +_discord_attachments_dup(struct discord_attachments *dest, + struct discord_attachments *src) +{ + __carray_init(dest, (size_t)src->size, struct discord_attachment, , ); + for (int i = 0; i < src->size; ++i) { + carray_insert(dest, i, src->array[i]); + if (src->array[i].content) { + dest->array[i].size = src->array[i].size + ? src->array[i].size + : strlen(src->array[i].content) + 1; + + dest->array[i].content = malloc(dest->array[i].size); + memcpy(dest->array[i].content, src->array[i].content, + dest->array[i].size); + } + if (src->array[i].filename) + dest->array[i].filename = strdup(src->array[i].filename); + if (src->array[i].content_type) + dest->array[i].content_type = strdup(src->array[i].content_type); + } +} + +static struct discord_request * +_discord_request_get(struct discord_requestor *rqtor) +{ + struct discord_request *req; + + pthread_mutex_lock(&rqtor->qlocks->recycling); + if (QUEUE_EMPTY(&rqtor->queues->recycling)) { /* new request struct */ + req = _discord_request_init(); + } + else { /* fetch a request struct from queues->recycling */ + QUEUE(struct discord_request) *qelem = + QUEUE_HEAD(&rqtor->queues->recycling); + + QUEUE_REMOVE(qelem); + req = QUEUE_DATA(qelem, struct discord_request, entry); + } + pthread_mutex_unlock(&rqtor->qlocks->recycling); + + QUEUE_INIT(&req->entry); + + return req; +} + +CCORDcode +discord_request_begin(struct discord_requestor *rqtor, + struct discord_attributes *attr, + struct ccord_szbuf *body, + enum http_method method, + char endpoint[DISCORD_ENDPT_LEN], + char key[DISCORD_ROUTE_LEN]) +{ + struct discord_rest *rest = + CONTAINEROF(rqtor, struct discord_rest, requestor); + struct discord *client = CLIENT(rest, rest); + + struct discord_request *req = _discord_request_get(rqtor); + CCORDcode code = CCORD_OK; + + req->method = method; + memcpy(req, attr, sizeof *attr); + + if (attr->attachments.size) + _discord_attachments_dup(&req->attachments, &attr->attachments); + + if (body) { /* copy request body */ + if (body->size > req->body.realsize) { /* buffer needs a resize */ + void *tmp = realloc(req->body.start, body->size); + ASSERT_S(tmp != NULL, "Out of memory"); + + req->body.start = tmp; + req->body.realsize = body->size; + } + memcpy(req->body.start, body->start, body->size); + req->body.size = body->size; + } + + /* copy endpoint over to req */ + memcpy(req->endpoint, endpoint, sizeof(req->endpoint)); + /* copy bucket's key */ + memcpy(req->key, key, sizeof(req->key)); + + if (attr->dispatch.keep) { + code = discord_refcounter_incr(&client->refcounter, + (void *)attr->dispatch.keep); + + ASSERT_S(code == CCORD_OK, + "'.keep' data must be a Concord callback parameter"); + } + if (attr->dispatch.data + && CCORD_UNAVAILABLE + == discord_refcounter_incr(&client->refcounter, + attr->dispatch.data)) + { + discord_refcounter_add_client(&client->refcounter, attr->dispatch.data, + attr->dispatch.cleanup, false); + } + + pthread_mutex_lock(&rqtor->qlocks->pending); + QUEUE_INSERT_TAIL(&rqtor->queues->pending, &req->entry); + io_poller_wakeup(rest->io_poller); + if (!req->dispatch.sync) { + pthread_mutex_unlock(&rqtor->qlocks->pending); + } + else { /* wait for request's completion if sync mode is active */ + pthread_cond_t temp_cond = PTHREAD_COND_INITIALIZER; + req->cond = &temp_cond; + pthread_cond_wait(req->cond, &rqtor->qlocks->pending); + req->cond = NULL; + pthread_mutex_unlock(&rqtor->qlocks->pending); + + code = _discord_request_dispatch_response(rqtor, req); + } + + return code; +} diff --git a/src/discord-timer.c b/src/discord-timer.c index 622bbd6cc..41c1b002c 100644 --- a/src/discord-timer.c +++ b/src/discord-timer.c @@ -20,34 +20,51 @@ cmp_timers(const void *a, const void *b) } void -discord_timers_init(struct discord *client) +discord_timers_init(struct discord_timers *timers) { - client->timers.internal.q = priority_queue_create( - sizeof(int64_t), sizeof(struct discord_timer), cmp_timers, 0); - client->timers.user.q = priority_queue_create( + timers->q = priority_queue_create( sizeof(int64_t), sizeof(struct discord_timer), cmp_timers, 0); } static void -discord_timers_cancel_all(struct discord *client, priority_queue *q) +discord_timers_cancel_all(struct discord *client, + struct discord_timers *timers) { struct discord_timer timer; - while ((timer.id = priority_queue_pop(q, NULL, &timer))) { + while ((timer.id = priority_queue_pop(timers->q, NULL, &timer))) { timer.flags |= DISCORD_TIMER_CANCELED; if (timer.cb) timer.cb(client, &timer); } } void -discord_timers_cleanup(struct discord *client) +discord_timers_cleanup(struct discord *client, struct discord_timers *timers) { - priority_queue_set_max_capacity(client->timers.user.q, 0); - discord_timers_cancel_all(client, client->timers.user.q); - priority_queue_destroy(client->timers.user.q); + priority_queue_set_max_capacity(timers->q, 0); + discord_timers_cancel_all(client, timers); + priority_queue_destroy(timers->q); +} + +int64_t +discord_timers_get_next_trigger(struct discord_timers *const timers[], + size_t n, + int64_t now, + int64_t max_time) +{ + if (max_time == 0) return 0; + + for (unsigned i = 0; i < n; i++) { + int64_t trigger; + if (priority_queue_peek(timers[i]->q, &trigger, NULL)) { + if (trigger < 0) continue; - priority_queue_set_max_capacity(client->timers.internal.q, 0); - discord_timers_cancel_all(client, client->timers.internal.q); - priority_queue_destroy(client->timers.internal.q); + if (trigger <= now) + max_time = 0; + else if (max_time > trigger - now) + max_time = trigger - now; + } + } + return max_time; } unsigned @@ -93,12 +110,11 @@ _discord_timer_ctl(struct discord *client, } #define TIMER_TRY_DELETE \ - do { \ - if (timer.flags & DISCORD_TIMER_DELETE) { \ - priority_queue_del(timers->q, timer.id); \ - continue; \ - } \ - } while (0) + if (timer.flags & DISCORD_TIMER_DELETE) { \ + priority_queue_del(timers->q, timer.id); \ + timers->active.skip_update_phase = false; \ + continue; \ + } void discord_timers_run(struct discord *client, struct discord_timers *timers) @@ -109,6 +125,7 @@ discord_timers_run(struct discord *client, struct discord_timers *timers) struct discord_timer timer; timers->active.timer = &timer; + timers->active.skip_update_phase = false; for (int64_t trigger, max_iterations = 100000; (timer.id = priority_queue_peek(timers->q, &trigger, &timer)) && max_iterations > 0; @@ -118,20 +135,23 @@ discord_timers_run(struct discord *client, struct discord_timers *timers) if ((max_iterations & 0x1F) == 0) { now = (int64_t)discord_timestamp_us(client); // break if we've spent too much time running timers - if (now - start_time > 3000) break; + if (now - start_time > 10000) break; } // no timers to run if (trigger > now || trigger == -1) break; - if (~timer.flags & DISCORD_TIMER_CANCELED) TIMER_TRY_DELETE; + if (~timer.flags & DISCORD_TIMER_CANCELED) { + TIMER_TRY_DELETE; - if (timer.repeat > 0 && ~timer.flags & DISCORD_TIMER_CANCELED) - timer.repeat--; + if (timer.repeat > 0) timer.repeat--; + } - timers->active.skip_update_phase = false; if (timer.cb) timer.cb(client, &timer); - if (timers->active.skip_update_phase) continue; + if (timers->active.skip_update_phase) { + timers->active.skip_update_phase = false; + continue; + } if ((timer.repeat == 0 || timer.flags & DISCORD_TIMER_CANCELED) && (timer.flags & DISCORD_TIMER_DELETE_AUTO)) @@ -142,16 +162,14 @@ discord_timers_run(struct discord *client, struct discord_timers *timers) TIMER_TRY_DELETE; int64_t next = -1; - if (timer.repeat != 0 && timer.delay != -1 + if (timer.delay != -1 && timer.interval >= 0 && timer.repeat != 0 && ~timer.flags & DISCORD_TIMER_CANCELED) { - if (timer.interval >= 0) { - next = ((timer.flags & DISCORD_TIMER_INTERVAL_FIXED) ? trigger - : now) - + ((timer.flags & DISCORD_TIMER_MICROSECONDS) - ? timer.interval - : timer.interval * 1000); - } + next = + ((timer.flags & DISCORD_TIMER_INTERVAL_FIXED) ? trigger : now) + + ((timer.flags & DISCORD_TIMER_MICROSECONDS) + ? timer.interval + : timer.interval * 1000); } timer.flags &= DISCORD_TIMER_ALLOWED_FLAGS; priority_queue_update(timers->q, timer.id, &next, &timer); diff --git a/src/discord-voice.c b/src/discord-voice.c index df14be13e..fa3a1ddd1 100644 --- a/src/discord-voice.c +++ b/src/discord-voice.c @@ -155,8 +155,8 @@ on_hello(struct discord_voice *vc) jsmnf_pair *f; vc->hbeat.tstamp = cog_timestamp_ms(); - if ((f = jsmnf_find(vc->payload.data, "heartbeat_interval", 18))) - hbeat_interval = strtof(f->value.contents, NULL); + if ((f = jsmnf_find(vc->payload.data, vc->json, "heartbeat_interval", 18))) + hbeat_interval = strtof(vc->json + f->v.pos, NULL); vc->hbeat.interval_ms = (hbeat_interval < 5000.0f) ? (u64unix_ms)hbeat_interval : 5000; @@ -199,14 +199,14 @@ on_speaking(struct discord_voice *vc) if (!client->voice_cbs.on_speaking) return; - if ((f = jsmnf_find(vc->payload.data, "user_id", 7))) - sscanf(f->value.contents, "%" SCNu64, &user_id); - if ((f = jsmnf_find(vc->payload.data, "speaking", 8))) - speaking = (int)strtol(f->value.contents, NULL, 10); - if ((f = jsmnf_find(vc->payload.data, "delay", 5))) - delay = (int)strtol(f->value.contents, NULL, 10); - if ((f = jsmnf_find(vc->payload.data, "ssrc", 4))) - ssrc = (int)strtol(f->value.contents, NULL, 10); + if ((f = jsmnf_find(vc->payload.data, vc->json, "user_id", 7))) + sscanf(vc->json + f->v.pos, "%" SCNu64, &user_id); + if ((f = jsmnf_find(vc->payload.data, vc->json, "speaking", 8))) + speaking = (int)strtol(vc->json + f->v.pos, NULL, 10); + if ((f = jsmnf_find(vc->payload.data, vc->json, "delay", 5))) + delay = (int)strtol(vc->json + f->v.pos, NULL, 10); + if ((f = jsmnf_find(vc->payload.data, vc->json, "ssrc", 4))) + ssrc = (int)strtol(vc->json + f->v.pos, NULL, 10); client->voice_cbs.on_speaking(client, vc, user_id, speaking, delay, ssrc); } @@ -228,8 +228,8 @@ on_client_disconnect(struct discord_voice *vc) if (!client->voice_cbs.on_client_disconnect) return; - if ((f = jsmnf_find(vc->payload.data, "user_id", 7))) - sscanf(f->value.contents, "%" SCNu64, &user_id); + if ((f = jsmnf_find(vc->payload.data, vc->json, "user_id", 7))) + sscanf(vc->json + f->v.pos, "%" SCNu64, &user_id); client->voice_cbs.on_client_disconnect(client, vc, user_id); } @@ -243,12 +243,12 @@ on_codec(struct discord_voice *vc) if (!client->voice_cbs.on_codec) return; - if ((f = jsmnf_find(vc->payload.data, "audio_codec", 11))) - snprintf(audio_codec, sizeof(audio_codec), "%.*s", f->value.length, - f->value.contents); - if ((f = jsmnf_find(vc->payload.data, "video_codec", 11))) - snprintf(video_codec, sizeof(video_codec), "%.*s", f->value.length, - f->value.contents); + if ((f = jsmnf_find(vc->payload.data, vc->json, "audio_codec", 11))) + snprintf(audio_codec, sizeof(audio_codec), "%.*s", (int)f->v.len, + vc->json + f->v.pos); + if ((f = jsmnf_find(vc->payload.data, vc->json, "video_codec", 11))) + snprintf(video_codec, sizeof(video_codec), "%.*s", (int)f->v.len, + vc->json + f->v.pos); client->voice_cbs.on_codec(client, vc, audio_codec, video_codec); } @@ -346,6 +346,9 @@ on_text_cb(void *p_vc, struct discord_voice *vc = p_vc; jsmn_parser parser; + vc->json = (char *)text; + vc->length = len; + jsmn_init(&parser); if (0 < jsmn_parse_auto(&parser, text, len, &vc->parse.tokens, &vc->parse.ntokens)) @@ -359,9 +362,10 @@ on_text_cb(void *p_vc, { jsmnf_pair *f; - if ((f = jsmnf_find(vc->parse.pairs, "op", 2))) - vc->payload.opcode = (int)strtol(f->value.contents, NULL, 10); - vc->payload.data = jsmnf_find(vc->parse.pairs, "d", 1); + if ((f = jsmnf_find(vc->parse.pairs, vc->json, "op", 2))) + vc->payload.opcode = + (int)strtol(vc->json + f->v.pos, NULL, 10); + vc->payload.data = jsmnf_find(vc->parse.pairs, vc->json, "d", 1); } } @@ -518,56 +522,6 @@ recycle_active_vc(struct discord_voice *vc, vc->shutdown = false; } -static void -send_voice_state_update(struct discord_voice *vc, - u64snowflake guild_id, - u64snowflake channel_id, - bool self_mute, - bool self_deaf) -{ - struct discord_gateway *gw = &vc->p_client->gw; - char buf[256]; - jsonb b; - - jsonb_init(&b); - jsonb_object(&b, buf, sizeof(buf)); - { - jsonb_key(&b, buf, sizeof(buf), "op", sizeof("op") - 1); - jsonb_number(&b, buf, sizeof(buf), 4); - jsonb_key(&b, buf, sizeof(buf), "d", sizeof("d") - 1); - jsonb_object(&b, buf, sizeof(buf)); - { - char tok[32]; - int toklen = snprintf(tok, sizeof(tok), "%" PRIu64, guild_id); - - jsonb_key(&b, buf, sizeof(buf), "guild_id", 8); - jsonb_token(&b, buf, sizeof(buf), tok, (size_t)toklen); - jsonb_key(&b, buf, sizeof(buf), "channel_id", 10); - if (channel_id) { - toklen = snprintf(tok, sizeof(tok), "%" PRIu64, channel_id); - jsonb_token(&b, buf, sizeof(buf), tok, (size_t)toklen); - } - else { - jsonb_null(&b, buf, sizeof(buf)); - } - jsonb_key(&b, buf, sizeof(buf), "self_mute", 9); - jsonb_bool(&b, buf, sizeof(buf), self_mute); - jsonb_key(&b, buf, sizeof(buf), "self_deaf", 9); - jsonb_bool(&b, buf, sizeof(buf), self_deaf); - jsonb_object_pop(&b, buf, sizeof(buf)); - } - jsonb_object_pop(&b, buf, sizeof(buf)); - } - - logconf_info( - &vc->conf, - ANSICOLOR("SEND", ANSI_FG_BRIGHT_GREEN) " VOICE_STATE_UPDATE (%d " - "bytes): %s channel", - b.pos, channel_id ? "join" : "leave"); - - ws_send_text(gw->ws, NULL, buf, b.pos); -} - enum discord_voice_status discord_voice_join(struct discord *client, u64snowflake guild_id, @@ -575,14 +529,17 @@ discord_voice_join(struct discord *client, bool self_mute, bool self_deaf) { + struct discord_update_voice_state state = { .guild_id = guild_id, + .channel_id = vchannel_id, + .self_mute = self_mute, + .self_deaf = self_deaf }; bool found_a_running_vcs = false; struct discord_voice *vc = NULL; - int i; if (!ws_is_functional(client->gw.ws)) return DISCORD_VOICE_ERROR; pthread_mutex_lock(&client_lock); - for (i = 0; i < DISCORD_MAX_VCS; ++i) { + for (int i = 0; i < DISCORD_MAX_VCS; ++i) { if (0 == client->vcs[i].guild_id) { vc = client->vcs + i; _discord_voice_init(vc, client, guild_id, vchannel_id); @@ -609,7 +566,8 @@ discord_voice_join(struct discord *client, } recycle_active_vc(vc, guild_id, vchannel_id); - send_voice_state_update(vc, guild_id, vchannel_id, self_mute, self_deaf); + discord_gateway_send_update_voice_state(&client->gw, &state); + return DISCORD_VOICE_JOINED; } @@ -621,18 +579,17 @@ discord_voice_join(struct discord *client, */ void _discord_on_voice_state_update(struct discord *client, - struct discord_voice_state *vs) + struct discord_voice_state *event) { struct discord_voice *vc = NULL; - int i; pthread_mutex_lock(&client_lock); - for (i = 0; i < DISCORD_MAX_VCS; ++i) { - if (vs->guild_id == client->vcs[i].guild_id) { + for (int i = 0; i < DISCORD_MAX_VCS; ++i) { + if (event->guild_id == client->vcs[i].guild_id) { vc = client->vcs + i; - if (vs->channel_id) { + if (event->channel_id) { int len = snprintf(vc->session_id, sizeof(vc->session_id), - "%s", vs->session_id); + "%s", event->session_id); ASSERT_NOT_OOB(len, sizeof(vc->session_id)); logconf_info(&vc->conf, @@ -646,7 +603,7 @@ _discord_on_voice_state_update(struct discord *client, pthread_mutex_unlock(&client_lock); if (!vc) { - if (vs->channel_id) { + if (event->channel_id) { logconf_fatal( &client->conf, "This should not happen, cannot find a discord_voice object"); @@ -655,7 +612,7 @@ _discord_on_voice_state_update(struct discord *client, return; } - if (vs->channel_id == 0) { + if (event->channel_id == 0) { logconf_info(&vc->conf, ANSICOLOR("Bot is leaving the current vc", ANSI_BG_BRIGHT_BLUE)); if (vc->ws && ws_is_alive(vc->ws)) @@ -745,17 +702,14 @@ start_voice_ws_thread(void *p_vc) */ void _discord_on_voice_server_update(struct discord *client, - u64snowflake guild_id, - char *token, - char *endpoint) + struct discord_voice_server_update *event) { struct discord_voice *vc = NULL; int len; - int i; pthread_mutex_lock(&client_lock); - for (i = 0; i < DISCORD_MAX_VCS; ++i) { - if (guild_id == client->vcs[i].guild_id) { + for (int i = 0; i < DISCORD_MAX_VCS; ++i) { + if (event->guild_id == client->vcs[i].guild_id) { vc = client->vcs + i; break; } @@ -767,11 +721,11 @@ _discord_on_voice_server_update(struct discord *client, return; } - len = snprintf(vc->new_token, sizeof(vc->new_token), "%s", token); + len = snprintf(vc->new_token, sizeof(vc->new_token), "%s", event->token); ASSERT_NOT_OOB(len, sizeof(vc->new_token)); len = snprintf(vc->new_url, sizeof(vc->new_url), - "wss://%s" DISCORD_VCS_URL_SUFFIX, endpoint); + "wss://%s" DISCORD_VCS_URL_SUFFIX, event->endpoint); ASSERT_NOT_OOB(len, sizeof(vc->new_url)); /* TODO: replace with the more reliable thread alive check */ @@ -798,9 +752,7 @@ _discord_on_voice_server_update(struct discord *client, void discord_voice_connections_init(struct discord *client) { - int i; - - for (i = 0; i < DISCORD_MAX_VCS; ++i) { + for (int i = 0; i < DISCORD_MAX_VCS; ++i) { client->vcs[i].p_voice_cbs = &client->voice_cbs; } } @@ -817,9 +769,7 @@ _discord_voice_cleanup(struct discord_voice *vc) void discord_voice_connections_cleanup(struct discord *client) { - int i; - - for (i = 0; i < DISCORD_MAX_VCS; ++i) { + for (int i = 0; i < DISCORD_MAX_VCS; ++i) { _discord_voice_cleanup(&client->vcs[i]); } } @@ -827,15 +777,14 @@ discord_voice_connections_cleanup(struct discord *client) void discord_voice_shutdown(struct discord_voice *vc) { + struct discord_update_voice_state state = { .guild_id = vc->guild_id }; const char reason[] = "Client triggered voice shutdown"; vc->reconnect.enable = false; vc->shutdown = true; vc->is_resumable = false; - /* TODO: check if send_voice_state_update() is not being ignored because of - * ws_close() */ - send_voice_state_update(vc, vc->guild_id, 0, false, false); + discord_gateway_send_update_voice_state(&vc->p_client->gw, &state); ws_close(vc->ws, WS_CLOSE_REASON_NORMAL, reason, sizeof(reason)); } @@ -862,7 +811,7 @@ discord_voice_is_alive(struct discord_voice *vc) void discord_set_voice_cbs(struct discord *client, - struct discord_voice_cbs *callbacks) + struct discord_voice_evcallbacks *callbacks) { if (callbacks->on_speaking) client->voice_cbs.on_speaking = callbacks->on_speaking; diff --git a/src/discord-worker.c b/src/discord-worker.c new file mode 100644 index 000000000..d4381a016 --- /dev/null +++ b/src/discord-worker.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include + +#include "threadpool.h" + +#include "discord.h" +#include "discord-internal.h" +#include "discord-worker.h" + +/** true after threadpool initialization */ +static _Bool once; + +/** global threadpool manager */ +threadpool_t *g_tpool; + +int +discord_worker_global_init(void) +{ + static int nthreads; + static int queue_size; + const char *val; + char *p_end; + + if (once) return 1; + + /* get threadpool thread amount */ + if (!nthreads) { + if ((val = getenv("CCORD_THREADPOOL_SIZE"))) + nthreads = (int)strtol(val, &p_end, 10); + if (nthreads < 2 || ERANGE == errno || p_end == val) nthreads = 2; + } + /* get threadpool queue size */ + if (!queue_size) { + if ((val = getenv("CCORD_THREADPOOL_QUEUE_SIZE"))) + queue_size = (int)strtol(val, &p_end, 10); + if (queue_size < 8 || ERANGE == errno || p_end == val) queue_size = 8; + } + + /* initialize threadpool */ + g_tpool = threadpool_create(nthreads, queue_size, 0); + + once = 1; + + return 0; +} + +struct discord_worker_context { + struct discord *client; + void *data; + void (*callback)(void *data); +}; + +static void +_discord_worker_cb(void *p_cxt) +{ + struct discord_worker_context *cxt = p_cxt; + + pthread_mutex_lock(&cxt->client->workers->lock); + ++cxt->client->workers->count; + pthread_mutex_unlock(&cxt->client->workers->lock); + + cxt->callback(cxt->data); + + pthread_mutex_lock(&cxt->client->workers->lock); + --cxt->client->workers->count; + pthread_cond_signal(&cxt->client->workers->cond); + pthread_mutex_unlock(&cxt->client->workers->lock); + + free(cxt); +} + +CCORDcode +discord_worker_add(struct discord *client, + void (*callback)(void *data), + void *data) +{ + struct discord_worker_context *cxt = malloc(sizeof *cxt); + *cxt = (struct discord_worker_context){ client, data, callback }; + + return 0 == threadpool_add(g_tpool, _discord_worker_cb, cxt, 0) + ? CCORD_OK + : CCORD_FULL_WORKER; +} + +CCORDcode +discord_worker_join(struct discord *client) +{ + pthread_mutex_lock(&client->workers->lock); + while (client->workers->count != 0) { + pthread_cond_wait(&client->workers->cond, &client->workers->lock); + } + pthread_mutex_unlock(&client->workers->lock); + return CCORD_OK; +} + +void +discord_worker_global_cleanup(void) +{ + /* cleanup thread-pool manager */ + threadpool_destroy(g_tpool, threadpool_graceful); + once = 0; +} diff --git a/src/emoji.c b/src/emoji.c index 5f1e37e10..1997aa216 100644 --- a/src/emoji.c +++ b/src/emoji.c @@ -11,14 +11,14 @@ discord_list_guild_emojis(struct discord *client, u64snowflake guild_id, struct discord_ret_emojis *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_emojis, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_emojis, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/emojis", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/emojis", guild_id); } CCORDcode @@ -27,16 +27,16 @@ discord_get_guild_emoji(struct discord *client, u64snowflake emoji_id, struct discord_ret_emoji *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, emoji_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_emoji, ret); + DISCORD_ATTR_INIT(attr, discord_emoji, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/emojis/%" PRIu64, guild_id, - emoji_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/emojis/%" PRIu64, guild_id, + emoji_id); } CCORDcode @@ -45,8 +45,8 @@ discord_create_guild_emoji(struct discord *client, struct discord_create_guild_emoji *params, struct discord_ret_emoji *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[2048]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -55,10 +55,10 @@ discord_create_guild_emoji(struct discord *client, body.size = discord_create_guild_emoji_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_emoji, ret); + DISCORD_ATTR_INIT(attr, discord_emoji, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/guilds/%" PRIu64 "/emojis", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/guilds/%" PRIu64 "/emojis", guild_id); } CCORDcode @@ -68,8 +68,8 @@ discord_modify_guild_emoji(struct discord *client, struct discord_modify_guild_emoji *params, struct discord_ret_emoji *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[2048]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -79,11 +79,11 @@ discord_modify_guild_emoji(struct discord *client, body.size = discord_modify_guild_emoji_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_emoji, ret); + DISCORD_ATTR_INIT(attr, discord_emoji, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/emojis/%" PRIu64, guild_id, - emoji_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/emojis/%" PRIu64, guild_id, + emoji_id); } CCORDcode @@ -92,14 +92,14 @@ discord_delete_guild_emoji(struct discord *client, u64snowflake emoji_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, emoji_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/guilds/%" PRIu64 "/emojis/%" PRIu64, guild_id, - emoji_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/guilds/%" PRIu64 "/emojis/%" PRIu64, guild_id, + emoji_id); } diff --git a/src/gateway.c b/src/gateway.c index e9246376f..ff216b612 100644 --- a/src/gateway.c +++ b/src/gateway.c @@ -16,8 +16,8 @@ discord_disconnect_guild_member(struct discord *client, u64snowflake user_id, struct discord_ret_guild_member *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[128]; jsonb b; @@ -36,45 +36,49 @@ discord_disconnect_guild_member(struct discord *client, body.start = buf; body.size = b.pos; - DISCORD_REQ_INIT(req, discord_guild_member, ret); + DISCORD_ATTR_INIT(attr, discord_guild_member, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/members/%" PRIu64, - guild_id, user_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/members/%" PRIu64, guild_id, + user_id); } /****************************************************************************** * REST functions ******************************************************************************/ +static size_t +_ccord_szbuf_from_json(const char str[], size_t len, void *p_buf) +{ + struct ccord_szbuf *buf = p_buf; + return buf->size = cog_strndup(str, len, &buf->start); +} + CCORDcode -discord_get_gateway(struct discord *client, struct sized_buffer *ret) +discord_get_gateway(struct discord *client, struct ccord_szbuf *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, ret != NULL, CCORD_BAD_PARAMETER, ""); - req.gnrc.from_json = - (size_t(*)(const char *, size_t, void *))cog_sized_buffer_from_json; - req.ret.has_type = true; - req.ret.sync = ret; + attr.response.from_json = &_ccord_szbuf_from_json; + attr.dispatch.has_type = true; + attr.dispatch.sync = ret; - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/gateway"); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, "/gateway"); } CCORDcode -discord_get_gateway_bot(struct discord *client, struct sized_buffer *ret) +discord_get_gateway_bot(struct discord *client, struct ccord_szbuf *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, ret != NULL, CCORD_BAD_PARAMETER, ""); - req.gnrc.from_json = - (size_t(*)(const char *, size_t, void *))cog_sized_buffer_from_json; - req.ret.has_type = true; - req.ret.sync = ret; + attr.response.from_json = &_ccord_szbuf_from_json; + attr.dispatch.has_type = true; + attr.dispatch.sync = ret; - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/gateway/bot"); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/gateway/bot"); } diff --git a/src/guild.c b/src/guild.c index 18bf634cb..d0b5c02dc 100644 --- a/src/guild.c +++ b/src/guild.c @@ -11,8 +11,8 @@ discord_create_guild(struct discord *client, struct discord_create_guild *params, struct discord_ret_guild *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, params != NULL, CCORD_BAD_PARAMETER, ""); @@ -20,10 +20,9 @@ discord_create_guild(struct discord *client, body.size = discord_create_guild_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_guild, ret); + DISCORD_ATTR_INIT(attr, discord_guild, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/guilds"); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, "/guilds"); } CCORDcode @@ -31,14 +30,14 @@ discord_get_guild(struct discord *client, u64snowflake guild_id, struct discord_ret_guild *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_guild, ret); + DISCORD_ATTR_INIT(attr, discord_guild, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64, guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64, guild_id); } CCORDcode @@ -46,14 +45,14 @@ discord_get_guild_preview(struct discord *client, u64snowflake guild_id, struct discord_ret_guild_preview *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_guild_preview, ret); + DISCORD_ATTR_INIT(attr, discord_guild_preview, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/preview", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/preview", guild_id); } CCORDcode @@ -62,8 +61,8 @@ discord_modify_guild(struct discord *client, struct discord_modify_guild *params, struct discord_ret_guild *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -72,10 +71,10 @@ discord_modify_guild(struct discord *client, body.size = discord_modify_guild_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_guild, ret); + DISCORD_ATTR_INIT(attr, discord_guild, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64, guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64, guild_id); } CCORDcode @@ -83,14 +82,14 @@ discord_delete_guild(struct discord *client, u64snowflake guild_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/guilds/%" PRIu64, guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/guilds/%" PRIu64, guild_id); } CCORDcode @@ -98,14 +97,14 @@ discord_get_guild_channels(struct discord *client, u64snowflake guild_id, struct discord_ret_channels *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_channels, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_channels, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/channels", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/channels", guild_id); } CCORDcode @@ -114,8 +113,8 @@ discord_create_guild_channel(struct discord *client, struct discord_create_guild_channel *params, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[2048]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -124,10 +123,10 @@ discord_create_guild_channel(struct discord *client, body.size = discord_create_guild_channel_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/guilds/%" PRIu64 "/channels", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/guilds/%" PRIu64 "/channels", guild_id); } CCORDcode @@ -137,8 +136,8 @@ discord_modify_guild_channel_positions( struct discord_modify_guild_channel_positions *params, struct discord_ret *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -148,10 +147,10 @@ discord_modify_guild_channel_positions( buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/channels", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/channels", guild_id); } CCORDcode @@ -160,16 +159,16 @@ discord_get_guild_member(struct discord *client, u64snowflake user_id, struct discord_ret_guild_member *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_guild_member, ret); + DISCORD_ATTR_INIT(attr, discord_guild_member, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/members/%" PRIu64, - guild_id, user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/members/%" PRIu64, guild_id, + user_id); } CCORDcode @@ -178,7 +177,7 @@ discord_list_guild_members(struct discord *client, struct discord_list_guild_members *params, struct discord_ret_guild_members *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char query[1024] = ""; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -199,11 +198,11 @@ discord_list_guild_members(struct discord *client, } } - DISCORD_REQ_LIST_INIT(req, discord_guild_members, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_guild_members, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/members%s%s", guild_id, - *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/members%s%s", guild_id, + *query ? "?" : "", query); } CCORDcode @@ -212,7 +211,7 @@ discord_search_guild_members(struct discord *client, struct discord_search_guild_members *params, struct discord_ret_guild_members *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; char query[1024] = ""; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -221,7 +220,8 @@ discord_search_guild_members(struct discord *client, int offset = 0; if (params->query) { - char *pe_query = curl_escape(params->query, (int)strlen(params->query)); + char *pe_query = + curl_escape(params->query, (int)strlen(params->query)); offset += snprintf(query + offset, sizeof(query) - (size_t)offset, "query=%s", pe_query); @@ -236,11 +236,11 @@ discord_search_guild_members(struct discord *client, } } - DISCORD_REQ_LIST_INIT(req, discord_guild_members, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_guild_members, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/members/search%s%s", - guild_id, *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/members/search%s%s", guild_id, + *query ? "?" : "", query); } CCORDcode @@ -250,8 +250,8 @@ discord_add_guild_member(struct discord *client, struct discord_add_guild_member *params, struct discord_ret_guild_member *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -263,11 +263,11 @@ discord_add_guild_member(struct discord *client, body.size = discord_add_guild_member_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_guild_member, ret); + DISCORD_ATTR_INIT(attr, discord_guild_member, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/guilds/%" PRIu64 "/members/%" PRIu64, - guild_id, user_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/guilds/%" PRIu64 "/members/%" PRIu64, guild_id, + user_id); } CCORDcode @@ -277,8 +277,8 @@ discord_modify_guild_member(struct discord *client, struct discord_modify_guild_member *params, struct discord_ret_guild_member *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[2048]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -288,11 +288,11 @@ discord_modify_guild_member(struct discord *client, body.size = discord_modify_guild_member_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_guild_member, ret); + DISCORD_ATTR_INIT(attr, discord_guild_member, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/members/%" PRIu64, - guild_id, user_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/members/%" PRIu64, guild_id, + user_id); } CCORDcode discord_modify_current_member(struct discord *client, @@ -300,8 +300,8 @@ discord_modify_current_member(struct discord *client, struct discord_modify_current_member *params, struct discord_ret_guild_member *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[512]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -312,10 +312,10 @@ discord_modify_current_member(struct discord *client, discord_modify_current_member_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_guild_member, ret); + DISCORD_ATTR_INIT(attr, discord_guild_member, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/members/@me", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/members/@me", guild_id); } CCORDcode discord_modify_current_user_nick( @@ -324,8 +324,8 @@ discord_modify_current_user_nick( struct discord_modify_current_user_nick *params, struct discord_ret_guild_member *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[512]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -340,11 +340,10 @@ discord_modify_current_user_nick( discord_modify_current_user_nick_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_guild_member, ret); + DISCORD_ATTR_INIT(attr, discord_guild_member, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/members/@me/nick", - guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/members/@me/nick", guild_id); } CCORDcode @@ -354,18 +353,18 @@ discord_add_guild_member_role(struct discord *client, u64snowflake role_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, role_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_PUT, - "/guilds/%" PRIu64 "/members/%" PRIu64 - "/roles/%" PRIu64, - guild_id, user_id, role_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_PUT, + "/guilds/%" PRIu64 "/members/%" PRIu64 + "/roles/%" PRIu64, + guild_id, user_id, role_id); } CCORDcode @@ -375,18 +374,18 @@ discord_remove_guild_member_role(struct discord *client, u64snowflake role_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, role_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/guilds/%" PRIu64 "/members/%" PRIu64 - "/roles/%" PRIu64, - guild_id, user_id, role_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/guilds/%" PRIu64 "/members/%" PRIu64 + "/roles/%" PRIu64, + guild_id, user_id, role_id); } CCORDcode @@ -395,16 +394,16 @@ discord_remove_guild_member(struct discord *client, u64snowflake user_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/guilds/%" PRIu64 "/members/%" PRIu64, - guild_id, user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/guilds/%" PRIu64 "/members/%" PRIu64, guild_id, + user_id); } CCORDcode @@ -412,14 +411,14 @@ discord_get_guild_bans(struct discord *client, u64snowflake guild_id, struct discord_ret_bans *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_bans, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_bans, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/bans", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/bans", guild_id); } CCORDcode @@ -428,16 +427,16 @@ discord_get_guild_ban(struct discord *client, u64snowflake user_id, struct discord_ret_ban *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_ban, ret); + DISCORD_ATTR_INIT(attr, discord_ban, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/bans/%" PRIu64, guild_id, - user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/bans/%" PRIu64, guild_id, + user_id); } CCORDcode @@ -447,8 +446,8 @@ discord_create_guild_ban(struct discord *client, struct discord_create_guild_ban *params, struct discord_ret *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[256]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -462,11 +461,11 @@ discord_create_guild_ban(struct discord *client, body.size = discord_create_guild_ban_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PUT, - "/guilds/%" PRIu64 "/bans/%" PRIu64, guild_id, - user_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PUT, + "/guilds/%" PRIu64 "/bans/%" PRIu64, guild_id, + user_id); } CCORDcode discord_remove_guild_ban(struct discord *client, @@ -474,16 +473,16 @@ discord_remove_guild_ban(struct discord *client, u64snowflake user_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/guilds/%" PRIu64 "/bans/%" PRIu64, guild_id, - user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/guilds/%" PRIu64 "/bans/%" PRIu64, guild_id, + user_id); } CCORDcode @@ -491,14 +490,14 @@ discord_get_guild_roles(struct discord *client, u64snowflake guild_id, struct discord_ret_roles *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_roles, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_roles, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/roles", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/roles", guild_id); } CCORDcode @@ -507,8 +506,8 @@ discord_create_guild_role(struct discord *client, struct discord_create_guild_role *params, struct discord_ret_role *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -516,10 +515,10 @@ discord_create_guild_role(struct discord *client, body.size = discord_create_guild_role_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_role, ret); + DISCORD_ATTR_INIT(attr, discord_role, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/guilds/%" PRIu64 "/roles", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/guilds/%" PRIu64 "/roles", guild_id); } CCORDcode @@ -529,8 +528,8 @@ discord_modify_guild_role_positions( struct discord_modify_guild_role_positions *params, struct discord_ret_roles *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -540,10 +539,10 @@ discord_modify_guild_role_positions( discord_modify_guild_role_positions_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_LIST_INIT(req, discord_roles, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_roles, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/roles", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/roles", guild_id); } CCORDcode @@ -553,8 +552,8 @@ discord_modify_guild_role(struct discord *client, struct discord_modify_guild_role *params, struct discord_ret_role *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[2048] = "{}"; size_t len = 2; @@ -567,11 +566,11 @@ discord_modify_guild_role(struct discord *client, body.size = len; body.start = buf; - DISCORD_REQ_INIT(req, discord_role, ret); + DISCORD_ATTR_INIT(attr, discord_role, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/guilds/%" PRIu64 "/roles/%" PRIu64, guild_id, - role_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/guilds/%" PRIu64 "/roles/%" PRIu64, guild_id, + role_id); } CCORDcode @@ -580,16 +579,16 @@ discord_delete_guild_role(struct discord *client, u64snowflake role_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, role_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/guilds/%" PRIu64 "/roles/%" PRIu64, guild_id, - role_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/guilds/%" PRIu64 "/roles/%" PRIu64, guild_id, + role_id); } CCORDcode @@ -598,8 +597,8 @@ discord_begin_guild_prune(struct discord *client, struct discord_begin_guild_prune *params, struct discord_ret *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[4096] = "{}"; size_t len = 2; @@ -611,10 +610,10 @@ discord_begin_guild_prune(struct discord *client, body.size = len; body.start = buf; - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/guilds/%" PRIu64 "/prune", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/guilds/%" PRIu64 "/prune", guild_id); } CCORDcode @@ -622,14 +621,14 @@ discord_get_guild_invites(struct discord *client, u64snowflake guild_id, struct discord_ret_invites *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_invites, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_invites, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/invites", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/invites", guild_id); } CCORDcode @@ -638,16 +637,16 @@ discord_delete_guild_integrations(struct discord *client, u64snowflake integration_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, integration_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/guilds/%" PRIu64 "/integrations/%" PRIu64, - guild_id, integration_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/guilds/%" PRIu64 "/integrations/%" PRIu64, + guild_id, integration_id); } CCORDcode @@ -655,14 +654,14 @@ discord_get_guild_vanity_url(struct discord *client, u64snowflake guild_id, struct discord_ret_invite *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_invite, ret); + DISCORD_ATTR_INIT(attr, discord_invite, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/vanity-url", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/vanity-url", guild_id); } CCORDcode @@ -670,12 +669,12 @@ discord_get_guild_welcome_screen(struct discord *client, u64snowflake guild_id, struct discord_ret_welcome_screen *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_welcome_screen, ret); + DISCORD_ATTR_INIT(attr, discord_welcome_screen, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/welcome-screen", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/welcome-screen", guild_id); } diff --git a/src/guild_template.c b/src/guild_template.c index d4611da58..ef027d44f 100644 --- a/src/guild_template.c +++ b/src/guild_template.c @@ -11,14 +11,14 @@ discord_get_guild_template(struct discord *client, char *code, struct discord_ret_guild_template *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, NOT_EMPTY_STR(code), CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_guild_template, ret); + DISCORD_ATTR_INIT(attr, discord_guild_template, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/templates/%s", code); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/templates/%s", code); } CCORDcode @@ -27,8 +27,8 @@ discord_create_guild_template(struct discord *client, struct discord_create_guild_template *params, struct discord_ret_guild_template *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[256]; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); @@ -37,10 +37,10 @@ discord_create_guild_template(struct discord *client, discord_create_guild_template_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_guild_template, ret); + DISCORD_ATTR_INIT(attr, discord_guild_template, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/guilds/%" PRIu64 "/templates", guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/guilds/%" PRIu64 "/templates", guild_id); } CCORDcode @@ -49,13 +49,13 @@ discord_sync_guild_template(struct discord *client, char *code, struct discord_ret_guild_template *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_guild_template, ret); + DISCORD_ATTR_INIT(attr, discord_guild_template, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_PUT, - "/guilds/%" PRIu64 "/templates/%s", guild_id, - code); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_PUT, + "/guilds/%" PRIu64 "/templates/%s", guild_id, + code); } diff --git a/src/interaction.c b/src/interaction.c index 2e09ddddf..501ad7667 100644 --- a/src/interaction.c +++ b/src/interaction.c @@ -14,8 +14,8 @@ discord_create_interaction_response( struct discord_interaction_response *params, struct discord_ret_interaction_response *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; enum http_method method; char buf[4096]; @@ -29,17 +29,17 @@ discord_create_interaction_response( if (params->data && params->data->attachments) { method = HTTP_MIMEPOST; - req.attachments = *params->data->attachments; + attr.attachments = *params->data->attachments; } else { method = HTTP_POST; } - DISCORD_REQ_INIT(req, discord_interaction_response, ret); + DISCORD_ATTR_INIT(attr, discord_interaction_response, ret); - return discord_adapter_run(&client->adapter, &req, &body, method, - "/interactions/%" PRIu64 "/%s/callback", - interaction_id, interaction_token); + return discord_rest_run(&client->rest, &attr, &body, method, + "/interactions/%" PRIu64 "/%s/callback", + interaction_id, interaction_token); } CCORDcode @@ -49,17 +49,17 @@ discord_get_original_interaction_response( const char interaction_token[], struct discord_ret_interaction_response *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(interaction_token), CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_interaction_response, ret); + DISCORD_ATTR_INIT(attr, discord_interaction_response, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/webhooks/%" PRIu64 "/%s/messages/@original", - application_id, interaction_token); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/webhooks/%" PRIu64 "/%s/messages/@original", + application_id, interaction_token); } CCORDcode @@ -70,8 +70,8 @@ discord_edit_original_interaction_response( struct discord_edit_original_interaction_response *params, struct discord_ret_interaction_response *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; enum http_method method; char buf[16384]; /**< @todo dynamic buffer */ @@ -86,17 +86,17 @@ discord_edit_original_interaction_response( if (params->attachments) { method = HTTP_MIMEPOST; - req.attachments = *params->attachments; + attr.attachments = *params->attachments; } else { method = HTTP_PATCH; } - DISCORD_REQ_INIT(req, discord_interaction_response, ret); + DISCORD_ATTR_INIT(attr, discord_interaction_response, ret); - return discord_adapter_run(&client->adapter, &req, &body, method, - "/webhooks/%" PRIu64 "/%s/messages/@original", - application_id, interaction_token); + return discord_rest_run(&client->rest, &attr, &body, method, + "/webhooks/%" PRIu64 "/%s/messages/@original", + application_id, interaction_token); } CCORDcode @@ -105,17 +105,17 @@ discord_delete_original_interaction_response(struct discord *client, const char interaction_token[], struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(interaction_token), CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/webhooks/%" PRIu64 "/%s/messages/@original", - application_id, interaction_token); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/webhooks/%" PRIu64 "/%s/messages/@original", + application_id, interaction_token); } CCORDcode @@ -125,8 +125,8 @@ discord_create_followup_message(struct discord *client, struct discord_create_followup_message *params, struct discord_ret_webhook *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; enum http_method method; char buf[16384]; /**< @todo dynamic buffer */ char query[4096] = ""; @@ -148,17 +148,17 @@ discord_create_followup_message(struct discord *client, if (params->attachments) { method = HTTP_MIMEPOST; - req.attachments = *params->attachments; + attr.attachments = *params->attachments; } else { method = HTTP_POST; } - DISCORD_REQ_INIT(req, discord_webhook, ret); + DISCORD_ATTR_INIT(attr, discord_webhook, ret); - return discord_adapter_run(&client->adapter, &req, &body, method, - "/webhooks/%" PRIu64 "/%s%s%s", application_id, - interaction_token, *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, &body, method, + "/webhooks/%" PRIu64 "/%s%s%s", application_id, + interaction_token, *query ? "?" : "", query); } CCORDcode @@ -168,18 +168,18 @@ discord_get_followup_message(struct discord *client, u64snowflake message_id, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(interaction_token), CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/webhooks/%" PRIu64 "/%s/%" PRIu64, - application_id, interaction_token, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/webhooks/%" PRIu64 "/%s/%" PRIu64, + application_id, interaction_token, message_id); } CCORDcode @@ -190,8 +190,8 @@ discord_edit_followup_message(struct discord *client, struct discord_edit_followup_message *params, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; enum http_method method; char buf[16384]; /**< @todo dynamic buffer */ @@ -207,17 +207,17 @@ discord_edit_followup_message(struct discord *client, if (params->attachments) { method = HTTP_MIMEPOST; - req.attachments = *params->attachments; + attr.attachments = *params->attachments; } else { method = HTTP_PATCH; } - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, &body, method, - "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, - application_id, interaction_token, message_id); + return discord_rest_run(&client->rest, &attr, &body, method, + "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, + application_id, interaction_token, message_id); } CCORDcode @@ -227,16 +227,16 @@ discord_delete_followup_message(struct discord *client, u64snowflake message_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, application_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(interaction_token), CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, - application_id, interaction_token, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, + application_id, interaction_token, message_id); } diff --git a/src/invite.c b/src/invite.c index e3d96141d..e5539a376 100644 --- a/src/invite.c +++ b/src/invite.c @@ -12,8 +12,8 @@ discord_get_invite(struct discord *client, struct discord_get_invite *params, struct discord_ret_invite *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, NOT_EMPTY_STR(invite_code), CCORD_BAD_PARAMETER, ""); @@ -22,10 +22,10 @@ discord_get_invite(struct discord *client, body.size = discord_get_invite_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_invite, ret); + DISCORD_ATTR_INIT(attr, discord_invite, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_GET, - "/invites/%s", invite_code); + return discord_rest_run(&client->rest, &attr, &body, HTTP_GET, + "/invites/%s", invite_code); } CCORDcode @@ -33,12 +33,12 @@ discord_delete_invite(struct discord *client, char *invite_code, struct discord_ret_invite *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, NOT_EMPTY_STR(invite_code), CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_invite, ret); + DISCORD_ATTR_INIT(attr, discord_invite, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/invites/%s", invite_code); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/invites/%s", invite_code); } diff --git a/src/user.c b/src/user.c index 4c06333e7..8121e5f52 100644 --- a/src/user.c +++ b/src/user.c @@ -9,12 +9,12 @@ CCORDcode discord_get_current_user(struct discord *client, struct discord_ret_user *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; - DISCORD_REQ_INIT(req, discord_user, ret); + DISCORD_ATTR_INIT(attr, discord_user, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/users/@me"); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/users/@me"); } CCORDcode @@ -22,14 +22,14 @@ discord_get_user(struct discord *client, u64snowflake user_id, struct discord_ret_user *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, user_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_user, ret); + DISCORD_ATTR_INIT(attr, discord_user, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/users/%" PRIu64, user_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/users/%" PRIu64, user_id); } CCORDcode @@ -37,8 +37,8 @@ discord_modify_current_user(struct discord *client, struct discord_modify_current_user *params, struct discord_ret_user *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, params != NULL, CCORD_BAD_PARAMETER, ""); @@ -46,22 +46,22 @@ discord_modify_current_user(struct discord *client, body.size = discord_modify_current_user_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_user, ret); + DISCORD_ATTR_INIT(attr, discord_user, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/users/@me"); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/users/@me"); } CCORDcode discord_get_current_user_guilds(struct discord *client, struct discord_ret_guilds *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; - DISCORD_REQ_LIST_INIT(req, discord_guilds, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_guilds, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/users/@me/guilds"); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/users/@me/guilds"); } CCORDcode @@ -69,15 +69,15 @@ discord_leave_guild(struct discord *client, u64snowflake guild_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body = { "{}", 2 }; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body = { "{}", 2 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_DELETE, - "/users/@me/guilds/%" PRIu64, guild_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_DELETE, + "/users/@me/guilds/%" PRIu64, guild_id); } CCORDcode @@ -85,8 +85,8 @@ discord_create_dm(struct discord *client, struct discord_create_dm *params, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[128]; CCORD_EXPECT(client, params != NULL, CCORD_BAD_PARAMETER, ""); @@ -94,10 +94,10 @@ discord_create_dm(struct discord *client, body.size = discord_create_dm_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/users/@me/channels"); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/users/@me/channels"); } CCORDcode @@ -105,8 +105,8 @@ discord_create_group_dm(struct discord *client, struct discord_create_group_dm *params, struct discord_ret_channel *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, params != NULL, CCORD_BAD_PARAMETER, ""); @@ -117,20 +117,20 @@ discord_create_group_dm(struct discord *client, body.size = discord_create_group_dm_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_channel, ret); + DISCORD_ATTR_INIT(attr, discord_channel, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/users/@me/channels"); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/users/@me/channels"); } CCORDcode discord_get_user_connections(struct discord *client, struct discord_ret_connections *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; - DISCORD_REQ_LIST_INIT(req, discord_connections, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_connections, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/users/@me/connections"); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/users/@me/connections"); } diff --git a/src/voice.c b/src/voice.c index e60a20a21..e0d006c56 100644 --- a/src/voice.c +++ b/src/voice.c @@ -10,10 +10,10 @@ CCORDcode discord_list_voice_regions(struct discord *client, struct discord_ret_voice_regions *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; - DISCORD_REQ_LIST_INIT(req, discord_voice_regions, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_voice_regions, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/voice/regions"); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/voice/regions"); } diff --git a/src/webhook.c b/src/webhook.c index 40a057a1c..8b60f150b 100644 --- a/src/webhook.c +++ b/src/webhook.c @@ -12,8 +12,8 @@ discord_create_webhook(struct discord *client, struct discord_create_webhook *params, struct discord_ret_webhook *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); @@ -23,10 +23,10 @@ discord_create_webhook(struct discord *client, body.size = discord_create_webhook_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_webhook, ret); + DISCORD_ATTR_INIT(attr, discord_webhook, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_POST, - "/channels/%" PRIu64 "/webhooks", channel_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_POST, + "/channels/%" PRIu64 "/webhooks", channel_id); } CCORDcode @@ -34,14 +34,14 @@ discord_get_channel_webhooks(struct discord *client, u64snowflake channel_id, struct discord_ret_webhooks *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, channel_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_webhooks, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_webhooks, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/channels/%" PRIu64 "/webhooks", channel_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/channels/%" PRIu64 "/webhooks", channel_id); } CCORDcode @@ -49,14 +49,14 @@ discord_get_guild_webhooks(struct discord *client, u64snowflake guild_id, struct discord_ret_webhooks *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, guild_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_LIST_INIT(req, discord_webhooks, ret); + DISCORD_ATTR_LIST_INIT(attr, discord_webhooks, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/guilds/%" PRIu64 "/webhooks", guild_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/guilds/%" PRIu64 "/webhooks", guild_id); } CCORDcode @@ -64,14 +64,14 @@ discord_get_webhook(struct discord *client, u64snowflake webhook_id, struct discord_ret_webhook *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_webhook, ret); + DISCORD_ATTR_INIT(attr, discord_webhook, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/webhooks/%" PRIu64, webhook_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/webhooks/%" PRIu64, webhook_id); } CCORDcode @@ -80,17 +80,17 @@ discord_get_webhook_with_token(struct discord *client, const char webhook_token[], struct discord_ret_webhook *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(webhook_token), CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_webhook, ret); + DISCORD_ATTR_INIT(attr, discord_webhook, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/webhooks/%" PRIu64 "/%s", webhook_id, - webhook_token); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/webhooks/%" PRIu64 "/%s", webhook_id, + webhook_token); } CCORDcode @@ -99,8 +99,8 @@ discord_modify_webhook(struct discord *client, struct discord_modify_webhook *params, struct discord_ret_webhook *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); @@ -108,10 +108,10 @@ discord_modify_webhook(struct discord *client, body.size = discord_modify_webhook_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_webhook, ret); + DISCORD_ATTR_INIT(attr, discord_webhook, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/webhooks/%" PRIu64, webhook_id); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/webhooks/%" PRIu64, webhook_id); } CCORDcode @@ -122,8 +122,8 @@ discord_modify_webhook_with_token( struct discord_modify_webhook_with_token *params, struct discord_ret_webhook *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; char buf[1024]; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); @@ -134,11 +134,11 @@ discord_modify_webhook_with_token( discord_modify_webhook_with_token_to_json(buf, sizeof(buf), params); body.start = buf; - DISCORD_REQ_INIT(req, discord_webhook, ret); + DISCORD_ATTR_INIT(attr, discord_webhook, ret); - return discord_adapter_run(&client->adapter, &req, &body, HTTP_PATCH, - "/webhooks/%" PRIu64 "/%s", webhook_id, - webhook_token); + return discord_rest_run(&client->rest, &attr, &body, HTTP_PATCH, + "/webhooks/%" PRIu64 "/%s", webhook_id, + webhook_token); } CCORDcode @@ -146,14 +146,14 @@ discord_delete_webhook(struct discord *client, u64snowflake webhook_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/webhooks/%" PRIu64, webhook_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/webhooks/%" PRIu64, webhook_id); } CCORDcode @@ -162,17 +162,17 @@ discord_delete_webhook_with_token(struct discord *client, const char webhook_token[], struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(webhook_token), CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/webhooks/%" PRIu64 "/%s", webhook_id, - webhook_token); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/webhooks/%" PRIu64 "/%s", webhook_id, + webhook_token); } CCORDcode @@ -182,8 +182,8 @@ discord_execute_webhook(struct discord *client, struct discord_execute_webhook *params, struct discord_ret *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; enum http_method method; char buf[16384]; /**< @todo dynamic buffer */ char query[4096] = ""; @@ -210,17 +210,17 @@ discord_execute_webhook(struct discord *client, if (params->attachments) { method = HTTP_MIMEPOST; - req.attachments = *params->attachments; + attr.attachments = *params->attachments; } else { method = HTTP_POST; } - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, &body, method, - "/webhooks/%" PRIu64 "/%s%s%s", webhook_id, - webhook_token, *query ? "?" : "", query); + return discord_rest_run(&client->rest, &attr, &body, method, + "/webhooks/%" PRIu64 "/%s%s%s", webhook_id, + webhook_token, *query ? "?" : "", query); } CCORDcode @@ -230,18 +230,18 @@ discord_get_webhook_message(struct discord *client, u64snowflake message_id, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(webhook_token), CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_GET, - "/webhooks/%" PRIu64 "/%s/%" PRIu64, webhook_id, - webhook_token, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_GET, + "/webhooks/%" PRIu64 "/%s/%" PRIu64, webhook_id, + webhook_token, message_id); } CCORDcode @@ -252,8 +252,8 @@ discord_edit_webhook_message(struct discord *client, struct discord_edit_webhook_message *params, struct discord_ret_message *ret) { - struct discord_request req = { 0 }; - struct sized_buffer body; + struct discord_attributes attr = { 0 }; + struct ccord_szbuf body; enum http_method method; char buf[16384]; /**< @todo dynamic buffer */ @@ -268,17 +268,17 @@ discord_edit_webhook_message(struct discord *client, if (params->attachments) { method = HTTP_MIMEPOST; - req.attachments = *params->attachments; + attr.attachments = *params->attachments; } else { method = HTTP_PATCH; } - DISCORD_REQ_INIT(req, discord_message, ret); + DISCORD_ATTR_INIT(attr, discord_message, ret); - return discord_adapter_run(&client->adapter, &req, &body, method, - "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, - webhook_id, webhook_token, message_id); + return discord_rest_run(&client->rest, &attr, &body, method, + "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, + webhook_id, webhook_token, message_id); } CCORDcode @@ -288,16 +288,16 @@ discord_delete_webhook_message(struct discord *client, u64snowflake message_id, struct discord_ret *ret) { - struct discord_request req = { 0 }; + struct discord_attributes attr = { 0 }; CCORD_EXPECT(client, webhook_id != 0, CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, NOT_EMPTY_STR(webhook_token), CCORD_BAD_PARAMETER, ""); CCORD_EXPECT(client, message_id != 0, CCORD_BAD_PARAMETER, ""); - DISCORD_REQ_BLANK_INIT(req, ret); + DISCORD_ATTR_BLANK_INIT(attr, ret); - return discord_adapter_run(&client->adapter, &req, NULL, HTTP_DELETE, - "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, - webhook_id, webhook_token, message_id); + return discord_rest_run(&client->rest, &attr, NULL, HTTP_DELETE, + "/webhooks/%" PRIu64 "/%s/messages/%" PRIu64, + webhook_id, webhook_token, message_id); } diff --git a/test/Makefile b/test/Makefile index c62251b40..141e463da 100644 --- a/test/Makefile +++ b/test/Makefile @@ -2,27 +2,24 @@ TOP = .. CC ?= gcc -COGUTILS_DIR := $(TOP)/cog-utils -CORE_DIR := $(TOP)/core -INCLUDE_DIR := $(TOP)/include -GENCODECS_DIR := $(TOP)/gencodecs +COGUTILS_DIR = $(TOP)/cog-utils +CORE_DIR = $(TOP)/core +INCLUDE_DIR = $(TOP)/include +GENCODECS_DIR = $(TOP)/gencodecs -TEST_DISCORD := rest sync async timeout -TEST_CORE := user-agent websockets +TEST_DISCORD = racecond rest timeout +TEST_CORE = user-agent websockets EXES := $(TEST_DISCORD) $(TEST_GITHUB) $(TEST_CORE) CFLAGS = -I$(INCLUDE_DIR) -I$(COGUTILS_DIR) -I$(CORE_DIR) \ -I$(CORE_DIR)/third-party -I$(GENCODECS_DIR) \ -O0 -g -pthread -Wall -LDFLAGS = -ldiscord -L$(TOP)/lib -lcurl +LDFLAGS = -L$(TOP)/lib +LDLIBS = -ldiscord -lcurl all: $(EXES) -.SUFFIXES: -.DEFAULT: - $(CC) $(CFLAGS) -o $@ $@.c $(LDFLAGS) - echo: @ echo -e 'CC: $(CC)\n' @ echo -e 'EXES: $(EXES)\n' diff --git a/test/async.c b/test/async.c deleted file mode 100644 index 4450e98a7..000000000 --- a/test/async.c +++ /dev/null @@ -1,201 +0,0 @@ -#include -#include -#include /* strcmp() */ -#include -#include - -#include "discord.h" - -struct user_cxt { - u64snowflake channel_id; - unsigned long long counter; -}; - -void -on_ready(struct discord *client) -{ - const struct discord_user *bot = discord_get_self(client); - - log_info("Succesfully connected to Discord as %s#%s!", bot->username, - bot->discriminator); -} - -void -disconnect(struct discord *client, - void *data, - const struct discord_message *msg) -{ - (void)data; - (void)msg; - discord_shutdown(client); -} - -void -reconnect(struct discord *client, - void *data, - const struct discord_message *msg) -{ - (void)data; - (void)msg; - discord_reconnect(client, true); -} - -void -on_disconnect(struct discord *client, const struct discord_message *msg) -{ - if (msg->author->bot) return; - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = "Disconnecting ...", - }, - &(struct discord_ret_message){ - .done = &disconnect, - .high_p = true, - }); -} - -void -on_reconnect(struct discord *client, const struct discord_message *msg) -{ - if (msg->author->bot) return; - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = "Reconnecting ...", - }, - &(struct discord_ret_message){ - .done = &reconnect, - .high_p = true, - }); -} - -void -on_single(struct discord *client, const struct discord_message *msg) -{ - if (msg->author->bot) return; - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = "Hello", - }, - NULL); -} - -void -send_batch(struct discord *client, - void *data, - const struct discord_message *msg) -{ - char text[32]; - - for (int i = 0; i < 128; ++i) { - snprintf(text, sizeof(text), "%d", i); - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = text, - }, - NULL); - } - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = "CHECKPOINT", - }, - &(struct discord_ret_message){ - .done = &send_batch, - }); -} - -void -on_spam(struct discord *client, const struct discord_message *msg) -{ - send_batch(client, NULL, msg); -} - -void -send_msg(struct discord *client, void *data, const struct discord_message *msg) -{ - struct user_cxt *cxt = discord_get_data(client); - char text[32]; - - snprintf(text, sizeof(text), "%llu", cxt->counter); - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = text, - }, - &(struct discord_ret_message){ - .done = &send_msg, - }); - - ++cxt->counter; -} - -void -on_spam_ordered(struct discord *client, const struct discord_message *msg) -{ - send_msg(client, NULL, msg); -} - -void -send_err(struct discord *client, CCORDcode code, void *data) -{ - u64snowflake channel_id = *(u64snowflake *)data; - - discord_create_message( - client, channel_id, - &(struct discord_create_message){ - .content = (char *)discord_strerror(code, client), - }, - NULL); -} - -void -on_force_error(struct discord *client, const struct discord_message *msg) -{ - const u64snowflake FAUX_CHANNEL_ID = 123; - u64snowflake *channel_id = malloc(sizeof(u64snowflake)); - - memcpy(channel_id, &msg->channel_id, sizeof(u64snowflake)); - - discord_delete_channel(client, FAUX_CHANNEL_ID, - &(struct discord_ret_channel){ - .fail = &send_err, - .data = channel_id, - .cleanup = &free, - }); -} - -int -main(int argc, char *argv[]) -{ - const char *config_file; - if (argc > 1) - config_file = argv[1]; - else - config_file = "../config.json"; - - ccord_global_init(); - - struct discord *client = discord_config_init(config_file); - assert(NULL != client && "Couldn't initialize client"); - - struct user_cxt cxt = { 0 }; - discord_set_data(client, &cxt); - - discord_set_on_ready(client, &on_ready); - - discord_set_prefix(client, "!"); - discord_set_on_command(client, "disconnect", &on_disconnect); - discord_set_on_command(client, "reconnect", &on_reconnect); - discord_set_on_command(client, "single", &on_single); - discord_set_on_command(client, "spam", &on_spam); - discord_set_on_command(client, "spam-ordered", &on_spam_ordered); - discord_set_on_command(client, "force_error", &on_force_error); - - discord_run(client); - - discord_cleanup(client); - ccord_global_cleanup(); -} diff --git a/test/racecond.c b/test/racecond.c new file mode 100644 index 000000000..5629eda5b --- /dev/null +++ b/test/racecond.c @@ -0,0 +1,241 @@ +#include +#include +#include /* strcmp() */ +#include +#include + +#define JSMN_HEADER +#include "jsmn.h" +#include "jsmn-find.h" + +#include "discord.h" + +#define THREADPOOL_SIZE "4" +#define PREFIX "!" + +void +on_ready(struct discord *client, const struct discord_ready *event) +{ + log_info("Succesfully connected to Discord as %s#%s!", + event->user->username, event->user->discriminator); +} + +void +disconnect(struct discord *client, + struct discord_response *resp, + const struct discord_message *msg) +{ + (void)resp; + (void)msg; + discord_shutdown(client); +} + +void +reconnect(struct discord *client, + struct discord_response *resp, + const struct discord_message *msg) +{ + (void)resp; + (void)msg; + discord_reconnect(client, true); +} + +void +on_disconnect(struct discord *client, const struct discord_message *event) +{ + if (event->author->bot) return; + + discord_create_message(client, event->channel_id, + &(struct discord_create_message){ + .content = "Disconnecting ...", + }, + &(struct discord_ret_message){ + .done = &disconnect, + .high_priority = true, + }); +} + +void +on_reconnect(struct discord *client, const struct discord_message *event) +{ + if (event->author->bot) return; + + discord_create_message(client, event->channel_id, + &(struct discord_create_message){ + .content = "Reconnecting ...", + }, + &(struct discord_ret_message){ + .done = &reconnect, + .high_priority = true, + }); +} + +void +on_single(struct discord *client, const struct discord_message *event) +{ + if (event->author->bot) return; + + discord_create_message(client, event->channel_id, + &(struct discord_create_message){ + .content = "Hello", + }, + NULL); +} + +void +on_spam_sync(struct discord *client, const struct discord_message *event) +{ + if (event->author->bot) return; + + char text[32]; + + for (int i = 0; i < 128; ++i) { + snprintf(text, sizeof(text), "%d", i); + discord_create_message(client, event->channel_id, + &(struct discord_create_message){ + .content = text, + }, + &(struct discord_ret_message){ + .sync = DISCORD_SYNC_FLAG, + }); + } + + discord_create_message(client, event->channel_id, + &(struct discord_create_message){ + .content = "CHECKPOINT", + }, + &(struct discord_ret_message){ + .sync = DISCORD_SYNC_FLAG, + }); +} + +void +send_batch(struct discord *client, + struct discord_response *resp, + const struct discord_message *msg) +{ + (void)resp; + char text[32]; + + for (int i = 0; i < 128; ++i) { + snprintf(text, sizeof(text), "%d", i); + discord_create_message(client, msg->channel_id, + &(struct discord_create_message){ + .content = text, + }, + NULL); + } + + discord_create_message(client, msg->channel_id, + &(struct discord_create_message){ + .content = "CHECKPOINT", + }, + &(struct discord_ret_message){ + .done = &send_batch, + }); +} + +void +on_spam_async(struct discord *client, const struct discord_message *event) +{ + send_batch(client, NULL, event); +} + +void +fail_delete_channel(struct discord *client, struct discord_response *resp) +{ + const struct discord_message *event = resp->keep; + + discord_create_message( + client, event->channel_id, + &(struct discord_create_message){ + .content = (char *)discord_strerror(resp->code, client), + }, + NULL); +} + +void +on_force_error(struct discord *client, const struct discord_message *event) +{ + const u64snowflake FAUX_CHANNEL_ID = 123; + + discord_delete_channel(client, FAUX_CHANNEL_ID, + &(struct discord_ret_channel){ + .fail = &fail_delete_channel, + .keep = event, + }); +} + +enum discord_event_scheduler +scheduler(struct discord *client, + const char data[], + size_t size, + enum discord_gateway_events event) +{ + if (event == DISCORD_EV_MESSAGE_CREATE) { + char cmd[DISCORD_MAX_MESSAGE_LEN] = ""; + + jsmntok_t *tokens = NULL; + unsigned ntokens = 0; + jsmn_parser parser; + + jsmn_init(&parser); + if (0 < jsmn_parse_auto(&parser, data, size, &tokens, &ntokens)) { + jsmnf_pair *pairs = NULL; + unsigned npairs = 0; + jsmnf_loader loader; + + jsmnf_init(&loader); + if (0 < jsmnf_load_auto(&loader, data, tokens, parser.toknext, + &pairs, &npairs)) + { + jsmnf_pair *f; + + if ((f = jsmnf_find(pairs, data, "content", 7))) + snprintf(cmd, sizeof(cmd), "%.*s", (int)f->v.len, + data + f->v.pos); + free(pairs); + } + free(tokens); + } + + if (0 == strcmp(PREFIX "spam_sync", cmd)) + return DISCORD_EVENT_WORKER_THREAD; + } + return DISCORD_EVENT_MAIN_THREAD; +} + +int +main(int argc, char *argv[]) +{ + const char *config_file; + if (argc > 1) + config_file = argv[1]; + else + config_file = "../config.json"; + + setenv("CCORD_THREADPOOL_SIZE", THREADPOOL_SIZE, 1); + setenv("CCORD_THREADPOOL_QUEUE_SIZE", "128", 1); + + ccord_global_init(); + + struct discord *client = discord_config_init(config_file); + assert(NULL != client && "Couldn't initialize client"); + + discord_set_event_scheduler(client, &scheduler); + + discord_set_on_ready(client, &on_ready); + + discord_set_prefix(client, PREFIX); + discord_set_on_command(client, "disconnect", &on_disconnect); + discord_set_on_command(client, "reconnect", &on_reconnect); + discord_set_on_command(client, "single", &on_single); + discord_set_on_command(client, "spam_sync", &on_spam_sync); + discord_set_on_command(client, "spam_async", &on_spam_async); + discord_set_on_command(client, "force_error", &on_force_error); + + discord_run(client); + + discord_cleanup(client); + ccord_global_cleanup(); +} diff --git a/test/rest.c b/test/rest.c index 7f6a12e08..a7c5e09b4 100644 --- a/test/rest.c +++ b/test/rest.c @@ -51,6 +51,8 @@ check_sync_fetch_nothing(void *data) u64snowflake ch_id = *(u64snowflake *)data; struct discord_ret ret = { 0 }; + if (!ch_id) SKIPm("Missing channel_id from config.json"); + ret.sync = true; ASSERT_EQ(CCORD_OK, discord_trigger_typing_indicator(CLIENT, ch_id, &ret)); @@ -71,10 +73,11 @@ check_sync_trigger_error_on_bogus_parameter(void) SUITE(synchronous) { - /* get test-channel id */ - struct logconf *conf = discord_get_logconf(CLIENT); char *path[] = { "test", "channel_id" }; - struct sized_buffer json = logconf_get_field(conf, path, 2); + + /* get test-channel id */ + struct ccord_szbuf_readonly json = + discord_config_get_field(CLIENT, path, 2); u64snowflake channel_id = strtoull(json.start, NULL, 10); RUN_TEST(check_sync_fetch_object); @@ -84,26 +87,23 @@ SUITE(synchronous) } void -on_done(struct discord *client, void *data) +on_done(struct discord *client, struct discord_response *resp) { - *(CCORDcode *)data = CCORD_OK; + *(CCORDcode *)resp->data = resp->code; discord_shutdown(client); } void -on_done1(struct discord *client, void *data, const void *obj) +on_done1(struct discord *client, + struct discord_response *resp, + const void *obj) { - on_done(client, data); + (void)obj; + on_done(client, resp); } -#define DONE1_CAST(_type) void (*)(struct discord *, void *, const _type *) - -void -on_fail(struct discord *client, CCORDcode code, void *data) -{ - *(CCORDcode *)data = code; - discord_shutdown(client); -} +#define DONE1_CAST(_type) \ + void (*)(struct discord *, struct discord_response *, const _type *) TEST check_async_fetch_object(void) @@ -112,7 +112,7 @@ check_async_fetch_object(void) CCORDcode result = CCORD_OK; ret.done = (DONE1_CAST(struct discord_user))on_done1; - ret.fail = on_fail; + ret.fail = on_done; ret.data = &result; discord_get_current_user(CLIENT, &ret); @@ -129,7 +129,7 @@ check_async_fetch_array(void) CCORDcode result = CCORD_OK; ret.done = (DONE1_CAST(struct discord_guilds))on_done1; - ret.fail = on_fail; + ret.fail = on_done; ret.data = &result; discord_get_current_user_guilds(CLIENT, &ret); @@ -146,8 +146,9 @@ check_async_fetch_nothing(void *data) struct discord_ret ret = { 0 }; CCORDcode result = CCORD_OK; - ret.done = on_done; - ret.fail = on_fail; + if (!ch_id) SKIPm("Missing channel_id from config.json"); + + ret.fail = ret.done = on_done; ret.data = &result; discord_trigger_typing_indicator(CLIENT, ch_id, &ret); @@ -165,7 +166,7 @@ check_async_trigger_error_on_bogus_parameter(void) CCORDcode result = CCORD_OK; ret.done = (DONE1_CAST(struct discord_channel))on_done1; - ret.fail = on_fail; + ret.fail = on_done; ret.data = &result; discord_delete_channel(CLIENT, BOGUS_ID, &ret); @@ -177,10 +178,11 @@ check_async_trigger_error_on_bogus_parameter(void) SUITE(asynchronous) { - /* get test-channel id */ - struct logconf *conf = discord_get_logconf(CLIENT); char *path[] = { "test", "channel_id" }; - struct sized_buffer json = logconf_get_field(conf, path, 2); + + /* get test-channel id */ + struct ccord_szbuf_readonly json = + discord_config_get_field(CLIENT, path, 2); u64snowflake channel_id = strtoull(json.start, NULL, 10); RUN_TEST(check_async_fetch_object); diff --git a/test/sync.c b/test/sync.c deleted file mode 100644 index 69ec7c193..000000000 --- a/test/sync.c +++ /dev/null @@ -1,283 +0,0 @@ -#include -#include -#include /* strcmp() */ -#include -#include - -#define JSMN_HEADER -#include "jsmn.h" -#include "jsmn-find.h" - -#include "discord.h" - -#define THREADPOOL_SIZE "4" -#define PREFIX "!" - -pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; -bool g_keep_spamming = true; -unsigned g_thread_count; - -void -on_ready(struct discord *client) -{ - const struct discord_user *bot = discord_get_self(client); - - log_info("Succesfully connected to Discord as %s#%s!", bot->username, - bot->discriminator); -} - -void -on_disconnect(struct discord *client, const struct discord_message *msg) -{ - if (msg->author->bot) return; - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = "Disconnecting ...", - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); - - discord_shutdown(client); -} - -void -on_reconnect(struct discord *client, const struct discord_message *msg) -{ - if (msg->author->bot) return; - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = "Reconnecting ...", - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); - - discord_reconnect(client, true); -} - -void -on_spam(struct discord *client, const struct discord_message *msg) -{ - const unsigned threadpool_size = strtol(THREADPOOL_SIZE, NULL, 10); - - if (msg->author->bot) return; - - // prevent blocking all threads - pthread_mutex_lock(&g_lock); - if (g_thread_count >= threadpool_size - 1) { - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = - "Too many threads (" THREADPOOL_SIZE - ") will block the threadpool!", - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); - - pthread_mutex_unlock(&g_lock); - return; - } - - ++g_thread_count; - g_keep_spamming = true; - pthread_mutex_unlock(&g_lock); - - char number[256]; - bool keep_alive = true; - for (int i = 0;; ++i) { - pthread_mutex_lock(&g_lock); - keep_alive = g_keep_spamming; - pthread_mutex_unlock(&g_lock); - - if (!keep_alive) break; - - snprintf(number, sizeof(number), "%d", i); - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = number, - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); - } -} - -void -on_spam_block(struct discord *client, const struct discord_message *msg) -{ - if (msg->author->bot) return; - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = "No 1", - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); -} - -void -on_spam_block_continue(struct discord *client, - const struct discord_message *msg) -{ - const struct discord_user *bot = discord_get_self(client); - char text[32]; - int number; - - if (msg->author->id != bot->id) return; - - sscanf(msg->content, "No %d", &number); - snprintf(text, sizeof(text), "No %d", 1 + number); - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = text, - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); -} - -void -on_stop(struct discord *client, const struct discord_message *msg) -{ - if (msg->author->bot) return; - - pthread_mutex_lock(&g_lock); - g_keep_spamming = false; - g_thread_count = 0; - pthread_mutex_unlock(&g_lock); -} - -void -on_force_error(struct discord *client, const struct discord_message *msg) -{ - const u64snowflake FAUX_CHANNEL_ID = 123ULL; - CCORDcode code; - - if (msg->author->bot) return; - - code = discord_delete_channel(client, FAUX_CHANNEL_ID, - &(struct discord_ret_channel){ - .sync = DISCORD_SYNC_FLAG, - }); - assert(code != CCORD_OK); - - discord_create_message( - client, msg->channel_id, - &(struct discord_create_message){ - .content = (char *)discord_strerror(code, client), - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); -} - -void -on_ping(struct discord *client, const struct discord_message *msg) -{ - char text[256]; - - if (msg->author->bot) return; - - sprintf(text, "Ping: %d", discord_get_ping(client)); - - discord_create_message(client, msg->channel_id, - &(struct discord_create_message){ - .content = text, - }, - &(struct discord_ret_message){ - .sync = DISCORD_SYNC_FLAG, - }); -} - -enum discord_event_scheduler -scheduler(struct discord *client, - const char data[], - size_t size, - enum discord_gateway_events event) -{ - if (event == DISCORD_GATEWAY_EVENTS_MESSAGE_CREATE) { - char cmd[1024] = ""; - - jsmntok_t *tokens = NULL; - unsigned ntokens = 0; - jsmn_parser parser; - - jsmn_init(&parser); - if (0 < jsmn_parse_auto(&parser, data, size, &tokens, &ntokens)) { - jsmnf_pair *pairs = NULL; - unsigned npairs = 0; - jsmnf_loader loader; - - jsmnf_init(&loader); - if (0 < jsmnf_load_auto(&loader, data, tokens, parser.toknext, - &pairs, &npairs)) - { - jsmnf_pair *f; - - if ((f = jsmnf_find(pairs, data, "content", 7))) - snprintf(cmd, sizeof(cmd), "%.*s", (int)f->v.len, - data + f->v.pos); - free(pairs); - } - free(tokens); - } - - if (0 == strcmp(PREFIX "ping", cmd) - || 0 == strcmp(PREFIX "spam-block", cmd)) { - return DISCORD_EVENT_MAIN_THREAD; - } - else if (0 == strncmp("No", cmd, 2)) { - struct discord_message msg = { 0 }; - - discord_message_from_json(data, size, &msg); - on_spam_block_continue(client, &msg); - discord_message_cleanup(&msg); - - return DISCORD_EVENT_IGNORE; - } - } - - return DISCORD_EVENT_WORKER_THREAD; -} - -int -main(int argc, char *argv[]) -{ - const char *config_file; - if (argc > 1) - config_file = argv[1]; - else - config_file = "../config.json"; - - setenv("CCORD_THREADPOOL_SIZE", THREADPOOL_SIZE, 1); - setenv("CCORD_THREADPOOL_QUEUE_SIZE", "128", 1); - - ccord_global_init(); - struct discord *client = discord_config_init(config_file); - assert(NULL != client && "Couldn't initialize client"); - - /* trigger event callbacks in a multi-threaded fashion */ - discord_set_event_scheduler(client, &scheduler); - - discord_set_on_ready(client, &on_ready); - - discord_set_prefix(client, PREFIX); - discord_set_on_command(client, "disconnect", &on_disconnect); - discord_set_on_command(client, "reconnect", &on_reconnect); - discord_set_on_command(client, "spam", &on_spam); - discord_set_on_command(client, "spam-block", &on_spam_block); - discord_set_on_command(client, "stop", &on_stop); - discord_set_on_command(client, "force_error", &on_force_error); - discord_set_on_command(client, "ping", &on_ping); - - discord_run(client); - - discord_cleanup(client); - ccord_global_cleanup(); -} diff --git a/test/user-agent.c b/test/user-agent.c index 7295e1617..7d953e303 100644 --- a/test/user-agent.c +++ b/test/user-agent.c @@ -16,7 +16,6 @@ commit(char *base_url, struct logconf *conf) struct user_agent *ua; struct ua_resp_handle handle = { .ok_cb = load, .ok_obj = NULL }; - struct sized_buffer body = { .start = "{ }", .size = 3 }; struct ua_conn_attr conn_attr = { 0 }; struct ua_info info = { 0 }; @@ -25,7 +24,8 @@ commit(char *base_url, struct logconf *conf) ua = ua_init(&ua_attr); ua_set_url(ua, base_url); - conn_attr.body = &body; + conn_attr.body = "{ }"; + conn_attr.body_size = 3; conn_attr.method = HTTP_POST; conn_attr.endpoint = "/echo?m=POST";