#!/usr/bin/env bash ### # feature switch toggling ### shopt -s dotglob extglob lastpipe nullglob ### # utility and helper functions ### any_file() { local files=( "${1:-.}"/* ) max idx if (( ${#files[@]} > 1 )); then (( max = ${#files[@]} - 1 )) idx=$(random 0 "$max") printf %s "${files[$idx]}" fi } 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 # 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 # cleanup unset key file ### # load default config ### declare -A config # connection settings 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 # irc registration settings get_option nick rowbot-dev get_option ident rowbot get_option realname rowbot get_option chan "" # bot control settings get_option owner "${USER:-uplime}" get_option trigger \` get_option dev yes ### # logger ### log() { if [[ -v LOG_LEVEL ]] && (( log_levels[$log_level] <= log_levels[$LOG_LEVEL] )); then printf "%s: $1\n" "${LOG_LEVEL^^}" "${@:2}" >&"$log_fd" fi } log_debug() { LOG_LEVEL=debug log "$@" } log_info() { LOG_LEVEL=info log "$@" } log_warn() { LOG_LEVEL=warn log "$@" } log_error() { LOG_LEVEL=error log "$@" } log_has_level() { local level for level in "${!log_levels[@]}"; do if [[ ${1,,} = "$level" ]]; then return 0 fi done return 1 } 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 get_option log "" get_option overwrite no log_level=${config[log-level]} 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 } on_exit_log() { if [[ -v log_fd ]] && (( log_fd != 1 )); then exec {log_fd}>&- fi } ### # cleanup ### cleanup() { run_callbacks on_exit_ } trap cleanup EXIT ### # live code reloader ### reload() { 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[@]}" } hup_reload() { log_info "received reload signal (HUP)" reload } trap hup_reload HUP ### # initialization sequence ### run_callbacks on_init_ if is_reloaded; then run_callbacks on_after_ else run_callbacks on_first_ fi declare -p config