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
@@ -4,26 +4,200 @@ module Irc
4
4
  require 'thread'
5
5
  require 'rbot/timer'
6
6
 
7
+ class QueueRing
8
+ # A QueueRing is implemented as an array with elements in the form
9
+ # [chan, [message1, message2, ...]
10
+ # Note that the channel +chan+ has no actual bearing with the channels
11
+ # to which messages will be sent
12
+
13
+ def initialize
14
+ @storage = Array.new
15
+ @last_idx = -1
16
+ end
17
+
18
+ def clear
19
+ @storage.clear
20
+ @last_idx = -1
21
+ end
22
+
23
+ def length
24
+ length = 0
25
+ @storage.each {|c|
26
+ length += c[1].length
27
+ }
28
+ return length
29
+ end
30
+
31
+ def empty?
32
+ @storage.empty?
33
+ end
34
+
35
+ def push(mess, chan)
36
+ cmess = @storage.assoc(chan)
37
+ if cmess
38
+ idx = @storage.index(cmess)
39
+ cmess[1] << mess
40
+ @storage[idx] = cmess
41
+ else
42
+ @storage << [chan, [mess]]
43
+ end
44
+ end
45
+
46
+ def next
47
+ if empty?
48
+ warning "trying to access empty ring"
49
+ return nil
50
+ end
51
+ save_idx = @last_idx
52
+ @last_idx = (@last_idx + 1) % @storage.length
53
+ mess = @storage[@last_idx][1].first
54
+ @last_idx = save_idx
55
+ return mess
56
+ end
57
+
58
+ def shift
59
+ if empty?
60
+ warning "trying to access empty ring"
61
+ return nil
62
+ end
63
+ @last_idx = (@last_idx + 1) % @storage.length
64
+ mess = @storage[@last_idx][1].shift
65
+ @storage.delete(@storage[@last_idx]) if @storage[@last_idx][1] == []
66
+ return mess
67
+ end
68
+
69
+ end
70
+
71
+ class MessageQueue
72
+ def initialize
73
+ # a MessageQueue is an array of QueueRings
74
+ # rings have decreasing priority, so messages in ring 0
75
+ # are more important than messages in ring 1, and so on
76
+ @rings = Array.new(3) { |i|
77
+ if i > 0
78
+ QueueRing.new
79
+ else
80
+ # ring 0 is special in that if it's not empty, it will
81
+ # be popped. IOW, ring 0 can starve the other rings
82
+ # ring 0 is strictly FIFO and is therefore implemented
83
+ # as an array
84
+ Array.new
85
+ end
86
+ }
87
+ # the other rings are satisfied round-robin
88
+ @last_ring = 0
89
+ end
90
+
91
+ def clear
92
+ @rings.each { |r|
93
+ r.clear
94
+ }
95
+ @last_ring = 0
96
+ end
97
+
98
+ def push(mess, chan=nil, cring=0)
99
+ ring = cring
100
+ if ring == 0
101
+ warning "message #{mess} at ring 0 has channel #{chan}: channel will be ignored" if !chan.nil?
102
+ @rings[0] << mess
103
+ else
104
+ error "message #{mess} at ring #{ring} must have a channel" if chan.nil?
105
+ @rings[ring].push mess, chan
106
+ end
107
+ end
108
+
109
+ def empty?
110
+ @rings.each { |r|
111
+ return false unless r.empty?
112
+ }
113
+ return true
114
+ end
115
+
116
+ def length
117
+ len = 0
118
+ @rings.each { |r|
119
+ len += r.length
120
+ }
121
+ len
122
+ end
123
+
124
+ def next
125
+ if empty?
126
+ warning "trying to access empty ring"
127
+ return nil
128
+ end
129
+ mess = nil
130
+ if !@rings[0].empty?
131
+ mess = @rings[0].first
132
+ else
133
+ save_ring = @last_ring
134
+ (@rings.length - 1).times {
135
+ @last_ring = (@last_ring % (@rings.length - 1)) + 1
136
+ if !@rings[@last_ring].empty?
137
+ mess = @rings[@last_ring].next
138
+ break
139
+ end
140
+ }
141
+ @last_ring = save_ring
142
+ end
143
+ error "nil message" if mess.nil?
144
+ return mess
145
+ end
146
+
147
+ def shift
148
+ if empty?
149
+ warning "trying to access empty ring"
150
+ return nil
151
+ end
152
+ mess = nil
153
+ if !@rings[0].empty?
154
+ return @rings[0].shift
155
+ end
156
+ (@rings.length - 1).times {
157
+ @last_ring = (@last_ring % (@rings.length - 1)) + 1
158
+ if !@rings[@last_ring].empty?
159
+ return @rings[@last_ring].shift
160
+ end
161
+ }
162
+ error "nil message" if mess.nil?
163
+ return mess
164
+ end
165
+
166
+ end
167
+
7
168
  # wrapped TCPSocket for communication with the server.
8
169
  # emulates a subset of TCPSocket functionality
9
170
  class IrcSocket
10
171
  # total number of lines sent to the irc server
11
172
  attr_reader :lines_sent
12
-
173
+
13
174
  # total number of lines received from the irc server
14
175
  attr_reader :lines_received
15
-
176
+
177
+ # total number of bytes sent to the irc server
178
+ attr_reader :bytes_sent
179
+
180
+ # total number of bytes received from the irc server
181
+ attr_reader :bytes_received
182
+
183
+ # accumulator for the throttle
184
+ attr_reader :throttle_bytes
185
+
186
+ # byterate components
187
+ attr_reader :bytes_per
188
+ attr_reader :seconds_per
189
+
16
190
  # delay between lines sent
17
191
  attr_reader :sendq_delay
18
-
192
+
19
193
  # max lines to burst
20
194
  attr_reader :sendq_burst
21
-
195
+
22
196
  # server:: server to connect to
23
197
  # port:: IRCd port
24
198
  # host:: optional local host to bind to (ruby 1.7+ required)
25
199
  # create a new IrcSocket
26
- def initialize(server, port, host, sendq_delay=2, sendq_burst=4)
200
+ def initialize(server, port, host, sendq_delay=2, sendq_burst=4, brt="400/2")
27
201
  @timer = Timer::Timer.new
28
202
  @timer.add(0.2) do
29
203
  spool
@@ -31,6 +205,7 @@ module Irc
31
205
  @server = server.dup
32
206
  @port = port.to_i
33
207
  @host = host
208
+ @sock = nil
34
209
  @spooler = false
35
210
  @lines_sent = 0
36
211
  @lines_received = 0
@@ -40,32 +215,58 @@ module Irc
40
215
  @sendq_delay = 2
41
216
  end
42
217
  @last_send = Time.new - @sendq_delay
218
+ @last_throttle = Time.new
43
219
  @burst = 0
44
220
  if sendq_burst
45
221
  @sendq_burst = sendq_burst.to_i
46
222
  else
47
223
  @sendq_burst = 4
48
224
  end
225
+ @bytes_per = 400
226
+ @seconds_per = 2
227
+ @throttle_bytes = 0
228
+ @throttle_div = 1
229
+ setbyterate(brt)
230
+ end
231
+
232
+ def setbyterate(brt)
233
+ if brt.match(/(\d+)\/(\d)/)
234
+ @bytes_per = $1.to_i
235
+ @seconds_per = $2.to_i
236
+ debug "Byterate now #{byterate}"
237
+ return true
238
+ else
239
+ debug "Couldn't set byterate #{brt}"
240
+ return false
241
+ end
242
+ end
243
+
244
+ def connected?
245
+ !@sock.nil?
49
246
  end
50
-
247
+
51
248
  # open a TCP connection to the server
52
249
  def connect
250
+ if connected?
251
+ warning "reconnecting while connected"
252
+ return
253
+ end
53
254
  if(@host)
54
255
  begin
55
256
  @sock=TCPSocket.new(@server, @port, @host)
56
257
  rescue ArgumentError => e
57
- $stderr.puts "Your version of ruby does not support binding to a "
58
- $stderr.puts "specific local address, please upgrade if you wish "
59
- $stderr.puts "to use HOST = foo"
60
- $stderr.puts "(this option has been disabled in order to continue)"
258
+ error "Your version of ruby does not support binding to a "
259
+ error "specific local address, please upgrade if you wish "
260
+ error "to use HOST = foo"
261
+ error "(this option has been disabled in order to continue)"
61
262
  @sock=TCPSocket.new(@server, @port)
62
263
  end
63
264
  else
64
265
  @sock=TCPSocket.new(@server, @port)
65
- end
266
+ end
66
267
  @qthread = false
67
268
  @qmutex = Mutex.new
68
- @sendq = Array.new
269
+ @sendq = MessageQueue.new
69
270
  end
70
271
 
71
272
  def sendq_delay=(newfreq)
@@ -87,9 +288,42 @@ module Irc
87
288
  end
88
289
  end
89
290
 
90
- # used to send lines to the remote IRCd
291
+ def byterate
292
+ return "#{@bytes_per}/#{@seconds_per}"
293
+ end
294
+
295
+ def byterate=(newrate)
296
+ @qmutex.synchronize do
297
+ setbyterate(newrate)
298
+ end
299
+ end
300
+
301
+ def run_throttle(more=0)
302
+ now = Time.new
303
+ if @throttle_bytes > 0
304
+ # If we ever reach the limit, we halve the actual allowed byterate
305
+ # until we manage to reset the throttle.
306
+ if @throttle_bytes >= @bytes_per
307
+ @throttle_div = 0.5
308
+ end
309
+ delta = ((now - @last_throttle)*@throttle_div*@bytes_per/@seconds_per).floor
310
+ if delta > 0
311
+ @throttle_bytes -= delta
312
+ @throttle_bytes = 0 if @throttle_bytes < 0
313
+ @last_throttle = now
314
+ end
315
+ else
316
+ @throttle_div = 1
317
+ end
318
+ @throttle_bytes += more
319
+ end
320
+
321
+ # used to send lines to the remote IRCd by skipping the queue
91
322
  # message: IRC message to send
92
- def puts(message)
323
+ # it should only be used for stuff that *must not* be queued,
324
+ # i.e. the initial PASS, NICK and USER command
325
+ # or the final QUIT message
326
+ def emergency_puts(message)
93
327
  @qmutex.synchronize do
94
328
  # debug "In puts - got mutex"
95
329
  puts_critical(message)
@@ -98,58 +332,89 @@ module Irc
98
332
 
99
333
  # get the next line from the server (blocks)
100
334
  def gets
101
- reply = @sock.gets
102
- @lines_received += 1
103
- reply.strip! if reply
104
- debug "RECV: #{reply.inspect}"
105
- reply
335
+ if @sock.nil?
336
+ warning "socket get attempted while closed"
337
+ return nil
338
+ end
339
+ begin
340
+ reply = @sock.gets
341
+ @lines_received += 1
342
+ reply.strip! if reply
343
+ debug "RECV: #{reply.inspect}"
344
+ return reply
345
+ rescue => e
346
+ warning "socket get failed: #{e.inspect}"
347
+ debug e.backtrace.join("\n")
348
+ return nil
349
+ end
106
350
  end
107
351
 
108
- def queue(msg)
352
+ def queue(msg, chan=nil, ring=0)
109
353
  if @sendq_delay > 0
110
354
  @qmutex.synchronize do
111
- @sendq.push msg
355
+ @sendq.push msg, chan, ring
356
+ @timer.start
112
357
  end
113
- @timer.start
114
358
  else
115
359
  # just send it if queueing is disabled
116
- self.puts(msg)
360
+ self.emergency_puts(msg)
117
361
  end
118
362
  end
119
363
 
120
364
  # pop a message off the queue, send it
121
365
  def spool
122
- if @sendq.empty?
123
- @timer.stop
124
- return
125
- end
126
- now = Time.new
127
- if (now >= (@last_send + @sendq_delay))
128
- # reset burst counter after @sendq_delay has passed
129
- @burst = 0
130
- debug "in spool, resetting @burst"
131
- elsif (@burst >= @sendq_burst)
132
- # nope. can't send anything, come back to us next tick...
133
- @timer.start
134
- return
135
- end
136
366
  @qmutex.synchronize do
137
- debug "(can send #{@sendq_burst - @burst} lines, there are #{@sendq.length} to send)"
138
- (@sendq_burst - @burst).times do
139
- break if @sendq.empty?
140
- puts_critical(@sendq.shift)
367
+ begin
368
+ debug "in spooler"
369
+ if @sendq.empty?
370
+ @timer.stop
371
+ return
372
+ end
373
+ now = Time.new
374
+ if (now >= (@last_send + @sendq_delay))
375
+ # reset burst counter after @sendq_delay has passed
376
+ debug "resetting @burst"
377
+ @burst = 0
378
+ elsif (@burst >= @sendq_burst)
379
+ # nope. can't send anything, come back to us next tick...
380
+ debug "can't send yet"
381
+ @timer.start
382
+ return
383
+ end
384
+ debug "can send #{@sendq_burst - @burst} lines, there are #{@sendq.length} to send"
385
+ (@sendq_burst - @burst).times do
386
+ break if @sendq.empty?
387
+ mess = @sendq.next
388
+ if @throttle_bytes == 0 or mess.length+@throttle_bytes < @bytes_per
389
+ debug "flood protection: sending message of length #{mess.length}"
390
+ debug "(byterate: #{byterate}, throttle bytes: #{@throttle_bytes})"
391
+ puts_critical(@sendq.shift)
392
+ else
393
+ debug "flood protection: throttling message of length #{mess.length}"
394
+ debug "(byterate: #{byterate}, throttle bytes: #{@throttle_bytes})"
395
+ run_throttle
396
+ break
397
+ end
398
+ end
399
+ if @sendq.empty?
400
+ @timer.stop
401
+ end
402
+ rescue => e
403
+ error "Spooling failed: #{e.inspect}"
404
+ error e.backtrace.join("\n")
405
+ end
141
406
  end
142
407
  end
143
- if @sendq.empty?
144
- @timer.stop
145
- end
146
- end
147
408
 
148
409
  def clearq
149
- unless @sendq.empty?
410
+ if @sock
150
411
  @qmutex.synchronize do
151
- @sendq.clear
412
+ unless @sendq.empty?
413
+ @sendq.clear
414
+ end
152
415
  end
416
+ else
417
+ warning "Clearing socket while disconnected"
153
418
  end
154
419
  end
155
420
 
@@ -165,19 +430,30 @@ module Irc
165
430
 
166
431
  # shutdown the connection to the server
167
432
  def shutdown(how=2)
168
- @sock.shutdown(how)
433
+ @sock.shutdown(how) unless @sock.nil?
434
+ @sock = nil
435
+ @burst = 0
169
436
  end
170
437
 
171
438
  private
172
-
439
+
173
440
  # same as puts, but expects to be called with a mutex held on @qmutex
174
441
  def puts_critical(message)
175
442
  # debug "in puts_critical"
176
- debug "SEND: #{message.inspect}"
177
- @sock.send(message + "\n",0)
178
- @last_send = Time.new
179
- @lines_sent += 1
180
- @burst += 1
443
+ begin
444
+ debug "SEND: #{message.inspect}"
445
+ if @sock.nil?
446
+ error "SEND attempted on closed socket"
447
+ else
448
+ @sock.send(message + "\n",0)
449
+ @last_send = Time.new
450
+ @lines_sent += 1
451
+ @burst += 1
452
+ run_throttle(message.length + 1)
453
+ end
454
+ rescue => e
455
+ error "SEND failed: #{e.inspect}"
456
+ end
181
457
  end
182
458
 
183
459
  end