From ff2bc5d0931d4a460d4d74183b6f9dd9394c40a0 Mon Sep 17 00:00:00 2001 From: clsr Date: Wed, 22 Mar 2017 21:07:10 +0100 Subject: Initial commit --- modules/sed.bash | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 modules/sed.bash (limited to 'modules/sed.bash') diff --git a/modules/sed.bash b/modules/sed.bash new file mode 100644 index 0000000..b75a252 --- /dev/null +++ b/modules/sed.bash @@ -0,0 +1,189 @@ +# modules/sed.bash +# +# Provides sedbot. +# +# Settings: +# SED_TIMEOUT_BIN: path to the timeout script¹; optional to prevent regex DoS +# SED_TIMEOUT_MEM: maximum amount of memory (in kilobytes) a sed process may use +# +# ¹: https://github.com/pshved/timeout + + +if [ -z "$IRCBOT_MODULE" ]; then + printf "error: %s is a module for ircbot.bash and should not be run separately\n" "$0" + exit 1 +fi + +declare -g -A sed_messages + +# seds a previous message if instucted to +on_privmsg() { # args: $1 - source, $2 - channel/target, $3 - message + local where nick msg + local targetednick origmsg origkey + local key target fromnick regexed ctcp + + where="$2" + nick="$(parse_source_nick "$1")" + msg="$3" + if [[ $where == "$IRCBOT_NICK" ]]; then + where="$nick" + fi + + # targeted means the message was `` user2: s/foo/bar/'' (targets user2's last message) + origmsg="$msg" + origkey="$where $nick" + if targetednick="$(parse_targeted_nick "$msg")"; then + nick="$targetednick" + msg="$(parse_targeted_msg "$msg")" + fi + + + key="$where $nick" + target="${sed_messages[$key]:-}" + + # handle regexing CTCP ACTIONs properly + ctcp="$(parse_ctcp_command "$target")" || true + if [[ $ctcp == ACTION ]]; then + target="$(parse_ctcp_message "$target")" + fromnick="$(printf "\\x02* %s\\x02" "$nick")" + else + fromnick="<$nick>" + fi + + if regexed="$(sed_replace "$msg" "$target")"; then # if a replacement was done, send it + sendmsg PRIVMSG "$where" "$fromnick $regexed" + else # otherwise, store the triggering message + sed_messages[$origkey]="$origmsg" + fi +} + +sed_replace() { # args: $1 - sed s expression, $2 - text to regex + local msg target + local l pos i p del + local regexps ok t target + + msg="$1" + target="$2" + + del="${msg:1:1}" + if [[ -z $del ]] || [[ $(indexof '/|,!:' "$del") -lt 0 ]]; then + return 1 + fi + + # TODO: rewrite so that only one expression is tokenized at once (allows different delimiters) + + # tokenize + l=() + pos="$(indexof "$msg" "$del")" + while ((pos >= 0)); do + i=0 + ((p=pos-i-1)) + while ((p >= 0)) && [[ ${msg:$p:1} == '\' ]]; do # count \ characters + ((i++)) + ((p=pos-i-1)) + done + if ((i%2 == 0)); then + l+=("${msg:0:$pos}") + ((p=pos+1)) + msg="${msg:$p}" + pos=0 + else + ((pos++)) + fi + p="$(indexof "${msg:$pos}" "$del")" + if ((p >= 0)); then + ((pos+=p)) + else + pos=$p + fi + done + l+=("$msg") + + # l is now an array of the expr separated by unescaped delimiters + + i=0 + regexps=() + ok=1 + + # s/expr1/repl1/opts1 s/expr2/repl2/opts2 s/expr3/repl3 + while ((i < ${#l[@]})); do + # begins with s + if [ "${l[$i]}" != "s" ]; then + break + fi + ((i++)) + + # expr + if ((i >= ${#l[@]})); then + break + fi + exp="${l[$i]}" + ((i++)) + + # repl + if ((i >= ${#l[@]})); then + break + fi + repl="${l[$i]}" + ((i++)) + + # opts + opts='' + if ((i < ${#l[@]})); then + opts="${l[$i]}" + p=0 + while ((p < ${#opts})); do + c="${opts:$p:1}" + if ! [[ $c =~ ^[ig0-9]$ ]]; then # allowed opts are 0-9, i and g + ok=0 + break + fi + ((p++)) + done + if ! ((ok)); then + # multiple regexps per line + if [[ "${opts:$p:1}" == ' ' || "${opts:$p:1}" == ';' ]]; then # expression separators are space and ; + p1=$p + while ((p < ${#opts})); do + if [[ "${opts:$p:1}" != ' ' && "${opts:$p:1}" != ';' ]]; then + break + fi + p=$((p+1)) + done + l[$i]="${opts:$p}" + opts="${opts:0:$p1}" + ok=1 + else + break + fi + fi + fi + + if ((ok)); then + regexps+=("s$del$exp$del$repl$del$opts") + fi + done + + + if ! ((ok)) || ((${#regexps[@]} == 0)); then + return 1 + fi + + t="$target" + for re in "${regexps[@]}"; do + if [[ -n ${SED_TIMEOUT_BIN:-} ]]; then + target="$("$SED_TIMEOUT_BIN" -m "$SED_TIMEOUT_MEM" sed -e "$re" <<< "$target")" + else + target="$(sed -e "$re" <<< "$target")" + fi + done + target="$(trimrn <<< "$target")" + verbose "sed '%s' <<< '%s' >>> '%s'" "$1" "$2" "$target" + if [[ $target != "$t" ]]; then + if [[ -n $target ]]; then + trimrn <<< "$target" + return 0 + fi + fi + return 1 +} -- cgit