vitalbits.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. # Vital bits of bashy2
  2. # This file is part of bashy2, an irc bot used on irc.sdf.org.
  3. # Copyright (C) 2015-2016 mlaine@sdfeu.org, grugly@sdf.org
  4. # Copyright (C) 2017-2018 mlaine@sdfeu.org
  5. # Permission is hereby granted, free of charge, to any person obtaining
  6. # a copy of this software and associated documentation files (the
  7. # "Software"), to deal in the Software without restriction, including
  8. # without limitation the rights to use, copy, modify, merge, publish,
  9. # distribute, sublicense, and/or sell copies of the Software, and to
  10. # permit persons to whom the Software is furnished to do so, subject to
  11. # the following conditions:
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  17. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  18. # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  19. # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  20. # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #-----------------------------------------------------------------------
  22. function debug {
  23. # Sends data to #bashy2-debug.
  24. if [ "${debug}" = 0 ]; then
  25. printf 'PRIVMSG #bashy2-debug :%s\n' "$*" >&3
  26. fi
  27. ## Writes debug data to ~/bashy.log
  28. if [ "${debug}" = "logfile" ]; then
  29. echo "$*" >> ${HOME}/bashy.log
  30. fi
  31. }
  32. function nonvital_join {
  33. # A parent function for nonvital function calls. Called for every JOIN.
  34. welcome
  35. }
  36. function nonvital_pm {
  37. # A parent function for nonvital function calls. Called for every PRIVMSG.
  38. seen
  39. }
  40. function parse {
  41. # Parses PRIVMSG formatted information.
  42. local x="${1%\!*}"
  43. local y="${1%@*}"
  44. nick="${x:1}"
  45. user="${y#*\!}"
  46. host="${1#*@}"
  47. chan="$3"
  48. msg="${4:1}"
  49. local z="${msg:1}"
  50. cmd="${z,,}"
  51. args=("${@:5}")
  52. prefix="${msg:0:1}"
  53. }
  54. function multiply {
  55. # "$(multiply 3 'foo')" -> 'foo foo foo'
  56. local x="$(printf "%"$1"s")"
  57. local y="${x// /"$2" }"
  58. printf '%s\n' "${y% *}"
  59. }
  60. function join {
  61. # Joins all channels.
  62. local chans=('./channels/'*)
  63. local chlist="${chans[*]##*/}"
  64. send 'join' "${chlist// /,}"
  65. }
  66. function chmode {
  67. # If called with no args, resets channel modes for all channels.
  68. # Otherwise lists all channels.
  69. local chans=('./channels/'*)
  70. if [[ "$1" ]]; then
  71. send 'pm' "Channels: ${chans[*]##*/}"
  72. else
  73. for channel in "${chans[@]##*/}"; do
  74. local mode="$(awk 'NR==2' "./channels/$channel")"
  75. local bans="$(awk 'NR==3' "./channels/$channel")"
  76. send 'mode' "$channel" "$mode"
  77. send 'mode' "$channel" "$bans"
  78. done
  79. fi
  80. }
  81. function chtopic {
  82. # If called with no args, queries the topic of every channel. A query
  83. # results main() calling chtopic() with args, resulting in the actual
  84. # setting of topics (should they differ from their default values).
  85. if (( ! "$#" )); then
  86. local chans=('./channels/'*)
  87. for channel in "${chans[@]##*/}"; do
  88. send 'qtopic' "$channel"
  89. done
  90. else
  91. local ch="$4"
  92. local foo="${*:5}"
  93. local topic="${foo:1}"
  94. local default="$(awk 'NR==1' "./channels/$ch")"
  95. if [[ -n "$default" && "$topic" != "$default" ]]; then
  96. send 'topic' "$ch" "$default"
  97. fi
  98. fi
  99. }
  100. function up {
  101. # Changes a user's mode based on the output of WHOIS received from
  102. # the server. WHOIS is issued by the user's request '!up'.
  103. if [[ "$2" = '311' ]]; then
  104. nick="$4"
  105. user="$5"
  106. host="$6"
  107. auth
  108. elif [[ -n "$flag" ]]; then
  109. local foo=("${@:5}")
  110. local chans=("${foo[@]//[:~&@%+]/}")
  111. local name="$(multiply "${#flag}" "$nick")"
  112. for channel in "${chans[@]}"; do
  113. if [[ -f "./channels/$channel" ]]; then
  114. send 'mode' "$channel" "+$flag" "$name"
  115. fi
  116. done
  117. fi
  118. }
  119. function upall {
  120. # Changes user modes based on the output of WHO received from the
  121. # server. WHO is issued by serv() 'upall'.
  122. if [[ "$2" = '315' ]]; then
  123. local ch="$4"
  124. sendup "$ch" 'o'
  125. sendup "$ch" 'h'
  126. sendup "$ch" 'v'
  127. ops=(); hops=(); voices=()
  128. else
  129. user="$5"
  130. host="$6"
  131. nick="$8"
  132. stats="$9"
  133. ## Test if sixth field of RPL_WHOREPLY indicates chan voice or higher,
  134. ## and shuck if true. This reduces unneeded shell commands and irc commands
  135. local reg_shuck="[~&@%+]" ## See E14) of https://tiswww.case.edu/php/chet/bash/FAQ
  136. if [[ ! "${stats}" =~ ${reg_shuck} ]]; then
  137. auth
  138. fi
  139. case "$flag" in
  140. 'ohv')
  141. ops+=("$nick")
  142. hops+=("$nick")
  143. voices+=("$nick") ;;
  144. 'hv')
  145. hops+=("$nick")
  146. voices+=("$nick") ;;
  147. 'v')
  148. voices+=("$nick") ;;
  149. esac
  150. fi
  151. }
  152. function sendup {
  153. # Simplifies upall().
  154. local ch="$1"
  155. case "$2" in
  156. 'o') local nicks=("${ops[@]}") ;;
  157. 'h') local nicks=("${hops[@]}") ;;
  158. 'v') local nicks=("${voices[@]}") ;;
  159. esac
  160. local f="$(printf 'f%.0s' $(seq 1 "$modes"))"
  161. while (( "${#nicks[@]}" > "$modes" )); do
  162. send 'mode' "$ch" "+${f//f/$2}" "${nicks[*]:0:$modes}"
  163. local nicks=("${nicks[@]:$modes}")
  164. sleep 3 ## Helps combat hitting sendq limit
  165. done
  166. if (( "${#nicks[@]}" > 0 )); then
  167. local f="$(printf 'f%.0s' $(seq 1 "${#nicks[@]}"))"
  168. send 'mode' "$ch" "+${f//f/$2}" "${nicks[*]}"
  169. sleep 3 ## Helps combat hitting sendq limit
  170. fi
  171. }
  172. function send {
  173. # A wrapper function to simplify sending messages to the server.
  174. debug 'OUT:' "$@"
  175. case "$1" in
  176. 'pong')
  177. printf 'PONG %s\n' "$2" >&3 ;;
  178. 'pm')
  179. printf 'PRIVMSG %s :%s\n' "$chan" "$2" >&3 ;;
  180. 'mode')
  181. printf 'MODE %s %s %s\n' "$2" "$3" "$4" >&3 ;;
  182. 'join')
  183. printf 'JOIN %s\n' "$2" >&3 ;;
  184. 'part')
  185. printf 'PART %s\n' "$2" >&3 ;;
  186. 'oper')
  187. printf 'OPER %s %s\n' "$2" "$3" >&3 ;;
  188. 'user')
  189. printf 'USER %s 0 * :%s\n' "$2" "$3" >&3 ;;
  190. 'nick')
  191. printf 'NICK %s\n' "$2" >&3 ;;
  192. 'topic')
  193. printf 'TOPIC %s :%s\n' "$2" "$3" >&3 ;;
  194. 'qtopic')
  195. printf 'TOPIC %s\n' "$2" >&3 ;;
  196. 'whois')
  197. printf 'WHOIS %s\n' "$2" >&3 ;;
  198. 'who')
  199. printf 'WHO %s\n' "$2" >&3 ;;
  200. 'quit')
  201. printf "QUIT :I'll be back!\n" >&3 ;;
  202. esac
  203. }
  204. function serv {
  205. # Maintainer only commands.
  206. case "$1" in
  207. 'list')
  208. chmode 0 ;;
  209. 'join')
  210. join
  211. sleep 5 && chmode &&
  212. sleep 5 && chtopic &&
  213. sleep 5 && serv 'upall' & ;;
  214. 'delete')
  215. local ch="${args[0]}"
  216. if [[ -f "./channels/$ch" ]]; then
  217. rm "./channels/$ch"
  218. send 'pm' "Channel $ch deleted; remember to push your changes!"
  219. send 'part' "$ch"
  220. else
  221. send 'pm' "Channel ${ch:-null} does not exist!"
  222. fi ;;
  223. 'chmode')
  224. chmode
  225. send 'pm' 'Channel modes set.' ;;
  226. 'chtopic')
  227. chtopic
  228. send 'pm' 'Channel topics set.' ;;
  229. 'upall')
  230. local chans=('./channels/'*)
  231. for channel in "${chans[@]##*/}"; do
  232. send 'who' "$channel"
  233. done ;;
  234. 'topic')
  235. local ch="${args[0]}"
  236. local to="${args[*]:1}"
  237. if [[ -f "./channels/$ch" ]]; then
  238. printf '%s\n' 1c "$to" . w | ed -s "./channels/$ch"
  239. (( "$quiet" == 1 )) && chtopic
  240. else
  241. send 'pm' "Channel ${ch:-null} does not exist!"
  242. fi ;;
  243. 'head')
  244. send 'pm' "$(git log -1 --pretty="format:%h: %s [%an, %ci]")" ;;
  245. 'pull')
  246. ( git fetch origin master && git reset --hard FETCH_HEAD &&
  247. git clean -df ) 1>'/dev/null' &&
  248. send 'pm' 'Pulled!' ;;
  249. 'reload')
  250. source './bits/conf.sh'
  251. source './bits/vitalbits.sh'
  252. source './bits/nonvitalbits.sh'
  253. send 'pm' 'Reloaded!' ;;
  254. 'restart')
  255. ( sleep 5 && './bashy2' & )
  256. send 'pm' 'Restarting...'
  257. send 'quit'
  258. exit 0 ;;
  259. 'debug')
  260. echo "DEBUG: debug toggle [${args[0]}]" >> $HOME/bashy.log
  261. if [ "${args[0]}" = 0 ]; then
  262. debug=0
  263. send 'pm' 'Debug log available at #bashy2-debug.'
  264. elif [ "${args[0]}" = "logfile" ]; then
  265. debug="logfile"
  266. send 'pm' 'Debug log available in ~/bashy.log'
  267. else
  268. debug=1
  269. send 'pm' 'Debug logging off.'
  270. fi ;;
  271. 'quiet')
  272. if (( "$quiet" == 0 )); then
  273. quiet=1
  274. send 'pm' 'Volume up!'
  275. else
  276. quiet=0
  277. send 'pm' 'I will shut up.'
  278. fi ;;
  279. 'die')
  280. send 'pm' "What'd I do?"
  281. send 'quit'
  282. exit 0 ;;
  283. esac
  284. }
  285. function run {
  286. # Runs commands.
  287. if [[ -x "$cmd_dir/$cmd" ]]; then
  288. ( cd "$cmd_dir" && "./$cmd" "$nick" "$chan" "${args[@]}" ) &
  289. else
  290. send 'pm' "'$cmd' not found. See !commands for available commands."
  291. fi
  292. }
  293. function priv {
  294. # Processes every PRIVMSG.
  295. parse "$@"
  296. if [[ "$chan" = "$bot_nick" ]]; then
  297. chan="$nick"
  298. else
  299. nonvital_pm
  300. fi
  301. 2botpriv
  302. if [[ "$msg" = '!up' ]]; then
  303. send 'whois' "$nick"
  304. return 0
  305. elif [[ -n "$cmd" && ("$quiet" -eq 1 || "$chan" = "$nick") &&
  306. "$prefix" = '!' && ! "$cmd" =~ [^[:alnum:]_] ]]; then
  307. run
  308. return 0
  309. fi
  310. if [[ "$prefix" = '%' && -f "./maintainers/$nick" ]]; then
  311. auth; [[ -n "$flag" ]] && serv "$cmd"
  312. elif [[ "$chan" = "$nick" ]]; then
  313. send 'pm' "I am a bot. Try '!help' instead."
  314. fi
  315. }
  316. function user_join {
  317. # Processes every JOIN.
  318. parse "$@"
  319. nonvital_join
  320. 2botjoin
  321. auth
  322. if [[ -n "$flag" ]]; then
  323. local ch="${chan:1}"
  324. local name="$(multiply "${#flag}" "$nick")"
  325. send 'mode' "$ch" "+$flag" "$name"
  326. fi
  327. }
  328. function user_quit {
  329. # Processes every QUIT.
  330. 2botquit "${1%\!*}"
  331. }