diff --git a/rowbot b/rowbot index 9284cdf..893819a 100755 --- a/rowbot +++ b/rowbot @@ -1,190 +1,54 @@ #!/usr/bin/env bash ### -# lore +# feature switch toggling ### -#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 +shopt -s dotglob extglob lastpipe nullglob ### -# 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 +# utility and helper functions ### has() { - hash "$1" 2>/dev/null -} - -die() { - local status=1 - - if (( $# > 1 )) && [[ $1 = -s ]]; then - status=$2 - shift - shift + if (( $# )); then + hash "$1" 2>/dev/null + else + return 1 fi - - error "$@" - exit "$status" } -is-parent() { +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" +is_reloaded() { + [[ $RELOADED = yes ]] || (( LORE_LIVES > 1 )) } ### -# argument parser for parsing arguments +# configure rowbot ### -original_args=( "$@" ) +# default config + +declare -A config=( + # connection settings + [server]=irc.libera.chat [port]="" [tls]=no [client-cert]="" + # irc registration settings + [nick]=rowbot-dev [ident]=rowbot [realname]=rowbot [chan]="" + # bot control settings + [owner]="${USER:-uplime}" [trigger]=\` [dev]=yes + # log settings + [log]="" [overwrite]=no [log-level]=info +) + +# parse command line arguments + declare -A opts +# This code is not used yet, but will be later. +# shellcheck disable=SC2034 +cmd_line=( "${@:0}" ) while (( $# )); do case $1 in @@ -211,1008 +75,45 @@ while (( $# )); do 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 -### +# apply custom configuration files 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" + # These files (if any) are provided dynamically at run-time. + # shellcheck disable=SC1090 + . "$file" # ha, dot file +done + +for setting in "${!config[@]}"; do + if [[ -v ${setting//-/_} ]]; then + declare -n setting_var=${setting//-/_} + config[$setting]=$setting_var fi done -### -# apply command line options -### +# apply command line arguments -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 +for setting in "${!config[@]}"; do # 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]} + if [[ -v opts[$setting] ]]; then + config[$setting]=${opts[$setting]} fi done -if [[ $log && $reload = no ]]; then - if [[ $overwrite = yes ]]; then - exec {log_fd}>"$log" +# apply default port if one isn't provided + +if [[ -z ${config[port]} ]]; then + if [[ ${config[tls]} = yes ]]; then + config[port]=6697 else - exec {log_fd}>>"$log" + config[port]=6667 fi fi -### -# apply reloaded values -### +# cleanup -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 +unset key file setting setting_var ### -# process/resource management +# initialization sequence ### - -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