blob: b75a2528b765127c3fd51d3815427462e949f705 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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 ``<user1> 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
}
|