diff options
-rw-r--r-- | .editorconfig | 12 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | connect.sh | 112 | ||||
-rwxr-xr-x | example/simple.sh | 14 | ||||
-rw-r--r-- | load.sh | 12 | ||||
-rw-r--r-- | logger.sh | 10 |
6 files changed, 163 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..038cd8b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ + +root = true + + +[*.sh] +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 4 + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..61e9f3c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Atrocity + +Discord bot development library for bash diff --git a/connect.sh b/connect.sh new file mode 100644 index 0000000..73d45ba --- /dev/null +++ b/connect.sh @@ -0,0 +1,112 @@ +_atrocity_prepare() { + ATROCITY_SESSION="$(mktemp -d)" + export ATROCITY_SESSION + atrocity_debug "Preparing session in $ATROCITY_SESSION" + # shellcheck disable=SC2064 + trap "rm -fr '$ATROCITY_SESSION'" EXIT + mkdir "$ATROCITY_SESSION"/online + printf null >"$ATROCITY_SESSION"/sequence + mkdir "$ATROCITY_SESSION"/write-queue + mkdir "$ATROCITY_SESSION"/write-buffer + echo 10 >"$ATROCITY_SESSION"/heartbeat-interval +} + +atrocity_connect() { + if [ -e "$ATROCITY_SESSION"/connection-started ]; then + atrocity_debug "Atrocity loop has already been set up" + exit 1 + fi + touch "$ATROCITY_SESSION"/connection-started + mkfifo "$ATROCITY_SESSION"/raw-write-queue + mkfifo "$ATROCITY_SESSION"/raw-read-queue + + ( + atrocity_debug "Write queue started" "$ATROCITY_SESSION/raw-write-queue" + while atrocity_is_online; do + find "$ATROCITY_SESSION"/write-queue -type l | + while read -r message; do + cat "$message" + atrocity_debug "Sending message $message: $(cat -- "$message")" + rm -f "$(readlink -f "$message")" + rm -f "$message" + done + done >"$ATROCITY_SESSION/raw-write-queue" + atrocite_debug "Exiting write queue" + ) & + ( + atrocity_debug "Heartbeat queue started" + while atrocity_is_online; do + atrocity_heartbeat + sleep "$(cat "$ATROCITY_SESSION"/heartbeat-interval)" + done + ) & + + ( + atrocity_debug "Websocket connected" + websocat wss://gateway.discord.gg/ + atrocity_debug "Websocket disconnected" + ) <"$ATROCITY_SESSION/raw-write-queue" >"$ATROCITY_SESSION/raw-read-queue" & +} +atrocity_heartbeat() { + atrocity_debug "Heartbeating" + atrocity_gateway_send_raw '{"op":1,"d":'"$(atrocity_sequence_get)"'}' +} + +atrocity_gateway_send_raw() { + local file + + file="$(mktemp -p "$ATROCITY_SESSION"/write-buffer)" + printf "%s\n" "${*}" >"$file" + ln -s "$file" "$ATROCITY_SESSION/write-queue" +} +atrocity_sequence_get() { + cat "$ATROCITY_SESSION"/sequence +} + +atrocity_is_online() { + [[ -d "$ATROCITY_SESSION"/online ]] + return $? +} + +atrocity_loop() { + if ! [ -e "$ATROCITY_SESSION"/connection-started ]; then + atrocity_debug "Atrocity loop started before connection has been set up" + exit 1 + fi + atrocity_debug "Loop started" + while true; do + local line + local operator + atrocity_debug "Trying to read line" + if read -r line; then + atrocity_debug "Reading message from discord: $line" + + printf %s "$line" | jq 'if .s then .s else '"$(atrocity_sequence_get)"' end' >"$ATROCITY_SESSION"/sequence + operator="$(printf %s "$line" | jq .op)" + case "$operator" in + 9) # Invalid session + atrocity_debug "Received invalid session: $line" + exit 1 + ;; + 10) # Hello + atrocity_debug "Sending hello" + echo "$(($(printf "%s" "$line" | jq -r .d.heartbeat_interval) / 1000))" >"$ATROCITY_SESSION"/heartbeat-interval + atrocity_gateway_send_raw '{"op":2,"d":{"token":"'$ATROCITY_TOKEN'", "properties":{"os":"linux","browser":"bash","device":"bash"},"presence":{"status":"online","afk":false, "activities":[{"type":0,"name":"Being coded in Bash"}]}, "intents":33287}}' + ;; + 0) # Dispatch + atrocity_dispatch "$line" + ;; + 1) # Heartbeat + atrocity_heartbeat + ;; + 11) # Heartbeat ACK + # TODO: Check for zombie connections + atrocity_debug "Heartbeat ACK" + ;; + *) + atrocity_debug "Unhandled operator $operator" + ;; + esac + fi + done <"$ATROCITY_SESSION"/raw-read-queue +} diff --git a/example/simple.sh b/example/simple.sh new file mode 100755 index 0000000..c90c451 --- /dev/null +++ b/example/simple.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +source "$(dirname -- "$0")"/../load.sh + +atrocity_debug Simple example loaded + +export ATROCITY_TOKEN="MzEyMjU2ODcxMTc0MTExMjMz.GPe_v6.4Acwm7tje3sMEvUu05NPyQrZalh8knTIHPgLmk" + +atrocity_dispatch() { + echo "Dispatching $1" +} + +atrocity_connect + +atrocity_loop @@ -0,0 +1,12 @@ +_atrocity_init_file="${BASH_SOURCE[0]}" +if [ "$(basename -- "$_atrocity_init_file")" != "load.sh" ] || [ "${#BASH_SOURCE[@]}" -lt 2 ]; then + echo "ERROR: Atrocity must be sourced. If you have atrocity in a sub module, use \`source \"\$(dirname -- \"\$0\")\"/atrocity/load.sh\`" + exit 1 +fi +_atrocity_base="$(dirname -- "$(readlink -f -- _atrocity_init_file)")" + +source "$_atrocity_base"/logger.sh +source "$_atrocity_base"/connect.sh + +atrocity_debug "Loaded atrocity from $_atrocity_base" +_atrocity_prepare diff --git a/logger.sh b/logger.sh new file mode 100644 index 0000000..600f0ba --- /dev/null +++ b/logger.sh @@ -0,0 +1,10 @@ +_atrocity_log_output="${ATROCITY_LOG:-$(tty)}" +if [ "$_atrocity_log_output" = "not a tty" ]; then + exec {_atrocity_log_descriptor}>&1 +else + exec {_atrocity_log_descriptor}>"$_atrocity_log_output" +fi + +atrocity_debug() { + printf 'DBG : %s\n' "${*}" >&"$_atrocity_log_descriptor" +} |