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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
|
#-- vim:sw=2:et
#++
#
# :title: Markov plugin
#
# Author:: Tom Gilbert <tom@linuxbrit.co.uk>
# Copyright:: (C) 2005 Tom Gilbert
#
# Contribute to chat with random phrases built from word sequences learned
# by listening to chat
class MarkovPlugin < Plugin
Config.register Config::BooleanValue.new('markov.enabled',
:default => false,
:desc => "Enable and disable the plugin")
Config.register Config::IntegerValue.new('markov.probability',
:default => 25,
:validate => Proc.new { |v| (0..100).include? v },
:desc => "Percentage chance of markov plugin chipping in")
Config.register Config::ArrayValue.new('markov.ignore',
:default => [],
:desc => "Hostmasks and channel names markov should NOT learn from (e.g. idiot*!*@*, #privchan).")
Config.register Config::IntegerValue.new('markov.max_words',
:default => 50,
:validate => Proc.new { |v| (0..100).include? v },
:desc => "Maximum number of words the bot should put in a sentence")
def initialize
super
@registry.set_default([])
if @registry.has_key?('enabled')
@bot.config['markov.enabled'] = @registry['enabled']
@registry.delete('enabled')
end
if @registry.has_key?('probability')
@bot.config['markov.probability'] = @registry['probability']
@registry.delete('probability')
end
if @bot.config['markov.ignore_users']
debug "moving markov.ignore_users to markov.ignore"
@bot.config['markov.ignore'] = @bot.config['markov.ignore_users'].dup
@bot.config.delete('markov.ignore_users'.to_sym)
end
@learning_queue = Queue.new
@learning_thread = Thread.new do
while s = @learning_queue.pop
learn_line s
sleep 0.5
end
end
@learning_thread.priority = -1
end
def cleanup
debug 'closing learning thread'
@learning_queue.push nil
@learning_thread.join
debug 'learning thread closed'
end
def generate_string(word1, word2)
# limit to max of markov.max_words words
if word2
output = "#{word1} #{word2}"
else
output = word1.to_s
end
if @registry.key? output
wordlist = @registry[output]
wordlist.delete(:nonword)
else
output.downcase!
keys = []
@registry.each_key(output) do |key|
if key.downcase.include? output
keys << key
else
break
end
end
if keys.empty?
keys = @registry.keys.select { |k| k.downcase.include? output }
end
return nil if keys.empty?
while key = keys.delete_one
wordlist = @registry[key]
wordlist.delete(:nonword)
unless wordlist.empty?
output = key
word1, word2 = output.split
break
end
end
end
return nil if wordlist.empty?
word3 = wordlist.pick_one
output << " #{word3}"
word1, word2 = word2, word3
(@bot.config['markov.max_words'] - 1).times do
wordlist = @registry["#{word1} #{word2}"]
break if wordlist.empty?
word3 = wordlist.pick_one
break if word3 == :nonword
output << " #{word3}"
word1, word2 = word2, word3
end
return output
end
def help(plugin, topic="")
"markov plugin: listens to chat to build a markov chain, with which it can (perhaps) attempt to (inanely) contribute to 'discussion'. Sort of.. Will get a *lot* better after listening to a lot of chat. usage: 'markov' to attempt to say something relevant to the last line of chat, if it can. other options to markov: 'ignore' => ignore a hostmask (accept no input), 'status' => show current status, 'probability [<chance>]' => set the % chance of rbot responding to input, or display the current probability, 'chat' => try and say something intelligent, 'chat about <foo> <bar>' => riff on a word pair (if possible)"
end
def clean_str(s)
str = s.dup
str.gsub!(/^\S+[:,;]/, "")
str.gsub!(/\s{2,}/, ' ') # fix for two or more spaces
return str.strip
end
def probability?
return @bot.config['markov.probability']
end
def status(m,params)
if @bot.config['markov.enabled']
reply = _("markov is currently enabled, %{p}% chance of chipping in") % { :p => probability? }
l = @learning_queue.length
reply << (_(", %{l} messages in queue") % {:l => l}) if l > 0
else
reply = _("markov is currently disabled")
end
m.reply reply
end
def ignore?(m=nil)
return false unless m
return true if m.address? or m.private?
@bot.config['markov.ignore'].each do |mask|
return true if m.channel.downcase == mask.downcase
return true if m.source.matches?(mask)
end
return false
end
def ignore(m, params)
action = params[:action]
user = params[:option]
case action
when 'remove':
if @bot.config['markov.ignore'].include? user
s = @bot.config['markov.ignore']
s.delete user
@bot.config['ignore'] = s
m.reply "#{user} removed"
else
m.reply "not found in list"
end
when 'add':
if user
if @bot.config['markov.ignore'].include?(user)
m.reply "#{user} already in list"
else
@bot.config['markov.ignore'] = @bot.config['markov.ignore'].push user
m.reply "#{user} added to markov ignore list"
end
else
m.reply "give the name of a person or channel to ignore"
end
when 'list':
m.reply "I'm ignoring #{@bot.config['markov.ignore'].join(", ")}"
else
m.reply "have markov ignore the input from a hostmask or a channel. usage: markov ignore add <mask or channel>; markov ignore remove <mask or channel>; markov ignore list"
end
end
def enable(m, params)
@bot.config['markov.enabled'] = true
m.okay
end
def probability(m, params)
if params[:probability]
@bot.config['markov.probability'] = params[:probability].to_i
m.okay
else
m.reply _("markov has a %{prob}% chance of chipping in") % { :prob => probability? }
end
end
def disable(m, params)
@bot.config['markov.enabled'] = false
m.okay
end
def should_talk
return false unless @bot.config['markov.enabled']
prob = probability?
return true if prob > rand(100)
return false
end
def delay
1 + rand(5)
end
def random_markov(m, message)
return unless should_talk
word1, word2 = message.split(/\s+/)
return unless word1 and word2
line = generate_string(word1, word2)
return unless line
# we do nothing if the line we return is just an initial substring
# of the line we received
return if message.index(line) == 0
@bot.timer.add_once(delay) {
m.reply line, :nick => false, :to => :public
}
end
def chat(m, params)
line = generate_string(params[:seed1], params[:seed2])
if line and line != [params[:seed1], params[:seed2]].compact.join(" ")
m.reply line
else
m.reply "I can't :("
end
end
def rand_chat(m, params)
# pick a random pair from the db and go from there
word1, word2 = :nonword, :nonword
output = Array.new
50.times do
wordlist = @registry["#{word1} #{word2}"]
break if wordlist.empty?
word3 = wordlist[rand(wordlist.length)]
break if word3 == :nonword
output << word3
word1, word2 = word2, word3
end
if output.length > 1
m.reply output.join(" ")
else
m.reply "I can't :("
end
end
def learn(*lines)
lines.each { |l| @learning_queue.push l }
end
def message(m)
return if ignore? m
# in channel message, the kind we are interested in
message = clean_str m.plainmessage
if m.action?
message = "#{m.sourcenick} #{message}"
end
learn message
random_markov(m, message) unless m.replied?
end
def learn_line(message)
# debug "learning #{message}"
wordlist = message.split(/\s+/)
return unless wordlist.length >= 2
word1, word2 = :nonword, :nonword
wordlist.each do |word3|
k = "#{word1} #{word2}"
@registry[k] = @registry[k].push(word3)
word1, word2 = word2, word3
end
k = "#{word1} #{word2}"
@registry[k] = @registry[k].push(:nonword)
end
end
plugin = MarkovPlugin.new
plugin.map 'markov ignore :action :option', :action => "ignore"
plugin.map 'markov ignore :action', :action => "ignore"
plugin.map 'markov ignore', :action => "ignore"
plugin.map 'markov enable', :action => "enable"
plugin.map 'markov disable', :action => "disable"
plugin.map 'markov status', :action => "status"
plugin.map 'chat about :seed1 [:seed2]', :action => "chat"
plugin.map 'chat', :action => "rand_chat"
plugin.map 'markov probability [:probability]', :action => "probability",
:requirements => {:probability => /^\d+%?$/}
plugin.default_auth('ignore', false)
plugin.default_auth('probability', false)
|