rowbot/rowbot

345 lines
5.8 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env bash
###
# feature switch toggling
###
shopt -s dotglob extglob lastpipe nullglob
###
2021-07-04 11:25:08 +00:00
# utility and helper functions
###
2021-07-04 11:25:08 +00:00
any_file() {
local files=( "${1:-.}"/* ) max idx
if (( ${#files[@]} > 1 )); then
(( max = ${#files[@]} - 1 ))
idx=$(random 0 "$max")
printf %s "${files[$idx]}"
fi
}
2021-07-04 11:25:08 +00:00
die() {
local fmt=$1
shift
# This is a wrapper around printf so the format string isn't known ahead of
# time.
# shellcheck disable=SC2059
printf "$fmt\n" "$@" >&2
exit "${STATUS:-42}"
}
get_option() {
if (( $# != 2 )); then
return 1
fi
local var_name=${1//-/_}
local env_var=${var_name^^}
if [[ -v $env_var ]]; then
config[$1]=${!env_var}
elif [[ -v opts[$1] ]]; then
config[$1]=${opts[$1]}
elif [[ -v $var_name ]]; then
config[$1]=${!var_name}
else
config[$1]=$2
fi
}
has() {
if (( $# )); then
hash "$1" 2>/dev/null
else
return 1
fi
}
is_parent() {
(( BASHPID == $$ ))
}
is_reloaded() {
[[ $RELOADED = yes ]] || (( LORE_LIVES > 1 ))
}
random() {
local min=$1 max=$2
(( (RANDOM % max) + min ))
}
run_callbacks() {
if (( ! $# )); then
return 1
fi
local filter=$1
shift
while IFS= read -r; do
"$REPLY" "$@"
done < <(compgen -A function "$filter")
return 0
}
###
# configure rowbot's environment
###
# parse command line arguments
declare -A opts
cmd_line=( "${@:0}" )
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
2021-07-04 11:25:08 +00:00
# load custom configuration files
for file do
if [[ -f $file ]]; then
# These files (if any) are provided dynamically at run-time.
# shellcheck disable=SC1090
. "$file" # ha, dot file
else
die "could not locate config file %s" "$file"
fi
done
2021-07-04 11:25:08 +00:00
# cleanup
2021-07-04 11:25:08 +00:00
unset key file
2021-07-04 11:25:08 +00:00
###
# load default config
###
2021-07-04 11:25:08 +00:00
declare -A config
2021-07-04 11:25:08 +00:00
# irc registration settings
2021-07-04 11:25:08 +00:00
get_option nick rowbot-dev
get_option ident rowbot
get_option realname rowbot
get_option chan ""
2021-07-04 11:25:08 +00:00
# bot control settings
get_option owner "${USER:-uplime}"
get_option trigger \`
get_option dev yes
2021-07-05 23:14:23 +00:00
###
# bootup sequence
###
on_first_001_bootup() {
log_info "rowbot's pid is %d" "$$"
}
###
2021-07-04 11:25:08 +00:00
# logger
###
2021-07-04 11:25:08 +00:00
log() {
if [[ -v LOG_LEVEL ]] && (( log_levels[$log_level] <= log_levels[$LOG_LEVEL] )); then
printf "%s: $1\n" "${LOG_LEVEL^^}" "${@:2}" >&"$log_fd"
2021-07-04 10:13:57 +00:00
fi
}
2021-07-04 11:25:08 +00:00
log_debug() {
LOG_LEVEL=debug log "$@"
}
2021-07-04 11:25:08 +00:00
log_info() {
LOG_LEVEL=info log "$@"
2021-07-04 09:59:50 +00:00
}
2021-07-04 11:25:08 +00:00
log_warn() {
LOG_LEVEL=warn log "$@"
2021-07-04 09:59:50 +00:00
}
2021-07-04 11:25:08 +00:00
log_error() {
LOG_LEVEL=error log "$@"
2021-07-04 09:59:50 +00:00
}
2021-07-04 11:25:08 +00:00
log_has_level() {
local level
2021-07-04 11:25:08 +00:00
for level in "${!log_levels[@]}"; do
if [[ ${1,,} = "$level" ]]; then
return 0
fi
done
return 1
2021-07-04 10:13:57 +00:00
}
2021-07-04 11:25:08 +00:00
on_init_001_log() {
declare -gA log_levels=( [debug]=1 [info]=2 [warn]=3 [error]=4 )
get_option log-level info
if ! log_has_level "${config[log-level]}"; then
die "%s is not a valid logging level" "${config[log-level]}"
fi
2021-07-04 11:25:08 +00:00
get_option log ""
get_option overwrite no
log_level=${config[log-level]}
2021-07-04 11:25:08 +00:00
if [[ ${config[log]} ]]; then
if [[ ${config[overwrite]} = yes ]]; then
exec {log_fd}>"${config[log]}"
else
exec {log_fd}>>"${config[log]}"
fi
else
log_fd=1
fi
}
2021-07-05 23:14:23 +00:00
on_exit_001_log() {
2021-07-04 11:25:08 +00:00
if [[ -v log_fd ]] && (( log_fd != 1 )); then
exec {log_fd}>&-
fi
}
2021-07-05 23:14:23 +00:00
###
# net code
###
net_recv() {
declare -n sock_line=$1
IFS= read -r "$1" <&"$in_sock"
sock_line=${sock_line%$'\r'}
log_debug "received line: %s" "$sock_line"
}
net_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"
log_debug "sending line: %s" "$fmt"
}
on_init_002_net() {
get_option server irc.libera.chat
get_option tls no
if [[ ${config[tls]} = no ]]; then
get_option port 6667
else
get_option client-cert ""
get_option port 6697
fi
}
on_first_002_net() {
local irc_sock conn_args
if [[ ${config[tls]} = no ]]; then
log_info "rowbot is connecting to irc://%s:%s" "${config[server]}" "${config[port]}"
exec {irc_sock}<>/dev/tcp/"${config[server]}"/"${config[port]}"
in_sock=$irc_sock out_sock=$irc_sock
else
if ! has socat; then
die "please install socat to use tls with rowbot."
fi
log_info "rowbot is connecting to ircs://%s:%s" "${config[server]}" "${config[port]}"
sock_dir=$(mktemp -d)
log_debug "socket directory is %s" "$sock_dir"
mkfifo "$sock_dir"/rowbot-{in,out}.sock
# This is a false positive
# shellcheck disable=SC2102
if [[ -v config[client-cert] ]]; then
if [[ ! -f ${config[client-cert]} ]]; then
die "client certificate not found: %s" "${config[client-cert]}"
fi
conn_args=OPENSSL:${config[server]}:${config[port]},cert=${config[client-cert]}
else
conn_args=OPENSSL:${config[server]}:${config[port]}
fi
socat "$conn_args" - <"$sock_dir"/rowbot-in.sock >"$sock_dir"/rowbot-out.sock &
tls_pid=$!
exec {out_sock}>"$sock_dir"/rowbot-out.sock {in_sock}<"$sock_dir"/rowbot-in.sock
log_debug "process %d is handling tls" "$tls_pid"
fi
}
2021-07-04 11:25:08 +00:00
###
# cleanup
###
cleanup() {
2021-07-05 23:14:23 +00:00
log_info "Theres a lot of beauty in ordinary things. Isnt that kind of the point?"
2021-07-04 11:25:08 +00:00
run_callbacks on_exit_
}
trap cleanup EXIT
###
# live code reloader
###
2021-07-05 23:14:23 +00:00
reload_config() {
2021-07-04 11:25:08 +00:00
local setting setting_name
run_callbacks on_before_
for setting in "${!config[@]}"; do
setting_name=${setting//-/_}
export "${setting_name^^}"="${config[$setting]}"
done
exec "${cmd_line[@]}"
}
2021-07-05 23:14:23 +00:00
reload_hup() {
2021-07-04 11:25:08 +00:00
log_info "received reload signal (HUP)"
2021-07-05 23:14:23 +00:00
reload_config
}
2021-07-05 23:14:23 +00:00
trap reload_hup HUP
###
# initialization sequence
###
run_callbacks on_init_
if is_reloaded; then
2021-07-04 11:25:08 +00:00
run_callbacks on_after_
else
run_callbacks on_first_
fi