2021-07-01 19:13:31 +00:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
|
|
###
|
2021-07-04 08:35:11 +00:00
|
|
|
# feature switch toggling
|
2021-07-01 19:13:31 +00:00
|
|
|
###
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
shopt -s dotglob extglob nullglob
|
2021-07-01 19:13:31 +00:00
|
|
|
|
2021-07-04 08:35:11 +00:00
|
|
|
###
|
2021-07-04 11:25:08 +00:00
|
|
|
# utility and helper functions
|
2021-07-04 08:35:11 +00:00
|
|
|
###
|
2021-07-01 19:13:31 +00:00
|
|
|
|
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-01 19:13:31 +00:00
|
|
|
|
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
|
2021-07-05 23:46:18 +00:00
|
|
|
printf "FATAL: $fmt\n" "$@" >&2
|
2021-07-04 11:25:08 +00:00
|
|
|
exit "${STATUS:-42}"
|
|
|
|
}
|
|
|
|
|
|
|
|
get_option() {
|
2021-07-06 12:48:36 +00:00
|
|
|
if (( ! $# )); then
|
2021-07-04 11:25:08 +00:00
|
|
|
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}
|
2021-07-06 12:48:36 +00:00
|
|
|
elif (( $# > 1 )); then
|
2021-07-04 11:25:08 +00:00
|
|
|
config[$1]=$2
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
has() {
|
|
|
|
if (( $# )); then
|
|
|
|
hash "$1" 2>/dev/null
|
|
|
|
else
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2021-07-08 06:15:01 +00:00
|
|
|
is_action() {
|
|
|
|
[[ ${msg[cmd]} = PRIVMSG && ${msg_args[-1]:0:${#config[trigger]}} = "${config[trigger]}" ]]
|
|
|
|
}
|
|
|
|
|
|
|
|
is_chan() {
|
|
|
|
[[ ${msg_args[${1:-0}]} = \# ]]
|
|
|
|
}
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
is_parent() {
|
|
|
|
(( BASHPID == $$ ))
|
|
|
|
}
|
|
|
|
|
|
|
|
is_reloaded() {
|
|
|
|
[[ $RELOADED = yes ]] || (( LORE_LIVES > 1 ))
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
is_running () {
|
|
|
|
kill -0 "$1" 2>/dev/null
|
|
|
|
}
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
random() {
|
|
|
|
local min=$1 max=$2
|
2021-07-11 00:23:10 +00:00
|
|
|
printf %d "$(( (RANDOM % max) + min ))"
|
2021-07-04 11:25:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
run_callbacks() {
|
|
|
|
if (( ! $# )); then
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
|
2021-07-08 06:15:01 +00:00
|
|
|
local status=0 filter=$1
|
2021-07-04 11:25:08 +00:00
|
|
|
shift
|
|
|
|
|
|
|
|
while IFS= read -r; do
|
|
|
|
"$REPLY" "$@"
|
2021-07-08 06:15:01 +00:00
|
|
|
(( status |= $? ))
|
2021-07-04 11:25:08 +00:00
|
|
|
done < <(compgen -A function "$filter")
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2021-07-10 23:40:15 +00:00
|
|
|
url() {
|
|
|
|
if [[ ${config[tls]} = no ]]; then
|
|
|
|
printf irc://
|
|
|
|
else
|
|
|
|
printf ircs://
|
|
|
|
fi
|
|
|
|
|
|
|
|
printf %s:%s "${config[server]}" "${config[port]}"
|
|
|
|
}
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
###
|
|
|
|
# configure rowbot's environment
|
|
|
|
###
|
2021-07-01 19:13:31 +00:00
|
|
|
|
2021-07-04 08:35:11 +00:00
|
|
|
# parse command line arguments
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
|
|
declare -A opts
|
2021-07-04 08:35:11 +00:00
|
|
|
cmd_line=( "${@:0}" )
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
|
|
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
|
2021-07-01 19:13:31 +00:00
|
|
|
|
|
|
|
for file do
|
2021-07-04 09:57:52 +00:00
|
|
|
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
|
2021-07-01 19:13:31 +00:00
|
|
|
done
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
# cleanup
|
2021-07-04 08:35:11 +00:00
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
unset key file
|
2021-07-01 19:13:31 +00:00
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
###
|
|
|
|
# load default config
|
|
|
|
###
|
2021-07-01 19:13:31 +00:00
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
declare -A config
|
2021-07-01 19:13:31 +00:00
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
get_option owner "${USER:-uplime}"
|
|
|
|
get_option trigger \`
|
|
|
|
get_option dev yes
|
2021-07-04 09:57:52 +00:00
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
###
|
2021-07-06 00:16:09 +00:00
|
|
|
# bootup/shutdown sequence
|
2021-07-05 23:14:23 +00:00
|
|
|
###
|
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
on_sys_first_001_bootup() {
|
2021-07-05 23:14:23 +00:00
|
|
|
log_info "rowbot's pid is %d" "$$"
|
2021-07-06 08:27:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
on_sys_before_999_bootup() {
|
|
|
|
local setting setting_name
|
|
|
|
log_debug "storing the config"
|
|
|
|
|
|
|
|
for setting in "${!config[@]}"; do
|
|
|
|
setting_name=${setting//-/_}
|
|
|
|
export "CONFIG_${setting_name^^}=${config[$setting]}"
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
on_sys_after_001_bootup() {
|
|
|
|
log_debug "reloading the config"
|
|
|
|
local setting setting_name
|
|
|
|
|
|
|
|
while IFS= read -r setting; do
|
|
|
|
setting_name=${setting#CONFIG_} setting_name=${setting_name//_/-}
|
|
|
|
config[${setting_name,,}]=${!setting}
|
|
|
|
unset "$setting"
|
|
|
|
done < <(compgen -e CONFIG_)
|
|
|
|
}
|
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
on_sys_exit_999_bootup() {
|
2021-07-08 04:51:14 +00:00
|
|
|
log_info "There's a lot of beauty in ordinary things. Isn't that kind of the point?"
|
2021-07-06 00:16:09 +00:00
|
|
|
}
|
|
|
|
|
2021-07-04 09:57:52 +00:00
|
|
|
###
|
2021-07-04 11:25:08 +00:00
|
|
|
# logger
|
2021-07-04 09:57:52 +00:00
|
|
|
###
|
|
|
|
|
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 09:57:52 +00:00
|
|
|
}
|
|
|
|
|
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 09:57:52 +00:00
|
|
|
|
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-06 00:16:09 +00:00
|
|
|
on_sys_init_001_log() {
|
2021-07-04 11:25:08 +00:00
|
|
|
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]}"
|
2021-07-04 09:57:52 +00:00
|
|
|
fi
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
get_option log ""
|
|
|
|
get_option overwrite no
|
|
|
|
log_level=${config[log-level]}
|
2021-07-04 09:57:52 +00:00
|
|
|
|
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-04 09:57:52 +00:00
|
|
|
|
2021-07-06 08:27:07 +00:00
|
|
|
on_sys_before_999_log() {
|
|
|
|
if [[ -v log_fd ]] && (( log_fd != 1 )); then
|
|
|
|
log_debug "shutting logger down for reload"
|
|
|
|
exec {log_fd}>&-
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
on_sys_exit_999_log() {
|
2021-07-04 11:25:08 +00:00
|
|
|
if [[ -v log_fd ]] && (( log_fd != 1 )); then
|
2021-07-06 08:27:07 +00:00
|
|
|
log_debug "shutting logger down for good"
|
2021-07-04 11:25:08 +00:00
|
|
|
exec {log_fd}>&-
|
|
|
|
fi
|
2021-07-04 09:57:52 +00:00
|
|
|
}
|
|
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
on_sys_init_002_net() {
|
2021-07-05 23:14:23 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
on_sys_first_002_net() {
|
|
|
|
local conn_args
|
2021-07-10 23:40:15 +00:00
|
|
|
log_info "rowbot is connecting to %s" "$(url)"
|
2021-07-05 23:14:23 +00:00
|
|
|
|
|
|
|
if [[ ${config[tls]} = no ]]; then
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
2021-07-05 23:46:18 +00:00
|
|
|
if [[ ${config[client-cert]} ]]; then
|
2021-07-05 23:14:23 +00:00
|
|
|
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=$!
|
2021-07-06 05:29:17 +00:00
|
|
|
exec {out_sock}>"$sock_dir"/rowbot-in.sock {in_sock}<"$sock_dir"/rowbot-out.sock
|
2021-07-05 23:14:23 +00:00
|
|
|
log_debug "process %d is handling tls" "$tls_pid"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2021-07-06 08:27:07 +00:00
|
|
|
on_sys_before_002_net() {
|
|
|
|
if [[ ${config[tls]} = no ]]; then
|
|
|
|
export IRC_SOCK=$irc_sock
|
|
|
|
else
|
|
|
|
export SOCK_DIR=$sock_dir TLS_PID=$tls_pid
|
|
|
|
export OUT_SOCK=$out_sock IN_SOCK=$in_sock
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
on_sys_after_002_net() {
|
|
|
|
if [[ ${config[tls]} = no ]]; then
|
|
|
|
irc_sock=$IRC_SOCK out_sock=$irc_sock in_sock=$irc_sock
|
|
|
|
unset IRC_SOCK
|
|
|
|
else
|
|
|
|
sock_dir=$SOCK_DIR tls_pid=$TLS_PID out_sock=$OUT_SOCK in_sock=$IN_SOCK
|
2021-07-06 12:48:36 +00:00
|
|
|
unset SOCK_DIR TLS_PID OUT_SOCK IN_SOCK
|
2021-07-06 08:27:07 +00:00
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
on_sys_exit_998_net() {
|
|
|
|
if [[ ${config[tls]} = no ]]; then
|
|
|
|
log_info "rowbot is closing the connection to irc://%s:%s" "${config[server]}" "${config[port]}"
|
|
|
|
exec {irc_sock}>&-
|
|
|
|
else
|
|
|
|
log_info "rowbot is closing the connection to ircs://%s:%s" "${config[server]}" "${config[port]}"
|
2021-07-06 05:29:17 +00:00
|
|
|
|
|
|
|
if [[ -v tls_pid ]]; then
|
|
|
|
kill -INT "$tls_pid"
|
|
|
|
fi
|
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
rm -rf -- "$sock_dir"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
###
|
|
|
|
# irc magic
|
|
|
|
###
|
|
|
|
|
|
|
|
magic_annoyatron900() {
|
|
|
|
irc_ping "row your bot gently down the stream"
|
|
|
|
}
|
|
|
|
|
|
|
|
on_sys_init_999_magic() {
|
|
|
|
get_option chan ""
|
|
|
|
}
|
|
|
|
|
2021-07-08 04:51:14 +00:00
|
|
|
on_sys_first_003_magic() {
|
|
|
|
get_option nick rowbot-dev
|
|
|
|
get_option ident rowbot
|
|
|
|
get_option realname rowbot
|
|
|
|
log_debug "registering with the server"
|
|
|
|
irc_nick "${config[nick]}"
|
|
|
|
irc_user "${config[ident]}" "${config[realname]}"
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
on_sys_before_999_magic() {
|
|
|
|
if [[ -v alarm_pid ]]; then
|
|
|
|
export ALARM_PID=$alarm_pid
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
on_sys_after_999_magic() {
|
|
|
|
trap magic_annoyatron900 USR1
|
|
|
|
|
|
|
|
if [[ -v ALARM_PID ]]; then
|
|
|
|
alarm_pid=$ALARM_PID
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
on_sys_register_999_magic() {
|
|
|
|
if [[ ${config[chan]} ]]; then
|
2021-07-12 05:03:47 +00:00
|
|
|
irc_join "${config[chan]}"
|
2021-07-06 12:48:36 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
while true; do
|
|
|
|
read -rt 10 </dev/zero
|
|
|
|
KILL -USR1 "$$"
|
|
|
|
done &
|
|
|
|
|
|
|
|
alarm_pid=$!
|
|
|
|
trap magic_annoyatron900 USR1
|
|
|
|
log_debug "process %d is being annoying" "$alarm_pid"
|
2021-07-12 05:03:47 +00:00
|
|
|
config[nick]=${msg_args[0]}
|
|
|
|
irc_who "${config[nick]}" %%uht,42
|
|
|
|
}
|
|
|
|
|
|
|
|
on_msg_354_magic() {
|
|
|
|
if (( msg_args[1] == 42 )); then
|
|
|
|
log_debug "received the identifying who"
|
|
|
|
config[ident]=${msg_args[2]} config[host]=${msg_args[3]}
|
|
|
|
fi
|
2021-07-06 12:48:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
on_sys_exit_997_magic() {
|
|
|
|
log_debug "shutting down annoyatron900"
|
|
|
|
|
|
|
|
if [[ -v alarm_pid ]] && is_running "$alarm_pid"; then
|
|
|
|
kill -STOP "$alarm_pid"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
###
|
|
|
|
# irc receive handlers
|
|
|
|
###
|
|
|
|
|
2021-07-08 04:51:14 +00:00
|
|
|
irc_on_ERROR() {
|
|
|
|
log_error "${msg_args[0]}"
|
|
|
|
exit
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_JOIN() {
|
|
|
|
log_info "%s has joined %s" "${msg[from]}" "${msg_args[0]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_KICK() {
|
|
|
|
if (( ${#msg_args[@]} == 3 )); then
|
|
|
|
log_info "%s has kicked %s from %s: %s" "${msg[from]}" "${msg_args[1]}" "${msg_args[0]}" "${msg_args[-1]}"
|
|
|
|
else
|
|
|
|
log_info "%s has kicked %s from %s" "${msg[from]}" "${msg_args[1]}" "${msg_args[0]}"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_MODE() {
|
|
|
|
if (( ${#msg_args[@]} == 2 )); then
|
|
|
|
log_info "%s sets mode(s) %s on %s" "${msg[from]}" "${msg_args[1]}" "${msg_args[0]}"
|
|
|
|
elif (( ${#msg_args[@]} > 2 )); then
|
|
|
|
log_info "%s: %s sets mode(s) %s" "${msg_args[0]}" "${msg[from]}" "${msg_args[*]:1}"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_NICK() {
|
|
|
|
log_info "%s has changed their name to %s" "${msg[from]}" "${msg_args[0]}"
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
irc_on_NOTICE() {
|
|
|
|
log_info "[%s/%s] %s" "${msg[from]}" "${msg_args[0]}" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
2021-07-08 04:51:14 +00:00
|
|
|
irc_on_PART() {
|
|
|
|
if (( ${#msg_args[@]} > 1 )); then
|
|
|
|
log_info "%s has left %s: %s" "${msg[from]}" "${msg_args[0]}" "${msg_args[1]}"
|
|
|
|
else
|
|
|
|
log_info "%s has left %s" "${msg[from]}" "${msg_args[0]}"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
irc_on_PING() {
|
|
|
|
irc_pong "${msg_args[1]}"
|
|
|
|
log_debug "received ping: %s" "${msg_args[0]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_PONG() {
|
|
|
|
log_debug "received pong: %s" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
2021-07-08 04:51:14 +00:00
|
|
|
irc_on_PRIVMSG() {
|
|
|
|
log_info "<%s/%s> %s" "${msg[from]}" "${msg_args[0]}" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_TOPIC() {
|
|
|
|
log_info "%s has changed the topic for %s: %s" "${msg[from]}" "${msg_args[0]}" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_QUIT() {
|
|
|
|
log_info "%s has disconnected: %s" "${msg[from]}" "${msg_args[0]}"
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
irc_on_001() {
|
|
|
|
log_info %s "${msg_args[1]}"
|
|
|
|
run_callbacks on_sys_register_
|
|
|
|
run_callbacks on_register_
|
|
|
|
}
|
|
|
|
|
2021-07-08 04:51:14 +00:00
|
|
|
irc_on_002() {
|
|
|
|
log_info %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_003() {
|
|
|
|
log_info %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_004() {
|
|
|
|
log_debug "%s " "${msg_args[@]:1}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_005() {
|
|
|
|
log_debug "received inotify specs"
|
|
|
|
}
|
|
|
|
|
2021-07-11 00:02:40 +00:00
|
|
|
irc_on_250() {
|
|
|
|
log_info %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_251() {
|
|
|
|
log_info %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_252() {
|
|
|
|
log_info "There are %d operators online" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_253() {
|
|
|
|
log_info "There are %d unknown connections" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_254() {
|
|
|
|
log_info "There are %d channels formed" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_255() {
|
|
|
|
log_info %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_265() {
|
|
|
|
log_info %s "${msg_args[3]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_266() {
|
|
|
|
log_info %s "${msg_args[3]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_315() {
|
|
|
|
log_debug "end of WHO for %s" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_332() {
|
|
|
|
log_info "topic for %s is %s" "${msg_args[1]}" "${msg_args[2]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_333() {
|
|
|
|
local date
|
|
|
|
printf -v date '%(%c)T' "${msg_args[3]}"
|
|
|
|
log_info "topic for %s set by %s at %s" "${msg_args[1]}" "${msg_args[2]}" "$date"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_353() {
|
|
|
|
log_info "members of %s: %s" "${msg_args[2]}" "${msg_args[3]}"
|
|
|
|
}
|
|
|
|
|
2021-07-12 05:03:47 +00:00
|
|
|
irc_on_354() {
|
|
|
|
log_debug "who: %s" "${msg_args[*]}"
|
|
|
|
}
|
|
|
|
|
2021-07-11 00:02:40 +00:00
|
|
|
irc_on_366() {
|
|
|
|
log_debug "%s: end of NAMES list" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_372() {
|
|
|
|
log_info %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_375() {
|
|
|
|
log_debug %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_376() {
|
|
|
|
log_debug %s "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_433() {
|
|
|
|
log_info "somebody is already using %s" "${msg_args[1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_438() {
|
|
|
|
log_error "%s couldn't change their nick to %s: %s" "${msg_args[1]}" "${msg_args[2]}" "${msg_args[-1]}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_on_473() {
|
|
|
|
log_error "%s: %s" "${msg_args[1]}" "${msg_args[2]}"
|
|
|
|
}
|
|
|
|
|
2021-07-12 05:22:31 +00:00
|
|
|
###
|
|
|
|
# plugin api
|
|
|
|
###
|
|
|
|
|
|
|
|
plugin_reg() {
|
|
|
|
declare -n plugins=irc_plugin_array_"$1"
|
|
|
|
local plugin
|
|
|
|
|
|
|
|
if [[ -v plugins ]]; then
|
|
|
|
for plugin in "${plugins[@]}"; do
|
|
|
|
if [[ $plugin = "$2" ]]; then
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
else
|
|
|
|
plugins=( )
|
|
|
|
fi
|
|
|
|
|
|
|
|
plugins+=( "$2" )
|
|
|
|
}
|
|
|
|
|
|
|
|
plugin_run() {
|
|
|
|
# This is a false positive.
|
|
|
|
# shellcheck disable=SC2178
|
|
|
|
declare -n plugins=irc_plugin_array_"$1"
|
|
|
|
shift
|
|
|
|
local plugin
|
|
|
|
|
|
|
|
if [[ -v plugins ]]; then
|
|
|
|
for plugin in "${plugins[@]}"; do
|
|
|
|
"$plugin" "$@"
|
|
|
|
done
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
###
|
|
|
|
# plugins
|
|
|
|
###
|
|
|
|
|
|
|
|
# lime-o-meter
|
|
|
|
|
|
|
|
limeometer() {
|
|
|
|
# We don't care about failures here, for better or for worse.
|
|
|
|
# shellcheck disable=SC2155
|
|
|
|
local limes=$(random 1 42) limeification
|
|
|
|
|
|
|
|
if [[ ${msg[from]} = Time-Warp ]]; then
|
|
|
|
limes=42
|
|
|
|
fi
|
|
|
|
|
|
|
|
(( limeification = (limes * 100) / 42 ))
|
|
|
|
irc_privmsg "${msg[to]}" "$limes limes (limes to $limeification%)"
|
|
|
|
}
|
|
|
|
|
|
|
|
on_init_limeometer() {
|
|
|
|
plugin_reg uplime limeometer
|
|
|
|
}
|
|
|
|
|
2021-07-06 08:27:07 +00:00
|
|
|
###
|
|
|
|
# irc send handlers
|
|
|
|
###
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
irc_join() {
|
|
|
|
local chans
|
|
|
|
printf -v chans %s, "$@"
|
|
|
|
net_send "JOIN %s" "${chans%,}"
|
|
|
|
}
|
|
|
|
|
|
|
|
irc_nick() {
|
2021-07-06 08:27:07 +00:00
|
|
|
net_send "NICK :%s" "$1"
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
irc_ping() {
|
|
|
|
net_send "PING :%s" "$1"
|
|
|
|
}
|
|
|
|
|
2021-07-08 04:51:14 +00:00
|
|
|
irc_pong() {
|
|
|
|
net_send "PONG %s" "$1"
|
|
|
|
}
|
|
|
|
|
2021-07-12 05:03:47 +00:00
|
|
|
irc_privmsg() {
|
|
|
|
local msg=$2 msg_len
|
|
|
|
|
|
|
|
if [[ -v config[host] ]]; then
|
|
|
|
(( msg_len = 493 - (${#config[nick]} + ${#config[ident]} + ${#config[host]} + ${#1}) ))
|
|
|
|
log_debug "max message length is %d" "$msg_len"
|
|
|
|
|
|
|
|
while (( ${#msg} > msg_len )); do
|
|
|
|
net_send "PRIVMSG %s :"$'\xe2\x80\x8b'"%s" "$1" "${msg:0:$msg_len}"
|
|
|
|
log_info "<%s/%s> %s" "${config[nick]}" "$1" "${msg:0:$msg_len}"
|
|
|
|
msg=${msg:$msg_len}
|
|
|
|
done
|
|
|
|
fi
|
|
|
|
|
|
|
|
net_send "PRIVMSG %s :"$'\xe2\x80\x8b'"%s" "$1" "$msg"
|
|
|
|
log_info "<%s/%s> %s" "${config[nick]}" "$1" "$msg"
|
|
|
|
}
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
irc_user() {
|
2021-07-06 08:27:07 +00:00
|
|
|
net_send "USER %s 0 * :%s" "$1" "$2"
|
|
|
|
}
|
|
|
|
|
2021-07-12 05:03:47 +00:00
|
|
|
irc_who() {
|
|
|
|
if (( $# > 1 )); then
|
|
|
|
net_send "WHO %s %s" "$1" "$2"
|
|
|
|
else
|
|
|
|
net_send "WHO %s" "$1"
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2021-07-04 11:25:08 +00:00
|
|
|
###
|
|
|
|
# cleanup
|
|
|
|
###
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
run_callbacks on_exit_
|
2021-07-06 00:16:09 +00:00
|
|
|
run_callbacks on_sys_exit_
|
2021-07-04 11:25:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
trap cleanup EXIT
|
|
|
|
|
2021-07-04 09:57:52 +00:00
|
|
|
###
|
|
|
|
# live code reloader
|
|
|
|
###
|
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
reload_config() {
|
2021-07-06 00:16:09 +00:00
|
|
|
run_callbacks on_sys_before_
|
2021-07-04 11:25:08 +00:00
|
|
|
run_callbacks on_before_
|
2021-07-06 08:27:07 +00:00
|
|
|
RELOADED=yes exec "${cmd_line[@]}"
|
2021-07-04 09:57:52 +00:00
|
|
|
}
|
|
|
|
|
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-04 09:57:52 +00:00
|
|
|
}
|
|
|
|
|
2021-07-05 23:14:23 +00:00
|
|
|
trap reload_hup HUP
|
2021-07-04 09:57:52 +00:00
|
|
|
|
2021-07-01 19:13:31 +00:00
|
|
|
###
|
2021-07-04 08:35:11 +00:00
|
|
|
# initialization sequence
|
2021-07-01 19:13:31 +00:00
|
|
|
###
|
2021-07-04 09:57:52 +00:00
|
|
|
|
2021-07-06 00:16:09 +00:00
|
|
|
run_callbacks on_sys_init_
|
2021-07-04 09:57:52 +00:00
|
|
|
run_callbacks on_init_
|
|
|
|
|
|
|
|
if is_reloaded; then
|
2021-07-06 00:16:09 +00:00
|
|
|
run_callbacks on_sys_after_
|
2021-07-04 11:25:08 +00:00
|
|
|
run_callbacks on_after_
|
2021-07-04 09:57:52 +00:00
|
|
|
else
|
2021-07-06 00:16:09 +00:00
|
|
|
run_callbacks on_sys_first_
|
2021-07-04 09:57:52 +00:00
|
|
|
run_callbacks on_first_
|
|
|
|
fi
|
2021-07-06 08:27:07 +00:00
|
|
|
|
|
|
|
###
|
|
|
|
# driver/protocol parser
|
|
|
|
###
|
|
|
|
|
|
|
|
while net_recv line; do
|
2021-07-06 12:48:36 +00:00
|
|
|
declare -A msg=( [words]=no [original]="$line" [score]=0 )
|
2021-07-06 08:27:07 +00:00
|
|
|
|
|
|
|
# parse prefix in the style of nick!ident@host
|
|
|
|
|
|
|
|
if [[ ${line:0:1} = :* ]]; then
|
|
|
|
prefix=${line%% *} prefix=${prefix#:} line=${line#:"$prefix"} line=${line# }
|
|
|
|
log_debug "parsing message prefix %s" "$prefix"
|
|
|
|
msg[host]=${prefix#*@} prefix=${prefix%"${msg[host]}"} prefix=${prefix%@}
|
2021-07-06 12:48:36 +00:00
|
|
|
msg[from]=${msg[host]}
|
2021-07-06 08:27:07 +00:00
|
|
|
|
|
|
|
if [[ $prefix ]]; then
|
|
|
|
msg[ident]=${prefix#*!}
|
2021-07-06 12:48:36 +00:00
|
|
|
msg[from]=${msg[ident]}
|
2021-07-06 08:27:07 +00:00
|
|
|
|
|
|
|
if [[ ${msg[ident]} != "$prefix" ]]; then
|
|
|
|
msg[nick]=${prefix%!*}
|
2021-07-06 12:48:36 +00:00
|
|
|
msg[from]=${msg[nick]}
|
2021-07-06 08:27:07 +00:00
|
|
|
fi
|
|
|
|
fi
|
2021-07-06 12:48:36 +00:00
|
|
|
|
|
|
|
if [[ /${msg[host]}/ = */bot/* ]]; then
|
|
|
|
(( msg[score] += 100 ))
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [[ ${msg[nick]} = *-bot ]]; then
|
|
|
|
(( msg[score] += 30 ))
|
|
|
|
elif [[ ${msg[nick]} = *bot ]]; then
|
|
|
|
(( msg[score] += 15 ))
|
|
|
|
fi
|
2021-07-06 08:27:07 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
# parse command formatted as a 3 digit integer or as a word consisting of
|
|
|
|
# alphabet values
|
|
|
|
|
|
|
|
msg[cmd]=${line%% *} line=${line#"${msg[cmd]}"} line=${line# }
|
2021-07-08 06:15:01 +00:00
|
|
|
msg[cmd]=${msg[cmd]^^}
|
2021-07-06 08:27:07 +00:00
|
|
|
log_debug "parsing message command %s" "${msg[cmd]}"
|
|
|
|
|
|
|
|
# parse the remaining values into white-space separated arguments
|
|
|
|
|
|
|
|
msg_args=()
|
|
|
|
|
|
|
|
while [[ $line ]]; do
|
|
|
|
if [[ ${line:0:1} = : ]]; then
|
|
|
|
msg_args+=( "${line:1}" ) msg[words]=yes line=
|
|
|
|
# Code using this array will be implemented later.
|
|
|
|
# shellcheck disable=SC2034
|
|
|
|
read -ra msg_words <<< "${msg_args[-1]}"
|
|
|
|
log_debug "parsed final argument %s" "${msg_args[-1]}"
|
|
|
|
else
|
|
|
|
arg=${line%% *} msg_args+=( "$arg" ) line=${line#"$arg"} line=${line# }
|
|
|
|
log_debug "parsed argument %s" "$arg"
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
|
2021-07-08 06:15:01 +00:00
|
|
|
if [[ ${msg[cmd]} = @(PRIVMSG|NOTICE) ]]; then
|
2021-07-11 00:21:10 +00:00
|
|
|
msg[to]=${msg[from]}
|
|
|
|
|
|
|
|
if [[ ${msg_args[0]:0:1} = \# ]]; then
|
|
|
|
msg[to]=${msg_args[0]}
|
|
|
|
fi
|
|
|
|
|
2021-07-06 12:48:36 +00:00
|
|
|
case ${msg_args[-1]} in
|
|
|
|
"["*"]")
|
|
|
|
(( msg[score] += 20 ))
|
|
|
|
;;
|
|
|
|
$'\xe2\x80\x8b'*)
|
|
|
|
(( msg[score] += 100 ))
|
|
|
|
esac
|
|
|
|
fi
|
|
|
|
|
|
|
|
log_debug "bot score is %d" "${msg[score]}"
|
|
|
|
|
2021-07-08 06:15:01 +00:00
|
|
|
if has irc_on_"${msg[cmd]}"; then
|
|
|
|
if run_callbacks "on_msg_${msg[cmd]}_"; then
|
|
|
|
irc_on_"${msg[cmd]}"
|
2021-07-10 23:47:17 +00:00
|
|
|
else
|
|
|
|
log_debug "handler for %s was skipped" "${msg[cmd]}"
|
2021-07-08 06:15:01 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
run_callbacks "on_late_msg_${msg[cmd]}_"
|
2021-07-06 08:27:07 +00:00
|
|
|
else
|
|
|
|
log_warn "unhandled line: %s" "${msg[original]}"
|
|
|
|
fi
|
2021-07-08 06:15:01 +00:00
|
|
|
|
|
|
|
if is_action; then
|
|
|
|
action=${msg_args[-1]#"${config[trigger]}"} action=${action%% *}
|
|
|
|
action_line=${msg_args[-1]#"$trigger$action"}
|
|
|
|
read -r action_line <<< "$action_line"
|
|
|
|
# This variable will be used in later code.
|
|
|
|
# shellcheck disable=SC2034
|
|
|
|
read -ra action_args <<< "$action_line"
|
2021-07-12 05:22:31 +00:00
|
|
|
plugin_run "$action"
|
2021-07-08 06:15:01 +00:00
|
|
|
fi
|
2021-07-06 08:27:07 +00:00
|
|
|
done
|