From a0afefadfdb67f6501b27247e7c2ffd4ec39c083 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Fri, 24 Oct 2025 21:35:41 -0400 Subject: [PATCH] Cleanup. --- README.md | 4 +- bot.cfg.example | 5 +- bot.sh | 29 ++++- modules/botsnack/botsnack.sh | 18 --- modules/cuss/cuss.sh | 19 --- modules/douchebag/douchebag.sh | 9 -- modules/fuck/fuck.sh | 19 --- modules/pimp/pimp.sh | 33 ----- modules/pimp/pimp.sh.orig | 11 -- modules/redneck/redneck.sh | 56 --------- modules/weather/weather.sh | 2 +- .../wordtrack-leaders/wordtrack-leaders.sh | 101 +++++++++++++++ modules/wordtrack-stats/wordtrack-stats.sh | 74 +++++++++++ modules/yomomma/yomomma.sh | 19 --- triggers/keywords/keywords.sh | 30 +---- triggers/wordtrack/README.md | 103 ++++++++++++++++ triggers/wordtrack/categories.sh | 61 +++++++++ triggers/wordtrack/categories.sh.example | 46 +++++++ triggers/wordtrack/data/#bots/storm.dat | 1 + triggers/wordtrack/wordtrack.sh | 116 ++++++++++++++++++ 20 files changed, 533 insertions(+), 223 deletions(-) delete mode 100755 modules/botsnack/botsnack.sh delete mode 100755 modules/cuss/cuss.sh delete mode 100755 modules/douchebag/douchebag.sh delete mode 100755 modules/fuck/fuck.sh delete mode 100755 modules/pimp/pimp.sh delete mode 100755 modules/pimp/pimp.sh.orig delete mode 100755 modules/redneck/redneck.sh create mode 100755 modules/wordtrack-leaders/wordtrack-leaders.sh create mode 100755 modules/wordtrack-stats/wordtrack-stats.sh delete mode 100755 modules/yomomma/yomomma.sh create mode 100644 triggers/wordtrack/README.md create mode 100644 triggers/wordtrack/categories.sh create mode 100644 triggers/wordtrack/categories.sh.example create mode 100644 triggers/wordtrack/data/#bots/storm.dat create mode 100755 triggers/wordtrack/wordtrack.sh diff --git a/README.md b/README.md index 5ae18e0..c85ebb8 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ A simple, modular IRC bot written in bash ./bot.sh ``` -2. Edit `bot.cfg` with your IRC server, channel, and bot settings +2. Edit `bot.cfg` with your IRC server, channels, and bot settings + - Configure multiple channels using an array: `channels=("channel1" "channel2")` + - Channel names should NOT include the # prefix 3. Run the bot again: ```bash diff --git a/bot.cfg.example b/bot.cfg.example index 4e8b2a8..6dc1160 100644 --- a/bot.cfg.example +++ b/bot.cfg.example @@ -1,5 +1,6 @@ -#enter channels here in quotes before the ) -channel="a11y" +# Enter channels here as an array. Add multiple channels like: channels=("a11y" "channel2" "channel3") +# Channel names should NOT include the # prefix +channels=("a11y") # The date format for log entries. man date for details. dateFormat='%B %d, %I:%m%P' # Greet people who enter the channel? (true/false) diff --git a/bot.sh b/bot.sh index 51edada..bc7dcf7 100755 --- a/bot.sh +++ b/bot.sh @@ -30,7 +30,7 @@ done # Variables important to modules need to be exported here. export allowList -export channel +export channels export input export ignoreList export nick @@ -90,7 +90,10 @@ while true; do echo -e "Session started $(date "+%I:%M%p%n %A, %B %d, %Y").\n\nTo gracefully exit, make sure you are in the allow list and send the command exit to the bot.\n\n" | tee -a "$log" echo "NICK $nick" > "$input" echo "USER $user" >> "$input" - echo "JOIN #$channel" >> "$input" + # Join all configured channels + for channelName in "${channels[@]}"; do + echo "JOIN #$channelName" >> "$input" + done # The main loop of the program where we watch for output from irc. # Use SSL if enabled, otherwise plain TCP @@ -136,12 +139,17 @@ while true; do ;; # for pings on nick/user *"You have not"*) - echo "JOIN #$channel" | tee -a "$input" + for channelName in "${channels[@]}"; do + echo "JOIN #$channelName" | tee -a "$input" + done ;; # Run on kick :*!*@*" KICK "*" $nick :"*) if [ "$autoRejoinChannel" = "true" ]; then - echo "JOIN #$channel" | tee -a "$input" + # Extract channel name from kick message and rejoin that specific channel + kickedChannel="${result##*#}" + kickedChannel="#${kickedChannel%% *}" + echo "JOIN $kickedChannel" | tee -a "$input" fi if [ "$curseKicker" = "true" ]; then kickerName="${result%!*}" @@ -197,7 +205,8 @@ while true; do echo "Calling module ./modules/${command% *}/${command% *}/${command% *}.sh \"$who\" \"$from\" $willSanitized" >> "$log" # Disable wildcards set -f - "./modules/${command% *}/${command% *}.sh" "$who" "#$channel" "$will" + # For PMs, respond directly to the user, not to a channel + "./modules/${command% *}/${command% *}.sh" "$who" "$from" "$will" # Enable wildcards set +f else @@ -213,7 +222,7 @@ while true; do who="${who:1}" from="${result#*#}" from="${from%% *}" - from="#${from:-$channel}" + from="#${from:-${channels[0]}}" # Trigger stuff happens here. # Call link trigger if msg contains a link: if [[ "$result" =~ .*http://|https://|www\..* ]]; then @@ -229,6 +238,8 @@ while true; do command="${command//# /}" will="${command#* }" command="${command%% *}" + # If will equals command, there were no arguments + [[ "$will" == "$command" ]] && will="" willSanitized="${will//[$'\001'-$'\037'$'\177']/}" echo "DEBUG: command='$command' will='$willSanitized'" >> "$log" if command -v "./modules/${command% *}/${command% *}.sh" &>/dev/null ; then @@ -245,6 +256,12 @@ while true; do if ! [[ "$who" =~ ^($ignoreList)$ ]]; then set -f ./triggers/keywords/keywords.sh "$who" "$from" "$result" + # Only call wordtrack for valid channel messages + if [[ "$from" =~ ^#[a-zA-Z0-9_-]+$ ]]; then + # Extract just the message text for wordtrack + messageText="${result#*PRIVMSG*:}" + ./triggers/wordtrack/wordtrack.sh "$who" "$from" "$messageText" + fi set +f fi fi diff --git a/modules/botsnack/botsnack.sh b/modules/botsnack/botsnack.sh deleted file mode 100755 index 6b77163..0000000 --- a/modules/botsnack/botsnack.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -[ -f functions.sh ] && source functions.sh - -userNick="$1" -shift -chan="$1" -shift -snack="${@#botsnack}" -snack="${snack:-$(shuf -n1 -e\ - "BBQ microchips" \ -"BBQ sunflower seeds" \ - "BBQ corn nuts" \ - "deep fried goat placenta" \ - "steak")} " -thanks="$(shuf -n1 -e "Thank you" "You're so awesome" "You shouldn't have" "You rock")" -favorite="$(shuf -n1 -e "my favorite" "yum yum" "this is bot heaven" "DELICIOUS")" - -msg "$chan" "$thanks $userNick: $snack! $favorite!" diff --git a/modules/cuss/cuss.sh b/modules/cuss/cuss.sh deleted file mode 100755 index dbeb8af..0000000 --- a/modules/cuss/cuss.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -[ -f functions.sh ] && source functions.sh - -# Add phrases in quotes to the array. -phrases=( - "cuss words, just let 'em roll, mother fucking shit god damn ass hole!" - "cuss words, just don't quit, mother fuck you damn shit head bitch!" - "damn!" - "fuck the fucking fuckers!" - "fuck the fuck off!" - "fuck!" - "fuck. fuck. fuck. Mother mother fuck. Mother mother fuck fuck. Mother fuck mother fuck. Noise noise noise." - "god damn it!" - "motherfucker" - "shit, piss, fuck, cunt, cocksucker, motherfucker, and tits." - "shit!" - "son of a bitch!" -) -msg "$2" "${phrases[$(($RANDOM % ${#phrases[@]}))]}" diff --git a/modules/douchebag/douchebag.sh b/modules/douchebag/douchebag.sh deleted file mode 100755 index 869e8f2..0000000 --- a/modules/douchebag/douchebag.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -[ -f functions.sh ] && source functions.sh - -douchebag=( -"Don't ask to ask, just ask!" -"STOP! READ THIS BEFORE YOU SPEAK! http://www.rockbox.org/wiki/IrcGuidelines" -"The human requesting this service can't be bothered to help you in person, so they requested a bot tell you that we don't discuss blah here, only blah+ which is totally different." -'Use a pastebin website, bun only the one approved by the users of this channel, else someone may flood your screen with whining and bitching!') -msg "$2" "$1: $(shuf -n1 -e "${douchebag[@]}")" diff --git a/modules/fuck/fuck.sh b/modules/fuck/fuck.sh deleted file mode 100755 index fd5e7c4..0000000 --- a/modules/fuck/fuck.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -[ -f functions.sh ] && source functions.sh - -# Dependencies required by this module -dependencies=("curl") - -# Check dependencies before running -if ! check_dependencies "${dependencies[@]}"; then - msg "$2" "$1: This module requires: ${dependencies[*]}" - exit 1 -fi - -type="$(shuf -n1 -e "off" "you" "donut" "shakespeare" "linus" "king" "chainsaw" "madison")" -response="$(curl -s --connect-timeout 5 --max-time 10 -H "Accept: text/plain" "https://foaas.com/${type}/$3/$nick")" -if [[ -z "$response" || ${#response} -lt 5 ]]; then - msg "$2" "$1: Sorry, the FOAAS service is unavailable right now." -else - msg "$2" "$response" -fi diff --git a/modules/pimp/pimp.sh b/modules/pimp/pimp.sh deleted file mode 100755 index 6a1d6a0..0000000 --- a/modules/pimp/pimp.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -[ -f functions.sh ] && source functions.sh - -shift -chan="$1" -shift - -pimp() { - echo -n "$*" | sed \ - -r -e "s/(^| )ask( |\?|\.|!)/\1aks\2/gI" \ - -e "s/(^| )A /\1Uh $(shuf -e -n1 "god damn" "motha fuckin'") /gI" \ - -e "s/(^| )I /\1Ah /gI" \ - -e "s/(^| )is /\1be /gI" \ - -e "s/(^| )are /\1is /gI" \ - -e "s/(^| )(boy|dude|friend|guy|man)( |\?|\.|!)/\1$(shuf -n1 -e "bruh" "bruh-man" "brutha")\3/gI" \ - -e "s/(^| )for( |\?|\.|!)/\1fuh\2/gI" \ - -e "s/(^| )(appartment|house)( |\?|\.|!)/\1crib\3/gI" \ - -e "s/(\w)ing( |\?|\.|!)/\1in'\2/gI" \ - -e "s/(^| )my /\1mah /gI" \ - -e "s/(^| )people /\1people /gI" \ - -e "s/(^| )that( |\?|\.|!)/\1dat\2/gI" \ - -e "s/(^| )this( |\?|\.|!)/\1dis\2/gI" - -echo " $(shuf -n1 -e \ - "Brace yourself foo'!" \ - "What 'chew thinking Gee!" \ - "and shit!" \ - "sho 'nuff! ya'eard!?" \ - "nd git Sheniquah's ass back ova' heeah!" \ -)" -} - -msg "$chan" "$(pimp "$*")" diff --git a/modules/pimp/pimp.sh.orig b/modules/pimp/pimp.sh.orig deleted file mode 100755 index 1df1ecc..0000000 --- a/modules/pimp/pimp.sh.orig +++ /dev/null @@ -1,11 +0,0 @@ -# The site on which this module relied is now down. -# This module is here in the hopes that it will one day come back. -# Thanks to joel.net for years of laughs. - -[ -f functions.sh ] && source functions.sh - -shift -chan="$1" -shift -pimpText="${*#pimp }" -echo "$chan" "$(curl -L -s --data-urlencode English="$pimpText" --data-urlencode submit="Submit send" http://joel.net/EBONICS/Translator | grep ' ##' -e 's/^$//g' -e 's/"/\\"/g')" diff --git a/modules/redneck/redneck.sh b/modules/redneck/redneck.sh deleted file mode 100755 index 280f1ef..0000000 --- a/modules/redneck/redneck.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -[ -f functions.sh ] && source functions.sh - -shift -chan="$1" -shift - -redneck() { - echo -n "$*" | sed \ - -r -e "s/ass/ice/gI" \ - -e "s/(^| )finger( |,|\?|\.|!|$)/\1fanger\2/gI" \ - -e "s/(^| )thing( |,|\?|\.|!|$)/\1thang\2/gI" \ - -e "s/(.\w+)ink(.*)/\1ank\2/gI" \ - -e "s/(^| )A /\1uh /gI" \ - -e "s/(.*)i([^a|^e|^u|^ble|^ck|^ft|^ll|^ndo|^on|^ps|^s|^v].*)/\1ah\2/gI" \ - -e "s/(^| )I( |,|\?|\.|!|$)/\1Ah\2/g" \ - -e "s/(^| )(boy|dude|fellow|guy|man)([?s])( |,|\?|\.|!|$)/\1feller\3\4/gI" \ - -e "s/(^| )for( |,|\?|\.|!|$)/\1fer\2/gI" \ - -e "s/(^| )(hello|hey|hi|how's it going|hows it going)( |,|\?|\.|!|$)/\1howdy\3/gI" \ - -e "s/(^| )men( |,|\?|\.|!|$)/\1fellers\2/gI" \ - -e "s/(^| )get( |,|\?|\.|!|$)/\1git\2/gI" \ - -e "s/(^| )(appartment|cottage|house)( |,|\?|\.|!|$)/\1shack\3/gI" \ - -e "s/(^| )(god damn|goddam)( |,|\?|\.|!|$)/\1gol-durn\3/gI" \ - -e "s/(^| )damn( |,|\?|\.|!|$)/\1durn\2/gI" \ - -e "s/(^| )(am not|is not|isn't|are not|aren't|will not)( |,|\?|\.|!|$)/\1ain't\3/gI" \ - -e "s/(.*)backward(.*)/\1backerd\2/gI" \ - -e "s/(^| )bear( |,|\?|\.|!|$)/\1bar\2/gI" \ - -e "s/(^| )(cannot|can't)( |,|\?|\.|!|$)/\1cain't\3/gI" \ - -e "s/(^| )careful( |,|\?|\.|!|$)/\1kerful\2/gI" \ - -e "s/(^| )terrible( |,|\?|\.|!|$)/\1ter'ble\2/gI" \ - -e "s/(\w)ing( |,|\?|\.|!|$)/\1in'\2/gI" \ - -e "s/(\w)(i|ah)ght( |,|\?|\.|!|$)/\1aht'\3/gI" \ - -e "s/(^| )my /\1mah /gI" \ - -e "s/(^| )people( |,|\?|\.|!|$)/\1folks\2/gI" \ - -e "s/(^| )pretty( |,|\?|\.|!|$)/\1purdy\2/gI" \ - -e "s/(^| )sure( |,|\?|\.|!|$)/\1shore\2/gI" \ - -e "s/(^| )there( |,|\?|\.|!|$)/\1thar'\2/gI" \ - -e "s/(.*)window(.*)/\1windder\2/gI" \ - -e "s/(.*)where|we're(.*)/\1wer\2/gI" \ - -e "s/(^| )that( |,|\?|\.|!|$)/\1'at thar'\2/gI" \ - -e "s/(^| )this( |,|\?|\.|!|$)/\1'is here\2/gI" \ - -e "s/(^| )wash( |,|\?|\.|!|$)/\1wahrsh\2/gI" \ - -e "s/(^| )([bg])ah([g|t])( |,|\?|\.|!|'|$)/\1\2i\3\4/gI" \ - -e "s/(^| )aht( |,|\?|\.|!|'|$)/\1it\2/gI" \ - -e "s/(^| )(ahf|if)( |,|\?|\.|!|$)/\1iffen\3/gI" \ - -e "s/^[Yy]ou( |,|\.|\?\!|$)/Y'all\1/g" - -echo " $(shuf -n1 -e \ - "Hold Mah beer." \ - "You're darn tootn" \ - "Y'all come back now, ye'hear?" \ - "Yyyyyyeeeeeeeeehaaaaaaaaawwwwww!" \ -)" -} - -msg "$chan" "$(redneck "$*")" diff --git a/modules/weather/weather.sh b/modules/weather/weather.sh index 454bb92..ada0513 100755 --- a/modules/weather/weather.sh +++ b/modules/weather/weather.sh @@ -335,7 +335,7 @@ case "$subcommand" in # Set user's location shift if [[ $# -eq 0 ]]; then - msg "$channelName" "$name: Usage: weather set " + msg "$channelName" "$name: Usage: weather -set " exit 0 fi diff --git a/modules/wordtrack-leaders/wordtrack-leaders.sh b/modules/wordtrack-leaders/wordtrack-leaders.sh new file mode 100755 index 0000000..39b76eb --- /dev/null +++ b/modules/wordtrack-leaders/wordtrack-leaders.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +# Word tracking leaderboard module - shows top users in a category + +# shellcheck disable=SC1091 +[ -f functions.sh ] && source functions.sh +# shellcheck disable=SC1091 +[ -f triggers/wordtrack/categories.sh ] && source triggers/wordtrack/categories.sh + +name="$1" +channelName="$2" +shift 2 + +category="$1" + +# If no category specified, list available categories +# shellcheck disable=SC2154 +if [[ -z "$category" ]]; then + msg "$channelName" "$name: Available categories: ${categories[*]}" + exit 0 +fi + +# Validate category exists +categoryValid=0 +# shellcheck disable=SC2154 +for cat in "${categories[@]}"; do + if [[ "$cat" == "$category" ]]; then + categoryValid=1 + break + fi +done + +if ((categoryValid == 0)); then + msg "$channelName" "$name: Invalid category. Available: ${categories[*]}" + exit 0 +fi + +# Data directory for this channel +dataDir="triggers/wordtrack/data/${channelName}" + +if [[ ! -d "$dataDir" ]]; then + msg "$channelName" "$name: No tracking data available yet." + exit 0 +fi + +# Collect all users' counts for this category +declare -A leaderboard + +for userFile in "$dataDir"/*.dat; do + [[ -f "$userFile" ]] || continue + + userName=$(basename "$userFile" .dat) + + while IFS='=' read -r cat count; do + if [[ "$cat" == "$category" ]]; then + leaderboard["$userName"]="$count" + fi + done < "$userFile" +done + +# Check if anyone has been tracked +if [[ ${#leaderboard[@]} -eq 0 ]]; then + msg "$channelName" "$name: No one has been tracked in the ${category} category yet." + exit 0 +fi + +# Sort users by count (descending) +sortedUsers=() +while IFS= read -r line; do + sortedUsers+=("$line") +done < <(for user in "${!leaderboard[@]}"; do + echo "${leaderboard[$user]} $user" +done | sort -rn | head -5) + +# Get level names for this category +levelsArrayName="${category}Levels" +declare -n levelsRef="$levelsArrayName" + +# Build leaderboard message +leaderParts=() +position=1 + +for entry in "${sortedUsers[@]}"; do + count="${entry%% *}" + userName="${entry#* }" + + # Find level for this count + level="Unranked" + for threshold in $(printf '%s\n' "${!levelsRef[@]}" | sort -n); do + if ((count >= threshold)); then + level="${levelsRef[$threshold]}" + fi + done + + leaderParts+=("${position}. ${userName}: ${level} (${count} words)") + ((position++)) +done + +unset -n levelsRef + +msg "$channelName" "$name: Top ${category} users: $(IFS=' | '; echo "${leaderParts[*]}")" diff --git a/modules/wordtrack-stats/wordtrack-stats.sh b/modules/wordtrack-stats/wordtrack-stats.sh new file mode 100755 index 0000000..7ace3f5 --- /dev/null +++ b/modules/wordtrack-stats/wordtrack-stats.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# Word tracking stats module - shows user's current stats + +# shellcheck disable=SC1091 +[ -f functions.sh ] && source functions.sh +# shellcheck disable=SC1091 +[ -f triggers/wordtrack/categories.sh ] && source triggers/wordtrack/categories.sh + +name="$1" +channelName="$2" +shift 2 + +# Optional: check another user's stats +targetUser="${1:-$name}" + +# User data file +dataDir="triggers/wordtrack/data/${channelName}" +userDataFile="${dataDir}/${targetUser}.dat" + +# Check if user has any data +if [[ ! -f "$userDataFile" ]]; then + msg "$channelName" "$name: ${targetUser} has not been tracked yet." + exit 0 +fi + +# Load user data +declare -A userCounts +while IFS='=' read -r category count; do + userCounts["$category"]="$count" +done < "$userDataFile" + +# Build stats message +statsMessage="${targetUser}'s word tracking stats: " +statsParts=() + +# shellcheck disable=SC2154 +for category in "${categories[@]}"; do + count="${userCounts[$category]:-0}" + + if ((count > 0)); then + # Get current level for this category + levelsArrayName="${category}Levels" + declare -n levelsRef="$levelsArrayName" + + currentLevel="Unranked" + nextThreshold="" + + # Find current level and next threshold + for threshold in $(printf '%s\n' "${!levelsRef[@]}" | sort -n); do + if ((count >= threshold)); then + currentLevel="${levelsRef[$threshold]}" + elif [[ -z "$nextThreshold" ]]; then + nextThreshold="$threshold" + fi + done + unset -n levelsRef + + # Build stat string + if [[ -n "$nextThreshold" ]]; then + remaining=$((nextThreshold - count)) + statsParts+=("${category}: ${currentLevel} (${count}/${nextThreshold}, ${remaining} to next)") + else + statsParts+=("${category}: ${currentLevel} (MAX LEVEL - ${count} words)") + fi + fi +done + +if [[ ${#statsParts[@]} -eq 0 ]]; then + msg "$channelName" "$name: ${targetUser} has not earned any levels yet." +else + statsMessage+=$(IFS=' | '; echo "${statsParts[*]}") + msg "$channelName" "$statsMessage" +fi diff --git a/modules/yomomma/yomomma.sh b/modules/yomomma/yomomma.sh deleted file mode 100755 index 79f9d37..0000000 --- a/modules/yomomma/yomomma.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -[ -f functions.sh ] && source functions.sh - -# Dependencies required by this module -dependencies=("curl" "sed") - -# Check dependencies before running -if ! check_dependencies "${dependencies[@]}"; then - msg "$2" "$1: This module requires: ${dependencies[*]}" - exit 1 -fi - -joke="$(curl -s --connect-timeout 5 --max-time 10 https://api.yomomma.info | sed -e 's/{"joke":"//' -e 's/"}$//')" -if [[ -z "$joke" || ${#joke} -lt 5 ]]; then - msg "$2" "$1: Sorry, couldn't fetch a yo momma joke right now." -else - joke="${joke//[[:space:]]/ }" - msg "$2" "$joke" -fi diff --git a/triggers/keywords/keywords.sh b/triggers/keywords/keywords.sh index 48713bf..493fb5e 100755 --- a/triggers/keywords/keywords.sh +++ b/triggers/keywords/keywords.sh @@ -24,28 +24,7 @@ keywords[windows]="msg \"$chan\" \"$(shuf -n1 -e\ "Windows - Just another pain in the glass."\ "Windows, it's not pretty, it's not ugly, but it's pretty ugly.")!\"" keywords[emacs]="msg \"$chan\" \"$who, Real men of genius use vim!\"" -keywords[eloquence]="msg \"$chan\" \"$(shuf -n1 -e \ - "anticaesure" \ - "caesure" \ - "Goodhesville" \ - "hh've" \ - "Hoobhestank" \ - "tzsche" \ - "uncosp" \ - "webhesday" \ - "wedhesday")\"" -keywords[eloquents]="msg \"$chan\" \"$(shuf -n1 -e \ - "anticaesure" \ - "caesure" \ - "hh've" \ - "tzsche" \ - "uncosp" \ - "webhesday" \ - "wedhesday")\"" -keywords[jaws]="msg \"$chan\" \"$(shuf -n1 -e \ - "${who}: watch out for sharks!"\ - "Ooooo! Jaws! Yeah, let's spend 1,500 bucks to buy what NVDA can do for free... Not much of an accountant are you ${who}?")\"" -keywords[jfw]="msg \"$chan\" JFW: Acronym that means: Jaws! FUCKING WORTHLESS!" +keywords[jaws]="msg \"$chan\" \"${who}: watch out for sharks!\"" keywords[emacspeak]="msg \"$chan\" \"$who, Real men of genius use vim!\"" keywords[nano]="msg \"$chan\" \"$who, Real men of genius use vim!\"" keywords[pidgin]="msg \"$chan\" \"$who, Real men of genius use irssi!\"" @@ -53,9 +32,6 @@ keywords[weechat]="msg \"$chan\" \"$who, Real men of genius use irssi!\"" keywords[thunderbird]="msg \"$chan\" \"$who, Real dogs use mutt, real men of genius use cat on a mailbox file!\"" keywords[gedit]="msg \"$chan\" \"$who, Real men of genius use vim!\"" keywords[pluma]="msg \"$chan\" \"$who, Real men of genius use vim!\"" -keywords[chicken]="msg \"$chan\" \"$who, I'm gonna grab me $(shuf -n1 -e "a case of beer" "a weed eater" "a 5 gallon jug of vaseline" "a can of wd40") and a $(shuf -n1 -e dead frozen live young baby) chicken, and $(shuf -n1 -e "have fun" "make chicks" "lay it like an egg" "put my beak where it don't belong") ALL NIGHT LONG!!!\"" -keywords[feather]="msg \"$chan\" \"$who: Erotic is using a feather. Kinky is using the whole chicken!!!\"" -keywords[feathers]="msg \"$chan\" \"$who: Erotic is using a feather. Kinky is using the whole chicken!!!\"" keywords[dragonforce]="msg \"$chan\" \"$who: I love DragonForce!!!\"" keywords[vim]="msg \"$chan\" \"$(shuf -n1 -e \ "Praise vim! HA"\ @@ -76,9 +52,5 @@ done # Reset wordList without sorting it and with spaces removed. wordList="$(echo "${@,,}" | tr -d '[:space:]')" if [[ "${wordList,,}" =~ .*nowplaying:.* ]]; then -if [ "$who" = "lilmike" ]; then -msg "$chan" "Ewww, it sounds like 2 robots making out!" -else act "$chan" "$(shuf -n1 -e "cranks the volume up to 11" "got soooo high at that show" "boogies down to the sound of the band")!" fi -fi diff --git a/triggers/wordtrack/README.md b/triggers/wordtrack/README.md new file mode 100644 index 0000000..bd16eaf --- /dev/null +++ b/triggers/wordtrack/README.md @@ -0,0 +1,103 @@ +# Wordtrack Trigger + +Automatically tracks word usage by users and awards level-ups based on configurable thresholds. + +## How It Works + +The wordtrack trigger monitors all channel messages and counts occurrences of tracked words across different categories. Users automatically level up when they reach configured thresholds. + +## Files + +- `wordtrack.sh` - Main trigger script (called automatically on messages) +- `categories.sh` - Configuration file defining categories, words, and levels +- `data//.dat` - Per-user tracking data + +## Modules + +Users can interact with wordtrack using these command modules: + +### `.wordtrack-stats [nick]` +Shows word tracking statistics for yourself or another user. + +Example: +``` +.wordtrack-stats +.wordtrack-stats alice +``` + +Output: `alice's word tracking stats: coffee: Coffee Lover (50/100, 50 to next) | tea: Tea Sipper (12/25, 13 to next)` + +### `.wordtrack-leaders ` +Shows top 5 users in a category. + +Example: +``` +.wordtrack-leaders coffee +.wordtrack-leaders +``` + +Output: `Top coffee users: 1. alice: Coffee Lover (50 words) | 2. bob: Coffee Drinker (30 words) | 3. charlie: Coffee Newbie (15 words)` + +If no category is provided, lists available categories. + +## Configuration + +Edit `categories.sh` to add new categories or modify existing ones. + +### Adding a New Category + +1. Create a word array: `categoryWords=("word1" "word2" "word3")` +2. Create a levels array: `declare -A categoryLevels=([threshold1]="Level Name" [threshold2]="Level Name")` +3. Add category to the categories list: `categories=("coffee" "tea" "yournewcategory")` + +Example: +```bash +# Category: programming +programmingWords=("code" "coding" "python" "javascript" "rust" "git" "debug") + +declare -A programmingLevels=( + [10]="Code Newbie" + [25]="Junior Dev" + [50]="Developer" + [100]="Senior Dev" + [200]="Code Wizard" +) + +# Add to categories list +categories=("coffee" "tea" "gaming" "programming") +``` + +### Array Structure + +- **Word arrays**: Simple indexed arrays containing words to track + - Words are matched case-insensitively + - Multiple word matches in one message count separately + +- **Level arrays**: Associative arrays with threshold as key, level name as value + - Keys must be integers representing word counts + - Users advance when their count meets or exceeds the threshold + - Thresholds can be any positive integer + +## Data Format + +User data files (`data//.dat`) use simple key=value format: + +``` +coffee=45 +tea=12 +gaming=78 +``` + +## Integration with bot.sh + +The wordtrack trigger is called automatically for all channel messages from users not in the ignoreList (bot.sh:254-262). It processes messages after the keywords trigger. + +Level-up announcements are sent to the channel automatically when thresholds are crossed. + +## Notes + +- Users in the `ignoreList` are not tracked +- Word matching is case-insensitive +- Multiple occurrences of tracked words in a single message all count +- Data persists across bot restarts (stored in flat files) +- Each channel has independent tracking data diff --git a/triggers/wordtrack/categories.sh b/triggers/wordtrack/categories.sh new file mode 100644 index 0000000..37d049b --- /dev/null +++ b/triggers/wordtrack/categories.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# Word tracking categories configuration +# Add your own categories by following the pattern below + +# Category: coffee +# Words that trigger tracking for coffee category +coffeeWords=("coffee" "espresso" "latte" "mocha" "cappuccino" "americano" "frappuccino" "macchiato" "cortado" "affogato") + +# Level thresholds and reward names for coffee category +# Array key is the threshold (word count needed), value is the level name +declare -A coffeeLevels=( + [10]="Coffee Newbie" + [25]="Coffee Drinker" + [50]="Coffee Lover" + [100]="Coffee Addict" + [200]="Coffee Fiend" + [500]="Coffee God" +) + +# Category: tea +teaWords=("tea" "matcha" "chai" "oolong" "earl" "green tea" "black tea" "herbal" "chamomile" "rooibos") + +declare -A teaLevels=( + [10]="Tea Sipper" + [25]="Tea Enthusiast" + [50]="Tea Connoisseur" + [100]="Tea Master" + [200]="Tea Guru" +) + +# Category: gaming +gamingWords=("game" "gaming" "play" "played" "console" "steam" "xbox" "playstation" "nintendo" "pc gaming") + +declare -A gamingLevels=( + [10]="Casual Gamer" + [25]="Regular Player" + [50]="Dedicated Gamer" + [100]="Hardcore Gamer" + [200]="Gaming Enthusiast" + [500]="Gaming Legend" +) + +# Words that trigger tracking for drugs category +drugsWords=("kratom" "gummy" "hemp" "nicotine") + +# Level thresholds and reward names for drugs category +# Array key is the threshold (word count needed), value is the level name +declare -A drugsLevels=( + [10]="Adict" + [20]="Junky" + [40]="Burnout" + [80]="Dope Fiend" + [160]="Intervention Candidate" + [320]="Drug Lord" + [640]="Pickled" +) + +# List all active categories (must match the prefix of your arrays above) +# This is used by the trigger to know which categories to track +categories=("coffee" "tea" "gaming" "drugs") diff --git a/triggers/wordtrack/categories.sh.example b/triggers/wordtrack/categories.sh.example new file mode 100644 index 0000000..2e0c301 --- /dev/null +++ b/triggers/wordtrack/categories.sh.example @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# Word tracking categories configuration +# Add your own categories by following the pattern below + +# Category: coffee +# Words that trigger tracking for coffee category +coffeeWords=("coffee" "espresso" "latte" "mocha" "cappuccino" "americano" "frappuccino" "macchiato" "cortado" "affogato") + +# Level thresholds and reward names for coffee category +# Array key is the threshold (word count needed), value is the level name +declare -A coffeeLevels=( + [10]="Coffee Newbie" + [25]="Coffee Drinker" + [50]="Coffee Lover" + [100]="Coffee Addict" + [200]="Coffee Fiend" + [500]="Coffee God" +) + +# Category: tea +teaWords=("tea" "matcha" "chai" "oolong" "earl" "green tea" "black tea" "herbal" "chamomile" "rooibos") + +declare -A teaLevels=( + [10]="Tea Sipper" + [25]="Tea Enthusiast" + [50]="Tea Connoisseur" + [100]="Tea Master" + [200]="Tea Guru" +) + +# Category: gaming +gamingWords=("game" "gaming" "play" "played" "console" "steam" "xbox" "playstation" "nintendo" "pc gaming") + +declare -A gamingLevels=( + [10]="Casual Gamer" + [25]="Regular Player" + [50]="Dedicated Gamer" + [100]="Hardcore Gamer" + [200]="Gaming Enthusiast" + [500]="Gaming Legend" +) + +# List all active categories (must match the prefix of your arrays above) +# This is used by the trigger to know which categories to track +categories=("coffee" "tea" "gaming") diff --git a/triggers/wordtrack/data/#bots/storm.dat b/triggers/wordtrack/data/#bots/storm.dat new file mode 100644 index 0000000..6300ae9 --- /dev/null +++ b/triggers/wordtrack/data/#bots/storm.dat @@ -0,0 +1 @@ +gaming=30 diff --git a/triggers/wordtrack/wordtrack.sh b/triggers/wordtrack/wordtrack.sh new file mode 100755 index 0000000..acefb94 --- /dev/null +++ b/triggers/wordtrack/wordtrack.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# Word tracking trigger - monitors messages and tracks word usage + +# shellcheck disable=SC1091 +[ -f functions.sh ] && source functions.sh +# shellcheck disable=SC1091 +[ -f triggers/wordtrack/categories.sh ] && source triggers/wordtrack/categories.sh + +name="$1" +channelName="$2" +shift 2 +message="$*" + +# Sanitize channel name (remove any IRC protocol remnants) +channelName="${channelName%%[[:space:]]*}" +channelName="${channelName//[^a-zA-Z0-9#_-]/}" + +# Only process if we have a valid channel name starting with # +if [[ ! "$channelName" =~ ^#[a-zA-Z0-9_-]+$ ]]; then + exit 0 +fi + +# Convert message to lowercase for case-insensitive matching +messageLower="${message,,}" + +# Create data directory for this channel if it doesn't exist +dataDir="triggers/wordtrack/data/${channelName}" +mkdir -p "$dataDir" + +# User data file +userDataFile="${dataDir}/${name}.dat" + +# Load existing user data +declare -A userCounts +if [[ -f "$userDataFile" ]]; then + while IFS='=' read -r category count; do + userCounts["$category"]="$count" + done < "$userDataFile" +fi + +# Track which categories had level-ups +declare -a levelUps + +# Process each category +# shellcheck disable=SC2154 +for category in "${categories[@]}"; do + # Get the word array and levels array for this category + levelsArrayName="${category}Levels" + + # Check if message contains any words from this category + wordCount=0 + wordsArrayNameClean="${category}Words" + declare -n wordsRef="$wordsArrayNameClean" + + for word in "${wordsRef[@]}"; do + # Count all occurrences of this word in the message + wordLower="${word,,}" + tempMessage="$messageLower" + while [[ "$tempMessage" =~ $wordLower ]]; do + ((wordCount++)) + # Remove the matched word to find more occurrences + tempMessage="${tempMessage/$wordLower/}" + done + done + + unset -n wordsRef + + # If words were found, update the counter + if ((wordCount > 0)); then + oldCount="${userCounts[$category]:-0}" + newCount=$((oldCount + wordCount)) + userCounts["$category"]="$newCount" + + # Check for level-up + oldLevel="" + newLevel="" + + # Get the thresholds for this category using nameref + declare -n levelsRef="$levelsArrayName" + + # Get old level + for threshold in $(printf '%s\n' "${!levelsRef[@]}" | sort -n); do + if ((oldCount >= threshold)); then + oldLevel="${levelsRef[$threshold]}" + fi + done + + # Get new level + for threshold in $(printf '%s\n' "${!levelsRef[@]}" | sort -n); do + if ((newCount >= threshold)); then + newLevel="${levelsRef[$threshold]}" + fi + done + + # If level changed, record the level-up + if [[ -n "$newLevel" && "$newLevel" != "$oldLevel" ]]; then + levelUps+=("$category:$newLevel:$newCount") + fi + + # Clean up nameref + unset -n levelsRef + fi +done + +# Save updated user data +: > "$userDataFile" +for category in "${!userCounts[@]}"; do + echo "${category}=${userCounts[$category]}" >> "$userDataFile" +done + +# Announce level-ups +for levelUp in "${levelUps[@]}"; do + IFS=':' read -r category level count <<< "$levelUp" + msg "$channelName" "$name just leveled up in ${category}! You are now a ${level} with ${count} words!" +done