libdiscord

libdiscord.git
git clone git://git.lenczewski.org/libdiscord.git
Log | Files | Refs | README | LICENSE

commit 8119a66137fbc20a624ecfd47b10e72ac3b2d422
Author: Mikołaj Lenczewski <mblenczewski@gmail.com>
Date:   Sat,  7 Jun 2025 15:42:45 +0000

Initial commit

Diffstat:
A.editorconfig | 22++++++++++++++++++++++
A.gitignore | 7+++++++
ALICENSE | 18++++++++++++++++++
AREADME | 11+++++++++++
Abuild.sh | 31+++++++++++++++++++++++++++++++
Aclean.sh | 5+++++
Aextras/testbot.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/libdiscord.h | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arena.c | 7+++++++
Asrc/arena.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/libdiscord.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/list.c | 23+++++++++++++++++++++++
Asrc/list.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/stringview.c | 1+
Asrc/stringview.h | 29+++++++++++++++++++++++++++++
Asrc/utils.c | 3+++
Asrc/utils.h | 8++++++++
17 files changed, 541 insertions(+), 0 deletions(-)

diff --git a/.editorconfig b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# visual studio editorconfig plugin specific settings +guidelines = 80, 120, 160 + +[*.{c,h}] +indent_style = tab +indent_size = 8 + +[*.{sh}] +indent_style = tab +indent_size = 8 + +[*.{conf,json,md,txt}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore @@ -0,0 +1,7 @@ +bin/ +lib/ +obj/ + +rfcs/ + +**/.*.swp diff --git a/LICENSE b/LICENSE @@ -0,0 +1,18 @@ +The MIT-Zero License + +Copyright (c) 2025 Mikołaj Lenczewski <mikolaj@lenczewski.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README b/README @@ -0,0 +1,11 @@ +libdiscord +============================================================================== +A simple discord library, written in C11. + +libdiscord: Building +------------------------------------------------------------------------------ +To build, simply run `./build.sh`. Similarly, to clean, run `./clean.sh`. + +To build with the "-Werror" compiler argument, run `WERROR=1 ./build.sh`. + +To build extra test programs, run `BUILD_EXTRAS=1 ./build.sh`. diff --git a/build.sh b/build.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +CC="${CC:-clang}" +AR="${AR:-llvm-ar}" +RANLIB="${RANLIB:-llvm-ranlib}" + +WARNINGS="-Wall -Wextra -Wpedantic ${WERROR:+-Werror} -Wno-unused-parameter -Wno-format-pedantic" +FLAGS="-std=c11 -Og -g" + +VERSION_MAJOR="$(grep -oe '#define LIBDISCORD_VERSION_MAJOR "\([0-9]\+\)"' include/libdiscord.h | grep -oe '[0-9]\+')" +VERSION_MINOR="$(grep -oe '#define LIBDISCORD_VERSION_MINOR "\([0-9]\+\)"' include/libdiscord.h | grep -oe '[0-9]\+')" +VERSION_PATCH="$(grep -oe '#define LIBDISCORD_VERSION_PATCH "\([0-9]\+\)"' include/libdiscord.h | grep -oe '[0-9]\+')" +VERSION="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" + +DEPS="liburing openssl" +INCS="$(pkg-config --cflags $DEPS)" +LIBS="$(pkg-config --libs $DEPS)" + +set -ex + +mkdir -p bin lib obj + +$CC -o obj/libdiscord.o -c src/libdiscord.c $WARNINGS $FLAGS -Iinclude $INCS +$AR rcs lib/libdiscord.$VERSION.a obj/libdiscord.o +$RANLIB lib/libdiscord.$VERSION.a + +$CC -o bin/testbot extras/testbot.c $WARNINGS $FLAGS \ + -Iinclude $INCS -Llib -ldiscord.$VERSION $LIBS + +[ -z "$BUILD_EXTRAS" ] && exit + diff --git a/clean.sh b/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin lib obj diff --git a/extras/testbot.c b/extras/testbot.c @@ -0,0 +1,60 @@ +#include <alloca.h> +#include <stdio.h> + +#include "libdiscord.h" + +static char buf[8192]; + +int +main(void) +{ + printf("discord version: %s\n", LIBDISCORD_VERSION); + + struct discord *discord = alloca(discord_sizeof()); + + if (discord_init(discord, buf, sizeof buf) < 0) { + fprintf(stderr, "Failed to initialise discord ctx\n"); + return -1; + } + + if (discord_connect_gateway(discord) < 0) { + fprintf(stderr, "Failed to connect to discord gateway\n"); + return -1; + } + + while (1) { + struct discord_event evs[16]; + size_t cap = sizeof evs / sizeof evs[0]; + int len = discord_poll_events(discord, evs, cap); + if (len < 0) { + fprintf(stderr, "error: "); + discord_error(discord); + break; + } + + for (int i = 0; i < len; i++) { + struct discord_event *ev = &evs[i]; + + switch (ev->type) { + case DISCORD_EVENT_API: + printf("got api message: \"%.*s\"\n", + ev->api.len, ev->api.ptr); + break; + + case DISCORD_EVENT_GATEWAY: + printf("got gateway message: \"%.*s\"\n", + ev->gateway.len, + ev->gateway.ptr); + break; + + case DISCORD_EVENT_VOICE: + printf("got voice message!\n"); + break; + } + } + } + + discord_free(discord); + + return 0; +} diff --git a/include/libdiscord.h b/include/libdiscord.h @@ -0,0 +1,112 @@ +#ifndef LIBDISCORD_H +#define LIBDISCORD_H + +#define LIBDISCORD_VERSION_MAJOR "0" +#define LIBDISCORD_VERSION_MINOR "1" +#define LIBDISCORD_VERSION_PATCH "0" + +#define LIBDISCORD_VERSION \ + LIBDISCORD_VERSION_MAJOR "." \ + LIBDISCORD_VERSION_MINOR "." \ + LIBDISCORD_VERSION_PATCH + +#include <stddef.h> +#include <stdint.h> + +struct discord_api_event { + char const *ptr; + int len; +}; + +enum discord_gateway_opcode { + DISCORD_GATEWAY_DISPATCH, + DISCORD_GATEWAY_HEARTBEAT, + DISCORD_GATEWAY_IDENTIFY, + DISCORD_GATEWAY_PRESENCE_UPDATE, + DISCORD_GATEWAY_VOICE_STATE_UPDATE, + DISCORD_GATEWAY_RESUME, + DISCORD_GATEWAY_RECONNECT, + DISCORD_GATEWAY_REQUEST_GUILD_MEMBERS, + DISCORD_GATEWAY_INVALID_SESSION, + DISCORD_GATEWAY_HELLO, + DISCORD_GATEWAY_HEARTBEAT_ACK, + DISCORD_GATEWAY_REQUEST_SOUNDBOARD_SOUNDS, +}; + +enum discord_gateway_error { + DISCORD_GATEWAY_UNKNOWN_ERROR = 4000, + DISCORD_GATEWAY_UNKNOWN_OPCODE = 4001, + DISCORD_GATEWAY_DECODE_ERROR = 4002, + DISCORD_GATEWAY_NOT_AUTHENTICATED = 4003, + DISCORD_GATEWAY_AUTHENTICATION_FAILED = 4004, + DISCORD_GATEWAY_ALREADY_AUTHENTICATED = 4005, + /* reserved */ + DISCORD_GATEWAY_INVALID_SEQ = 4007, + DISCORD_GATEWAY_RATE_LIMITED = 4008, + DISCORD_GATEWAY_SESSION_TIMED_OUT = 4009, + DISCORD_GATEWAY_INVALID_SHARD = 4010, + DISCORD_GATEWAY_SHARDING_REQUIRED = 4011, + DISCORD_GATEWAY_INVALID_API_VERSION = 4012, + DISCORD_GATEWAY_INVALID_INTENTS = 4013, + DISCORD_GATEWAY_DISALLOWED_INTENTS = 4014, +}; + +struct discord_gateway_event { + char const *ptr; + int len; +}; + +struct discord_voice_event { + char const *ptr; + int len; +}; + +enum discord_event_type { + DISCORD_EVENT_API, + DISCORD_EVENT_GATEWAY, + DISCORD_EVENT_VOICE, +}; + +struct discord_event { + enum discord_event_type type; + union { + struct discord_api_event api; + struct discord_gateway_event gateway; + struct discord_voice_event voice; + }; +}; + +struct discord; + +size_t +discord_sizeof(void); + +int +discord_init(struct discord *ctx, void *mem, size_t len); + +int +discord_connect_gateway(struct discord *ctx); + +int +discord_connect_voice(struct discord *ctx); + +int +discord_poll_events(struct discord *ctx, + struct discord_event *evs, size_t cap); + +void +discord_error(struct discord *ctx); + +void +discord_free(struct discord *ctx); + +// discord api calls +// ======================================================================== + +// discord gateway calls +// ======================================================================== + +// discord voice calls +// ======================================================================== + +#endif /* LIBDISCORD_H */ diff --git a/src/arena.c b/src/arena.c @@ -0,0 +1,7 @@ +#include "arena.h" + +extern inline void +arena_reset(struct arena *arena); + +extern inline void * +arena_alloc(struct arena *arena, size_t size, size_t alignment); diff --git a/src/arena.h b/src/arena.h @@ -0,0 +1,51 @@ +#ifndef ARENA_H +#define ARENA_H + +#include <assert.h> +#include <stdalign.h> +#include <stddef.h> +#include <stdint.h> + +#define IS_POW2(v) (((v) & ((v) - 1)) == 0) + +#define IS_ALIGNED(v, align) (((v) & ((align) - 1)) == 0) +#define ALIGN_PREV(v, align) ((v) & ~((align) - 1)) +#define ALIGN_NEXT(v, align) ALIGN_PREV((v) + ((align) - 1), (align)) + +struct arena { + void *ptr; + size_t cap, len; +}; + +inline void +arena_reset(struct arena *arena) +{ + arena->len = 0; +} + +inline void * +arena_alloc(struct arena *arena, size_t size, size_t alignment) +{ + assert(size); + assert(alignment); + assert(IS_POW2(alignment)); + + uintptr_t base = (uintptr_t) arena->ptr; + uintptr_t end = base + arena->cap; + + uintptr_t aligned_ptr = ALIGN_NEXT(base + arena->len, alignment); + if (end < aligned_ptr + size) + return NULL; + + arena->len = (aligned_ptr + size) - base; + + return (void *) aligned_ptr; +} + +#define ARENA_ALLOC_ARRAY(arena, T, n) \ + arena_alloc((arena), sizeof(T) * (n), alignof(T)) + +#define ARENA_ALLOC_SIZED(arena, T) \ + ARENA_ALLOC_ARRAY((arena), T, 1) + +#endif /* ARENA_H */ diff --git a/src/libdiscord.c b/src/libdiscord.c @@ -0,0 +1,54 @@ +#include "libdiscord.h" + +#include "utils.h" + +struct discord { + struct arena arena; +}; + +size_t +discord_sizeof(void) +{ + return sizeof(struct discord); +} + +int +discord_init(struct discord *ctx, void *mem, size_t cap) +{ + ctx->arena.ptr = mem; + ctx->arena.cap = cap; + arena_reset(&ctx->arena); + + return 0; +} + +int +discord_connect_gateway(struct discord *ctx) +{ + return -1; +} + +int +discord_connect_voice(struct discord *ctx) +{ + return -1; +} + +int +discord_poll_events(struct discord *ctx, + struct discord_event *evs, size_t cap) +{ + return -1; +} + +void +discord_error(struct discord *ctx) +{ +} + +void +discord_free(struct discord *ctx) +{ +} + +#include "utils.c" diff --git a/src/list.c b/src/list.c @@ -0,0 +1,23 @@ +#include "list.h" + +extern inline void +list_node_link(struct list_node *node, + struct list_node *prev, + struct list_node *next); + +extern inline struct list_node * +list_node_unlink(struct list_node *node); + +extern inline void +list_push_head(struct list_node *restrict list, + struct list_node *restrict node); + +extern inline void +list_push_tail(struct list_node *restrict list, + struct list_node *restrict node); + +extern inline struct list_node * +list_pop_head(struct list_node *list); + +extern inline struct list_node * +list_pop_tail(struct list_node *list); diff --git a/src/list.h b/src/list.h @@ -0,0 +1,99 @@ +#ifndef LIST_H +#define LIST_H + +#include <stddef.h> +#include <stdint.h> + +#define TYPEOF(v) (__typeof__ (v)) + +#define TO_PARENT_PTR(ptr, T, member) \ + ((T *) (((uintptr_t) (ptr)) - offsetof(T, member))) + +struct list_node { + struct list_node *prev, *next; +}; + +#define LIST_INIT(list) ((struct list_node) { &(list), &(list), }) + +#define LIST_HEAD(list) ((list)->next) +#define LIST_TAIL(list) ((list)->prev) + +#define LIST_EMPTY(list) \ + (LIST_HEAD(list) == (list) && LIST_TAIL(list) == (list)) + +#define LIST_NODE_ITER(list, it) \ + for ((it) = LIST_HEAD(list); (it) != (list); (it) = LIST_HEAD(it)) + +#define LIST_NODE_RITER(list, it) \ + for ((it) = LIST_TAIL(list); (it) != (list); (it) = LIST_TAIL(it)) + +#define LIST_NODE_ENTRY(node, T, member) \ + TO_PARENT_PTR((node), T, member) + +#define LIST_ENTRY_ITER(list, it, member) \ + for ((it) = LIST_NODE_ENTRY(LIST_HEAD(list), \ + __typeof__ (*(it)), \ + member); \ + &(it)->member != (list); \ + (it) = LIST_NODE_ENTRY(LIST_HEAD(&(it)->member), \ + __typeof__ (*(it)), \ + member)) + +#define LIST_ENTRY_RITER(list, it, member) \ + for ((it) = LIST_NODE_ENTRY(LIST_TAIL(list), \ + __typeof__ (*(it)), \ + member); \ + &(it)->member != (list); \ + (it) = LIST_NODE_ENTRY(LIST_TAIL(&(it)->member), \ + __typeof__ (*(it)), \ + member)) + + +inline void +list_node_link(struct list_node *node, + struct list_node *prev, + struct list_node *next) +{ + node->prev = prev; + prev->next = node; + node->next = next; + next->prev = node; +} + +inline struct list_node * +list_node_unlink(struct list_node *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; + return node; +} + +inline void +list_push_head(struct list_node *restrict list, + struct list_node *restrict node) +{ + list_node_link(node, list, LIST_HEAD(list)); +} + +inline void +list_push_tail(struct list_node *restrict list, + struct list_node *restrict node) +{ + list_node_link(node, LIST_TAIL(list), list); +} + +inline struct list_node * +list_pop_head(struct list_node *list) +{ + struct list_node *res = list_node_unlink(LIST_HEAD(list)); + return res; +} + +inline struct list_node * +list_pop_tail(struct list_node *list) +{ + struct list_node *res = list_node_unlink(LIST_TAIL(list)); + return res; +} + +#endif /* LIST_H */ diff --git a/src/stringview.c b/src/stringview.c @@ -0,0 +1 @@ +#include "stringview.h" diff --git a/src/stringview.h b/src/stringview.h @@ -0,0 +1,29 @@ +#ifndef STRINGVIEW_H +#define STRINGVIEW_H + +#include <stddef.h> +#include <string.h> + +struct stringview { + unsigned char *ptr; + size_t len; +}; + +#define FROM_CSTR(cstr) ((struct stringview) { (cstr), strlen(cstr), }) + +inline int +svcmp(struct stringview *a, struct stringview *b) +{ + if (a->len != b->len) + return a->len - b->len; + + return strncmp((char *) a->ptr, (char *) b->ptr, a->len); +} + +inline unsigned char * +svstr(struct stringview *haystack, struct stringview *needle) +{ + return NULL; +} + +#endif /* STRINGVIEW_H */ diff --git a/src/utils.c b/src/utils.c @@ -0,0 +1,3 @@ +#include "arena.c" +#include "list.c" +#include "stringview.c" diff --git a/src/utils.h b/src/utils.h @@ -0,0 +1,8 @@ +#ifndef UTILS_H +#define UTILS_H + +#include "arena.h" +#include "list.h" +#include "stringview.h" + +#endif /* UTILS_H */