rbot 0.9.9

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 (76) hide show
  1. data/AUTHORS +16 -0
  2. data/COPYING +21 -0
  3. data/ChangeLog +418 -0
  4. data/INSTALL +8 -0
  5. data/README +44 -0
  6. data/REQUIREMENTS +34 -0
  7. data/TODO +5 -0
  8. data/Usage_en.txt +129 -0
  9. data/bin/rbot +81 -0
  10. data/data/rbot/contrib/plugins/figlet.rb +20 -0
  11. data/data/rbot/contrib/plugins/ri.rb +83 -0
  12. data/data/rbot/contrib/plugins/stats.rb +232 -0
  13. data/data/rbot/contrib/plugins/vandale.rb +49 -0
  14. data/data/rbot/languages/dutch.lang +73 -0
  15. data/data/rbot/languages/english.lang +75 -0
  16. data/data/rbot/languages/french.lang +39 -0
  17. data/data/rbot/languages/german.lang +67 -0
  18. data/data/rbot/plugins/autoop.rb +68 -0
  19. data/data/rbot/plugins/autorejoin.rb +16 -0
  20. data/data/rbot/plugins/cal.rb +15 -0
  21. data/data/rbot/plugins/dice.rb +81 -0
  22. data/data/rbot/plugins/eightball.rb +19 -0
  23. data/data/rbot/plugins/excuse.rb +470 -0
  24. data/data/rbot/plugins/fish.rb +61 -0
  25. data/data/rbot/plugins/fortune.rb +22 -0
  26. data/data/rbot/plugins/freshmeat.rb +98 -0
  27. data/data/rbot/plugins/google.rb +51 -0
  28. data/data/rbot/plugins/host.rb +14 -0
  29. data/data/rbot/plugins/httpd.rb.disabled +35 -0
  30. data/data/rbot/plugins/insult.rb +258 -0
  31. data/data/rbot/plugins/karma.rb +85 -0
  32. data/data/rbot/plugins/lart.rb +181 -0
  33. data/data/rbot/plugins/math.rb +122 -0
  34. data/data/rbot/plugins/nickserv.rb +89 -0
  35. data/data/rbot/plugins/nslookup.rb +43 -0
  36. data/data/rbot/plugins/opme.rb +19 -0
  37. data/data/rbot/plugins/quakeauth.rb +51 -0
  38. data/data/rbot/plugins/quotes.rb +321 -0
  39. data/data/rbot/plugins/remind.rb +228 -0
  40. data/data/rbot/plugins/roshambo.rb +54 -0
  41. data/data/rbot/plugins/rot13.rb +10 -0
  42. data/data/rbot/plugins/roulette.rb +147 -0
  43. data/data/rbot/plugins/rss.rb.disabled +414 -0
  44. data/data/rbot/plugins/seen.rb +89 -0
  45. data/data/rbot/plugins/slashdot.rb +94 -0
  46. data/data/rbot/plugins/spell.rb +36 -0
  47. data/data/rbot/plugins/tube.rb +71 -0
  48. data/data/rbot/plugins/url.rb +88 -0
  49. data/data/rbot/plugins/weather.rb +649 -0
  50. data/data/rbot/plugins/wserver.rb +71 -0
  51. data/data/rbot/plugins/xmlrpc.rb.disabled +52 -0
  52. data/data/rbot/templates/keywords.rbot +4 -0
  53. data/data/rbot/templates/lart/larts +98 -0
  54. data/data/rbot/templates/lart/praises +5 -0
  55. data/data/rbot/templates/levels.rbot +30 -0
  56. data/data/rbot/templates/users.rbot +1 -0
  57. data/lib/rbot/auth.rb +203 -0
  58. data/lib/rbot/channel.rb +54 -0
  59. data/lib/rbot/config.rb +363 -0
  60. data/lib/rbot/dbhash.rb +112 -0
  61. data/lib/rbot/httputil.rb +141 -0
  62. data/lib/rbot/ircbot.rb +808 -0
  63. data/lib/rbot/ircsocket.rb +185 -0
  64. data/lib/rbot/keywords.rb +433 -0
  65. data/lib/rbot/language.rb +69 -0
  66. data/lib/rbot/message.rb +256 -0
  67. data/lib/rbot/messagemapper.rb +262 -0
  68. data/lib/rbot/plugins.rb +291 -0
  69. data/lib/rbot/post-install.rb +8 -0
  70. data/lib/rbot/rbotconfig.rb +36 -0
  71. data/lib/rbot/registry.rb +271 -0
  72. data/lib/rbot/rfc2812.rb +1104 -0
  73. data/lib/rbot/timer.rb +201 -0
  74. data/lib/rbot/utils.rb +83 -0
  75. data/setup.rb +1360 -0
  76. metadata +129 -0
@@ -0,0 +1,54 @@
1
+ module Irc
2
+
3
+ # class to store IRC channel data (users, topic, per-channel configurations)
4
+ class IRCChannel
5
+ # name of channel
6
+ attr_reader :name
7
+
8
+ # current channel topic
9
+ attr_reader :topic
10
+
11
+ # hash containing users currently in the channel
12
+ attr_accessor :users
13
+
14
+ # if true, bot won't talk in this channel
15
+ attr_accessor :quiet
16
+
17
+ # name:: channel name
18
+ # create a new IRCChannel
19
+ def initialize(name)
20
+ @name = name
21
+ @users = Hash.new
22
+ @quiet = false
23
+ @topic = Topic.new
24
+ end
25
+
26
+ # eg @bot.channels[chan].topic = topic
27
+ def topic=(name)
28
+ @topic.name = name
29
+ end
30
+
31
+ # class to store IRC channel topic information
32
+ class Topic
33
+ # topic name
34
+ attr_accessor :name
35
+
36
+ # timestamp
37
+ attr_accessor :timestamp
38
+
39
+ # topic set by
40
+ attr_accessor :by
41
+
42
+ def initialize
43
+ @name = ""
44
+ end
45
+
46
+ # when called like "puts @bots.channels[chan].topic"
47
+ def to_s
48
+ @name
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,363 @@
1
+ module Irc
2
+
3
+ require 'yaml'
4
+ require 'rbot/messagemapper'
5
+
6
+ class BotConfigValue
7
+ # allow the definition order to be preserved so that sorting by
8
+ # definition order is possible. The BotConfigWizard does this to allow
9
+ # the :wizard questions to be in a sensible order.
10
+ @@order = 0
11
+ attr_reader :type
12
+ attr_reader :desc
13
+ attr_reader :key
14
+ attr_reader :wizard
15
+ attr_reader :requires_restart
16
+ attr_reader :order
17
+ def initialize(key, params)
18
+ unless key =~ /^.+\..+$/
19
+ raise ArgumentError,"key must be of the form 'module.name'"
20
+ end
21
+ @order = @@order
22
+ @@order += 1
23
+ @key = key
24
+ if params.has_key? :default
25
+ @default = params[:default]
26
+ else
27
+ @default = false
28
+ end
29
+ @desc = params[:desc]
30
+ @type = params[:type] || String
31
+ @on_change = params[:on_change]
32
+ @validate = params[:validate]
33
+ @wizard = params[:wizard]
34
+ @requires_restart = params[:requires_restart]
35
+ end
36
+ def default
37
+ if @default.instance_of?(Proc)
38
+ @default.call
39
+ else
40
+ @default
41
+ end
42
+ end
43
+ def get
44
+ return BotConfig.config[@key] if BotConfig.config.has_key?(@key)
45
+ return @default
46
+ end
47
+ alias :value :get
48
+ def set(value, on_change = true)
49
+ BotConfig.config[@key] = value
50
+ @on_change.call(BotConfig.bot, value) if on_change && @on_change
51
+ end
52
+ def unset
53
+ BotConfig.config.delete(@key)
54
+ end
55
+
56
+ # set string will raise ArgumentErrors on failed parse/validate
57
+ def set_string(string, on_change = true)
58
+ value = parse string
59
+ if validate value
60
+ set value, on_change
61
+ else
62
+ raise ArgumentError, "invalid value: #{string}"
63
+ end
64
+ end
65
+
66
+ # override this. the default will work for strings only
67
+ def parse(string)
68
+ string
69
+ end
70
+
71
+ def to_s
72
+ get.to_s
73
+ end
74
+
75
+ private
76
+ def validate(value)
77
+ return true unless @validate
78
+ if @validate.instance_of?(Proc)
79
+ return @validate.call(value)
80
+ elsif @validate.instance_of?(Regexp)
81
+ raise ArgumentError, "validation via Regexp only supported for strings!" unless value.instance_of? String
82
+ return @validate.match(value)
83
+ else
84
+ raise ArgumentError, "validation type #{@validate.class} not supported"
85
+ end
86
+ end
87
+ end
88
+
89
+ class BotConfigStringValue < BotConfigValue
90
+ end
91
+ class BotConfigBooleanValue < BotConfigValue
92
+ def parse(string)
93
+ return true if string == "true"
94
+ return false if string == "false"
95
+ raise ArgumentError, "#{string} does not match either 'true' or 'false'"
96
+ end
97
+ end
98
+ class BotConfigIntegerValue < BotConfigValue
99
+ def parse(string)
100
+ raise ArgumentError, "not an integer: #{string}" unless string =~ /^-?\d+$/
101
+ string.to_i
102
+ end
103
+ end
104
+ class BotConfigFloatValue < BotConfigValue
105
+ def parse(string)
106
+ raise ArgumentError, "not a float #{string}" unless string =~ /^-?[\d.]+$/
107
+ string.to_f
108
+ end
109
+ end
110
+ class BotConfigArrayValue < BotConfigValue
111
+ def parse(string)
112
+ string.split(/,\s+/)
113
+ end
114
+ def to_s
115
+ get.join(", ")
116
+ end
117
+ end
118
+ class BotConfigEnumValue < BotConfigValue
119
+ def initialize(key, params)
120
+ super
121
+ @values = params[:values]
122
+ end
123
+ def values
124
+ if @values.instance_of?(Proc)
125
+ return @values.call(BotConfig.bot)
126
+ else
127
+ return @values
128
+ end
129
+ end
130
+ def parse(string)
131
+ unless @values.include?(string)
132
+ raise ArgumentError, "invalid value #{string}, allowed values are: " + @values.join(", ")
133
+ end
134
+ string
135
+ end
136
+ def desc
137
+ "#{@desc} [valid values are: " + values.join(", ") + "]"
138
+ end
139
+ end
140
+
141
+ # container for bot configuration
142
+ class BotConfig
143
+ # Array of registered BotConfigValues for defaults, types and help
144
+ @@items = Hash.new
145
+ def BotConfig.items
146
+ @@items
147
+ end
148
+ # Hash containing key => value pairs for lookup and serialisation
149
+ @@config = Hash.new(false)
150
+ def BotConfig.config
151
+ @@config
152
+ end
153
+ def BotConfig.bot
154
+ @@bot
155
+ end
156
+
157
+ def BotConfig.register(item)
158
+ unless item.kind_of?(BotConfigValue)
159
+ raise ArgumentError,"item must be a BotConfigValue"
160
+ end
161
+ @@items[item.key] = item
162
+ end
163
+
164
+ # currently we store values in a hash but this could be changed in the
165
+ # future. We use hash semantics, however.
166
+ # components that register their config keys and setup defaults are
167
+ # supported via []
168
+ def [](key)
169
+ return @@items[key].value if @@items.has_key?(key)
170
+ # try to still support unregistered lookups
171
+ return @@config[key] if @@config.has_key?(key)
172
+ return false
173
+ end
174
+
175
+ # TODO should I implement this via BotConfigValue or leave it direct?
176
+ # def []=(key, value)
177
+ # end
178
+
179
+ # pass everything else through to the hash
180
+ def method_missing(method, *args, &block)
181
+ return @@config.send(method, *args, &block)
182
+ end
183
+
184
+ def handle_list(m, params)
185
+ modules = []
186
+ if params[:module]
187
+ @@items.each_key do |key|
188
+ mod, name = key.split('.')
189
+ next unless mod == params[:module]
190
+ modules.push key unless modules.include?(name)
191
+ end
192
+ if modules.empty?
193
+ m.reply "no such module #{params[:module]}"
194
+ else
195
+ m.reply modules.join(", ")
196
+ end
197
+ else
198
+ @@items.each_key do |key|
199
+ name = key.split('.').first
200
+ modules.push name unless modules.include?(name)
201
+ end
202
+ m.reply "modules: " + modules.join(", ")
203
+ end
204
+ end
205
+
206
+ def handle_get(m, params)
207
+ key = params[:key]
208
+ unless @@items.has_key?(key)
209
+ m.reply "no such config key #{key}"
210
+ return
211
+ end
212
+ value = @@items[key].to_s
213
+ m.reply "#{key}: #{value}"
214
+ end
215
+
216
+ def handle_desc(m, params)
217
+ key = params[:key]
218
+ unless @@items.has_key?(key)
219
+ m.reply "no such config key #{key}"
220
+ end
221
+ puts @@items[key].inspect
222
+ m.reply "#{key}: #{@@items[key].desc}"
223
+ end
224
+
225
+ def handle_unset(m, params)
226
+ key = params[:key]
227
+ unless @@items.has_key?(key)
228
+ m.reply "no such config key #{key}"
229
+ end
230
+ @@items[key].unset
231
+ handle_get(m, params)
232
+ end
233
+
234
+ def handle_set(m, params)
235
+ key = params[:key]
236
+ value = params[:value].to_s
237
+ unless @@items.has_key?(key)
238
+ m.reply "no such config key #{key}"
239
+ return
240
+ end
241
+ begin
242
+ @@items[key].set_string(value)
243
+ rescue ArgumentError => e
244
+ m.reply "failed to set #{key}: #{e.message}"
245
+ return
246
+ end
247
+ if @@items[key].requires_restart
248
+ m.reply "this config change will take effect on the next restart"
249
+ else
250
+ m.okay
251
+ end
252
+ end
253
+
254
+ def handle_help(m, params)
255
+ topic = params[:topic]
256
+ case topic
257
+ when false
258
+ m.reply "config module - bot configuration. usage: list, desc, get, set, unset"
259
+ when "list"
260
+ m.reply "config list => list configuration modules, config list <module> => list configuration keys for module <module>"
261
+ when "get"
262
+ m.reply "config get <key> => get configuration value for key <key>"
263
+ when "unset"
264
+ m.reply "reset key <key> to the default"
265
+ when "set"
266
+ m.reply "config set <key> <value> => set configuration value for key <key> to <value>"
267
+ when "desc"
268
+ m.reply "config desc <key> => describe what key <key> configures"
269
+ else
270
+ m.reply "no help for config #{topic}"
271
+ end
272
+ end
273
+ def usage(m,params)
274
+ m.reply "incorrect usage, try '#{@@bot.nick}: help config'"
275
+ end
276
+
277
+ # bot:: parent bot class
278
+ # create a new config hash from #{botclass}/conf.rbot
279
+ def initialize(bot)
280
+ @@bot = bot
281
+
282
+ # respond to config messages, to provide runtime configuration
283
+ # management
284
+ # messages will be:
285
+ # get
286
+ # set
287
+ # unset
288
+ # desc
289
+ # and for arrays:
290
+ # add TODO
291
+ # remove TODO
292
+ @handler = MessageMapper.new(self)
293
+ @handler.map 'config list :module', :action => 'handle_list',
294
+ :defaults => {:module => false}
295
+ @handler.map 'config get :key', :action => 'handle_get'
296
+ @handler.map 'config desc :key', :action => 'handle_desc'
297
+ @handler.map 'config describe :key', :action => 'handle_desc'
298
+ @handler.map 'config set :key *value', :action => 'handle_set'
299
+ @handler.map 'config unset :key', :action => 'handle_unset'
300
+ @handler.map 'config help :topic', :action => 'handle_help',
301
+ :defaults => {:topic => false}
302
+ @handler.map 'help config :topic', :action => 'handle_help',
303
+ :defaults => {:topic => false}
304
+
305
+ if(File.exist?("#{@@bot.botclass}/conf.yaml"))
306
+ newconfig = YAML::load_file("#{@@bot.botclass}/conf.yaml")
307
+ @@config.update newconfig
308
+ else
309
+ # first-run wizard!
310
+ BotConfigWizard.new(@@bot).run
311
+ # save newly created config
312
+ save
313
+ end
314
+ end
315
+
316
+ # write current configuration to #{botclass}/conf.rbot
317
+ def save
318
+ File.open("#{@@bot.botclass}/conf.yaml", "w") do |file|
319
+ file.puts @@config.to_yaml
320
+ end
321
+ end
322
+
323
+ def privmsg(m)
324
+ @handler.handle(m)
325
+ end
326
+ end
327
+
328
+ class BotConfigWizard
329
+ def initialize(bot)
330
+ @bot = bot
331
+ @questions = BotConfig.items.values.find_all {|i| i.wizard }
332
+ end
333
+
334
+ def run()
335
+ puts "First time rbot configuration wizard"
336
+ puts "===================================="
337
+ puts "This is the first time you have run rbot with a config directory of:"
338
+ puts @bot.botclass
339
+ puts "This wizard will ask you a few questions to get you started."
340
+ puts "The rest of rbot's configuration can be manipulated via IRC once"
341
+ puts "rbot is connected and you are auth'd."
342
+ puts "-----------------------------------"
343
+
344
+ return unless @questions
345
+ @questions.sort{|a,b| a.order <=> b.order }.each do |q|
346
+ puts q.desc
347
+ begin
348
+ print q.key + " [#{q.to_s}]: "
349
+ response = STDIN.gets
350
+ response.chop!
351
+ unless response.empty?
352
+ q.set_string response, false
353
+ end
354
+ puts "configured #{q.key} => #{q.to_s}"
355
+ puts "-----------------------------------"
356
+ rescue ArgumentError => e
357
+ puts "failed to set #{q.key}: #{e.message}"
358
+ retry
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,112 @@
1
+ begin
2
+ require 'bdb'
3
+ rescue Exception => e
4
+ puts "Got exception: "+e
5
+ puts "rbot couldn't load the bdb module, perhaps you need to install it? try: http://www.ruby-lang.org/en/raa-list.rhtml?name=bdb"
6
+ exit 2
7
+ end
8
+
9
+ # make BTree lookups case insensitive
10
+ module BDB
11
+ class CIBtree < Btree
12
+ def bdb_bt_compare(a, b)
13
+ a.downcase <=> b.downcase
14
+ end
15
+ end
16
+ end
17
+
18
+ module Irc
19
+
20
+ # DBHash is for tying a hash to disk (using bdb).
21
+ # Call it with an identifier, for example "mydata". It'll look for
22
+ # mydata.db, if it exists, it will load and reference that db.
23
+ # Otherwise it'll create and empty db called mydata.db
24
+ class DBHash
25
+
26
+ # absfilename:: use +key+ as an actual filename, don't prepend the bot's
27
+ # config path and don't append ".db"
28
+ def initialize(bot, key, absfilename=false)
29
+ @bot = bot
30
+ @key = key
31
+ if absfilename && File.exist?(key)
32
+ # db already exists, use it
33
+ @db = DBHash.open_db(key)
34
+ elsif File.exist?(@bot.botclass + "/#{key}.db")
35
+ # db already exists, use it
36
+ @db = DBHash.open_db(@bot.botclass + "/#{key}.db")
37
+ elsif absfilename
38
+ # create empty db
39
+ @db = DBHash.create_db(key)
40
+ else
41
+ # create empty db
42
+ @db = DBHash.create_db(@bot.botclass + "/#{key}.db")
43
+ end
44
+ end
45
+
46
+ def method_missing(method, *args, &block)
47
+ return @db.send(method, *args, &block)
48
+ end
49
+
50
+ def DBHash.create_db(name)
51
+ debug "DBHash: creating empty db #{name}"
52
+ return BDB::Hash.open(name, nil,
53
+ BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
54
+ 0600, "set_pagesize" => 1024,
55
+ "set_cachesize" => [(0), (32 * 1024), (0)])
56
+ end
57
+
58
+ def DBHash.open_db(name)
59
+ debug "DBHash: opening existing db #{name}"
60
+ return BDB::Hash.open(name, nil,
61
+ "r+", 0600, "set_pagesize" => 1024,
62
+ "set_cachesize" => [(0), (32 * 1024), (0)])
63
+ end
64
+
65
+ end
66
+
67
+
68
+ # DBTree is a BTree equivalent of DBHash, with case insensitive lookups.
69
+ class DBTree
70
+
71
+ # absfilename:: use +key+ as an actual filename, don't prepend the bot's
72
+ # config path and don't append ".db"
73
+ def initialize(bot, key, absfilename=false)
74
+ @bot = bot
75
+ @key = key
76
+ if absfilename && File.exist?(key)
77
+ # db already exists, use it
78
+ @db = DBTree.open_db(key)
79
+ elsif absfilename
80
+ # create empty db
81
+ @db = DBTree.create_db(key)
82
+ elsif File.exist?(@bot.botclass + "/#{key}.db")
83
+ # db already exists, use it
84
+ @db = DBTree.open_db(@bot.botclass + "/#{key}.db")
85
+ else
86
+ # create empty db
87
+ @db = DBTree.create_db(@bot.botclass + "/#{key}.db")
88
+ end
89
+ end
90
+
91
+ def method_missing(method, *args, &block)
92
+ return @db.send(method, *args, &block)
93
+ end
94
+
95
+ def DBTree.create_db(name)
96
+ debug "DBTree: creating empty db #{name}"
97
+ return BDB::CIBtree.open(name, nil,
98
+ BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
99
+ 0600, "set_pagesize" => 1024,
100
+ "set_cachesize" => [(0), (32 * 1024), (0)])
101
+ end
102
+
103
+ def DBTree.open_db(name)
104
+ debug "DBTree: opening existing db #{name}"
105
+ return BDB::CIBtree.open(name, nil,
106
+ "r+", 0600, "set_pagesize" => 1024,
107
+ "set_cachesize" => [0, 32 * 1024, 0])
108
+ end
109
+
110
+ end
111
+
112
+ end