aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig12
-rw-r--r--README.md3
-rw-r--r--connect.sh112
-rwxr-xr-xexample/simple.sh14
-rw-r--r--load.sh12
-rw-r--r--logger.sh10
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
diff --git a/load.sh b/load.sh
new file mode 100644
index 0000000..7e7f0af
--- /dev/null
+++ b/load.sh
@@ -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"
+}