#!/usr/bin/env bash cd "${0%/*}" # twitch chat bot # twitch docs reference: https://dev.twitch.tv/docs/irc CHAN=$1 source ~/.twitch_secrets_bot goodcop_ source ../utils/url-parsing MYID=56931496 LOG_FILE="$HOME/data/twitch-logs/$(date "+%Y-%m-%d_%H-%M-%S").log" touch "$LOG_FILE" TUNNEL=/tmp/twitch_tunnel rm -f "$TUNNEL" mkfifo "$TUNNEL" # Example chat msg: # @badge-info=;badges=;client-nonce=31ab40a7bef7162bfc8eade860eb185a;color=#DAA520;display-name=BuddysPizza;emotes=;first-msg=0;flags=;id=e86038a5-4d49-4868-aa96-b563da8fc689;mod=0;room-id=56931496;subscriber=0;tmi-sent-ts=1643181677955;turbo=0;user-id=42089909;user-type= :buddyspizza!buddyspizza@buddyspizza.tmi.twitch.tv PRIVMSG #badcop_ :!help colorize() { printf "\x1b[38;2;$((16#${1:0:2}));$((16#${1:2:2}));$((16#${1:4:2}))m" } reply() { if [[ "$*" == "" ]]; then return fi { colorize "ff0000" echo -n "bot" printf '\033[0m' echo -n ": " echo "$@" } 1>&2 printf "%s\r\n" "PRIVMSG #${CHAN} :$*" } parsecmd() { msg_loud="$msg" msg="${msg,,}" reply_parent="${tags[reply-parent-user-login]}" name="${tags[display-name]}" user_id="${tags[user-id]}" mod="${tags[mod]}" case "$msg" in "!crab") ;& *"🦀"*) reply "🦀🚫🦀🚫🦀🚫🦀🚫" ;; "!ping") reply "pong!" ;; "!boot") reply "check out https://boot.gay/badcop i worked there it is a cool website" ;; "!tau") reply "its a twitch api thing by FiniteSingularity https://github.com/Team-TAU/tau" ;; "!team") ;& "!claw") reply "https://www.twitch.tv/team/theclaw" ;; "!plant") reply "plant. https://badcop.itch.io/plant" ;; "!posix") reply "https://twitter.com/badcop_/status/1692837652477718917" ;; "!aoc") reply "advent of code: https://aoc.cgs.dev/" ;; "!blue") reply "it is my favorite color" ;; "!termsand") reply "https://github.com/cgsdev0/termsand" ;; "!stack") reply "bash stack is a web framework in bash: http://bashsta.cc" ;; "!emotes") ;& "!artist") reply "https://x.com/RiriiMeow" ;; "!botsnack") count=$(cat ~/.twitch_snack_counter) count=$(( count + 1 )) echo "$count" > ~/.twitch_snack_counter if (( $count % 5 == 0 )) then reply "my tummy hurts :(" else reply "yum, thanks :)" fi ;; "!rtfm") ;& "!man") reply "refer to the manual: https://www.youtube.com/watch?v=7taNCDMpPvc" ;; "!vods") reply "https://youtube.com/@badcopVODs" ;; "!yt") ;& "!youtube") ;& "!bashcop") reply "https://youtube.com/@bashcop" ;; "!summoning") ;& "!salmoning") reply "https://clips.twitch.tv/FriendlyGenerousWolfPastaThat-xTl0b_eJ1D94qriv" ;; "!winrar") ;& *"tmux"*) popup-queue 'winrar-tmux-popup | popup -h 13 -w 50' & ;; "!hi") reply "/me waves" ;; "!discord") reply "join our discord! https://discord.badcop.games/" ;; "!theme") reply "tokyonight https://github.com/folke/tokyonight.nvim" ;; "!mouse") reply "Logitech G305 >>>>>>>>" ;; "!keyboard") reply "novelkeys NK65" ;; "!dotfiles") reply "https://github.com/cgsdev0/dotfiles" ;; "!camera") reply "my main camera is a sony a6400 with a sigma 30mm lens" ;; "!font") reply "I use the hack font: https://sourcefoundry.org/hack/" ;; "!pronouns") ;& "!gender") reply "i prefer to go by she/her or they/them pronouns" ;; "badcop3bonk") reply "You bonk the fish for $RANDOM damage!" ;; "badcop3rage") ;& "!fist") reply "You punch the fish for $RANDOM damage!" ;; "!fish "*) ;& "!fish") reply "cop.fish" ;; "!jump") reply "/me jumps" ;; "!consulting") reply "https://bash.consulting/" ;; "!lurk"*) reply "sounds good, $name :)" ;; "!twitter") reply "https://twitter.com/badcop_" ;; "!tomorrow") reply "rust stream?" ;; "!today") reply "$(tail -n1 $HOME/.today)" ;; "!settoday "*) if [[ "$mod" != "0" ]] || [[ "$name" == "badcop_" ]]; then echo "$msg_loud" | cut -d' ' -f2- >> "$HOME/.today" fi ;; "!song") URL="$(baton share | grep "Share URL" | cut -d' ' -f3-)" reply "$(baton status | head -n2 | cut -d':' -f2- | paste -sd' ') $URL" ;; "!rollycubes") reply "https://rollycubes.com/" ;; "!github") reply "https://github.com/cgsdev0" ;; # memes "!sha2") reply "It is balderdash by scam artists trying to bamboozle people into buying fintech snake oil" ;; "!modules") reply "they are orthogonal to namespaces" ;; "!bot") reply "i live here: https://github.com/cgsdev0/dotfiles/blob/main/bin/twitch-cmds/twitch-chat" ;; "!bible") reply "https://github.com/dylanaraps/pure-bash-bible" ;; "!itch") ;& "!games") reply "https://badcop.itch.io/" ;; *"haha"*) reply "lol" ;; *"heh"*) reply "haha" ;; # make the bot sassy *"golang"*) reply "more like nolang" ;; *"emacs"*) reply "vim is better" ;; *"vim"*) reply "emacs is better" ;; *"linux"*) reply "i use ubuntu btw" ;; "!"[^!]*) reply "what" ;; *"goodcop_"*) if [[ "$reply_parent" == "goodcop_" ]]; then reply "@$name don't reply to me LUL" else reply "@$name don't at me" fi ;; esac } parse() { while IFS= read -r line; do printf "time=%s;%s\n" $(date "+%Y-%m-%d_%H:%M:%S") "$line" >> "$LOG_FILE" read -r tagstr user cmd room msg <<< "$line" if [[ "$tagstr" == "PING" ]]; then # i hate that this is like this, but if you just pretend # that it's not like this then it's not so bad # # it turns out that my way of parsing is not exactly correct # but it's close enough for our needs so eff it printf "PONG %s\r\n" "$user" continue fi case $cmd in PRIVMSG) unset tags local -A tags while read -r -d';' tag; do IFS='=' read -r key val <<< "$tag" tags["$key"]="$val" done <<< "$tagstr" redeemed="${tags[custom-reward-id]}" msg=${msg:1} color=${tags[color]:1} if [[ ${#color} != 6 ]]; then colorhash=$(echo "$name" | md5sum) color=${colorhash:3:9} fi colorize "$color" 1>&2 name=${tags[display-name]} printf "%s" "$name" 1>&2 printf '\033[0m' 1>&2 if [[ "$msg" == $'\x01'"ACTION "* ]]; then printf '\033[3m' 1>&2 echo " ${msg#* }" 1>&2 else echo ": $msg" 1>&2 fi printf '\033[0m' 1>&2 parsecmd ;; esac done } auth() { source ~/.twitch_secrets goodcop_ printf "%s\r\n" \ "CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands" \ "PASS oauth:${TWITCH_ACCESS_TOKEN}" \ "NICK ${TWITCH_NICK}" \ "JOIN #${CHAN}" } set -o pipefail while true; do (auth; parse < "$TUNNEL") \ | websocat \ -E wss://irc-ws.chat.twitch.tv \ --ping-timeout 15 \ --ping-interval 10 \ > "$TUNNEL" [[ $? -ge 130 ]] && exit 0 echo "$(date) IT CRASHED, RESTARTING" done