rowbot/rowbot

1219 lines
23 KiB
Bash
Executable File

#!/usr/bin/env bash
###
# lore
###
#RELEASE_CODE=Bender
#RELEASE_VER=1.0
#RELEASE_NAME="Bender (Rowbot v1.0)"
#RELEASE_DESC="A smol bot for remembering things"
printf -v LORE_THIS_RELOAD '%(%s)T' -1
if [[ ! -v LORE_START_TIME ]]; then
export LORE_START_TIME=$LORE_THIS_RELOAD
fi
if [[ ! -v LORE_LONGEST_LIFE ]]; then
export LORE_LONGEST_LIFE=0
fi
if [[ -v LORE_LIVES ]]; then
(( LORE_LIVES += 1 ))
else
export LORE_LIVES=1
fi
###
# switch toggler
###
shopt -s nullglob dotglob extglob
###
# logger
###
declare -A levels=(
[debug]=1 [info]=2
[warn]=3 [error]=4
)
log() {
if [[ -v LEVEL ]] && (( levels[$level] <= levels[$LEVEL] )); then
printf "%s: $1\n" "${LEVEL^^}" "${@:2}" >&"$log_fd"
fi
}
debug() {
LEVEL=debug log "$@"
}
info() {
LEVEL=info log "$@"
}
warn() {
LEVEL=warn log "$@"
}
error() {
LEVEL=error log "$@"
}
###
# utilities
###
has() {
hash "$1" 2>/dev/null
}
die() {
local status=1
if (( $# > 1 )) && [[ $1 = -s ]]; then
status=$2
shift
shift
fi
error "$@"
exit "$status"
}
is-parent() {
(( BASHPID == $$ ))
}
is-channel() {
[[ ${1:0:1} = \# ]]
}
is-bot() {
local score=0
if [[ ${args[-1]:0:3} = $'\xe2\x80\x8b' ]]; then
(( score += 100 ))
fi
if [[ $from = *-bot ]]; then
(( score += 30 ))
elif [[ $from = *bot ]]; then
(( score += 15 ))
fi
if [[ /$host/ = */bot/* ]]; then
(( score += 100 ))
fi
if [[ ${args[-1]} = '['*']' ]]; then
(( score += 20 ))
fi
printf %d "$score"
}
is-log-level() {
local level
for level in "${!levels[@]}"; do
if [[ ${1,,} = "$level" ]]; then
return 0
fi
done
return 1
}
seconds() {
local days hours minutes seconds time
(( days = $1 / 60 / 60 / 24 ))
(( hours = $1 / 60 / 60 % 24 ))
(( minutes = $1 / 60 % 60 ))
(( seconds = $1 % 60 ))
if (( days )); then
if (( days == 1 )); then
time="1 day"
else
time="$days day"
fi
fi
if (( hours )); then
if [[ $time ]]; then
time+=", "
fi
time+="$hours hour"
if (( hours > 1 )); then
time+=s
fi
fi
if (( minutes )); then
if [[ $time ]]; then
time+=", "
fi
time+="$minutes minute"
if (( minutes > 1 )); then
time+=s
fi
fi
if [[ $time ]]; then
time+=" and "
fi
time+="$seconds second"
if (( seconds > 1 )); then
time+=s
fi
printf -- %s "$time"
}
###
# argument parser for parsing arguments
###
original_args=( "$@" )
declare -A opts
while (( $# )); do
case $1 in
--*=*)
key=${1#--} key=${key%%=*}
opts[$key]=${1#--*=}
;;
--no-*)
key=${1#--no-}
opts[$key]=no
;;
--)
shift
break
;;
--*)
key=${1#--}
opts[$key]=yes
;;
*)
break
esac
shift
done
prog_args=( "$@" )
###
# default config
###
server=irc.libera.chat port=6667 tls=no client_cert=
nick=rowbot-dev ident=rowbot realname=rowbot chan=
trigger=\` fact_root=. reload=no level=info log_fd=1 log=
sys_root=sysfacts owner=${USER:-uplime} dev=no markov_seed=
overwrite=no
###
# apply custom config
###
for file do
if [[ -f $file ]]; then
# These files are provided dynamically at run-time.
# shellcheck disable=SC1090
. "$file" # ha, dot file
else
die "could not locate config file %s" "$file"
fi
done
###
# apply command line options
###
if [[ -v opts[tls] ]]; then
tls=${opts[tls]}
fi
# This is a false positive.
# shellcheck disable=SC2102
if [[ -v opts[level] ]]; then
if is-log-level "${opts[level]}"; then
level=${opts[level],,}
else
die "%s is not a valid logging level" "${opts[level]}"
fi
fi
if [[ $tls = yes ]]; then
# This is a false positive.
# shellcheck disable=SC2102
if ! has socat; then
die "please install socat to use tls with rowbot."
elif [[ -v opts[client-cert] ]]; then
client_cert=${opts[client-cert]}
fi
port=6697
fi
config=(
server port nick ident realname chan trigger
fact_root reload dev log owner sys_root reload
markov_seed
)
for opt in "${config[@]}"; do
declare -n config_var=$opt
cmd_arg=${opt//_/-}
if [[ -v opts[$cmd_arg] ]]; then
# This variable is only used for assignment.
# shellcheck disable=SC2034
config_var=${opts[$cmd_arg]}
fi
done
if [[ $log && $reload = no ]]; then
if [[ $overwrite = yes ]]; then
exec {log_fd}>"$log"
else
exec {log_fd}>>"$log"
fi
fi
###
# apply reloaded values
###
if [[ $markov_seed ]]; then
if [[ -f $markov_seed ]]; then
debug "loading markov seed file"
declare -A markov_chains enabled
read -ra words < "$markov_seed"
for (( idx = 0; idx < ${#words[@]} - 2; idx++ )); do
key="${words[$idx]} ${words[$idx + 1]}"
if [[ -z ${markov_chains[$key]} ]]; then
markov_chains[$key]=${words[$idx + 2]}
else
markov_chains[$key]+=" ${words[$idx + 2]}"
fi
done
else
die "seed file does not exist: %s" "$markov_seed"
fi
fi
if [[ $reload = yes ]]; then
if [[ -v RECORDING ]]; then
IFS=, read -ra recording <<< "$RECORDING"
for pair in "${recording[@]}"; do
enabled[${pair%=*}]=${pair##*=}
done
fi
reload_vars=(
nick ident host level log log_fd alarm_pid tls_pid in_sock
out_sock sock_dir sys_root fact_root dev trigger registered
keep_trying desired_nick overwrite
)
for var in "${reload_vars[@]}"; do
env_var=${var^^}
declare -n reload_var=$var
if [[ -v $env_var ]]; then
# This variable is only used for assignment.
# shellcheck disable=SC2034
reload_var=${!env_var}
fi
done
fi
###
# process/resource management
###
cleanup() {
debug "reaping rowbot's children"
if [[ -v alarm_pid ]]; then
debug "killing alarm process (%d) from %d" "$alarm_pid" "$BASHPID"
kill "$alarm_pid"
fi
if [[ -v tls_pid ]]; then
debug "killing tls process (%d) from %d" "$tls_pid" "$BASHPID"
kill "$tls_pid"
rm -rf "$sock_dir"
fi
if [[ -v tls_pid || $tls = no ]]; then
exec {in_sock}>&- {out_sock}>&-
if (( log_fd != 1 )); then
exec {log_fd}>&-
fi
fi
}
if is-parent; then
trap cleanup EXIT
fi
alarm-handler() {
ping "row your bot gently down the stream"
if [[ $keep_trying = yes ]]; then
info "trying for %s again" "$desired_nick"
nick "$desired_nick"
fi
}
trap alarm-handler ALRM
config-reload() {
local file key recording
info "received HUP signal"
for file in "${prog_args[@]}"; do
if [[ -f $file ]]; then
debug "loading config file %s" "$file"
# These files are provided dynamically at run-time.
# shellcheck disable=SC1090
. "$file"
else
die "could not locate config file %s" "$file"
fi
done
if (( log_fd != 1 )); then
debug "closing and re-opening log"
exec {log_fd}>&-
if [[ $overwrite = yes ]]; then
exec {log_fd}>"$log"
else
exec {log_fd}>>"$log"
fi
fi
if [[ -v markov_chains[@] ]]; then
for key in "${!enabled[@]}"; do
# This is a scalar string, not an array.
# shellcheck disable=SC2179
recording+=",$key=${enabled[$key]}"
done
export RECORDING=${recording#,}
fi
reload_vars=(
nick ident host level log log_fd alarm_pid tls_pid in_sock
out_sock sock_dir sys_root fact_root dev trigger registered
keep_trying desired_nick overwrite
)
for env_var in "${reload_vars[@]}"; do
export "${env_var^^}"="${!env_var}"
done
if (( SECONDS > LORE_LONGEST_LIFE )); then
export LORE_LONGEST_LIFE=$SECONDS
fi
info "reloading rowbot"
exec "$0" --reload "${original_args[@]}"
}
trap config-reload HUP
###
# net code
###
if [[ $reload = no && $tls = yes ]]; then
sock_dir=$(mktemp -d)
mkfifo "$sock_dir"/rb{in,out}
if [[ $client_cert ]]; then
if [[ ! -f $client_cert ]]; then
die "client certificate not found: %s" "$client_cert"
fi
conn_args=OPENSSL:$server:$port,cert=$client_cert
else
conn_args=OPENSSL:$server:$port
fi
socat "$conn_args" - <"$sock_dir"/rbin >"$sock_dir"/rbout &
tls_pid=$!
exec {out_sock}>"$sock_dir"/rbin {in_sock}<"$sock_dir"/rbout
debug "created tls connection (pid %d)" "$tls_pid"
elif [[ $reload = no ]]; then
exec {sock}<>/dev/tcp/"$server"/"$port"
in_sock=$sock out_sock=$sock
debug "created plaintext connection"
fi
send() {
local fmt
# As this is a printf wrapper, the format string is provided as an argument.
# shellcheck disable=SC2059
printf -v fmt "$1" "${@:2}"
printf '%s\r\n' "$fmt" >&"$out_sock"
debug "sending line: %s" "$fmt"
}
recv() {
declare -n sock_line=$1
IFS= read -r "$1" <&"$in_sock"
sock_line=${sock_line%$'\r'}
debug "received line: %s" "$sock_line"
}
###
# irc recv code
###
on_ERROR() {
error "${args[0]}"
exit
}
on_JOIN() {
info "%s has joined %s" "$from" "${args[0]}"
}
on_KICK() {
if (( ${#args[@]} == 3 )); then
info "%s has kicked %s from %s: %s" "$from" "${args[1]}" "${args[0]}" "${args[-1]}"
else
info "%s has kicked %s from %s" "$from" "${args[1]}" "${args[0]}"
fi
}
on_MODE() {
if (( ${#args[@]} == 2 )); then
info "%s sets mode(s) %s on %s" "$from" "${args[1]}" "${args[0]}"
elif (( ${#args[@]} > 2 )); then
info "%s: %s sets mode(s) %s" "${args[0]}" "$from" "${args[*]:1}"
fi
}
on_NICK() {
if [[ $from = "$nick" ]]; then
nick=${args[0]}
fi
info "%s has changed their name to %s" "$from" "${args[0]}"
}
on_NOTICE() {
info "[%s/%s] %s" "$from" "${args[0]}" "${args[1]}"
}
on_PART() {
if (( ${#args[@]} > 1 )); then
info "%s has left %s: %s" "$from" "${args[0]}" "${args[1]}"
else
info "%s has left %s" "$from" "${args[0]}"
fi
}
on_PING() {
pong "${args[1]}"
debug "received ping: %s" "${args[0]}"
}
on_PONG() {
debug "received pong: %s" "${args[1]}"
}
on_PRIVMSG() {
info "<%s/%s> %s" "$from" "${args[0]}" "${args[1]}"
}
on_TOPIC() {
info "%s has changed the topic for %s: %s" "$from" "${args[0]}" "${args[1]}"
}
on_QUIT() {
info "%s has disconnected: %s" "$from" "${args[0]}"
}
on_001() {
info %s "${args[1]}"
if [[ $chan ]]; then
join "$chan"
fi
{
debug "timer pid is %d" "$BASHPID"
while true; do
kill -ALRM "$$"
sleep 10
done
} &
alarm_pid=$!
nick=${args[0]}
registered=yes
who "$nick" %%uht,42
}
on_002() {
info %s "${args[1]}"
}
on_003() {
info %s "${args[1]}"
}
on_004() {
debug "%s " "${args[@]:1}"
}
declare -A isupport
on_005() {
local param key value
for param in "${args[@]:1:${#args[@]}-2}"; do
# This is a valid assignment, not a comparison.
# shellcheck disable=SC1097
IFS== read -r key value <<< "$param"
# While isupport is unused, it's still there in case later code wants to
# use it.
# shellcheck disable=SC2034
isupport[$key]=$value
debug "isupport: %s = %s" "$key" "$value"
done
}
on_250() {
info %s "${args[1]}"
}
on_251() {
info %s "${args[1]}"
}
on_252() {
info "There are %d operators online" "${args[1]}"
}
on_253() {
info "There are %d unknown connections" "${args[1]}"
}
on_254() {
info "There are %d channels formed" "${args[1]}"
}
on_255() {
info %s "${args[1]}"
}
on_265() {
info %s "${args[3]}"
}
on_266() {
info %s "${args[3]}"
}
on_315() {
debug "end of WHO for %s" "${args[1]}"
}
on_332() {
info "topic for %s is %s" "${args[1]}" "${args[2]}"
}
on_333() {
local date
printf -v date '%(%c)T' "${args[3]}"
info "topic for %s set by %s at %s" "${args[1]}" "${args[2]}" "$date"
}
on_353() {
info "members of %s: %s" "${args[2]}" "${args[3]}"
}
on_354() {
if (( args[1] == 42 )); then
debug "received the identifying who"
ident=${args[2]} host=${args[3]}
debug "ident=%s host=%s" "$ident" "$host"
fi
}
on_366() {
debug "%s: end of NAMES list" "${args[1]}"
}
on_372() {
info %s "${args[1]}"
}
on_375() {
debug %s "${args[1]}"
}
on_376() {
debug %s "${args[1]}"
}
on_433() {
info "somebody is already using %s" "${args[1]}"
if [[ $registered = no ]]; then
nick "${nick}_"
fi
}
on_438() {
error "${args[1]} couldn't change their nick to ${args[2]}: ${args[-1]}"
}
on_473() {
error "%s: %s" "${args[1]}" "${args[2]}"
}
###
# irc send code
###
join() {
send "JOIN %s" "$1"
}
nick() {
send "NICK %s" "$1"
}
notice() {
if [[ -v host ]]; then
local msg_len msg=$2
(( msg_len = 494 - (${#nick} + ${#ident} + ${#host} + ${#1}) ))
debug "max message length is %d" "$msg_len"
while (( ${#msg} > msg_len )); do
send "NOTICE %s :"$'\xe2\x80\x8b'"%s" "$1" "${msg:0:$msg_len}"
info "[%s/%s] %s" "$nick" "$1" "${msg:0:$msg_len}"
msg=${msg:$msg_len}
done
fi
send "NOTICE %s :%s" "$1" "$2"
info "[%s/%s] %s" "$nick" "$1" "$2"
}
part() {
if (( $# )); then
if (( $# > 1 )); then
send "PART $1 :$2"
else
send "PART $1"
fi
fi
}
ping() {
send "PING :%s" "$1"
}
pong() {
send "PONG %s" "$1"
}
privmsg() {
if [[ -v host ]]; then
local msg_len msg=$2
(( msg_len = 493 - (${#nick} + ${#ident} + ${#host} + ${#1}) ))
debug "max message length is %d" "$msg_len"
while (( ${#msg} > msg_len )); do
send "PRIVMSG %s :"$'\xe2\x80\x8b'"%s" "$1" "${msg:0:$msg_len}"
info "<%s/%s> %s" "$nick" "$1" "${msg:0:$msg_len}"
msg=${msg:$msg_len}
done
fi
send "PRIVMSG %s :"$'\xe2\x80\x8b'"%s" "$1" "$msg"
info "<%s/%s> %s" "$nick" "$1" "$msg"
}
quit() {
if (( $# )); then
send "QUIT :%s" "$1"
else
send QUIT
fi
}
user() {
send "USER %s 0 * :%s" "$ident" "$realname"
}
who() {
if (( $# > 1 )); then
send "WHO %s %s" "$1" "$2"
else
send "WHO %s" "$1"
fi
}
###
# app hooks
###
hook_pre_PRIVMSG_nolog() {
local words
read -ra words <<< "${args[-1]}"
if [[ ${words[0]} = "[nolog]" || ${words[0]} = nolog: ]]; then
info "this message was redacted"
return 1
else
return 0
fi
}
hook_pre_PRIVMSG_CTCP() {
if [[ ${args[1]} != $'\x01'*$'\x01' ]]; then
return 0
fi
local cmd msg
cmd=${args[1]#$'\x01'} cmd=${cmd%% *}
msg=${args[1]#* } msg=${msg%$'\x01'}
if [[ ${cmd^^} = ACTION ]]; then
if [[ ${args[0]:0:1} = \# ]]; then
info "ctcp: %s: %s %s" "${args[0]}" "$from" "$msg"
else
info "privately, %s %s" "$from" "$msg"
fi
return 1
elif [[ ${args[0]:0:1} = \# ]]; then
info "ctcp: %s has requested %s in %s" "$from" "${cmd^^}" "${args[0]}"
return 1
fi
info "ctcp: sending %s to %s" "${cmd^^}" "$from"
case ${cmd^^} in
CLIENTINFO)
notice "$from" $'\x01'"CLIENTINFO ACTION CLIENTINFO PING SOURCE TIME VERSION"$'\x01'
;;
PING)
local msg
msg=${args[1]#* } msg=${msg%$'\x01'}
notice "$from" $'\x01'"PING $msg"$'\x01'
;;
SOURCE)
notice "$from" $'\x01'"SOURCE https://github.com/uplime/rowbot"$'\x01'
;;
TIME)
notice "$from" $'\x01'"TIME time for you to get a watch"$'\x01'
;;
VERSION)
notice "$from" $'\x01'"VERSION rowbot v2"$'\x01'
esac
return 1
}
hook_post_PRIVMSG_markov() {
if [[ ! -v markov_chains[@] ]]; then
return 0
elif [[ ${to:0:1} = \# && ${enabled[$to]} != yes ]]; then
return 0
elif (( ${#action_args[@]} < 3 )); then
return 0
fi
local idx key
for (( idx = 0; idx < ${#action_args[@]} - 2; idx++ )); do
key="${action_args[$idx]} ${action_args[$idx + 1]}"
if [[ -z ${markov_chains[$key]} ]]; then
markov_chains[$key]=${words[$idx + 2]}
else
markov_chains[$key]+=" ${words[$idx + 2]}"
fi
done
}
hook_cmd_markov() {
local keys idx key str chains next vals val_idx
if [[ ! -v markov_chains[@] ]]; then
if [[ $action = markov ]]; then
privmsg "$to" "markov chains are not enabled"
fi
return 0
fi
case $action in
enable)
if (( ${#action_args[@]} )); then
enabled[${action_args[0]}]=yes
privmsg "$to" "now recording ${action_args[0]}"
else
enabled[$to]=yes
privmsg "$to" "now recording $to"
fi
;;
disable)
if (( ${#action_args[@]} )); then
enabled[${action_args[0]}]=no
privmsg "$to" "no longer recording ${action_args[0]}"
else
enabled[$to]=yes
privmsg "$to" "no longer recording $to"
fi
;;
enabled\?)
if [[ ${to:0:1} != \# || ${enabled[$to]} = yes ]]; then
privmsg "$to" "yes, I am recording $to for markov chains."
else
privmsg "$to" "no, I am not recording $to for markov chains"
fi
;;
markov)
keys=("${!markov_chains[@]}")
(( idx = RANDOM % ${#keys[@]} ))
key=${keys[$idx]} str=$key
chains=0
while [[ -v markov_chains[$key] ]] && (( chains++ < 100 )); do
read -ra vals <<< "${markov_chains[$key]}"
(( val_idx = RANDOM % ${#vals[@]} ))
next="$key ${vals[$val_idx]}" next=${next#* }
str+=" ${next#* }" key=$next
done
privmsg "$to" "$from: $str"
esac
}
hook_cmd_factoids() {
local key val facts msg
case $action in
is)
key=${action_line%% *}
if [[ $key = "$action_line" ]]; then
privmsg "$to" "$from: no fact provided"
return 0
fi
val=${action_line#"$key" }
privmsg "$to" "I'm sure I'll remember that."
mkdir -p "$fact_root"/"$to"
printf %s "$val" > "$fact_root"/"$to"/"$key"
;;
isnt)
if [[ -f $fact_root/$to/$action_line ]]; then
privmsg "$to" "I forgot what that was anyways."
rm -f "$fact_root"/"$to"/"$action_line"
fi
;;
ls)
facts=( "$fact_root"/"$to"/* )
privmsg "$to" "${facts[*]##*/}"
;;
*)
if [[ -f $fact_root/$to/$action ]]; then
msg=$(<"$fact_root"/"$to"/"$action")
if [[ ${action_args[0]} = \> ]] && (( ${#action_args[@]} > 1 )); then
privmsg "$to" "${action_args[-1]}: $msg"
else
privmsg "$to" "$from: $msg"
fi
fi
esac
}
hook_cmd_sysroot() {
if [[ -d $sys_root && $action = sysfact ]]; then
local files=( "$sys_root"/* ) idx msg
if (( ${#files[@]} )); then
(( idx = RANDOM % ${#files[@]} ))
msg=$(<"${files[$idx]}")
privmsg "$to" "sysfact #$(( idx + 1 )): $msg"
fi
fi
}
hook_cmd_control_panel() {
if [[ $from != "$owner" && $dev != yes ]]; then
if [[ $action = @(raw|join|reload|level|dev|dev\?|trigger|cycle|dev) ]]; then
privmsg "$to" "$from is not in the sudoers file. This incident will be reported."
info "security breach from %s in sector %s" "$from" "$to"
fi
return 0
fi
local channel key recording env_var reload_vars recipient msg
case $action in
raw)
info "%s is executing command: %s" "$from" "$action_line"
send "$action_line"
;;
join)
for channel in "${action_args[@]}"; do
join "$channel"
privmsg "$to" "joined $channel"
done
;;
reload)
if [[ -v markov_chains[@] ]]; then
for key in "${!enabled[@]}"; do
recording+=",$key=${enabled[$key]}"
done
export RECORDING=${recording#,}
fi
reload_vars=(
nick ident host level log log_fd alarm_pid tls_pid in_sock
out_sock sock_dir sys_root fact_root dev trigger registered
keep_trying desired_nick to overwrite
)
for env_var in "${reload_vars[@]}"; do
export "${env_var^^}"="${!env_var}"
done
if (( SECONDS > LORE_LONGEST_LIFE )); then
export LORE_LONGEST_LIFE=$SECONDS
fi
privmsg "$to" "reloading..."
exec "$0" --reload "${original_args[@]}"
;;
level)
if is-log-level "${action_args[0]}"; then
level=${action_args[0],,}
privmsg "$to" "log level is now set to $level"
else
privmsg "$to" "${action_args[0]} is not a valid logging level"
fi
;;
dev)
if [[ $dev = yes ]]; then
dev=no
privmsg "$to" "developer mode disabled"
else
dev=yes
privmsg "$to" "developer mode enabled"
fi
;;
dev\?)
if [[ $dev = yes ]]; then
privmsg "$to" "developer mode is enabled"
else
privmsg "$to" "developer mode is disabled"
fi
;;
trigger)
trigger=${action_args[0]}
privmsg "$to" "trigger is now '$trigger'"
;;
cycle)
privmsg "$to" "cycling channel $to"
part "$to" "be right back!"
join "$to"
;;
msg)
recipient=${action_line%% *}
msg=${action_line#"$recipient"* }
read -r msg <<< "$msg"
privmsg "$recipient" "$msg"
privmsg "$to" "sent message to $recipient"
;;
dashboard|lore)
privmsg "$to" "Legend began on $(printf '%(%c)T' "$LORE_START_TIME")"
privmsg "$to" "This life began on $(printf '%(%c)T' "$LORE_THIS_RELOAD")"
if (( LORE_LONGEST_LIFE > SECONDS )); then
privmsg "$to" "Longest life so far was $(seconds "$LORE_LONGEST_LIFE")"
else
privmsg "$to" "Longest life so far is $(seconds "$SECONDS") (this one)"
fi
if (( LORE_LIVES > 1 )); then
privmsg "$to" "Lived $LORE_LIVES times"
else
privmsg "$to" "Lived $LORE_LIVES time"
fi
esac
}
hook_post_433_alternick() {
if [[ -z $desired_nick && $registered = no ]]; then
desired_nick=${args[1]}
keep_trying=yes
fi
}
hook_post_NICK_alternick() {
if [[ ${args[0]} = "$desired_nick" ]]; then
keep_trying=no
info "obtained nick %s" "$desired_nick"
fi
}
hook_cmd_limes() {
local limes
case $action in
uplime)
(( limes = (RANDOM % 42) + 1 ))
if [[ $from = Time-Warp ]]; then
limes=42
fi
(( limeification = (limes * 100) / 42 ))
privmsg "$to" "$limes limes (limes to $limeification%)"
esac
}
###
# driver
###
if [[ $reload = yes ]]; then
if [[ -v TO ]]; then
privmsg "$TO" done.
unset TO
else
info done.
fi
else
registered=no
info "rowbot's pid is %d" "$BASHPID"
nick "$nick"
user "$ident" "$realname"
fi
# Always available:
# - from: name of the entity sending the message
# - ident: username of the entity sending the message
# - host: host of the entity sending the message
# - cmd: IRC command or numeric
# - args: array of arguments to the command
# Available if `cmd` is set to "privmsg":
# - to: location to send the message back to
# Additionally, if args[-1] starts with the trigger:
# - action: directive specified by the sender
# - action_line: data sent by the sender without the trigger or action
# - action_args: words sent by the sender without the trigger or action
while recv line; do
args=( )
orig_line=$line
if [[ ${line:0:1} = : ]]; then
src=${line%% *} src=${src#:}
line=${line#:"$src"} line=${line# }
from=${src%@*} ident=${from#*!}
from=${from%!*} host=${src#*@}
fi
cmd=${line%% *}
line=${line#"$cmd"}
line=${line# }
while [[ $line ]]; do
if [[ ${line:0:1} = : ]]; then
args+=( "${line:1}" )
line=""
else
arg=${line%% *}
args+=( "$arg" )
line=${line#"$arg"} line=${line# }
fi
done
is_action=no
if [[ ${cmd^^} = PRIVMSG ]]; then
to=${args[0]} last=${args[-1]}
if [[ ${to:0:1} != \# ]]; then
to=$from
fi
if [[ $last = "$trigger"* ]]; then
is_action=yes
bot_score=$(is-bot)
action=${last#"$trigger"} action=${action%% *}
action_line=${last#"$trigger$action" }
read -r action_line <<< "$action_line"
read -ra action_args <<< "$action_line"
debug "bot score: %d" "$bot_score"
fi
fi
skip_handler=0
while IFS= read -r hook; do
"$hook"
(( skip_handler |= $? ))
done < <(compgen -A function "hook_pre_${cmd^^}_")
if has "on_${cmd^^}"; then
if (( ! skip_handler )); then
"on_${cmd^^}"
else
debug "handler for %s was skipped" "${cmd^^}"
fi
else
warn "unhandled line: %s" "$orig_line"
fi
while IFS= read -r hook; do
"$hook"
done < <(compgen -A function "hook_post_${cmd^^}_")
if [[ $is_action = yes ]] && (( bot_score < 40 )); then
while IFS= read -r hook; do
"$hook"
done < <(compgen -A function "hook_cmd_")
fi
unset to
done