123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #!/bin/bash
  2. #
  3. # Extra defaults file
  4. #
  5. # Options here override /etc and ~/, but are overrided
  6. # by command line options.
  7. #
  8. NOTIFIRC_RC=${NOTIFIRC_RC:-}
  9. usage() {
  10. #
  11. # Print usage message and exit with status 2
  12. #
  13. local self # our name
  14. self=$(basename "$0")
  15. {
  16. echo "usage: $self [options] message"
  17. echo "usage: $self [options] [-l max_lines] [-L max_width] -f message_file|-"
  18. echo "options: [-h host] [-p port] [-n nick] [-c context] [-u user]"
  19. echo ""
  20. echo "user can be user name but also channel name, in that case notifirc"
  21. echo "will JOIN the channel first."
  22. echo ""
  23. echo "Defaults for options can be defined as POSIX shell assignments under"
  24. echo "/etc/notifirc.rc, ~/.notifirc.rc or file specified by NOTIFIRC_RC"
  25. echo "envvar. (E.g. 'nick=jdoe', 'max_width=40')."
  26. echo ""
  27. echo "All is logged under /var/log/notifirc.log, ~/.notifirc.log, or"
  28. echo "/tmp/notifirc.log, whichever $self can write to."
  29. } >&2
  30. exit 2
  31. }
  32. mkcommands() {
  33. #
  34. # Print all IRC commands
  35. #
  36. local user="$1" # target user
  37. local nick="$2" # our nickname
  38. local message
  39. echo "NICK $nick"
  40. echo "USER $nick 8 * :notifirc bot"
  41. if test "${user:0:1}" == '#'; then
  42. echo "JOIN $user"
  43. fi
  44. while read -r message;
  45. do
  46. echo "PRIVMSG $user :$message"
  47. done
  48. echo "QUIT"
  49. }
  50. mknick() {
  51. #
  52. # Compose bot's nickname
  53. #
  54. case "$context" in
  55. "") echo "$nick" ;;
  56. *) echo "$nick|$context" ;;
  57. esac
  58. }
  59. die() {
  60. #
  61. # End script with fatal error $1
  62. #
  63. local msg="fatal: $1" # message
  64. log "$msg"
  65. log "-----END *-----"
  66. echo "$msg" >&2
  67. exit 3
  68. }
  69. warn() {
  70. #
  71. # Issue message $1 as warning to logfile and stderr
  72. #
  73. local msg="warning: $1" # full message
  74. echo "$msg" >&2
  75. log "$msg"
  76. }
  77. log_pipe() {
  78. #
  79. # Log contents of standard input
  80. #
  81. local line # each line of input
  82. local stamp
  83. while IFS= read -r line;
  84. do
  85. stamp=$(date -Isec)
  86. log "$stamp $line"
  87. done
  88. }
  89. log() {
  90. #
  91. # Log message $1 to $logfile
  92. #
  93. local stamp
  94. stamp=$(date -Isec)
  95. echo "$stamp | $1" >>"$logfile"
  96. }
  97. load_defaults() {
  98. #
  99. # Load defaults from RC file $1
  100. #
  101. local rcfile=$1 # RC file to load
  102. test -e "$rcfile" || return 0
  103. test -f "$rcfile" || {
  104. warn "defaults file is not a file: $rcfile"
  105. return 3
  106. }
  107. test -r "$rcfile" || {
  108. warn "defaults file not readable: $rcfile"
  109. return 3
  110. }
  111. bash -n "$rcfile" || {
  112. warn "syntax error in defaults file: $rcfile"
  113. return 3
  114. }
  115. #shellcheck disable=SC1090
  116. . "$rcfile" || {
  117. warn "error in defaults file: $rcfile"
  118. return 3
  119. }
  120. }
  121. trim() {
  122. #
  123. # Trim to maximum $1 lines and $2 characters per line
  124. #
  125. local limit_l=$1 # max. lines per message
  126. local limit_c=$2 # max. chars per line
  127. local lines_read=0 # how many lines we read
  128. local suff="" # suffix (ellipsis)
  129. while true;
  130. do
  131. test $lines_read -ge "$limit_l" && break
  132. IFS= read -r line || break
  133. (( lines_read++ ))
  134. test ${#line} -gt "$limit_c" && suff=…
  135. line=${line:0:$limit_c}$suff
  136. echo "$line"
  137. done
  138. }
  139. choose_logfile() {
  140. #
  141. # Decide which log file to use (/var, /home or /tmp)
  142. #
  143. local path # path to log file
  144. {
  145. echo /var/log/notifirc.log
  146. echo "$HOME/.notifirc.log"
  147. echo /tmp/notifirc.log
  148. } \
  149. | while read -r path;
  150. do
  151. if test -w "$(dirname "$path")" \
  152. || test -w "$path";
  153. then
  154. echo "$path"
  155. break
  156. fi
  157. done
  158. }
  159. main() {
  160. local context # context tag for nickname
  161. local nick # bot's nickname
  162. local host # IRC server hostname
  163. local port # ... ^^ ... port
  164. local user # user to notify
  165. local message # message to send
  166. local logfile # logfile to use
  167. local msgfile # file to read message from
  168. local f_maxlines=3 # maximum number of lines per notification
  169. local f_maxwidth=80 # maximum characters per notification line
  170. logfile=$(choose_logfile)
  171. test -n "$logfile" || {
  172. echo "could not find writable logfile location" >&2
  173. echo "logging will be off!" >&2
  174. logfile=/dev/null
  175. }
  176. load_defaults /etc/notifirc.rc
  177. load_defaults "$HOME/.notifirc.rc"
  178. load_defaults "$NOTIFIRC_RC"
  179. while true; do case $1 in
  180. -c) context=$2; shift 2 || usage ;;
  181. -f) msgfile=$2; shift 2 || usage ;;
  182. -h) host=$2; shift 2 || usage ;;
  183. -l) f_maxlines=$2; shift 2 || usage ;;
  184. -L) f_maxwidth=$2; shift 2 || usage ;;
  185. -n) nick=$2; shift 2 || usage ;;
  186. -p) port=$2; shift 2 || usage ;;
  187. -u) user=$2; shift 2 || usage ;;
  188. --) shift; break ;;
  189. -*) usage ;;
  190. *) break ;;
  191. esac done
  192. message="$*"
  193. test -n "$host" || usage
  194. test -n "$port" || usage
  195. test -n "$user" || usage
  196. test -n "$nick" || nick="notifirc"
  197. test -z "$message$msgfile" && usage
  198. log "-----BEGIN sending notification-----"
  199. log "host='$host'"
  200. log "port='$port'"
  201. log "nick='$nick'"
  202. log "context='$context'"
  203. log "user='$user'"
  204. log "msgfile='$msgfile'"
  205. log "f_maxlines='$f_maxlines'"
  206. log "f_maxwidth='$f_maxwidth'"
  207. log "message='$message'"
  208. {
  209. test -n "$message" && printf '%s\n' "$message"
  210. test -n "$msgfile" && grep . "$msgfile" | trim "$f_maxlines" "$f_maxwidth"
  211. } \
  212. | mkcommands "$user" "$(mknick)" \
  213. | nc "$host" "$port" 2>&1 \
  214. | log_pipe
  215. log "-----END sending notification-----"
  216. }
  217. main "$@"