rivescript 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Changes.md +13 -0
- data/LICENSE +22 -0
- data/README.md +131 -0
- data/bin/riveshell +81 -0
- data/eg/brain/admin.rive +15 -0
- data/eg/brain/begin.rive +190 -0
- data/eg/brain/clients.rive +72 -0
- data/eg/brain/eliza.rive +301 -0
- data/eg/brain/myself.rive +61 -0
- data/eg/brain/rpg.rive +294 -0
- data/lib/rivescript/brain.rb +986 -0
- data/lib/rivescript/inheritance.rb +105 -0
- data/lib/rivescript/lang/ruby.rb +72 -0
- data/lib/rivescript/parser.rb +581 -0
- data/lib/rivescript/sessions.rb +205 -0
- data/lib/rivescript/sorting.rb +158 -0
- data/lib/rivescript/utils.rb +116 -0
- data/lib/rivescript/version.rb +9 -0
- data/lib/rivescript.rb +460 -0
- metadata +69 -0
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
# RiveScript Ruby port, https://jvmlab.org/, MIT License
|
|
2
|
+
|
|
3
|
+
# Brain logic for RiveScript
|
|
4
|
+
|
|
5
|
+
require_relative "utils"
|
|
6
|
+
require_relative "inheritance"
|
|
7
|
+
|
|
8
|
+
class RiveScript
|
|
9
|
+
class Brain
|
|
10
|
+
# Reply weights above this are clamped to prevent trivially-crafted
|
|
11
|
+
# {weight=N} tags from ballooning the random-choice bucket.
|
|
12
|
+
MAX_REPLY_WEIGHT = 10_000
|
|
13
|
+
|
|
14
|
+
# Private-use-area token used to shield a fully-processed inner reply
|
|
15
|
+
# from being tag-processed a second time when substituted into a BEGIN
|
|
16
|
+
# block's {ok} placeholder.
|
|
17
|
+
BEGIN_OK_TOKEN = "\uE000RIVE_OK\uE000"
|
|
18
|
+
|
|
19
|
+
# Simple text-transform tags that are allowed to wrap {ok} in a BEGIN
|
|
20
|
+
# block (e.g. "{uppercase}{ok}{/uppercase}"). These are resolved against
|
|
21
|
+
# the already-processed inner reply directly (bypassing the tag engine)
|
|
22
|
+
# so the wrapping author's intent is preserved without re-running the
|
|
23
|
+
# full tag processor (with its side-effecting tags) over user-influenced
|
|
24
|
+
# reply text a second time.
|
|
25
|
+
BEGIN_OK_FORMAT_TAGS = %w[person formal sentence uppercase lowercase].freeze
|
|
26
|
+
|
|
27
|
+
def self.parse_int_js(str)
|
|
28
|
+
m = str.to_s.strip.match(/\A[-+]?\d+/)
|
|
29
|
+
m ? m[0].to_i : nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Splits "name=value" assignment data on the first "=" only, so that
|
|
33
|
+
# values which themselves contain "=" are preserved intact.
|
|
34
|
+
def self.split_assignment(data)
|
|
35
|
+
data.to_s.split("=", 2)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
TAGS = {
|
|
39
|
+
"bot" => {
|
|
40
|
+
"self_closing" => true,
|
|
41
|
+
"handle" => lambda { |rive, data, _user, _scope|
|
|
42
|
+
vars = rive._var
|
|
43
|
+
split = Brain.split_assignment(data)
|
|
44
|
+
if split.length > 1
|
|
45
|
+
vars[split[0].strip] = split[1]
|
|
46
|
+
""
|
|
47
|
+
elsif split.length == 1
|
|
48
|
+
val = vars[split[0].strip]
|
|
49
|
+
val = "undefined" if val.nil?
|
|
50
|
+
val
|
|
51
|
+
else
|
|
52
|
+
"undefined"
|
|
53
|
+
end
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"env" => {
|
|
57
|
+
"self_closing" => true,
|
|
58
|
+
"handle" => lambda { |rive, data, _user, _scope|
|
|
59
|
+
globals = rive._global
|
|
60
|
+
split = Brain.split_assignment(data)
|
|
61
|
+
if split.length > 1
|
|
62
|
+
globals[split[0].strip] = split[1]
|
|
63
|
+
""
|
|
64
|
+
elsif split.length == 1
|
|
65
|
+
val = globals[split[0].strip]
|
|
66
|
+
val = "undefined" if val.nil?
|
|
67
|
+
val
|
|
68
|
+
else
|
|
69
|
+
"undefined"
|
|
70
|
+
end
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"set" => {
|
|
74
|
+
"self_closing" => true,
|
|
75
|
+
"handle" => lambda { |rive, data, user, _scope|
|
|
76
|
+
split = Brain.split_assignment(data)
|
|
77
|
+
rive.set_uservar(user, split[0].strip, split[1]) if split.length > 1
|
|
78
|
+
""
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"get" => {
|
|
82
|
+
"self_closing" => true,
|
|
83
|
+
"handle" => lambda { |rive, data, user, _scope|
|
|
84
|
+
rive.get_uservar(user, data.strip)
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"add" => {
|
|
88
|
+
"self_closing" => true,
|
|
89
|
+
"handle" => lambda { |rive, data, user, _scope|
|
|
90
|
+
split = Brain.split_assignment(data)
|
|
91
|
+
name = split[0].strip
|
|
92
|
+
raw_value = split[1]
|
|
93
|
+
existing_value = rive.get_uservar(user, name) || 0
|
|
94
|
+
existing_value = 0 if existing_value == "undefined"
|
|
95
|
+
value = raw_value.nil? ? nil : Brain.parse_int_js(raw_value.strip)
|
|
96
|
+
existing_number = Brain.parse_int_js(existing_value.to_s)
|
|
97
|
+
if value.nil?
|
|
98
|
+
return "[ERR: Math can't 'add' non-numeric value '#{raw_value}']"
|
|
99
|
+
elsif existing_number.nil?
|
|
100
|
+
return "[ERR: Math can't 'add' non-numeric user variable '#{name}']"
|
|
101
|
+
else
|
|
102
|
+
result = existing_number + value
|
|
103
|
+
rive.set_uservar(user, name, result)
|
|
104
|
+
end
|
|
105
|
+
""
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"sub" => {
|
|
109
|
+
"self_closing" => true,
|
|
110
|
+
"handle" => lambda { |rive, data, user, _scope|
|
|
111
|
+
split = Brain.split_assignment(data)
|
|
112
|
+
name = split[0].strip
|
|
113
|
+
raw_value = split[1]
|
|
114
|
+
existing_value = rive.get_uservar(user, name) || 0
|
|
115
|
+
value = raw_value.nil? ? nil : Brain.parse_int_js(raw_value.strip)
|
|
116
|
+
existing_value = 0 if existing_value == "undefined"
|
|
117
|
+
existing_number = Brain.parse_int_js(existing_value.to_s)
|
|
118
|
+
if value.nil?
|
|
119
|
+
return "[ERR: Math can't 'sub' non-numeric value '#{raw_value}']"
|
|
120
|
+
elsif existing_number.nil?
|
|
121
|
+
return "[ERR: Math can't 'sub' non-numeric user variable '#{name}']"
|
|
122
|
+
else
|
|
123
|
+
result = existing_number - value
|
|
124
|
+
rive.set_uservar(user, name, result)
|
|
125
|
+
end
|
|
126
|
+
""
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"mult" => {
|
|
130
|
+
"self_closing" => true,
|
|
131
|
+
"handle" => lambda { |rive, data, user, _scope|
|
|
132
|
+
split = Brain.split_assignment(data)
|
|
133
|
+
name = split[0].strip
|
|
134
|
+
raw_value = split[1]
|
|
135
|
+
existing_value = rive.get_uservar(user, name) || 0
|
|
136
|
+
value = raw_value.nil? ? nil : Brain.parse_int_js(raw_value.strip)
|
|
137
|
+
existing_value = 0 if existing_value == "undefined"
|
|
138
|
+
existing_number = Brain.parse_int_js(existing_value.to_s)
|
|
139
|
+
if value.nil?
|
|
140
|
+
return "[ERR: Math can't 'mult' non-numeric value '#{raw_value}']"
|
|
141
|
+
elsif existing_number.nil?
|
|
142
|
+
return "[ERR: Math can't 'mult' non-numeric user variable '#{name}']"
|
|
143
|
+
else
|
|
144
|
+
result = existing_number * value
|
|
145
|
+
rive.set_uservar(user, name, result)
|
|
146
|
+
end
|
|
147
|
+
""
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"div" => {
|
|
151
|
+
"self_closing" => true,
|
|
152
|
+
"handle" => lambda { |rive, data, user, _scope|
|
|
153
|
+
split = Brain.split_assignment(data)
|
|
154
|
+
name = split[0].strip
|
|
155
|
+
raw_value = split[1]
|
|
156
|
+
existing_value = rive.get_uservar(user, name) || 0
|
|
157
|
+
value = raw_value.nil? ? nil : Brain.parse_int_js(raw_value.strip)
|
|
158
|
+
existing_value = 0 if existing_value == "undefined"
|
|
159
|
+
existing_number = Brain.parse_int_js(existing_value.to_s)
|
|
160
|
+
if value.nil?
|
|
161
|
+
return "[ERR: Math can't 'div' non-numeric value '#{raw_value}']"
|
|
162
|
+
elsif existing_number.nil?
|
|
163
|
+
return "[ERR: Math can't 'div' non-numeric user variable '#{name}']"
|
|
164
|
+
elsif value == 0
|
|
165
|
+
return "[ERR: Can't Divide By Zero]"
|
|
166
|
+
else
|
|
167
|
+
result = existing_number.fdiv(value)
|
|
168
|
+
result = result.to_i if result == result.to_i
|
|
169
|
+
rive.set_uservar(user, name, result)
|
|
170
|
+
end
|
|
171
|
+
""
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"call" => {
|
|
175
|
+
"self_closing" => false,
|
|
176
|
+
"handle" => lambda { |rive, data, _user, scope|
|
|
177
|
+
trimmed = Utils.trim(data)
|
|
178
|
+
m = trimmed.match(/\A(\S+)(?:\s+(.*))?\z/m)
|
|
179
|
+
output = rive.errors["objectNotFound"]
|
|
180
|
+
return output unless m
|
|
181
|
+
|
|
182
|
+
obj = m[1]
|
|
183
|
+
args = m[2] ? Utils.parse_call_args(m[2]) : []
|
|
184
|
+
objlangs = rive._objlangs
|
|
185
|
+
handlers = rive._handlers
|
|
186
|
+
|
|
187
|
+
if objlangs.key?(obj)
|
|
188
|
+
lang = objlangs[obj]
|
|
189
|
+
if handlers[lang]
|
|
190
|
+
begin
|
|
191
|
+
output = handlers[lang].call(rive, obj, args, scope)
|
|
192
|
+
rescue StandardError => e
|
|
193
|
+
rive.brain.warn(e.message) unless e.nil?
|
|
194
|
+
output = "[ERR: Error raised by object macro: #{e.message}]"
|
|
195
|
+
end
|
|
196
|
+
else
|
|
197
|
+
output = "[ERR: No Object Handler]"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
output
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}.freeze
|
|
204
|
+
|
|
205
|
+
def initialize(master)
|
|
206
|
+
@master = master
|
|
207
|
+
@strict = master._strict
|
|
208
|
+
@utf8 = master._utf8
|
|
209
|
+
@mutex = Mutex.new
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# The user ID currently being processed by #reply (only meaningful from
|
|
213
|
+
# within object macros invoked during a reply). Stored per-thread so
|
|
214
|
+
# concurrent calls to #reply from different threads don't clobber
|
|
215
|
+
# each other's notion of "the current user".
|
|
216
|
+
def current_user
|
|
217
|
+
Thread.current[:rivescript_current_user]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def say(message)
|
|
221
|
+
@master.send(:say, message)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def warn(message, filename = nil, lineno = nil)
|
|
225
|
+
@master.warn(message, filename, lineno)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def reply(user, msg, scope = nil)
|
|
229
|
+
@mutex.synchronize do
|
|
230
|
+
say("Asked to reply to [#{user}] #{msg}")
|
|
231
|
+
|
|
232
|
+
Thread.current[:rivescript_current_user] = user
|
|
233
|
+
msg = format_message(msg)
|
|
234
|
+
reply = ""
|
|
235
|
+
|
|
236
|
+
bot_session.set(user, { "__initialmatch__" => nil })
|
|
237
|
+
|
|
238
|
+
if bot_topics["__begin__"]
|
|
239
|
+
begin_reply = get_reply(user, "request", "begin", 0, scope)
|
|
240
|
+
|
|
241
|
+
if begin_reply.include?("{ok}")
|
|
242
|
+
inner_reply = get_reply(user, msg, "normal", 0, scope)
|
|
243
|
+
ok_replacement = inner_reply
|
|
244
|
+
|
|
245
|
+
BEGIN_OK_FORMAT_TAGS.each do |type|
|
|
246
|
+
wrap_pattern = /\{#{type}\}\{ok\}\{\/#{type}\}/i
|
|
247
|
+
next unless begin_reply.match?(wrap_pattern)
|
|
248
|
+
|
|
249
|
+
ok_replacement = type == "person" ? substitute(inner_reply, "person") : Utils.string_format(type, inner_reply)
|
|
250
|
+
begin_reply = begin_reply.gsub(wrap_pattern, BEGIN_OK_TOKEN)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
begin_reply = begin_reply.gsub("{ok}", BEGIN_OK_TOKEN)
|
|
254
|
+
reply = process_tags(user, msg, begin_reply, [], [], 0, scope)
|
|
255
|
+
reply = reply.gsub(BEGIN_OK_TOKEN, ok_replacement)
|
|
256
|
+
else
|
|
257
|
+
reply = process_tags(user, msg, begin_reply, [], [], 0, scope)
|
|
258
|
+
end
|
|
259
|
+
else
|
|
260
|
+
reply = get_reply(user, msg, "normal", 0, scope)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
history = bot_session.get(user, "__history__")
|
|
264
|
+
history = new_history if history == "undefined"
|
|
265
|
+
begin
|
|
266
|
+
history["input"].pop
|
|
267
|
+
history["input"].unshift(msg)
|
|
268
|
+
history["reply"].pop
|
|
269
|
+
history["reply"].unshift(reply)
|
|
270
|
+
rescue StandardError
|
|
271
|
+
history = new_history
|
|
272
|
+
end
|
|
273
|
+
bot_session.set(user, { "__history__" => history })
|
|
274
|
+
|
|
275
|
+
reply
|
|
276
|
+
end
|
|
277
|
+
ensure
|
|
278
|
+
Thread.current[:rivescript_current_user] = nil
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def format_message(msg, botreply = nil)
|
|
282
|
+
msg = msg.to_s
|
|
283
|
+
msg = msg.downcase unless case_sensitive?
|
|
284
|
+
|
|
285
|
+
msg = substitute(msg, "sub")
|
|
286
|
+
|
|
287
|
+
if @utf8
|
|
288
|
+
msg = msg.gsub(/[\\<>]+/, "")
|
|
289
|
+
|
|
290
|
+
if !@master.unicode_punctuation.nil?
|
|
291
|
+
msg = msg.gsub(@master.unicode_punctuation, "")
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
if !botreply.nil?
|
|
295
|
+
msg = msg.gsub(/[.?,!;:@#$%^&*()]/, "")
|
|
296
|
+
end
|
|
297
|
+
else
|
|
298
|
+
msg = Utils.strip_nasties(msg, @utf8)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
msg.strip.gsub(/\s+/, " ")
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def trigger_regexp(user, regexp)
|
|
305
|
+
regexp = regexp.gsub(/^\*$/, "<zerowidthstar>")
|
|
306
|
+
regexp = regexp.gsub("*", "(.+?)")
|
|
307
|
+
regexp = regexp.gsub("#", "(\\d+?)")
|
|
308
|
+
regexp = regexp.gsub("_", "(\\w+?)")
|
|
309
|
+
regexp = regexp.gsub(/\s*\{weight=\d+\}\s*/i, "")
|
|
310
|
+
regexp = regexp.gsub("<zerowidthstar>", "(.*?)")
|
|
311
|
+
regexp = regexp.gsub(/\|{2,}/, "|")
|
|
312
|
+
regexp = regexp.gsub(/(\(|\[)\|/, '\1')
|
|
313
|
+
regexp = regexp.gsub(/\|(\)|\])/, '\1')
|
|
314
|
+
|
|
315
|
+
if @utf8
|
|
316
|
+
regexp = regexp.gsub("\\@", "\\u0040")
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
giveup = 0
|
|
320
|
+
while (match = regexp.match(/\[(.+?)\]/))
|
|
321
|
+
if (giveup += 1) > 50
|
|
322
|
+
warn("Infinite loop when trying to process optionals in a trigger!")
|
|
323
|
+
return ""
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
parts = match[1].split("|")
|
|
327
|
+
opts = parts.map { |p| "(?:\\s|\\b)+#{p}(?:\\s|\\b)+" }
|
|
328
|
+
|
|
329
|
+
pipes = opts.join("|")
|
|
330
|
+
pipes = pipes.gsub(Regexp.new(Regexp.escape("(.+?)")), "(?:.+?)")
|
|
331
|
+
pipes = pipes.gsub(Regexp.new(Regexp.escape("(\\d+?)")), "(?:\\d+?)")
|
|
332
|
+
pipes = pipes.gsub(Regexp.new(Regexp.escape("(\\w+?)")), "(?:\\w+?)")
|
|
333
|
+
pipes = pipes.gsub("[", "__lb__").gsub("]", "__rb__")
|
|
334
|
+
regexp = regexp.sub(
|
|
335
|
+
Regexp.new("\\s*\\[#{Regexp.escape(match[1])}\\]\\s*"),
|
|
336
|
+
"(?:#{pipes}|(?:\\b|\\s)+)"
|
|
337
|
+
)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
regexp = regexp.gsub("__lb__", "[").gsub("__rb__", "]")
|
|
341
|
+
regexp = regexp.gsub("\\w", "[^\\s\\d]")
|
|
342
|
+
|
|
343
|
+
giveup = 0
|
|
344
|
+
while regexp.include?("@")
|
|
345
|
+
if (giveup += 1) > 50
|
|
346
|
+
break
|
|
347
|
+
end
|
|
348
|
+
if (match = regexp.match(/@(.+?)\b/))
|
|
349
|
+
name = match[1]
|
|
350
|
+
rep = ""
|
|
351
|
+
arrays = bot_array
|
|
352
|
+
if arrays[name] && !arrays[name].empty?
|
|
353
|
+
rep = "(?:" + arrays[name].map { |item| Utils.quotemeta(item) }.join("|") + ")"
|
|
354
|
+
end
|
|
355
|
+
regexp = regexp.sub(/@#{Regexp.escape(name)}\b/, rep)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
giveup = 0
|
|
360
|
+
while regexp.include?("<bot")
|
|
361
|
+
if (giveup += 1) > 50
|
|
362
|
+
break
|
|
363
|
+
end
|
|
364
|
+
if (match = regexp.match(/<bot (.+?)>/i))
|
|
365
|
+
name = match[1]
|
|
366
|
+
rep = ""
|
|
367
|
+
vars = bot_var
|
|
368
|
+
rep = Utils.quotemeta(Utils.strip_nasties(vars[name], @utf8).downcase) if vars[name]
|
|
369
|
+
regexp = regexp.sub(/<bot #{Regexp.escape(name)}>/i, rep)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
giveup = 0
|
|
374
|
+
while regexp.include?("<get")
|
|
375
|
+
if (giveup += 1) > 50
|
|
376
|
+
break
|
|
377
|
+
end
|
|
378
|
+
if (match = regexp.match(/<get (.+?)>/i))
|
|
379
|
+
name = match[1]
|
|
380
|
+
rep = @master.get_uservar(user, name)
|
|
381
|
+
regexp = regexp.sub(/<get #{Regexp.escape(name)}>/i, Utils.quotemeta(rep.to_s.downcase))
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
giveup = 0
|
|
386
|
+
regexp = regexp.gsub(/<input>/i, "<input1>")
|
|
387
|
+
regexp = regexp.gsub(/<reply>/i, "<reply1>")
|
|
388
|
+
history = bot_session.get(user, "__history__")
|
|
389
|
+
history = new_history if history == "undefined"
|
|
390
|
+
while regexp.include?("<input") || regexp.include?("<reply")
|
|
391
|
+
if (giveup += 1) > 50
|
|
392
|
+
break
|
|
393
|
+
end
|
|
394
|
+
%w[input reply].each do |type|
|
|
395
|
+
(1..9).each do |i|
|
|
396
|
+
tag = "<#{type}#{i}>"
|
|
397
|
+
next unless regexp.include?(tag)
|
|
398
|
+
|
|
399
|
+
value = Utils.quotemeta(format_message(history[type][i - 1], type == "reply"))
|
|
400
|
+
regexp = regexp.gsub(tag, value)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
if @utf8 && regexp.include?("\\u")
|
|
406
|
+
regexp = regexp.gsub(/\\u([A-Fa-f0-9]{4})/) { Regexp.last_match(1).to_i(16).chr(Encoding::UTF_8) }
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
regexp.gsub(/\|{2,}/m, "|")
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def handle_tag(rive, user, content, scope, depth)
|
|
413
|
+
tag = ""
|
|
414
|
+
reminder = ""
|
|
415
|
+
i = 0
|
|
416
|
+
while i < content.length
|
|
417
|
+
if TAGS.key?(tag)
|
|
418
|
+
reminder = content[(i + 1)..]
|
|
419
|
+
break
|
|
420
|
+
elsif content[i] == " "
|
|
421
|
+
reminder = content[(i + 1)..]
|
|
422
|
+
break
|
|
423
|
+
elsif content[i] == ">"
|
|
424
|
+
reminder = content[(i + 1)..]
|
|
425
|
+
return { "response" => "<#{tag}>", "reminder" => reminder }
|
|
426
|
+
end
|
|
427
|
+
tag += content[i]
|
|
428
|
+
i += 1
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
tag_def = TAGS[tag]
|
|
432
|
+
self_closing = tag_def ? tag_def["self_closing"] : true
|
|
433
|
+
end_tag = self_closing ? ">" : "</#{tag}>"
|
|
434
|
+
result = parse_complex_tags(rive, user, reminder, scope, depth, end_tag)
|
|
435
|
+
reminder = result["reminder"]
|
|
436
|
+
|
|
437
|
+
response = if tag_def && tag_def["handle"]
|
|
438
|
+
tag_def["handle"].call(rive, result["response"], user, scope)
|
|
439
|
+
else
|
|
440
|
+
"<#{tag} #{result["response"]}>"
|
|
441
|
+
end
|
|
442
|
+
{ "response" => response, "reminder" => reminder }
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def parse_complex_tags(rive, user, content, scope, depth, end_tag = "")
|
|
446
|
+
return { "response" => content, "reminder" => "" } if depth > 50
|
|
447
|
+
|
|
448
|
+
response = ""
|
|
449
|
+
reminder = content
|
|
450
|
+
next_tag = reminder.index("<")
|
|
451
|
+
next_end = end_tag.empty? ? reminder.length : (reminder.index(end_tag) || reminder.length)
|
|
452
|
+
|
|
453
|
+
while !reminder.empty? && next_tag && next_tag < next_end
|
|
454
|
+
response += reminder[0...next_tag]
|
|
455
|
+
reminder = reminder[(next_tag + 1)..]
|
|
456
|
+
result = handle_tag(rive, user, reminder, scope, depth + 1)
|
|
457
|
+
response += result["response"].to_s
|
|
458
|
+
reminder = result["reminder"].to_s
|
|
459
|
+
next_tag = reminder.index("<")
|
|
460
|
+
next_end = end_tag.empty? ? reminder.length : (reminder.index(end_tag) || reminder.length)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
response += reminder[0...next_end].to_s
|
|
464
|
+
reminder = reminder[(next_end + end_tag.length)..] || ""
|
|
465
|
+
|
|
466
|
+
{ "response" => response, "reminder" => reminder }
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def process_tags(user, msg, reply, st, bst, step, scope)
|
|
470
|
+
stars = [""]
|
|
471
|
+
stars.concat(st)
|
|
472
|
+
botstars = [""]
|
|
473
|
+
botstars.concat(bst)
|
|
474
|
+
stars.push("undefined") if stars.length == 1
|
|
475
|
+
botstars.push("undefined") if botstars.length == 1
|
|
476
|
+
|
|
477
|
+
giveup = 0
|
|
478
|
+
while (match = reply.match(/\(@([A-Za-z0-9_]+)\)/i))
|
|
479
|
+
if (giveup += 1) > bot_depth
|
|
480
|
+
warn("Infinite loop looking for arrays in reply!")
|
|
481
|
+
break
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
name = match[1]
|
|
485
|
+
arrays = bot_array
|
|
486
|
+
result = if arrays[name]
|
|
487
|
+
"{random}#{arrays[name].join("|")}{/random}"
|
|
488
|
+
else
|
|
489
|
+
"\x00@#{name}\x00"
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
reply = reply.sub(/\(@#{Regexp.escape(name)}\)/i, result)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
reply = reply.gsub(/\x00@([A-Za-z0-9_]+)\x00/, '(@\1)')
|
|
496
|
+
|
|
497
|
+
reply = reply.gsub(/<person>/i, "{person}<star>{/person}")
|
|
498
|
+
reply = reply.gsub(/<@>/i, "{@<star>}")
|
|
499
|
+
reply = reply.gsub(/<formal>/i, "{formal}<star>{/formal}")
|
|
500
|
+
reply = reply.gsub(/<sentence>/i, "{sentence}<star>{/sentence}")
|
|
501
|
+
reply = reply.gsub(/<uppercase>/i, "{uppercase}<star>{/uppercase}")
|
|
502
|
+
reply = reply.gsub(/<lowercase>/i, "{lowercase}<star>{/lowercase}")
|
|
503
|
+
|
|
504
|
+
reply = reply.gsub(/\{weight=\d+\}/i, "")
|
|
505
|
+
reply = reply.gsub(/<star>/i, stars[1].to_s)
|
|
506
|
+
reply = reply.gsub(/<botstar>/i, botstars[1].to_s)
|
|
507
|
+
(1...stars.length).each do |i|
|
|
508
|
+
reply = reply.gsub(/<star#{i}>/i, stars[i].to_s)
|
|
509
|
+
end
|
|
510
|
+
(1...botstars.length).each do |i|
|
|
511
|
+
reply = reply.gsub(/<botstar#{i}>/i, botstars[i].to_s)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
history = bot_session.get(user, "__history__")
|
|
515
|
+
history = new_history if history == "undefined"
|
|
516
|
+
reply = reply.gsub(/<input>/i, history["input"] ? history["input"][0] : "undefined")
|
|
517
|
+
reply = reply.gsub(/<reply>/i, history["reply"] ? history["reply"][0] : "undefined")
|
|
518
|
+
(1..9).each do |i|
|
|
519
|
+
reply = reply.gsub(/<input#{i}>/i, history["input"][i - 1]) if reply.include?("<input#{i}>")
|
|
520
|
+
reply = reply.gsub(/<reply#{i}>/i, history["reply"][i - 1]) if reply.include?("<reply#{i}>")
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
reply = reply.gsub(/<id>/i, user)
|
|
524
|
+
reply = reply.gsub(/\\s/i, " ")
|
|
525
|
+
reply = reply.gsub(/\\n/i, "\n")
|
|
526
|
+
reply = reply.gsub(/\\#/i, "#")
|
|
527
|
+
|
|
528
|
+
giveup = 0
|
|
529
|
+
while (match = reply.match(/\{random\}(.+?)\{\/random\}/i))
|
|
530
|
+
if (giveup += 1) > bot_depth
|
|
531
|
+
warn("Infinite loop looking for random tag!")
|
|
532
|
+
break
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
text = match[1]
|
|
536
|
+
random = text.include?("|") ? text.split("|") : text.split(" ")
|
|
537
|
+
output = random[(rand * random.length).floor]
|
|
538
|
+
reply = reply.sub(/\{random\}#{Regexp.escape(text)}\{\/random\}/i, output)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
%w[person formal sentence uppercase lowercase].each do |type|
|
|
542
|
+
giveup = 0
|
|
543
|
+
while (match = reply.match(/\{#{type}\}(.+?)\{\/#{type}\}/i))
|
|
544
|
+
giveup += 1
|
|
545
|
+
if giveup >= 50
|
|
546
|
+
warn("Infinite loop looking for #{type} tag!")
|
|
547
|
+
break
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
content = match[1]
|
|
551
|
+
replace = if type == "person"
|
|
552
|
+
substitute(content, "person")
|
|
553
|
+
else
|
|
554
|
+
Utils.string_format(type, content)
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
reply = reply.sub(/\{#{type}\}#{Regexp.escape(content)}\{\/#{type}\}/i, replace)
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
reply = parse_complex_tags(@master, user, reply, scope, 0)["response"]
|
|
562
|
+
|
|
563
|
+
giveup = 0
|
|
564
|
+
while (match = reply.match(/\{topic=(.+?)\}/i))
|
|
565
|
+
giveup += 1
|
|
566
|
+
if giveup >= 50
|
|
567
|
+
warn("Infinite loop looking for topic tag!")
|
|
568
|
+
break
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
name = match[1]
|
|
572
|
+
@master.set_uservar(user, "topic", name)
|
|
573
|
+
reply = reply.sub(/\{topic=#{Regexp.escape(name)}\}/i, "")
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
giveup = 0
|
|
577
|
+
while (match = reply.match(/\{@([^\}]*?)\}/))
|
|
578
|
+
giveup += 1
|
|
579
|
+
if giveup >= 50
|
|
580
|
+
warn("Infinite loop looking for redirect tag!")
|
|
581
|
+
break
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
target = format_message(Utils.strip(match[1]))
|
|
585
|
+
say("Inline redirection to: #{target}")
|
|
586
|
+
|
|
587
|
+
subreply = get_reply(user, target, "normal", step + 1, scope)
|
|
588
|
+
reply = reply.sub(/\{@#{Regexp.escape(match[1])}\}/i, subreply)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
reply
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def substitute(msg, type)
|
|
595
|
+
sort_key = type == "sub" ? "sub" : "person"
|
|
596
|
+
unless bot_sorted && bot_sorted[sort_key]
|
|
597
|
+
@master.warn("You forgot to call sortReplies()!")
|
|
598
|
+
return msg
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
subs = type == "sub" ? bot_sub : bot_person
|
|
602
|
+
maxwords = type == "sub" ? bot_submax : bot_personmax
|
|
603
|
+
result = ""
|
|
604
|
+
|
|
605
|
+
pattern = if !@master.unicode_punctuation.nil?
|
|
606
|
+
msg.gsub(@master.unicode_punctuation, "")
|
|
607
|
+
else
|
|
608
|
+
msg.gsub(/[.,!?;:]/, "")
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
giveup = 0
|
|
612
|
+
subgiveup = 0
|
|
613
|
+
|
|
614
|
+
while pattern.include?(" ")
|
|
615
|
+
giveup += 1
|
|
616
|
+
if giveup >= 1000
|
|
617
|
+
warn("Too many loops when handling substitutions!")
|
|
618
|
+
break
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
li = Utils.n_index_of(pattern, " ", maxwords)
|
|
622
|
+
subpattern = pattern[0...li]
|
|
623
|
+
|
|
624
|
+
result = subs[subpattern]
|
|
625
|
+
if !result.nil?
|
|
626
|
+
msg = msg.sub(subpattern, result)
|
|
627
|
+
else
|
|
628
|
+
while subpattern.include?(" ")
|
|
629
|
+
subgiveup += 1
|
|
630
|
+
if subgiveup >= 1000
|
|
631
|
+
warn("Too many loops when handling substitutions!")
|
|
632
|
+
break
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
li = subpattern.rindex(" ")
|
|
636
|
+
subpattern = subpattern[0...li]
|
|
637
|
+
|
|
638
|
+
result = subs[subpattern]
|
|
639
|
+
if !result.nil?
|
|
640
|
+
msg = msg.sub(subpattern, result)
|
|
641
|
+
break
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
fi = pattern.index(" ")
|
|
647
|
+
pattern = pattern[(fi + 1)..]
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
result = subs[pattern]
|
|
651
|
+
msg = msg.sub(pattern, result) if !result.nil?
|
|
652
|
+
|
|
653
|
+
msg
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
private
|
|
657
|
+
|
|
658
|
+
def bot_session
|
|
659
|
+
@master._session
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def bot_sorted
|
|
663
|
+
@master._sorted
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def bot_topics
|
|
667
|
+
@master._topics
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def bot_thats
|
|
671
|
+
@master._thats
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def bot_var
|
|
675
|
+
@master._var
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
def bot_array
|
|
679
|
+
@master._array
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def bot_sub
|
|
683
|
+
@master._sub
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def bot_person
|
|
687
|
+
@master._person
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def bot_submax
|
|
691
|
+
@master._submax
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def bot_personmax
|
|
695
|
+
@master._personmax
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def bot_depth
|
|
699
|
+
@master._depth
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def bot_includes
|
|
703
|
+
@master._includes
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def bot_inherits
|
|
707
|
+
@master._inherits
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
def case_sensitive?
|
|
711
|
+
@master._case_sensitive == true
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
# Attempts to match +subject+ against +regexp+ (the compiled form of
|
|
715
|
+
# +pattern+). Atomic triggers are compared with plain string equality;
|
|
716
|
+
# non-atomic triggers are matched case-insensitively unless the bot is
|
|
717
|
+
# running in case-sensitive mode.
|
|
718
|
+
#
|
|
719
|
+
# Returns an array of captured stars on success (empty for an atomic
|
|
720
|
+
# match), or +nil+ if there was no match.
|
|
721
|
+
def trigger_match(subject, pattern, regexp)
|
|
722
|
+
if Utils.is_atomic(pattern)
|
|
723
|
+
subject == regexp ? [] : nil
|
|
724
|
+
else
|
|
725
|
+
m = subject.match(match_regexp(regexp))
|
|
726
|
+
m ? m.to_a.drop(1) : nil
|
|
727
|
+
end
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def match_regexp(regexp)
|
|
731
|
+
case_sensitive? ? /\A#{regexp}\z/ : /\A#{regexp}\z/i
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def get_reply(user, msg, context, step, scope)
|
|
735
|
+
unless bot_sorted["topics"]
|
|
736
|
+
warn("You forgot to call sortReplies()!")
|
|
737
|
+
return "ERR: Replies Not Sorted"
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
topic = @master.get_uservar(user, "topic")
|
|
741
|
+
topic = "random" if topic.nil? || topic == "undefined"
|
|
742
|
+
|
|
743
|
+
stars = []
|
|
744
|
+
thatstars = []
|
|
745
|
+
reply = ""
|
|
746
|
+
|
|
747
|
+
if !bot_topics[topic]
|
|
748
|
+
warn("User #{user} was in an empty topic named '#{topic}'")
|
|
749
|
+
topic = "random"
|
|
750
|
+
@master.set_uservar(user, "topic", topic)
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
return @master.errors["deepRecursion"] if step > bot_depth
|
|
754
|
+
|
|
755
|
+
topic = "__begin__" if context == "begin"
|
|
756
|
+
|
|
757
|
+
history = bot_session.get(user, "__history__")
|
|
758
|
+
if history == "undefined"
|
|
759
|
+
history = new_history
|
|
760
|
+
bot_session.set(user, { "__history__" => history })
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
unless bot_topics[topic]
|
|
764
|
+
return "ERR: No default topic 'random' was found!"
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
matched = nil
|
|
768
|
+
matched_trigger = nil
|
|
769
|
+
found_match = false
|
|
770
|
+
|
|
771
|
+
if step == 0
|
|
772
|
+
all_topics = [topic]
|
|
773
|
+
includes = bot_includes[topic] || {}
|
|
774
|
+
inherits = bot_inherits[topic] || {}
|
|
775
|
+
if includes.any? || inherits.any?
|
|
776
|
+
all_topics = Inheritance.get_topic_tree(@master, topic)
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
all_topics.each do |top|
|
|
780
|
+
say("Checking topic #{top} for any %Previous's")
|
|
781
|
+
thats_list = bot_sorted["thats"][top] || []
|
|
782
|
+
if !thats_list.empty?
|
|
783
|
+
say("There's a %Previous in this topic!")
|
|
784
|
+
|
|
785
|
+
last_reply = history["reply"] ? history["reply"][0] : "undefined"
|
|
786
|
+
last_reply = format_message(last_reply, true)
|
|
787
|
+
say("Last reply: #{last_reply}")
|
|
788
|
+
|
|
789
|
+
thats_list.each do |trig|
|
|
790
|
+
pattern = trig[1]["previous"]
|
|
791
|
+
botside = trigger_regexp(user, pattern)
|
|
792
|
+
|
|
793
|
+
say("Try to match lastReply (#{last_reply}) to #{botside}")
|
|
794
|
+
|
|
795
|
+
botstars_captured = trigger_match(last_reply, pattern, botside)
|
|
796
|
+
if botstars_captured
|
|
797
|
+
say("Bot side matched!")
|
|
798
|
+
thatstars = botstars_captured
|
|
799
|
+
|
|
800
|
+
user_side = trig[1]
|
|
801
|
+
regexp = trigger_regexp(user, user_side["trigger"])
|
|
802
|
+
say("Try to match \"#{msg}\" against #{user_side["trigger"]} (#{regexp})")
|
|
803
|
+
|
|
804
|
+
stars_captured = trigger_match(msg, user_side["trigger"], regexp)
|
|
805
|
+
|
|
806
|
+
if stars_captured
|
|
807
|
+
stars = stars_captured
|
|
808
|
+
matched = user_side
|
|
809
|
+
found_match = true
|
|
810
|
+
matched_trigger = user_side["trigger"]
|
|
811
|
+
break
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
else
|
|
816
|
+
say("No %Previous in this topic!")
|
|
817
|
+
end
|
|
818
|
+
break if found_match
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
unless found_match
|
|
823
|
+
say("Searching their topic for a match...")
|
|
824
|
+
(bot_sorted["topics"][topic] || []).each do |trig|
|
|
825
|
+
pattern = trig[0]
|
|
826
|
+
regexp = trigger_regexp(user, pattern)
|
|
827
|
+
|
|
828
|
+
say("Try to match \"#{msg}\" against #{pattern} (#{regexp})")
|
|
829
|
+
|
|
830
|
+
stars_captured = trigger_match(msg, pattern, regexp)
|
|
831
|
+
|
|
832
|
+
if stars_captured
|
|
833
|
+
say("Found a match!")
|
|
834
|
+
stars = stars_captured
|
|
835
|
+
matched = trig[1]
|
|
836
|
+
found_match = true
|
|
837
|
+
matched_trigger = pattern
|
|
838
|
+
break
|
|
839
|
+
end
|
|
840
|
+
end
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
bot_session.set(user, { "__lastmatch__" => matched_trigger })
|
|
844
|
+
if step == 0
|
|
845
|
+
bot_session.set(user, {
|
|
846
|
+
"__initialmatch__" => matched_trigger,
|
|
847
|
+
"__last_triggers__" => []
|
|
848
|
+
})
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
if matched
|
|
852
|
+
existing_triggers = bot_session.get(user, "__last_triggers__")
|
|
853
|
+
existing_triggers = [] unless existing_triggers.is_a?(Array)
|
|
854
|
+
last_triggers = existing_triggers.dup
|
|
855
|
+
last_triggers.push(matched)
|
|
856
|
+
bot_session.set(user, { "__last_triggers__" => last_triggers })
|
|
857
|
+
|
|
858
|
+
if !matched["redirect"].nil?
|
|
859
|
+
say("Redirecting us to #{matched["redirect"]}")
|
|
860
|
+
redirect = process_tags(user, msg, matched["redirect"], stars, thatstars, step, scope)
|
|
861
|
+
redirect = format_message(redirect)
|
|
862
|
+
|
|
863
|
+
say("Pretend user said: #{redirect}")
|
|
864
|
+
reply = get_reply(user, redirect, context, step + 1, scope)
|
|
865
|
+
else
|
|
866
|
+
matched["condition"].each do |row|
|
|
867
|
+
halves = row.split(/\s*=>\s*/)
|
|
868
|
+
next unless halves && halves.length == 2
|
|
869
|
+
|
|
870
|
+
condition = halves[0].match(/^(.+?)\s+(==|eq|!=|ne|<>|<|<=|>|>=)\s+(.*?)$/)
|
|
871
|
+
next unless condition
|
|
872
|
+
|
|
873
|
+
left = Utils.strip(condition[1])
|
|
874
|
+
eq = condition[2]
|
|
875
|
+
right = Utils.strip(condition[3])
|
|
876
|
+
potreply = halves[1].strip
|
|
877
|
+
|
|
878
|
+
left = process_tags(user, msg, left, stars, thatstars, step, scope)
|
|
879
|
+
right = process_tags(user, msg, right, stars, thatstars, step, scope)
|
|
880
|
+
|
|
881
|
+
left = "undefined" if left.empty?
|
|
882
|
+
right = "undefined" if right.empty?
|
|
883
|
+
|
|
884
|
+
say("Check if #{left} #{eq} #{right}")
|
|
885
|
+
|
|
886
|
+
passed = false
|
|
887
|
+
if %w[eq ==].include?(eq)
|
|
888
|
+
passed = (left == right)
|
|
889
|
+
elsif %w[ne != <>].include?(eq)
|
|
890
|
+
passed = (left != right)
|
|
891
|
+
else
|
|
892
|
+
begin
|
|
893
|
+
left_num = left.to_i
|
|
894
|
+
right_num = right.to_i
|
|
895
|
+
passed = case eq
|
|
896
|
+
when "<" then left_num < right_num
|
|
897
|
+
when "<=" then left_num <= right_num
|
|
898
|
+
when ">" then left_num > right_num
|
|
899
|
+
when ">=" then left_num >= right_num
|
|
900
|
+
else false
|
|
901
|
+
end
|
|
902
|
+
rescue StandardError
|
|
903
|
+
warn("Failed to evaluate numeric condition!")
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
if passed
|
|
908
|
+
reply = potreply
|
|
909
|
+
break
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
if reply.nil? || reply.empty?
|
|
914
|
+
bucket = []
|
|
915
|
+
matched["reply"].each do |rep|
|
|
916
|
+
weight = 1
|
|
917
|
+
if (wmatch = rep.match(/\{weight=(\d+?)\}/i))
|
|
918
|
+
weight = wmatch[1].to_i
|
|
919
|
+
if weight <= 0
|
|
920
|
+
warn("Can't have a weight <= 0!")
|
|
921
|
+
weight = 1
|
|
922
|
+
elsif weight > MAX_REPLY_WEIGHT
|
|
923
|
+
warn("Reply weight #{weight} exceeds maximum of #{MAX_REPLY_WEIGHT}, clamping!")
|
|
924
|
+
weight = MAX_REPLY_WEIGHT
|
|
925
|
+
end
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
weight.times { bucket.push(rep) }
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
choice = (rand * bucket.length).floor
|
|
932
|
+
reply = bucket[choice]
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
if !found_match
|
|
938
|
+
reply = @master.errors["replyNotMatched"]
|
|
939
|
+
elsif reply.nil? || reply.empty?
|
|
940
|
+
reply = @master.errors["replyNotFound"]
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
say("Reply: #{reply}")
|
|
944
|
+
|
|
945
|
+
if context == "begin"
|
|
946
|
+
giveup = 0
|
|
947
|
+
while (match = reply.match(/\{topic=(.+?)\}/i))
|
|
948
|
+
giveup += 1
|
|
949
|
+
if giveup >= 50
|
|
950
|
+
warn("Infinite loop looking for topic tag!")
|
|
951
|
+
break
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
name = match[1]
|
|
955
|
+
@master.set_uservar(user, "topic", name)
|
|
956
|
+
reply = reply.sub(/\{topic=#{Regexp.escape(name)}\}/i, "")
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
giveup = 0
|
|
960
|
+
while (match = reply.match(/<set (.+?)=(.+?)>/i))
|
|
961
|
+
giveup += 1
|
|
962
|
+
if giveup >= 50
|
|
963
|
+
warn("Infinite loop looking for set tag!")
|
|
964
|
+
break
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
name = match[1]
|
|
968
|
+
value = match[2]
|
|
969
|
+
@master.set_uservar(user, name, value)
|
|
970
|
+
reply = reply.sub(/<set #{Regexp.escape(name)}=#{Regexp.escape(value)}>/i, "")
|
|
971
|
+
end
|
|
972
|
+
else
|
|
973
|
+
reply = process_tags(user, msg, reply, stars, thatstars, step, scope)
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
reply
|
|
977
|
+
end
|
|
978
|
+
|
|
979
|
+
def new_history
|
|
980
|
+
{
|
|
981
|
+
"input" => Array.new(10, "undefined"),
|
|
982
|
+
"reply" => Array.new(10, "undefined")
|
|
983
|
+
}
|
|
984
|
+
end
|
|
985
|
+
end
|
|
986
|
+
end
|