rbot 0.9.9 → 0.9.10

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.
Files changed (72) hide show
  1. data/AUTHORS +8 -0
  2. data/ChangeLog +51 -0
  3. data/INSTALL +4 -0
  4. data/README +1 -0
  5. data/REQUIREMENTS +11 -0
  6. data/TODO +2 -0
  7. data/bin/rbot +21 -2
  8. data/data/rbot/languages/german.lang +4 -1
  9. data/data/rbot/languages/russian.lang +75 -0
  10. data/data/rbot/plugins/autoop.rb +42 -51
  11. data/data/rbot/plugins/bans.rb +205 -0
  12. data/data/rbot/plugins/bash.rb +56 -0
  13. data/data/rbot/plugins/chucknorris.rb +74 -0
  14. data/data/rbot/plugins/chucknorris.yml.gz +0 -0
  15. data/data/rbot/plugins/deepthoughts.rb +95 -0
  16. data/data/rbot/plugins/demauro.rb +95 -0
  17. data/data/rbot/plugins/digg.rb +51 -0
  18. data/data/rbot/plugins/figlet.rb +24 -0
  19. data/data/rbot/plugins/forecast.rb +133 -0
  20. data/data/rbot/plugins/freshmeat.rb +13 -7
  21. data/data/rbot/plugins/google.rb +2 -0
  22. data/data/rbot/plugins/grouphug.rb +36 -0
  23. data/data/rbot/plugins/imdb.rb +92 -0
  24. data/data/rbot/plugins/insult.rb +8 -1
  25. data/data/rbot/plugins/iplookup.rb +227 -0
  26. data/data/rbot/plugins/karma.rb +2 -2
  27. data/data/rbot/plugins/keywords.rb +470 -0
  28. data/data/rbot/plugins/lart.rb +132 -146
  29. data/data/rbot/plugins/lastfm.rb +25 -0
  30. data/data/rbot/plugins/markov.rb +204 -0
  31. data/data/rbot/plugins/math.rb +5 -1
  32. data/data/rbot/plugins/nickserv.rb +71 -11
  33. data/data/rbot/plugins/opme.rb +19 -19
  34. data/data/rbot/plugins/quakeauth.rb +2 -2
  35. data/data/rbot/plugins/quotes.rb +40 -25
  36. data/data/rbot/plugins/remind.rb +1 -1
  37. data/data/rbot/plugins/rot13.rb +2 -2
  38. data/data/rbot/plugins/roulette.rb +49 -15
  39. data/data/rbot/plugins/rss.rb +585 -0
  40. data/data/rbot/plugins/rubyurl.rb +39 -0
  41. data/data/rbot/plugins/seen.rb +2 -1
  42. data/data/rbot/plugins/slashdot.rb +5 -5
  43. data/data/rbot/plugins/spell.rb +5 -0
  44. data/data/rbot/plugins/theyfightcrime.rb +121 -0
  45. data/data/rbot/plugins/threat.rb +55 -0
  46. data/data/rbot/plugins/tinyurl.rb +39 -0
  47. data/data/rbot/plugins/topic.rb +204 -0
  48. data/data/rbot/plugins/urban.rb +71 -0
  49. data/data/rbot/plugins/url.rb +399 -4
  50. data/data/rbot/plugins/wow.rb +123 -0
  51. data/data/rbot/plugins/wserver.rb +1 -1
  52. data/data/rbot/templates/levels.rbot +2 -0
  53. data/lib/rbot/auth.rb +207 -96
  54. data/lib/rbot/channel.rb +5 -5
  55. data/lib/rbot/config.rb +125 -24
  56. data/lib/rbot/dbhash.rb +87 -21
  57. data/lib/rbot/httputil.rb +181 -13
  58. data/lib/rbot/ircbot.rb +525 -179
  59. data/lib/rbot/ircsocket.rb +330 -54
  60. data/lib/rbot/message.rb +66 -23
  61. data/lib/rbot/messagemapper.rb +25 -17
  62. data/lib/rbot/plugins.rb +244 -115
  63. data/lib/rbot/post-clean.rb +1 -0
  64. data/lib/rbot/{post-install.rb → post-config.rb} +1 -1
  65. data/lib/rbot/rbotconfig.rb +29 -14
  66. data/lib/rbot/registry.rb +111 -72
  67. data/lib/rbot/rfc2812.rb +208 -197
  68. data/lib/rbot/timer.rb +4 -0
  69. data/lib/rbot/utils.rb +2 -2
  70. metadata +127 -104
  71. data/data/rbot/plugins/rss.rb.disabled +0 -414
  72. data/lib/rbot/keywords.rb +0 -433
data/lib/rbot/keywords.rb DELETED
@@ -1,433 +0,0 @@
1
- require 'pp'
2
-
3
- module Irc
4
-
5
- # Keyword class
6
- #
7
- # Encapsulates a keyword ("foo is bar" is a keyword called foo, with type
8
- # is, and has a single value of bar).
9
- # Keywords can have multiple values, to_s() will choose one at random
10
- class Keyword
11
-
12
- # type of keyword (e.g. "is" or "are")
13
- attr_reader :type
14
-
15
- # type:: type of keyword (e.g "is" or "are")
16
- # values:: array of values
17
- #
18
- # create a keyword of type +type+ with values +values+
19
- def initialize(type, values)
20
- @type = type.downcase
21
- @values = values
22
- end
23
-
24
- # pick a random value for this keyword and return it
25
- def to_s
26
- if(@values.length > 1)
27
- Keyword.unescape(@values[rand(@values.length)])
28
- else
29
- Keyword.unescape(@values[0])
30
- end
31
- end
32
-
33
- # describe the keyword (show all values without interpolation)
34
- def desc
35
- @values.join(" | ")
36
- end
37
-
38
- # return the keyword in a stringified form ready for storage
39
- def dump
40
- @type + "/" + Keyword.unescape(@values.join("<=or=>"))
41
- end
42
-
43
- # deserialize the stringified form to an object
44
- def Keyword.restore(str)
45
- if str =~ /^(\S+?)\/(.*)$/
46
- type = $1
47
- vals = $2.split("<=or=>")
48
- return Keyword.new(type, vals)
49
- end
50
- return nil
51
- end
52
-
53
- # values:: array of values to add
54
- # add values to a keyword
55
- def <<(values)
56
- if(@values.length > 1 || values.length > 1)
57
- values.each {|v|
58
- @values << v
59
- }
60
- else
61
- @values[0] += " or " + values[0]
62
- end
63
- end
64
-
65
- # unescape special words/characters in a keyword
66
- def Keyword.unescape(str)
67
- str.gsub(/\\\|/, "|").gsub(/ \\is /, " is ").gsub(/ \\are /, " are ").gsub(/\\\?(\s*)$/, "?\1")
68
- end
69
-
70
- # escape special words/characters in a keyword
71
- def Keyword.escape(str)
72
- str.gsub(/\|/, "\\|").gsub(/ is /, " \\is ").gsub(/ are /, " \\are ").gsub(/\?(\s*)$/, "\\?\1")
73
- end
74
- end
75
-
76
- # keywords class.
77
- #
78
- # Handles all that stuff like "bot: foo is bar", "bot: foo?"
79
- #
80
- # Fallback after core and auth have had a look at a message and refused to
81
- # handle it, checks for a keyword command or lookup, otherwise the message
82
- # is delegated to plugins
83
- class Keywords
84
- BotConfig.register BotConfigBooleanValue.new('keyword.listen',
85
- :default => false,
86
- :desc => "Should the bot listen to all chat and attempt to automatically detect keywords? (e.g. by spotting someone say 'foo is bar')")
87
- BotConfig.register BotConfigBooleanValue.new('keyword.address',
88
- :default => true,
89
- :desc => "Should the bot require that keyword lookups are addressed to it? If not, the bot will attempt to lookup foo if someone says 'foo?' in channel")
90
-
91
- # create a new Keywords instance, associated to bot +bot+
92
- def initialize(bot)
93
- @bot = bot
94
- @statickeywords = Hash.new
95
- upgrade_data
96
- @keywords = DBTree.new bot, "keyword"
97
-
98
- scan
99
-
100
- # import old format keywords into DBHash
101
- if(File.exist?("#{@bot.botclass}/keywords.rbot"))
102
- puts "auto importing old keywords.rbot"
103
- IO.foreach("#{@bot.botclass}/keywords.rbot") do |line|
104
- if(line =~ /^(.*?)\s*<=(is|are)?=?>\s*(.*)$/)
105
- lhs = $1
106
- mhs = $2
107
- rhs = $3
108
- mhs = "is" unless mhs
109
- rhs = Keyword.escape rhs
110
- values = rhs.split("<=or=>")
111
- @keywords[lhs] = Keyword.new(mhs, values).dump
112
- end
113
- end
114
- File.delete("#{@bot.botclass}/keywords.rbot")
115
- end
116
- end
117
-
118
- # drop static keywords and reload them from files, picking up any new
119
- # keyword files that have been added
120
- def rescan
121
- @statickeywords = Hash.new
122
- scan
123
- end
124
-
125
- # load static keywords from files, picking up any new keyword files that
126
- # have been added
127
- def scan
128
- # first scan for old DBHash files, and convert them
129
- Dir["#{@bot.botclass}/keywords/*"].each {|f|
130
- next unless f =~ /\.db$/
131
- puts "upgrading keyword db #{f} (rbot 0.9.5 or prior) database format"
132
- newname = f.gsub(/\.db$/, ".kdb")
133
- old = BDB::Hash.open f, nil,
134
- "r+", 0600, "set_pagesize" => 1024,
135
- "set_cachesize" => [0, 32 * 1024, 0]
136
- new = BDB::CIBtree.open newname, nil,
137
- BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
138
- 0600, "set_pagesize" => 1024,
139
- "set_cachesize" => [0, 32 * 1024, 0]
140
- old.each {|k,v|
141
- new[k] = v
142
- }
143
- old.close
144
- new.close
145
- File.delete(f)
146
- }
147
-
148
- # then scan for current DBTree files, and load them
149
- Dir["#{@bot.botclass}/keywords/*"].each {|f|
150
- next unless f =~ /\.kdb$/
151
- hsh = DBTree.new @bot, f, true
152
- key = File.basename(f).gsub(/\.kdb$/, "")
153
- debug "keywords module: loading DBTree file #{f}, key #{key}"
154
- @statickeywords[key] = hsh
155
- }
156
-
157
- # then scan for non DB files, and convert/import them and delete
158
- Dir["#{@bot.botclass}/keywords/*"].each {|f|
159
- next if f =~ /\.kdb$/
160
- next if f =~ /CVS$/
161
- puts "auto converting keywords from #{f}"
162
- key = File.basename(f)
163
- unless @statickeywords.has_key?(key)
164
- @statickeywords[key] = DBHash.new @bot, "#{f}.db", true
165
- end
166
- IO.foreach(f) {|line|
167
- if(line =~ /^(.*?)\s*<?=(is|are)?=?>\s*(.*)$/)
168
- lhs = $1
169
- mhs = $2
170
- rhs = $3
171
- # support infobot style factfiles, by fixing them up here
172
- rhs.gsub!(/\$who/, "<who>")
173
- mhs = "is" unless mhs
174
- rhs = Keyword.escape rhs
175
- values = rhs.split("<=or=>")
176
- @statickeywords[key][lhs] = Keyword.new(mhs, values).dump
177
- end
178
- }
179
- File.delete(f)
180
- @statickeywords[key].flush
181
- }
182
- end
183
-
184
- # upgrade data files found in old rbot formats to current
185
- def upgrade_data
186
- if File.exist?("#{@bot.botclass}/keywords.db")
187
- puts "upgrading old keywords (rbot 0.9.5 or prior) database format"
188
- old = BDB::Hash.open "#{@bot.botclass}/keywords.db", nil,
189
- "r+", 0600, "set_pagesize" => 1024,
190
- "set_cachesize" => [0, 32 * 1024, 0]
191
- new = BDB::CIBtree.open "#{@bot.botclass}/keyword.db", nil,
192
- BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
193
- 0600, "set_pagesize" => 1024,
194
- "set_cachesize" => [0, 32 * 1024, 0]
195
- old.each {|k,v|
196
- new[k] = v
197
- }
198
- old.close
199
- new.close
200
- File.delete("#{@bot.botclass}/keywords.db")
201
- end
202
- end
203
-
204
- # save dynamic keywords to file
205
- def save
206
- @keywords.flush
207
- end
208
- def oldsave
209
- File.open("#{@bot.botclass}/keywords.rbot", "w") do |file|
210
- @keywords.each do |key, value|
211
- file.puts "#{key}<=#{value.type}=>#{value.dump}"
212
- end
213
- end
214
- end
215
-
216
- # lookup keyword +key+, return it or nil
217
- def [](key)
218
- debug "keywords module: looking up key #{key}"
219
- if(@keywords.has_key?(key))
220
- return Keyword.restore(@keywords[key])
221
- else
222
- # key name order for the lookup through these
223
- @statickeywords.keys.sort.each {|k|
224
- v = @statickeywords[k]
225
- if v.has_key?(key)
226
- return Keyword.restore(v[key])
227
- end
228
- }
229
- end
230
- return nil
231
- end
232
-
233
- # does +key+ exist as a keyword?
234
- def has_key?(key)
235
- if @keywords.has_key?(key) && Keyword.restore(@keywords[key]) != nil
236
- return true
237
- end
238
- @statickeywords.each {|k,v|
239
- if v.has_key?(key) && Keyword.restore(v[key]) != nil
240
- return true
241
- end
242
- }
243
- return false
244
- end
245
-
246
- # m:: PrivMessage containing message info
247
- # key:: key being queried
248
- # dunno:: optional, if true, reply "dunno" if +key+ not found
249
- #
250
- # handle a message asking about a keyword
251
- def keyword(m, key, dunno=true)
252
- unless(kw = self[key])
253
- m.reply @bot.lang.get("dunno") if (dunno)
254
- return
255
- end
256
- response = kw.to_s
257
- response.gsub!(/<who>/, m.sourcenick)
258
- if(response =~ /^<reply>\s*(.*)/)
259
- m.reply "#$1"
260
- elsif(response =~ /^<action>\s*(.*)/)
261
- @bot.action m.replyto, "#$1"
262
- elsif(m.public? && response =~ /^<topic>\s*(.*)/)
263
- topic = $1
264
- @bot.topic m.target, topic
265
- else
266
- m.reply "#{key} #{kw.type} #{response}"
267
- end
268
- end
269
-
270
-
271
- # m:: PrivMessage containing message info
272
- # target:: channel/nick to tell about the keyword
273
- # key:: key being queried
274
- #
275
- # handle a message asking the bot to tell someone about a keyword
276
- def keyword_tell(m, target, key)
277
- unless(kw = self[key])
278
- @bot.say m.sourcenick, @bot.lang.get("dunno_about_X") % key
279
- return
280
- end
281
- response = kw.to_s
282
- response.gsub!(/<who>/, m.sourcenick)
283
- if(response =~ /^<reply>\s*(.*)/)
284
- @bot.say target, "#{m.sourcenick} wanted me to tell you: (#{key}) #$1"
285
- m.reply "okay, I told #{target}: (#{key}) #$1"
286
- elsif(response =~ /^<action>\s*(.*)/)
287
- @bot.action target, "#$1 (#{m.sourcenick} wanted me to tell you)"
288
- m.reply "okay, I told #{target}: * #$1"
289
- else
290
- @bot.say target, "#{m.sourcenick} wanted me to tell you that #{key} #{kw.type} #{response}"
291
- m.reply "okay, I told #{target} that #{key} #{kw.type} #{response}"
292
- end
293
- end
294
-
295
- # handle a message which alters a keyword
296
- # like "foo is bar", or "no, foo is baz", or "foo is also qux"
297
- def keyword_command(sourcenick, target, lhs, mhs, rhs, quiet=false)
298
- debug "got keyword command #{lhs}, #{mhs}, #{rhs}"
299
- overwrite = false
300
- overwrite = true if(lhs.gsub!(/^no,\s*/, ""))
301
- also = true if(rhs.gsub!(/^also\s+/, ""))
302
- values = rhs.split(/\s+\|\s+/)
303
- lhs = Keyword.unescape lhs
304
- if(overwrite || also || !has_key?(lhs))
305
- if(also && has_key?(lhs))
306
- kw = self[lhs]
307
- kw << values
308
- @keywords[lhs] = kw.dump
309
- else
310
- @keywords[lhs] = Keyword.new(mhs, values).dump
311
- end
312
- @bot.okay target if !quiet
313
- elsif(has_key?(lhs))
314
- kw = self[lhs]
315
- @bot.say target, "but #{lhs} #{kw.type} #{kw.desc}" if kw && !quiet
316
- end
317
- end
318
-
319
- # return help string for Keywords with option topic +topic+
320
- def help(topic="")
321
- case topic
322
- when "overview"
323
- return "set: <keyword> is <definition>, overide: no, <keyword> is <definition>, add to definition: <keyword> is also <definition>, random responses: <keyword> is <definition> | <definition> [| ...], plurals: <keyword> are <definition>, escaping: \\is, \\are, \\|, specials: <reply>, <action>, <who>"
324
- when "set"
325
- return "set => <keyword> is <definition>"
326
- when "plurals"
327
- return "plurals => <keywords> are <definition>"
328
- when "override"
329
- return "overide => no, <keyword> is <definition>"
330
- when "also"
331
- return "also => <keyword> is also <definition>"
332
- when "random"
333
- return "random responses => <keyword> is <definition> | <definition> [| ...]"
334
- when "get"
335
- return "asking for keywords => (with addressing) \"<keyword>?\", (without addressing) \"'<keyword>\""
336
- when "tell"
337
- return "tell <nick> about <keyword> => if <keyword> is known, tell <nick>, via /msg, its definition"
338
- when "forget"
339
- return "forget <keyword> => forget fact <keyword>"
340
- when "keywords"
341
- return "keywords => show current keyword counts"
342
- when "<reply>"
343
- return "<reply> => normal response is \"<keyword> is <definition>\", but if <definition> begins with <reply>, the response will be \"<definition>\""
344
- when "<action>"
345
- return "<action> => makes keyword respnse \"/me <definition>\""
346
- when "<who>"
347
- return "<who> => replaced with questioner in reply"
348
- when "<topic>"
349
- return "<topic> => respond by setting the topic to the rest of the definition"
350
- when "search"
351
- return "keywords search [--all] [--full] <regexp> => search keywords for <regexp>. If --all is set, search static keywords too, if --full is set, search definitions too."
352
- else
353
- return "Keyword module (Fact learning and regurgitation) topics: overview, set, plurals, override, also, random, get, tell, forget, keywords, keywords search, <reply>, <action>, <who>, <topic>"
354
- end
355
- end
356
-
357
- # privmsg handler
358
- def privmsg(m)
359
- return if m.replied?
360
- if(m.address?)
361
- if(!(m.message =~ /\\\?\s*$/) && m.message =~ /^(.*\S)\s*\?\s*$/)
362
- keyword m, $1 if(@bot.auth.allow?("keyword", m.source, m.replyto))
363
- elsif(m.message =~ /^(.*?)\s+(is|are)\s+(.*)$/)
364
- keyword_command(m.sourcenick, m.replyto, $1, $2, $3) if(@bot.auth.allow?("keycmd", m.source, m.replyto))
365
- elsif (m.message =~ /^tell\s+(\S+)\s+about\s+(.+)$/)
366
- keyword_tell(m, $1, $2) if(@bot.auth.allow?("keyword", m.source, m.replyto))
367
- elsif (m.message =~ /^forget\s+(.*)$/)
368
- key = $1
369
- if((@bot.auth.allow?("keycmd", m.source, m.replyto)) && @keywords.has_key?(key))
370
- @keywords.delete(key)
371
- @bot.okay m.replyto
372
- end
373
- elsif (m.message =~ /^keywords$/)
374
- if(@bot.auth.allow?("keyword", m.source, m.replyto))
375
- length = 0
376
- @statickeywords.each {|k,v|
377
- length += v.length
378
- }
379
- m.reply "There are currently #{@keywords.length} keywords, #{length} static facts defined."
380
- end
381
- elsif (m.message =~ /^keywords search\s+(.*)$/)
382
- str = $1
383
- all = false
384
- all = true if str.gsub!(/--all\s+/, "")
385
- full = false
386
- full = true if str.gsub!(/--full\s+/, "")
387
-
388
- re = Regexp.new(str, Regexp::IGNORECASE)
389
- if(@bot.auth.allow?("keyword", m.source, m.replyto))
390
- matches = Array.new
391
- @keywords.each {|k,v|
392
- kw = Keyword.restore(v)
393
- if re.match(k) || (full && re.match(kw.desc))
394
- matches << [k,kw]
395
- end
396
- }
397
- if all
398
- @statickeywords.each {|k,v|
399
- v.each {|kk,vv|
400
- kw = Keyword.restore(vv)
401
- if re.match(kk) || (full && re.match(kw.desc))
402
- matches << [kk,kw]
403
- end
404
- }
405
- }
406
- end
407
- if matches.length == 1
408
- rkw = matches[0]
409
- m.reply "#{rkw[0]} #{rkw[1].type} #{rkw[1].desc}"
410
- elsif matches.length > 0
411
- i = 0
412
- matches.each {|rkw|
413
- m.reply "[#{i+1}/#{matches.length}] #{rkw[0]} #{rkw[1].type} #{rkw[1].desc}"
414
- i += 1
415
- break if i == 3
416
- }
417
- else
418
- m.reply "no keywords match #{str}"
419
- end
420
- end
421
- end
422
- else
423
- # in channel message, not to me
424
- if(m.message =~ /^'(.*)$/ || (!@bot.config["keyword.address"] && m.message =~ /^(.*\S)\s*\?\s*$/))
425
- keyword m, $1, false if(@bot.auth.allow?("keyword", m.source))
426
- elsif(@bot.config["keyword.listen"] == true && (m.message =~ /^(.*?)\s+(is|are)\s+(.*)$/))
427
- # TODO MUCH more selective on what's allowed here
428
- keyword_command(m.sourcenick, m.replyto, $1, $2, $3, true) if(@bot.auth.allow?("keycmd", m.source))
429
- end
430
- end
431
- end
432
- end
433
- end