rbot 0.9.9 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,585 @@
1
+ # RSS feed plugin for RubyBot
2
+ # (c) 2004 Stanislav Karchebny <berkus@madfire.net>
3
+ # (c) 2005 Ian Monroe <ian@monroe.nu>
4
+ # (c) 2005 Mark Kretschmann <markey@web.de>
5
+ # Licensed under MIT License.
6
+
7
+ require 'rss/parser'
8
+ require 'rss/1.0'
9
+ require 'rss/2.0'
10
+ require 'rss/dublincore'
11
+ # begin
12
+ # require 'rss/dublincore/2.0'
13
+ # rescue
14
+ # warning "Unable to load RSS libraries, RSS plugin functionality crippled"
15
+ # end
16
+
17
+ class ::String
18
+ def shorten(limit)
19
+ if self.length > limit
20
+ self+". " =~ /^(.{#{limit}}[^.!;?]*[.!;?])/mi
21
+ return $1
22
+ end
23
+ self
24
+ end
25
+
26
+ def riphtml
27
+ self.gsub(/<[^>]+>/, '').gsub(/&amp;/,'&').gsub(/&quot;/,'"').gsub(/&lt;/,'<').gsub(/&gt;/,'>').gsub(/&ellip;/,'...').gsub(/&apos;/, "'").gsub("\n",'')
28
+ end
29
+
30
+ def mysqlize
31
+ self.gsub(/'/, "''")
32
+ end
33
+ end
34
+
35
+ class ::RssBlob
36
+ attr :url
37
+ attr :handle
38
+ attr :type
39
+ attr :watchers
40
+
41
+ def initialize(url,handle=nil,type=nil,watchers=[])
42
+ @url = url
43
+ if handle
44
+ @handle = handle
45
+ else
46
+ @handle = url
47
+ end
48
+ @type = type
49
+ @watchers = watchers
50
+ end
51
+
52
+ def watched?
53
+ !@watchers.empty?
54
+ end
55
+
56
+ def watched_by?(who)
57
+ @watchers.include?(who)
58
+ end
59
+
60
+ def add_watch(who)
61
+ if watched_by?(who)
62
+ return nil
63
+ end
64
+ @watchers << who unless watched_by?(who)
65
+ return who
66
+ end
67
+
68
+ def rm_watch(who)
69
+ @watchers.delete(who)
70
+ end
71
+
72
+ def to_a
73
+ [@handle,@url,@type,@watchers]
74
+ end
75
+
76
+ def to_s(watchers=false)
77
+ if watchers
78
+ a = self.to_a.flatten
79
+ else
80
+ a = self.to_a[0,3]
81
+ end
82
+ a.join(" | ")
83
+ end
84
+ end
85
+
86
+ class RSSFeedsPlugin < Plugin
87
+ BotConfig.register BotConfigIntegerValue.new('rss.head_max',
88
+ :default => 30, :validate => Proc.new{|v| v > 0 && v < 200},
89
+ :desc => "How many characters to use of a RSS item header")
90
+
91
+ BotConfig.register BotConfigIntegerValue.new('rss.text_max',
92
+ :default => 90, :validate => Proc.new{|v| v > 0 && v < 400},
93
+ :desc => "How many characters to use of a RSS item text")
94
+
95
+ BotConfig.register BotConfigIntegerValue.new('rss.thread_sleep',
96
+ :default => 300, :validate => Proc.new{|v| v > 30},
97
+ :desc => "How many characters to use of a RSS item text")
98
+
99
+ @@watchThreads = Hash.new
100
+ @@mutex = Mutex.new
101
+
102
+ def initialize
103
+ super
104
+ kill_threads
105
+ if @registry.has_key?(:feeds)
106
+ @feeds = @registry[:feeds]
107
+ else
108
+ @feeds = Hash.new
109
+ end
110
+ rewatch_rss
111
+ end
112
+
113
+ def watchlist
114
+ @feeds.select { |h, f| f.watched? }
115
+ end
116
+
117
+ def cleanup
118
+ kill_threads
119
+ end
120
+
121
+ def save
122
+ @registry[:feeds] = @feeds
123
+ end
124
+
125
+ def kill_threads
126
+ @@mutex.synchronize {
127
+ # Abort all running threads.
128
+ @@watchThreads.each { |url, thread|
129
+ debug "Killing thread for #{url}"
130
+ thread.kill
131
+ }
132
+ @@watchThreads = Hash.new
133
+ }
134
+ end
135
+
136
+ def help(plugin,topic="")
137
+ case topic
138
+ when "show"
139
+ "rss show #{Bold}handle#{Bold} [#{Bold}limit#{Bold}] : show #{Bold}limit#{Bold} (default: 5, max: 15) entries from rss #{Bold}handle#{Bold}; #{Bold}limit#{Bold} can also be in the form a..b, to display a specific range of items"
140
+ when "list"
141
+ "rss list [#{Bold}handle#{Bold}] : list all rss feeds (matching #{Bold}handle#{Bold})"
142
+ when "watched"
143
+ "rss watched [#{Bold}handle#{Bold}] : list all watched rss feeds (matching #{Bold}handle#{Bold})"
144
+ when "add"
145
+ "rss add #{Bold}handle#{Bold} #{Bold}url#{Bold} [#{Bold}type#{Bold}] : add a new rss called #{Bold}handle#{Bold} from url #{Bold}url#{Bold} (of type #{Bold}type#{Bold})"
146
+ when /^(del(ete)?|rm)$/
147
+ "rss del(ete)|rm #{Bold}handle#{Bold} : delete rss feed #{Bold}handle#{Bold}"
148
+ when "replace"
149
+ "rss replace #{Bold}handle#{Bold} #{Bold}url#{Bold} [#{Bold}type#{Bold}] : try to replace the url of rss called #{Bold}handle#{Bold} with #{Bold}url#{Bold} (of type #{Bold}type#{Bold}); only works if nobody else is watching it"
150
+ when "forcereplace"
151
+ "rss forcereplace #{Bold}handle#{Bold} #{Bold}url#{Bold} [#{Bold}type#{Bold}] : replace the url of rss called #{Bold}handle#{Bold} with #{Bold}url#{Bold} (of type #{Bold}type#{Bold})"
152
+ when "watch"
153
+ "rss watch #{Bold}handle#{Bold} [#{Bold}url#{Bold} [#{Bold}type#{Bold}]] : watch rss #{Bold}handle#{Bold} for changes; when the other parameters are present, it will be created if it doesn't exist yet"
154
+ when /(un|rm)watch/
155
+ "rss unwatch|rmwatch #{Bold}handle#{Bold} : stop watching rss #{Bold}handle#{Bold} for changes"
156
+ when "rewatch"
157
+ "rss rewatch : restart threads that watch for changes in watched rss"
158
+ else
159
+ "manage RSS feeds: rss show|list|watched|add|del(ete)|rm|(force)replace|watch|unwatch|rmwatch|rewatch"
160
+ end
161
+ end
162
+
163
+ def report_problem(report, e=nil, m=nil)
164
+ if m && m.respond_to?(:reply)
165
+ m.reply report
166
+ else
167
+ warning report
168
+ end
169
+ if e
170
+ debug e.inspect
171
+ debug e.backtrace.join("\n") if e.respond_to?(:backtrace)
172
+ end
173
+ end
174
+
175
+ def show_rss(m, params)
176
+ handle = params[:handle]
177
+ lims = params[:limit].to_s.match(/(\d+)(?:..(\d+))?/)
178
+ debug lims.to_a.inspect
179
+ if lims[2]
180
+ ll = [[lims[1].to_i-1,lims[2].to_i-1].min, 0].max
181
+ ul = [[lims[1].to_i-1,lims[2].to_i-1].max, 14].min
182
+ rev = lims[1].to_i > lims[2].to_i
183
+ else
184
+ ll = 0
185
+ ul = [[lims[1].to_i-1, 1].max, 14].min
186
+ rev = false
187
+ end
188
+
189
+ feed = @feeds.fetch(handle, nil)
190
+ unless feed
191
+ m.reply "I don't know any feeds named #{handle}"
192
+ return
193
+ end
194
+
195
+ m.reply "lemme fetch it..."
196
+ title = items = nil
197
+ @@mutex.synchronize {
198
+ title, items = fetchRss(feed, m)
199
+ }
200
+ return unless items
201
+
202
+ # We sort the feeds in freshness order (newer ones first)
203
+ items = freshness_sort(items)
204
+ disp = items[ll..ul]
205
+ disp.reverse! if rev
206
+
207
+ m.reply "Channel : #{title}"
208
+ disp.each do |item|
209
+ printFormattedRss(feed, item, {:places=>[m.replyto],:handle=>nil,:date=>true})
210
+ end
211
+ end
212
+
213
+ def itemDate(item,ex=nil)
214
+ return item.pubDate if item.respond_to?(:pubDate)
215
+ return item.date if item.respond_to?(:date)
216
+ return ex
217
+ end
218
+
219
+ def freshness_sort(items)
220
+ notime = Time.at(0)
221
+ items.sort { |a, b|
222
+ itemDate(b, notime) <=> itemDate(a, notime)
223
+ }
224
+ end
225
+
226
+ def list_rss(m, params)
227
+ wanted = params[:handle]
228
+ reply = String.new
229
+ @@mutex.synchronize {
230
+ @feeds.each { |handle, feed|
231
+ next if wanted and !handle.match(wanted)
232
+ reply << "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})"
233
+ (reply << " (watched)") if feed.watched_by?(m.replyto)
234
+ reply << "\n"
235
+ }
236
+ }
237
+ if reply.empty?
238
+ reply = "no feeds found"
239
+ reply << " matching #{wanted}" if wanted
240
+ end
241
+ m.reply reply
242
+ end
243
+
244
+ def watched_rss(m, params)
245
+ wanted = params[:handle]
246
+ reply = String.new
247
+ @@mutex.synchronize {
248
+ watchlist.each { |handle, feed|
249
+ next if wanted and !handle.match(wanted)
250
+ next unless feed.watched_by?(m.replyto)
251
+ reply << "#{feed.handle}: #{feed.url} (in format: #{feed.type ? feed.type : 'default'})\n"
252
+ }
253
+ }
254
+ if reply.empty?
255
+ reply = "no watched feeds"
256
+ reply << " matching #{wanted}" if wanted
257
+ end
258
+ m.reply reply
259
+ end
260
+
261
+ def add_rss(m, params, force=false)
262
+ handle = params[:handle]
263
+ url = params[:url]
264
+ unless url.match(/https?/)
265
+ m.reply "I only deal with feeds from HTTP sources, so I can't use #{url} (maybe you forgot the handle?)"
266
+ return
267
+ end
268
+ type = params[:type]
269
+ if @feeds.fetch(handle, nil) && !force
270
+ m.reply "There is already a feed named #{handle} (URL: #{@feeds[handle].url})"
271
+ return
272
+ end
273
+ unless url
274
+ m.reply "You must specify both a handle and an url to add an RSS feed"
275
+ return
276
+ end
277
+ @@mutex.synchronize {
278
+ @feeds[handle] = RssBlob.new(url,handle,type)
279
+ }
280
+ reply = "Added RSS #{url} named #{handle}"
281
+ if type
282
+ reply << " (format: #{type})"
283
+ end
284
+ m.reply reply
285
+ return handle
286
+ end
287
+
288
+ def del_rss(m, params, pass=false)
289
+ feed = unwatch_rss(m, params, true)
290
+ if feed.watched?
291
+ m.reply "someone else is watching #{feed.handle}, I won't remove it from my list"
292
+ return
293
+ end
294
+ @@mutex.synchronize {
295
+ @feeds.delete(feed.handle)
296
+ }
297
+ m.okay unless pass
298
+ return
299
+ end
300
+
301
+ def replace_rss(m, params)
302
+ handle = params[:handle]
303
+ if @feeds.key?(handle)
304
+ del_rss(m, {:handle => handle}, true)
305
+ end
306
+ if @feeds.key?(handle)
307
+ m.reply "can't replace #{feed.handle}"
308
+ else
309
+ add_rss(m, params, true)
310
+ end
311
+ end
312
+
313
+ def forcereplace_rss(m, params)
314
+ add_rss(m, params, true)
315
+ end
316
+
317
+ def watch_rss(m, params)
318
+ handle = params[:handle]
319
+ url = params[:url]
320
+ type = params[:type]
321
+ if url
322
+ add_rss(m, params)
323
+ end
324
+ feed = nil
325
+ @@mutex.synchronize {
326
+ feed = @feeds.fetch(handle, nil)
327
+ }
328
+ if feed
329
+ @@mutex.synchronize {
330
+ if feed.add_watch(m.replyto)
331
+ watchRss(feed, m)
332
+ m.okay
333
+ else
334
+ m.reply "Already watching #{feed.handle}"
335
+ end
336
+ }
337
+ else
338
+ m.reply "Couldn't watch feed #{handle} (no such feed found)"
339
+ end
340
+ end
341
+
342
+ def unwatch_rss(m, params, pass=false)
343
+ handle = params[:handle]
344
+ unless @feeds.has_key?(handle)
345
+ m.reply("dunno that feed")
346
+ return
347
+ end
348
+ feed = @feeds[handle]
349
+ if feed.rm_watch(m.replyto)
350
+ m.reply "#{m.replyto} has been removed from the watchlist for #{feed.handle}"
351
+ else
352
+ m.reply("#{m.replyto} wasn't watching #{feed.handle}") unless pass
353
+ end
354
+ if !feed.watched?
355
+ @@mutex.synchronize {
356
+ if @@watchThreads[handle].kind_of? Thread
357
+ @@watchThreads[handle].kill
358
+ debug "rmwatch: Killed thread for #{handle}"
359
+ @@watchThreads.delete(handle)
360
+ end
361
+ }
362
+ end
363
+ return feed
364
+ end
365
+
366
+ def rewatch_rss(m=nil)
367
+ kill_threads
368
+
369
+ # Read watches from list.
370
+ watchlist.each{ |handle, feed|
371
+ watchRss(feed, m)
372
+ }
373
+ m.okay if m
374
+ end
375
+
376
+ private
377
+ def watchRss(feed, m=nil)
378
+ if @@watchThreads.has_key?(feed.handle)
379
+ report_problem("watcher thread for #{feed.handle} is already running", nil, m)
380
+ return
381
+ end
382
+ @@watchThreads[feed.handle] = Thread.new do
383
+ debug "watcher for #{feed} started"
384
+ oldItems = []
385
+ firstRun = true
386
+ failures = 0
387
+ loop do
388
+ begin
389
+ debug "fetching #{feed}"
390
+ title = newItems = nil
391
+ @@mutex.synchronize {
392
+ title, newItems = fetchRss(feed)
393
+ }
394
+ unless newItems
395
+ debug "no items in feed #{feed}"
396
+ failures +=1
397
+ else
398
+ debug "Checking if new items are available for #{feed}"
399
+ if firstRun
400
+ debug "First run, we'll see next time"
401
+ firstRun = false
402
+ else
403
+ otxt = oldItems.map { |item| item.to_s }
404
+ dispItems = newItems.reject { |item|
405
+ otxt.include?(item.to_s)
406
+ }
407
+ if dispItems.length > 0
408
+ debug "Found #{dispItems.length} new items in #{feed}"
409
+ dispItems.each { |item|
410
+ @@mutex.synchronize {
411
+ printFormattedRss(feed, item)
412
+ }
413
+ }
414
+ else
415
+ debug "No new items found in #{feed}"
416
+ end
417
+ end
418
+ oldItems = newItems.dup
419
+ end
420
+ rescue Exception => e
421
+ error "Error watching #{feed}: #{e.inspect}"
422
+ debug e.backtrace.join("\n")
423
+ failures += 1
424
+ end
425
+
426
+ seconds = @bot.config['rss.thread_sleep'] * (failures + 1)
427
+ seconds += seconds * (rand(100)-50)/100
428
+ debug "watcher for #{feed} going to sleep #{seconds} seconds.."
429
+ sleep seconds
430
+ end
431
+ end
432
+ end
433
+
434
+ def printFormattedRss(feed, item, opts=nil)
435
+ places = feed.watchers
436
+ handle = "::#{feed.handle}:: "
437
+ date = String.new
438
+ if opts
439
+ places = opts[:places] if opts.key?(:places)
440
+ handle = opts[:handle].to_s if opts.key?(:handle)
441
+ if opts.key?(:date) && opts[:date]
442
+ if item.respond_to?(:pubDate)
443
+ if item.pubDate.class <= Time
444
+ date = item.pubDate.strftime("%Y/%m/%d %H.%M.%S")
445
+ else
446
+ date = item.pubDate.to_s
447
+ end
448
+ elsif item.respond_to?(:date)
449
+ if item.date.class <= Time
450
+ date = item.date.strftime("%Y/%m/%d %H.%M.%S")
451
+ else
452
+ date = item.date.to_s
453
+ end
454
+ else
455
+ date = "(no date)"
456
+ end
457
+ date += " :: "
458
+ end
459
+ end
460
+ title = "#{Bold}#{item.title.chomp.riphtml}#{Bold}" if item.title
461
+ desc = item.description.gsub(/\s+/,' ').strip.riphtml.shorten(@bot.config['rss.text_max']) if item.description
462
+ link = item.link.chomp if item.link
463
+ places.each { |loc|
464
+ case feed.type
465
+ when 'blog'
466
+ @bot.say loc, "#{handle}#{date}#{item.category.content} blogged at #{link}"
467
+ @bot.say loc, "#{handle}#{title} - #{desc}"
468
+ when 'forum'
469
+ @bot.say loc, "#{handle}#{date}#{title}#{' @ ' if item.title && item.link}#{link}"
470
+ when 'wiki'
471
+ @bot.say loc, "#{handle}#{date}#{item.title} has been edited by #{item.dc_creator}. #{desc} #{link}"
472
+ when 'gmame'
473
+ @bot.say loc, "#{handle}#{date}Message #{title} sent by #{item.dc_creator}. #{desc}"
474
+ when 'trac'
475
+ @bot.say loc, "#{handle}#{date}#{title} @ #{link}"
476
+ unless item.title =~ /^Changeset \[(\d+)\]/
477
+ @bot.say loc, "#{handle}#{date}#{desc}"
478
+ end
479
+ else
480
+ @bot.say loc, "#{handle}#{date}#{title}#{' @ ' if item.title && item.link}#{link}"
481
+ end
482
+ }
483
+ end
484
+
485
+ def fetchRss(feed, m=nil)
486
+ begin
487
+ # Use 60 sec timeout, cause the default is too low
488
+ # Do not use get_cached for RSS until we have proper cache handling
489
+ # xml = @bot.httputil.get_cached(feed.url,60,60)
490
+ xml = @bot.httputil.get(feed.url,60,60)
491
+ rescue URI::InvalidURIError, URI::BadURIError => e
492
+ report_problem("invalid rss feed #{feed.url}", e, m)
493
+ return
494
+ rescue => e
495
+ report_problem("error getting #{feed.url}", e, m)
496
+ return
497
+ end
498
+ debug "fetched #{feed}"
499
+ unless xml
500
+ report_problem("reading feed #{feed} failed", nil, m)
501
+ return
502
+ end
503
+
504
+ begin
505
+ ## do validate parse
506
+ rss = RSS::Parser.parse(xml)
507
+ debug "parsed #{feed}"
508
+ rescue RSS::InvalidRSSError
509
+ ## do non validate parse for invalid RSS 1.0
510
+ begin
511
+ rss = RSS::Parser.parse(xml, false)
512
+ rescue RSS::Error => e
513
+ report_problem("parsing rss stream failed, whoops =(", e, m)
514
+ return
515
+ end
516
+ rescue RSS::Error => e
517
+ report_problem("parsing rss stream failed, oioi", e, m)
518
+ return
519
+ rescue => e
520
+ report_problem("processing error occured, sorry =(", e, m)
521
+ return
522
+ end
523
+ items = []
524
+ if rss.nil?
525
+ report_problem("#{feed} does not include RSS 1.0 or 0.9x/2.0", nil, m)
526
+ else
527
+ begin
528
+ rss.output_encoding = 'UTF-8'
529
+ rescue RSS::UnknownConvertMethod => e
530
+ report_problem("bah! something went wrong =(", e, m)
531
+ return
532
+ end
533
+ rss.channel.title ||= "Unknown"
534
+ title = rss.channel.title
535
+ rss.items.each do |item|
536
+ item.title ||= "Unknown"
537
+ items << item
538
+ end
539
+ end
540
+
541
+ if items.empty?
542
+ report_problem("no items found in the feed, maybe try weed?", e, m)
543
+ return
544
+ end
545
+ return [title, items]
546
+ end
547
+ end
548
+
549
+ plugin = RSSFeedsPlugin.new
550
+
551
+ plugin.map 'rss show :handle :limit',
552
+ :action => 'show_rss',
553
+ :requirements => {:limit => /^\d+(?:\.\.\d+)?$/},
554
+ :defaults => {:limit => 5}
555
+ plugin.map 'rss list :handle',
556
+ :action => 'list_rss',
557
+ :defaults => {:handle => nil}
558
+ plugin.map 'rss watched :handle',
559
+ :action => 'watched_rss',
560
+ :defaults => {:handle => nil}
561
+ plugin.map 'rss add :handle :url :type',
562
+ :action => 'add_rss',
563
+ :defaults => {:type => nil}
564
+ plugin.map 'rss del :handle',
565
+ :action => 'del_rss'
566
+ plugin.map 'rss delete :handle',
567
+ :action => 'del_rss'
568
+ plugin.map 'rss rm :handle',
569
+ :action => 'del_rss'
570
+ plugin.map 'rss replace :handle :url :type',
571
+ :action => 'replace_rss',
572
+ :defaults => {:type => nil}
573
+ plugin.map 'rss forcereplace :handle :url :type',
574
+ :action => 'forcereplace_rss',
575
+ :defaults => {:type => nil}
576
+ plugin.map 'rss watch :handle :url :type',
577
+ :action => 'watch_rss',
578
+ :defaults => {:url => nil, :type => nil}
579
+ plugin.map 'rss unwatch :handle',
580
+ :action => 'unwatch_rss'
581
+ plugin.map 'rss rmwatch :handle',
582
+ :action => 'unwatch_rss'
583
+ plugin.map 'rss rewatch :handle',
584
+ :action => 'rewatch_rss'
585
+