Reorganization to hopefully prevent git conflicts.

This commit is contained in:
Storm Dragon
2025-10-25 01:30:02 -04:00
parent f6990bcc81
commit d684623974
13 changed files with 296 additions and 77 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,7 @@
response/error.txt
response/exit.txt
triggers/greet/greetings.txt
triggers/keywords/keywords.cfg
log.txt
.*
bot.cfg

48
bot.sh
View File

@@ -18,6 +18,47 @@ if [[ ! -f "bot.cfg" ]]; then
fi
fi
# Check and initialize customizable configuration files
check_config_files() {
local configFiles=(
"response/error.txt:response/error.txt.example"
"response/exit.txt:response/exit.txt.example"
"triggers/greet/greetings.txt:triggers/greet/greetings.txt.example"
"triggers/keywords/keywords.cfg:triggers/keywords/keywords.cfg.example"
)
for entry in "${configFiles[@]}"; do
local target="${entry%:*}"
local example="${entry#*:}"
if [[ ! -f "$target" ]]; then
if [[ -f "$example" ]]; then
cp "$example" "$target"
echo "Created $target from $example"
else
# Hardcoded fallback for critical files if example is missing
case "$target" in
"response/error.txt")
mkdir -p "$(dirname "$target")"
printf "I don't understand\nI'm not sure how to help you with that.\n" > "$target"
echo "Warning: $example not found. Created $target with default content."
;;
"response/exit.txt")
mkdir -p "$(dirname "$target")"
printf "Ta ta for now.\nbye.\n" > "$target"
echo "Warning: $example not found. Created $target with default content."
;;
*)
echo "Warning: Missing $example and no hardcoded fallback available."
;;
esac
fi
fi
done
}
check_config_files
# Load required files.
for i in "bot.cfg" "functions.sh" ; do
if [[ -f "$i" ]]; then
@@ -231,6 +272,13 @@ while true; do
from="${result#*#}"
from="${from%% *}"
from="#${from:-${channels[0]}}"
# Validate channel name format (IRC RFC 2812: must start with #, contain only valid chars)
if [[ ! "$from" =~ ^#[a-zA-Z0-9_-]+$ ]]; then
# Invalid channel, use default
from="#${channels[0]}"
fi
# Trigger stuff happens here.
# Call link trigger if msg contains a link:
if [[ "$result" =~ .*http://|https://|www\..* ]]; then

View File

@@ -14,4 +14,11 @@ if ! check_dependencies "${dependencies[@]}"; then
exit 1
fi
msg "$chan" "$(units -v ${*#* } | head -n1 | tr -d '[:space:]')"
# Validate input
if [[ -z "$*" ]]; then
msg "$chan" "Please provide a unit conversion (e.g., '10 meters to feet')."
exit 0
fi
# Quote variables to prevent command injection
msg "$chan" "$(units -v "${*#* }" | head -n1 | tr -d '[:space:]')"

View File

@@ -5,6 +5,9 @@
dependencies=("fortune")
target="${3#fortune}"
# Trim leading/trailing whitespace
target="${target#"${target%%[![:space:]]*}"}"
target="${target%"${target##*[![:space:]]}"}"
# Check dependencies before running
if ! check_dependencies "${dependencies[@]}"; then
@@ -12,6 +15,7 @@ if ! check_dependencies "${dependencies[@]}"; then
exit 1
fi
fortuneText="$(fortune -a -e -s -n 512 $target || echo "No fortunes found.")"
# Quote target to prevent command injection
fortuneText="$(fortune -a -e -s -n 512 "$target" || echo "No fortunes found.")"
fortuneText="$(echo "$fortuneText" | tr '[:space:]' ' ' | sed -e 's/"/\"/g')"
msg "$2" "$fortuneText"

View File

@@ -4,8 +4,35 @@
user=$1
shift
shift
if [[ "$user" =~ $allowList ]]; then
./modules/do/do.sh "$1" "#$channel" "does a magical gesture and turns into ${1}!"
nick $1
sed -i bot.cfg -e "s/nick=.*/nick=\"$1\"/"
newNick="$1"
# Validate that user is authorized
if [[ ! "$user" =~ $allowList ]]; then
exit 0
fi
# Validate IRC nickname format (RFC 2812)
# Nicknames can contain: a-z A-Z 0-9 _ - [ ] { } \ | ^
if [[ -z "$newNick" ]]; then
msg "#$channel" "$user: Please provide a nickname."
exit 0
fi
if ! [[ "$newNick" =~ ^[a-zA-Z0-9_\[\]\{\}\\|\^-]+$ ]]; then
msg "#$channel" "$user: Invalid nickname format. Only alphanumeric and _-[]{}\\|^ allowed."
exit 0
fi
if [[ ${#newNick} -gt 30 ]]; then
msg "#$channel" "$user: Nickname too long (max 30 characters)."
exit 0
fi
# Change the nick
./modules/do/do.sh "$newNick" "#$channel" "does a magical gesture and turns into ${newNick}!"
nick "$newNick"
# Safely update config file - escape forward slashes for sed
escapedNick="${newNick//\//\\/}"
sed -i "s/^nick=.*/nick=\"${escapedNick}\"/" bot.cfg

View File

@@ -13,15 +13,15 @@ if ! check_dependencies "${dependencies[@]}"; then
msg "$chan" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
#get the lyric text into a variable
lyricText="$(clyrics $@ | tr '[:space:]' ' ' | tr -s ' ' | fold -s -w 384)"
# Get the lyric text into a variable (quote $@ to prevent command injection)
lyricText="$(clyrics "$@" | tr '[:space:]' ' ' | tr -s ' ' | fold -s -w 384)"
i=$(echo "$lyricText" | wc -l)
i=$(($RANDOM % $i + 1))
i=$((RANDOM % i + 1))
lyricText="$(echo "$lyricText" | tail +$i | head -1 | rev | cut -d '.' -f2- | rev)"
#Display the lyric text
if [ ${#lyricText} -gt 15 ] ; then
msg "$chan" "${lyricText}"
exit 0
fi
msg "$chan" "no lyrics found for $@."
msg "$chan" "no lyrics found for $*."
exit 0

View File

@@ -1,16 +1,26 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
greetingsFile="triggers/greet/greetings.txt"
# All names to match are completely lowercase.
case "${1,,}" in
storm_dragon)
msg "$2" "my lord, $1: how may I serve you?"
;;
*)
greeting=(
Greetings
"Howdy, welcome to $2!"
"Wazzup Moe Fugger!"
"Welcome to $2!"
)
msg "$2" "$1: ${greeting[$(($RANDOM % ${#greeting[@]}))]}"
# Read greetings from file into array
if [[ -f "$greetingsFile" ]]; then
mapfile -t greeting < "$greetingsFile"
else
# Fallback if file doesn't exist
greeting=("Greetings" "Welcome!")
fi
# Replace {channel} placeholder with actual channel name
selectedGreeting="${greeting[$((RANDOM % ${#greeting[@]}))]}"
selectedGreeting="${selectedGreeting//\{channel\}/$2}"
msg "$2" "$1: $selectedGreeting"
;;
esac

View File

@@ -0,0 +1,4 @@
Greetings
Howdy, welcome to {channel}!
Wazzup Moe Fugger!
Welcome to {channel}!

View File

@@ -0,0 +1,26 @@
# Keywords Configuration
# Format: keyword|action|percentage (percentage is optional, defaults to 100%)
# Available variables: $chan (channel), $who (user nickname)
# Use {random:option1|option2|option3} for random selection in messages
#
# Examples:
# word|msg "$chan" "Hello there!"|50%
# test|act "$chan" "does something cool"
linux|msg "$chan" "Linux is {random:awesome|God|great|lovely|fantastic|amazing|wonderful}!"|25%
windows|msg "$chan" "{random:Failure is not an option, it comes bundled with Windows!|Apple got all pissed off because I farted in their store. It's not my falt they don't have Windows...|Windows is dumb!|Did you know that Micro Soft is Linda's pet name for Bill Gates?|A computer without Windows is like a chocolate cake without the mustard.|Windows is stupid|In a world without walls and fences - who needs windows and gates?|Windows, plug and pray.|Windows - Just another pain in the glass.|Windows, it's not pretty, it's not ugly, but it's pretty ugly.}!"|25%
emacs|msg "$chan" "$who, Real men of genius use vim!"|50%
jaws|msg "$chan" "${who}: watch out for sharks!"
emacspeak|msg "$chan" "$who, Real men of genius use vim!"
nano|msg "$chan" "$who, Real men of genius use vim!"
pidgin|msg "$chan" "$who, Real men of genius use irssi!"
weechat|msg "$chan" "$who, Real men of genius use irssi!"
thunderbird|msg "$chan" "$who, Real dogs use mutt, real men of genius use cat on a mailbox file!"
gedit|msg "$chan" "$who, Real men of genius use vim!"
pluma|msg "$chan" "$who, Real men of genius use vim!"
dragonforce|msg "$chan" "$who: I love DragonForce!!!"
vim|msg "$chan" "{random:Praise vim! HA|In times of trouble, just ask yourself, 'What would Bram Moolenaar do?'.|Vim is like a Ferrari, if you're a beginner, it handles like a bitch, but once you get the hang of it, it's small, powerful and FAST!|VIM is like a new model Ferrari, and sounds like one too - 'VIIIIIIMMM!'|Only through vim can you be saved! HA}"
# Multi-word triggers (match anywhere in message, spaces removed)
# Format: ~phrase|action|percentage
~nowplaying:|act "$chan" "{random:cranks the volume up to 11|got soooo high at that show|boogies down to the sound of the band}!"

View File

@@ -4,67 +4,142 @@
who="${1%!*}"
who="${who//:}"
shift
# shellcheck disable=SC2034 # Used in action strings via execute_action
chan="$1"
shift
# each word is stored in an associative array, with the actions to be taken as the array's contents.
# the variable $chan contains the channel that caused the trigger.
# the variable $who contains the nick that caused the trigger.
# Optional: Add a percentage (e.g., "50%") as the last element to respond only that percent of the time.
declare -A keywords
keywords[linux]="msg \"$chan\" \"Linux is $(shuf -n1 -e awesome God great lovely fantastic amazing wonderful)!\" 25%"
keywords[windows]="msg \"$chan\" \"$(shuf -n1 -e\
"Failure is not an option, it comes bundled with Windows!"\
"Apple got all pissed off because I farted in their store. It's not my falt they don't have Windows..."\
"Windows is dumb!"\
"Did you know that Micro Soft is Linda's pet name for Bill Gates?"\
"A computer without Windows is like a chocolate cake without the mustard."\
"Windows is stupid"\
"In a world without walls and fences - who needs windows and gates?"\
"Windows, plug and pray."\
"Windows - Just another pain in the glass."\
"Windows, it's not pretty, it's not ugly, but it's pretty ugly.")!\" 25%"
keywords[emacs]="msg \"$chan\" \"$who, Real men of genius use vim!\" 50%"
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!\""
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[dragonforce]="msg \"$chan\" \"$who: I love DragonForce!!!\""
keywords[vim]="msg \"$chan\" \"$(shuf -n1 -e \
"Praise vim! HA"\
"In times of trouble, just ask yourself, 'What would Bram Moolenaar do?'."\
"Vim is like a Ferrari, if you're a beginner, it handles like a bitch, but once you get the hang of it, it's small, powerful and FAST!"\
"VIM is like a new model Ferrari, and sounds like one too - 'VIIIIIIMMM!'"\
"Only through vim can you be saved! HA")\""
keywordsFile="triggers/keywords/keywords.cfg"
# Function to process random selection syntax: {random:opt1|opt2|opt3}
process_random() {
local text="$1"
while [[ "$text" =~ \{random:([^}]+)\} ]]; do
local options="${BASH_REMATCH[1]}"
IFS='|' read -ra optArray <<< "$options"
local selected="${optArray[$((RANDOM % ${#optArray[@]}))]}"
text="${text/\{random:$options\}/$selected}"
done
echo "$text"
}
# Safe execution function - only allows predefined IRC functions
execute_action() {
local action="$1"
# Parse the action to extract function name and arguments
if [[ "$action" =~ ^msg[[:space:]]+(\"[^\"]+\"|[^[:space:]]+)[[:space:]]+(.+)$ ]]; then
local target="${BASH_REMATCH[1]}"
local message="${BASH_REMATCH[2]}"
# Remove quotes from target and message if present
target="${target//\"/}"
# Safely expand only $chan and $who variables - NO EVAL
message="${message//\"\$chan\"/$chan}"
message="${message//\$chan/$chan}"
message="${message//\"\$who\"/$who}"
message="${message//\$who/$who}"
message="${message//\$\{chan\}/$chan}"
message="${message//\$\{who\}/$who}"
message="$(process_random "$message")"
msg "$target" "$message"
elif [[ "$action" =~ ^act[[:space:]]+(\"[^\"]+\"|[^[:space:]]+)[[:space:]]+(.+)$ ]]; then
local target="${BASH_REMATCH[1]}"
local message="${BASH_REMATCH[2]}"
# Remove quotes from target and message if present
target="${target//\"/}"
# Safely expand only $chan and $who variables - NO EVAL
message="${message//\"\$chan\"/$chan}"
message="${message//\$chan/$chan}"
message="${message//\"\$who\"/$who}"
message="${message//\$who/$who}"
message="${message//\$\{chan\}/$chan}"
message="${message//\$\{who\}/$who}"
message="$(process_random "$message")"
act "$target" "$message"
elif [[ "$action" =~ ^reply[[:space:]]+(\"[^\"]+\"|[^[:space:]]+)[[:space:]]+(.+)$ ]]; then
local target="${BASH_REMATCH[1]}"
local message="${BASH_REMATCH[2]}"
# Remove quotes from target and message if present
target="${target//\"/}"
# Safely expand only $chan and $who variables - NO EVAL
message="${message//\"\$chan\"/$chan}"
message="${message//\$chan/$chan}"
message="${message//\"\$who\"/$who}"
message="${message//\$who/$who}"
message="${message//\$\{chan\}/$chan}"
message="${message//\$\{who\}/$who}"
message="$(process_random "$message")"
reply "$target" "$message"
fi
}
# Load keywords from config file into associative array
declare -A keywords
if [[ -f "$keywordsFile" ]]; then
while IFS='|' read -r keyword action percentage || [[ -n "$keyword" ]]; do
# Skip comments and empty lines
[[ "$keyword" =~ ^[[:space:]]*# ]] && continue
[[ -z "$keyword" ]] && continue
# Trim whitespace
keyword="${keyword#"${keyword%%[![:space:]]*}"}"
keyword="${keyword%"${keyword##*[![:space:]]}"}"
action="${action#"${action%%[![:space:]]*}"}"
action="${action%"${action##*[![:space:]]}"}"
percentage="${percentage#"${percentage%%[![:space:]]*}"}"
percentage="${percentage%"${percentage##*[![:space:]]}"}"
# Store in array (key includes ~ prefix for multi-word triggers)
if [[ -n "$percentage" ]]; then
keywords["$keyword"]="$action $percentage"
else
keywords["$keyword"]="$action"
fi
done < "$keywordsFile"
fi
# Process single-word triggers
wordList="$(echo "${@,,}" | tr '[:space:]' $'\n' | sort -u)"
for w in ${wordList//[[:punct:]]/} ; do
if [[ -n "${keywords[${w,,}]}" && "$lastWordMatch" != "${keywords[${w,,}]}" ]]; then
keywordAction="${keywords[${w,,}]}"
# Check if the last element is a percentage
if [[ "$keywordAction" =~ (.*)\ ([0-9]+)%$ ]]; then
command="${BASH_REMATCH[1]}"
percentage="${BASH_REMATCH[2]}"
# Generate random number between 1-100 and only respond if within percentage
randomNum=$((RANDOM % 100 + 1))
if [[ $randomNum -le $percentage ]]; then
eval "$command"
fi
else
# No percentage specified, always respond
eval "$keywordAction"
fi
lastWordMatch="${keywords[${w,,}]}"
fi
if [[ -n "${keywords[${w,,}]}" && "$lastWordMatch" != "${keywords[${w,,}]}" ]]; then
keywordAction="${keywords[${w,,}]}"
# Check if the last element is a percentage
if [[ "$keywordAction" =~ (.*)\ ([0-9]+)%$ ]]; then
command="${BASH_REMATCH[1]}"
percentage="${BASH_REMATCH[2]}"
# Generate random number between 1-100 and only respond if within percentage
randomNum=$((RANDOM % 100 + 1))
if [[ $randomNum -le $percentage ]]; then
execute_action "$command"
fi
else
# No percentage specified, always respond
execute_action "$keywordAction"
fi
lastWordMatch="${keywords[${w,,}]}"
fi
done
# Example of dealing with multi word triggers.
# Reset wordList without sorting it and with spaces removed.
# Process multi-word triggers (those starting with ~)
wordList="$(echo "${@,,}" | tr -d '[:space:]')"
if [[ "${wordList,,}" =~ .*nowplaying:.* ]]; then
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
for trigger in "${!keywords[@]}"; do
if [[ "$trigger" =~ ^~ ]]; then
# Remove the ~ prefix for matching
triggerPattern="${trigger#\~}"
if [[ "${wordList,,}" =~ .*${triggerPattern}.* ]]; then
keywordAction="${keywords[$trigger]}"
# Check if the last element is a percentage
if [[ "$keywordAction" =~ (.*)\ ([0-9]+)%$ ]]; then
command="${BASH_REMATCH[1]}"
percentage="${BASH_REMATCH[2]}"
# Generate random number between 1-100 and only respond if within percentage
randomNum=$((RANDOM % 100 + 1))
if [[ $randomNum -le $percentage ]]; then
execute_action "$command"
fi
else
# No percentage specified, always respond
execute_action "$keywordAction"
fi
fi
fi
done

View File

@@ -12,10 +12,25 @@ fi
for l in $3 ; do
text="${l#:}"
if [[ "${text}" =~ http://|https://|www\..* ]]; then
pageTitle="$(curl -L -s --connect-timeout 5 "$text" | sed -n -e 'H;${x;s!.*<head[^>]*>\(.*\)</head>.*!\1!;T;s!.*<title>\(.*\)</title>.*!\1!p}' | w3m -dump -T text/html | tr '[:space:]' ' ')"
pageTitle="$(echo "$pageTitle" | tr -cd '[:print:]')"
if [[ ${#pageTitle} -gt 1 ]]; then
msg "$2" "$pageTitle"
fi
# Security: Only allow http:// and https:// protocols
if [[ ! "$text" =~ ^https?:// ]]; then
# Convert www. to http://www.
if [[ "$text" =~ ^www\. ]]; then
text="http://$text"
else
# Skip unknown protocols
continue
fi
fi
# Remove potentially dangerous shell metacharacters from URL
text="${text//[;&|]/}"
# Fetch page title with timeout and security limits
pageTitle="$(curl -L -s --connect-timeout 5 --max-time 10 "$text" | sed -n -e 'H;${x;s!.*<head[^>]*>\(.*\)</head>.*!\1!;T;s!.*<title>\(.*\)</title>.*!\1!p}' | w3m -dump -T text/html | tr '[:space:]' ' ')"
pageTitle="$(echo "$pageTitle" | tr -cd '[:print:]')"
if [[ ${#pageTitle} -gt 1 ]]; then
msg "$2" "$pageTitle"
fi
fi
done