rbot 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
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