#!/bin/bash usage() { # # Print usage message and exit with status 2 # local self # our name self=$(basename "$0") echo "usage: $self [options] message" >&2 echo "usage: $self [options] [-l max_lines] [-L max_width] -f message_file|-" >&2 echo "options: [-h host] [-p port] [-n nick] [-c context] [-u user]" >&2 exit 2 } mkcommands() { # # Print all IRC commands # local user="$1" # target user local nick="$2" # our nickname local message echo "NICK $nick" echo "USER $nick 8 * :notifirc bot" while read -r message; do echo "PRIVMSG $user :$message" done echo "QUIT" } mknick() { # # Compose bot's nickname # case "$context" in "") echo "$nick" ;; *) echo "$nick|$context" ;; esac } die() { # # End script with fatal error $1 # local msg="fatal: $1" # message log "$msg" log "-----END *-----" echo "$msg" >&2 exit 3 } warn() { # # Issue message $1 as warning to logfile and stderr # local msg="warning: $1" # full message echo "$msg" >&2 log "$msg" } log_pipe() { # # Log contents of standard input # local line # each line of input while IFS= read -r line; do log "$line" done } log() { # # Log message $1 to $logfile # echo "$1" >>"$logfile" } load_defaults() { # # Load defaults from RC file $1 # local rcfile=$1 # RC file to load test -e "$rcfile" || return 0 test -f "$rcfile" || { warn "defaults file is not a file: $rcfile" return 3 } test -r "$rcfile" || { warn "defaults file not readable: $rcfile" return 3 } bash -n "$rcfile" || { warn "syntax error in defaults file: $rcfile" return 3 } #shellcheck disable=SC1090 . "$rcfile" || { warn "error in defaults file: $rcfile" return 3 } } trim() { # # Trim to maximum $1 lines and $2 characters per line # local limit_l=$1 # max. lines per message local limit_c=$2 # max. chars per line local lines_read=0 # how many lines we read local suff="" # suffix (ellipsis) while true; do test $lines_read -ge "$limit_l" && break IFS= read -r line || break (( lines_read++ )) test ${#line} -gt "$limit_c" && suff=… line=${line:0:$limit_c}$suff echo "$line" done } choose_logfile() { # # Decide which log file to use (/var, /home or /tmp) # local path # path to log file { echo /var/log/notifirc.log echo "$HOME/.notifirc.log" echo /tmp/notifirc.log } \ | while read -r path; do if test -w "$(dirname "$path")" \ || test -w "$path"; then echo "$path" break fi done } main() { local context # context tag for nickname local nick # bot's nickname local host # IRC server hostname local port # ... ^^ ... port local user # user to notify local message # message to send local logfile # logfile to use local msgfile # file to read message from local f_maxlines=3 # maximum number of lines per notification local f_maxwidth=80 # maximum characters per notification line logfile=$(choose_logfile) test -n "$logfile" || { echo "could not find writable logfile location" >&2 echo "logging will be off!" >&2 logfile=/dev/null } load_defaults /etc/notifirc.rc load_defaults "$HOME/.notifirc.rc" while true; do case $1 in -c) context=$2; shift 2 || usage ;; -f) msgfile=$2; shift 2 || usage ;; -h) host=$2; shift 2 || usage ;; -l) f_maxlines=$2; shift 2 || usage ;; -L) f_maxwidth=$2; shift 2 || usage ;; -n) nick=$2; shift 2 || usage ;; -p) port=$2; shift 2 || usage ;; -u) user=$2; shift 2 || usage ;; --) shift; break ;; -*) usage ;; *) break ;; esac done message="$*" test -n "$host" || usage test -n "$port" || usage test -n "$user" || usage test -n "$nick" || nick="notifirc" test -z "$message$msgfile" && usage log "-----BEGIN sending notification-----" log "host='$host'" log "port='$port'" log "nick='$nick'" log "context='$context'" log "user='$user'" log "msgfile='$msgfile'" log "f_maxlines='$f_maxlines'" log "f_maxwidth='$f_maxwidth'" log "message='$message'" { test -n "$message" && printf '%s\n' "$message" test -n "$msgfile" && grep . "$msgfile" | trim "$f_maxlines" "$f_maxwidth" } \ | mkcommands "$user" "$(mknick)" \ | nc "$host" "$port" 2>&1 \ | log_pipe log "-----END sending notification-----" } main "$@"