campfire-bot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/.autotest +11 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +57 -0
  5. data/README.textile +52 -0
  6. data/TODO +72 -0
  7. data/bin/bot +13 -0
  8. data/campfire-bot.gemspec +27 -0
  9. data/cfbot-stop.sh +8 -0
  10. data/config.example.yml +31 -0
  11. data/lib/bot.rb +194 -0
  12. data/lib/event.rb +114 -0
  13. data/lib/message.rb +30 -0
  14. data/lib/plugin.rb +77 -0
  15. data/lib/version.rb +3 -0
  16. data/plugins/accountability.rb +45 -0
  17. data/plugins/austin.rb +29 -0
  18. data/plugins/basecamp.rb +48 -0
  19. data/plugins/beer.rb +214 -0
  20. data/plugins/beijing_tally.rb +30 -0
  21. data/plugins/boop.rb +127 -0
  22. data/plugins/bruce.rb +15 -0
  23. data/plugins/bugzilla.rb +198 -0
  24. data/plugins/calvin.rb +43 -0
  25. data/plugins/chuck.rb +23 -0
  26. data/plugins/dilbert.rb +51 -0
  27. data/plugins/excuse.rb +478 -0
  28. data/plugins/fail.rb +16 -0
  29. data/plugins/figlet.rb +10 -0
  30. data/plugins/fun.rb +95 -0
  31. data/plugins/garfield.rb +43 -0
  32. data/plugins/generic_search.rb +66 -0
  33. data/plugins/help.rb +13 -0
  34. data/plugins/infobot.rb +58 -0
  35. data/plugins/insult.rb +87 -0
  36. data/plugins/jira.rb +197 -0
  37. data/plugins/lolcats.rb +17 -0
  38. data/plugins/our_quotes.rb +195 -0
  39. data/plugins/quote.rb +31 -0
  40. data/plugins/schneier.rb +28 -0
  41. data/plugins/seen.rb +88 -0
  42. data/plugins/signal_filter.rb +9 -0
  43. data/plugins/svn.rb +167 -0
  44. data/plugins/twitter_echo.rb +54 -0
  45. data/plugins/unfuddle.rb +69 -0
  46. data/plugins/weather.rb +25 -0
  47. data/plugins/xkcd.rb +43 -0
  48. data/spec/beer_spec.rb +224 -0
  49. data/spec/bugzilla_spec.rb +90 -0
  50. data/spec/command_spec.rb +96 -0
  51. data/spec/jira_spec.rb +264 -0
  52. data/spec/our_quotes_spec.rb +186 -0
  53. data/spec/plugin_spec.rb +43 -0
  54. data/spec/spec.opts +1 -0
  55. data/vendor/escape/ChangeLog +30 -0
  56. data/vendor/escape/Makefile +5 -0
  57. data/vendor/escape/README +81 -0
  58. data/vendor/escape/VERSION +1 -0
  59. data/vendor/escape/escape.rb +302 -0
  60. data/vendor/escape/install.rb +109 -0
  61. data/vendor/escape/misc/README.erb +85 -0
  62. data/vendor/escape/rdoc/classes/Escape.html +427 -0
  63. data/vendor/escape/rdoc/classes/Escape.src/M000022.html +19 -0
  64. data/vendor/escape/rdoc/classes/Escape.src/M000023.html +32 -0
  65. data/vendor/escape/rdoc/classes/Escape.src/M000024.html +24 -0
  66. data/vendor/escape/rdoc/classes/Escape.src/M000025.html +19 -0
  67. data/vendor/escape/rdoc/classes/Escape.src/M000026.html +48 -0
  68. data/vendor/escape/rdoc/classes/Escape.src/M000027.html +19 -0
  69. data/vendor/escape/rdoc/classes/Escape.src/M000028.html +19 -0
  70. data/vendor/escape/rdoc/classes/Escape/HTMLAttrValue.html +113 -0
  71. data/vendor/escape/rdoc/classes/Escape/HTMLEscaped.html +113 -0
  72. data/vendor/escape/rdoc/classes/Escape/PercentEncoded.html +113 -0
  73. data/vendor/escape/rdoc/classes/Escape/ShellEscaped.html +113 -0
  74. data/vendor/escape/rdoc/classes/Escape/StringWrapper.html +242 -0
  75. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000029.html +18 -0
  76. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000030.html +18 -0
  77. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000031.html +18 -0
  78. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000032.html +18 -0
  79. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000033.html +18 -0
  80. data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000035.html +18 -0
  81. data/vendor/escape/rdoc/classes/TestEscapeHTML.html +182 -0
  82. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000008.html +18 -0
  83. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000009.html +18 -0
  84. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000010.html +18 -0
  85. data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000011.html +18 -0
  86. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.html +182 -0
  87. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000012.html +18 -0
  88. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000013.html +19 -0
  89. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000014.html +20 -0
  90. data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000015.html +22 -0
  91. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.html +167 -0
  92. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000016.html +18 -0
  93. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000017.html +20 -0
  94. data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000018.html +20 -0
  95. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.html +167 -0
  96. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000019.html +20 -0
  97. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000020.html +24 -0
  98. data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000021.html +22 -0
  99. data/vendor/escape/rdoc/files/escape_rb.html +136 -0
  100. data/vendor/escape/rdoc/files/install_rb.html +250 -0
  101. data/vendor/escape/rdoc/files/install_rb.src/M000001.html +23 -0
  102. data/vendor/escape/rdoc/files/install_rb.src/M000002.html +31 -0
  103. data/vendor/escape/rdoc/files/install_rb.src/M000003.html +27 -0
  104. data/vendor/escape/rdoc/files/install_rb.src/M000004.html +27 -0
  105. data/vendor/escape/rdoc/files/install_rb.src/M000005.html +21 -0
  106. data/vendor/escape/rdoc/files/install_rb.src/M000006.html +23 -0
  107. data/vendor/escape/rdoc/files/install_rb.src/M000007.html +21 -0
  108. data/vendor/escape/rdoc/files/test/test-escape_rb.html +109 -0
  109. data/vendor/escape/rdoc/fr_class_index.html +36 -0
  110. data/vendor/escape/rdoc/fr_file_index.html +29 -0
  111. data/vendor/escape/rdoc/fr_method_index.html +61 -0
  112. data/vendor/escape/rdoc/index.html +24 -0
  113. data/vendor/escape/rdoc/rdoc-style.css +208 -0
  114. data/vendor/escape/test/test-escape.rb +90 -0
  115. metadata +259 -0
@@ -0,0 +1,30 @@
1
+ require 'open-uri'
2
+ require 'hpricot'
3
+
4
+ class BeijingTally < CampfireBot::Plugin
5
+
6
+ on_command 'tally', :tally
7
+
8
+ def tally(msg)
9
+ output = "#{'Pos.'.rjust(6)} - #{'Country'.ljust(25)} - G - S - B - Total\n"
10
+ rows = ((Hpricot(open('http://results.beijing2008.cn/WRM/ENG/INF/GL/95A/GL0000000.shtml'))/'//table')[1]/'tr')[2..-1]
11
+ rows.each_with_index do |row, i|
12
+ cells = row/'td'
13
+ output += "#{strip_tags_or_zero(cells[0].inner_html).rjust(6)} - " # position
14
+ output += "#{((i == rows.length - 1) ? '' : strip_tags_or_zero(cells[1].inner_html)).ljust(25)} - " # country
15
+ output += "#{strip_tags_or_zero(cells[-5].inner_html).rjust(3)} - " # gold
16
+ output += "#{strip_tags_or_zero(cells[-4].inner_html).rjust(3)} - " # silver
17
+ output += "#{strip_tags_or_zero(cells[-3].inner_html).rjust(3)} - " # bronze
18
+ output += "#{strip_tags_or_zero(cells[-2].inner_html).rjust(3)}\n" # total
19
+ end
20
+
21
+ msg.paste(output)
22
+ end
23
+
24
+ private
25
+
26
+ # Take away the HTML tags from the string and insert a '0' if it is empty
27
+ def strip_tags_or_zero(str)
28
+ (out = str.gsub(/<\/?[^>]*>/, "").strip).blank? ? '0' : out
29
+ end
30
+ end
@@ -0,0 +1,127 @@
1
+ # TODO
2
+ #
3
+ # - convert/filter out HTML entities like &quot;
4
+ # - add a callback to prime the chains when the bot is ready, and do this in a thread.
5
+
6
+ class Boop < CampfireBot::Plugin
7
+
8
+ # Markov chain implementation courtesy of http://blog.segment7.net/articles/2006/02/25/markov-chain
9
+
10
+ on_message /.*/, :listen
11
+ on_command 'speak', :random_chatter
12
+ on_command 'prime_chains', :load_transcripts
13
+
14
+ def initialize
15
+ @phrases = Hash.new { |hash, key| hash[key] = [] } # phrase => next-word possibilities
16
+ @word_count = 0
17
+ end
18
+
19
+ def listen(msg)
20
+ add_line(msg[:message])
21
+ end
22
+
23
+ def random_chatter(msg)
24
+ puts "random_chatter"
25
+ msg.speak(generate_line)
26
+ end
27
+
28
+ def focused_chatter(msg)
29
+
30
+ end
31
+
32
+ def load_transcripts(msg)
33
+ msg.speak("Filling my brain with transcripts...")
34
+
35
+ puts "available transcripts - #{bot.room.available_transcripts.to_yaml}"
36
+
37
+ bot.room.available_transcripts.to_a.each do |date|
38
+ puts "loading transcript #{date}"
39
+
40
+ transcript = bot.room.transcript(date)
41
+
42
+ transcript.each do |message|
43
+ puts "message: #{message[:message]}"
44
+
45
+ filtered_text = strip_message(message)
46
+
47
+ filtered_text.split("\n").each { |line| add_line(line) unless line.blank? }
48
+ filtered_text.split("\n").each { |line| puts "ACCEPTED: " + line unless line.blank? }
49
+
50
+ end
51
+ end
52
+
53
+ msg.speak("Primed!")
54
+ end
55
+
56
+ private
57
+
58
+ def add_line(line)
59
+ words = line.scan(/\S+/)
60
+ @word_count += words.length
61
+
62
+ words.each_with_index do |word, index|
63
+ phrase = words[index, phrase_length] # current phrase
64
+ @phrases[phrase] << words[index + phrase_length] # next possibility
65
+ end
66
+ end
67
+
68
+ def generate_line
69
+ # our seed phrase
70
+ # phrase = words[0, phrase_length]
71
+ phrase = [random_word]
72
+
73
+ output = []
74
+
75
+ @word_count.times do
76
+ # grab all possibilities for our state
77
+ options = @phrases[phrase]
78
+
79
+ # add the first word to our output and discard
80
+ output << phrase.shift
81
+
82
+ # select at random and add it to our phrase
83
+ phrase.push(options.rand)
84
+
85
+ # the last phrase of the input text will map to an empty array of
86
+ # possibilities so exit cleanly.
87
+ break if phrase.compact.empty? # all out of words
88
+ end
89
+
90
+ # return our output
91
+ output.join(' ')
92
+ end
93
+
94
+ def random_word
95
+ @phrases.keys.rand.first
96
+ end
97
+
98
+ # amount of state (order-k)
99
+ def phrase_length
100
+ 1
101
+ end
102
+
103
+ def strip_message(msg)
104
+ str = msg[:message].to_s
105
+
106
+ return '' if str.blank?
107
+
108
+ # return nothing if the line is a bot command
109
+ return '' if str[0..0] == '!' || str =~ Regexp.new("^#{bot.config['nickname']},", Regexp::IGNORECASE)
110
+
111
+ # and get rid of the messages that are generated by the campfire system itself
112
+ return '' if str =~ /has (entered|left) the room/
113
+
114
+ # keep the contents of the pastes, but strip out the 'view paste' link.
115
+ str.gsub!(/<a href=.*?>View paste<\/a>/, '')
116
+
117
+ # also get rid of the stuff spoken by the bot
118
+ return '' if msg[:person] == bot.config['nickname']
119
+
120
+ # now strip out all image tags completely
121
+ str.gsub!(/<img\s.*?\/>/, '')
122
+
123
+ # and now strip out all other html tags, leaving their contents intact
124
+ str.gsub(/<\/?[^>]*>/, "")
125
+ end
126
+
127
+ end
@@ -0,0 +1,15 @@
1
+ require 'open-uri'
2
+ require 'hpricot'
3
+ require 'tempfile'
4
+
5
+ class Bruce < CampfireBot::Plugin
6
+ on_command 'bruce', :fail
7
+
8
+ def fail(msg)
9
+ # Scrape random fail
10
+ bruce = (Hpricot(open('http://www.schneierfacts.com/'))/'p.fact').first
11
+ msg.speak(CGI.unescapeHTML(bruce.inner_html))
12
+ rescue => e
13
+ msg.speak e
14
+ end
15
+ end
@@ -0,0 +1,198 @@
1
+ require 'yaml'
2
+
3
+ # Lookup bug titles and URLs when their number is mentioned or on command.
4
+ #
5
+ # You'll probably want to at least configure bugzilla_bug_url with the
6
+ # URL to your bug tracking tool. Put a '%s' where the bug ID should be
7
+ # inserted.
8
+ #
9
+ # Several other options are available, including the interval in which
10
+ # to avoid volunteering the same information, and whether to show the
11
+ # url with the title.
12
+ #
13
+ # HTMLEntities will be used for better entity (&ndash;) decoding if
14
+ # present, but is not required.
15
+ #
16
+ # Similarly, net/netrc will be used to supply HTTP Basic Auth
17
+ # credentials, but only if it's available.
18
+ #
19
+ # While is designed to work with Bugzilla, it also works fine with:
20
+ # * Debian bug tracking system
21
+ # * KDE Bug tracking system
22
+ # * Trac - if you configure to recognize "tickets" instead of "bugs"
23
+ # * Redmine - if you configure to recognize "issues" instead of "bugs"
24
+ class Bugzilla < CampfireBot::Plugin
25
+ on_command 'bug', :describe_command
26
+ # on_message registered below...
27
+
28
+ config_var :data_file, File.join(BOT_ROOT, 'tmp', 'bugzilla.yml')
29
+ config_var :min_period, 30.minutes
30
+ config_var :debug_enabled, false
31
+ config_var :bug_url, "https://bugzilla/show_bug.cgi?id=%s"
32
+ config_var :link_enabled, true
33
+ config_var :bug_id_pattern, '(?:[0-9]{3,6})'
34
+ config_var :bug_word_pattern, 'bugs?:?\s+'
35
+ config_var :mention_pattern,
36
+ '%2$s%1$s(?:(?:,\s*|,?\sand\s|,?\sor\s|\s+)%1$s)*'
37
+
38
+ attr_reader :bug_timestamps, :bug_id_regexp, :mention_regexp,
39
+ :use_htmlentities, :use_netrc
40
+
41
+ def initialize()
42
+ super
43
+ @bug_id_regexp = Regexp.new(bug_id_pattern, Regexp::IGNORECASE)
44
+ @mention_regexp = Regexp.new(sprintf(mention_pattern,
45
+ bug_id_pattern, bug_word_pattern),
46
+ Regexp::IGNORECASE)
47
+ self.class.on_message mention_regexp, :describe_mention
48
+
49
+ @bug_timestamps = YAML::load(File.read(@data_file)) rescue {}
50
+ if link_enabled
51
+ require 'shorturl'
52
+ end
53
+
54
+ # Put this in the constructor so we don't fail to find htmlentities
55
+ # every time we fetch a bug title.
56
+ begin
57
+ require 'htmlentities'
58
+ @use_htmlentities = true
59
+ rescue LoadError
60
+ debug "Falling back to 'cgi', install 'htmlentities' better unescaping"
61
+ require 'cgi'
62
+ end
63
+ begin
64
+ require 'net/netrc'
65
+ @use_netrc = true
66
+ rescue LoadError
67
+ debug "Can't load 'net/netrc': HTTP Auth from .netrc will be unavailable"
68
+ require 'cgi'
69
+ end
70
+ end
71
+
72
+ def debug(spew)
73
+ $stderr.puts "#{self.class.name}: #{spew}" if debug_enabled
74
+ end
75
+
76
+ def describe_mention(msg)
77
+ debug "heard a mention"
78
+ match = msg[:message].match(mention_regexp)
79
+ describe_bugs msg, match.to_s, true
80
+ end
81
+
82
+ def describe_command(msg)
83
+ debug "received a command"
84
+ debug "msg[:message] = #{msg[:message].inspect}"
85
+ describe_bugs msg, msg[:message], false
86
+ end
87
+
88
+ protected
89
+
90
+ def describe_bugs(msg, text, check_timestamp)
91
+ summaries = text.to_s.scan(bug_id_regexp).collect { |bug|
92
+ debug "mentioned bug #{bug}"
93
+ now = Time.new
94
+ last_spoke = (bug_timestamps[msg[:room].name] ||= {})[bug]
95
+ if check_timestamp && !last_spoke.nil? && last_spoke > now - min_period
96
+ debug "keeping quiet, last spoke at #{last_spoke}"
97
+ nil
98
+ else
99
+ debug "fetching title for #{bug}"
100
+ url = sprintf(bug_url, bug)
101
+ html = http_fetch_body(url)
102
+ if !m = html.match("<title>([^<]+)</title>")
103
+ raise "no title for bug #{bug}!"
104
+ end
105
+ debug "fetched."
106
+ title = html_decode(m[1])
107
+ title += " (#{ShortURL.shorten(url)})" if link_enabled
108
+ bug_timestamps[msg[:room].name][bug] = now
109
+ title
110
+ end
111
+ }.reject { |s| s.nil? }
112
+ if !summaries.empty?
113
+ expire_timestamps
114
+ n = bug_timestamps.inject(0) { |sum, pair| sum + pair[1].size }
115
+ debug "Writing #{n} timestamps"
116
+ File.open(data_file, 'w') do |out|
117
+ YAML.dump(bug_timestamps, out)
118
+ end
119
+ # Speak the summaries all at once so they're more readable and
120
+ # not interleaved with someone else's speach
121
+ summaries.each { |s|
122
+ debug "sending response: #{s}"
123
+ msg.speak s
124
+ }
125
+ else
126
+ debug "nothing to say."
127
+ end
128
+ end
129
+
130
+ # Don't let the datafile or the in-memory list grow too
131
+ # large over long periods of time. Remove entries that are
132
+ # well over min_period.
133
+ def expire_timestamps
134
+ debug "Expiring bug timestamps"
135
+ cutoff = Time.new - (2 * min_period)
136
+ recent = {}
137
+ bug_timestamps.each { |room, hash|
138
+ recent[room] = {}
139
+ hash.each { |bug, ts|
140
+ recent[room][bug] = ts if ts > cutoff
141
+ }
142
+ recent.delete(room) if recent[room].empty?
143
+ }
144
+ @bug_timestamps = recent
145
+ end
146
+
147
+ # Returns the non-HTML version of the given string using
148
+ # htmlentities if available, or else unescapeHTML
149
+ def html_decode(html)
150
+ if use_htmlentities
151
+ HTMLEntities.new.decode(html)
152
+ else
153
+ CGI.unescapeHTML(html)
154
+ end
155
+ end
156
+
157
+ # Return the HTTPResponse
158
+ #
159
+ # Use SSL if necessary, and check .netrc for
160
+ # passwords.
161
+ def http_fetch(url)
162
+ uri = URI.parse url
163
+ http = Net::HTTP.new(uri.host, uri.port)
164
+
165
+ # Unfortunately the net/http(s) API can't seem to do this for us,
166
+ # even if we require net/https from the beginning (ruby 1.8)
167
+ if uri.scheme == "https"
168
+ require 'net/https'
169
+ http.use_ssl = true
170
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
171
+ end
172
+
173
+ res = http.start { |http|
174
+ req = Net::HTTP::Get.new uri.request_uri
175
+ cred = netrc_credentials uri.host
176
+ req.basic_auth *cred if cred
177
+ http.request req
178
+ }
179
+ end
180
+
181
+ # Returns only the document body
182
+ def http_fetch_body(url)
183
+ res = http_fetch(url)
184
+ case res
185
+ when Net::HTTPSuccess
186
+ res.body
187
+ else res.error!
188
+ end
189
+ end
190
+
191
+ # Returns [username, password] for the given host or nil
192
+ def netrc_credentials(host)
193
+ # Don't crash just b/c the gem is not installed
194
+ return nil if !use_netrc
195
+ obj = Net::Netrc.locate(host)
196
+ obj ? [obj.login, obj.password] : nil
197
+ end
198
+ end
@@ -0,0 +1,43 @@
1
+ require 'open-uri'
2
+ require 'hpricot'
3
+ require 'tempfile'
4
+
5
+ class Calvin < CampfireBot::Plugin
6
+ BASE_URL = 'http://marcel-oehler.marcellosendos.ch/comics/ch/'
7
+ START_DATE = Date.parse('1985-11-18')
8
+ END_DATE = Date.parse('1995-12-31') # A sad day
9
+
10
+ on_command 'calvin', :calvin
11
+
12
+ def calvin(msg)
13
+ comic = case msg[:message].split(/\s+/)[0]
14
+ when 'random'
15
+ fetch_random
16
+ when /d+/
17
+ fetch_comic(msg[:message].split(/\s+/)[0])
18
+ else
19
+ fetch_random
20
+ end
21
+
22
+ msg.speak(comic)
23
+ end
24
+
25
+ private
26
+
27
+ def fetch_random
28
+ fetch_comic(rand(number_of_comics))
29
+ end
30
+
31
+ def fetch_comic(id = nil)
32
+ date = id_to_date(id)
33
+ "#{BASE_URL}#{date.strftime('%Y')}/#{date.strftime('%m')}/#{date.strftime('%Y%m%d')}.gif"
34
+ end
35
+
36
+ def id_to_date(id)
37
+ (START_DATE + id.days).to_date
38
+ end
39
+
40
+ def number_of_comics
41
+ (END_DATE - START_DATE).to_i
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ require 'open-uri'
2
+ require 'hpricot'
3
+
4
+ class Chuck < CampfireBot::Plugin
5
+ on_command 'chuck', :chuck
6
+
7
+ def initialize
8
+ @log = Logging.logger["CampfireBot::Plugin::Chuck"]
9
+ end
10
+
11
+ def chuck(msg)
12
+ url = "http://www.chucknorrisfacts.com/all-chuck-norris-facts?page=#{rand(172)+1}"
13
+ doc = Hpricot(open(url))
14
+
15
+ facts = []
16
+
17
+ (doc/".item-list a.createYourOwn").each do |a_tag|
18
+ facts << CGI.unescapeHTML(a_tag.inner_html)
19
+ end
20
+
21
+ msg.speak(facts[rand(facts.size)])
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ require 'open-uri'
2
+ require 'hpricot'
3
+ require 'tempfile'
4
+
5
+
6
+ class Dilbert < CampfireBot::Plugin
7
+ BASE_URL = 'http://dilbert.com/'
8
+ START_DATE = Date.parse('1996-01-01')
9
+
10
+ on_command 'dilbert', :dilbert
11
+
12
+ def dilbert(msg)
13
+ comic = case msg[:message].split(/\s+/)[0]
14
+ when 'latest'
15
+ fetch_latest
16
+ when 'random'
17
+ fetch_random
18
+ when /d+/
19
+ fetch_comic(msg[:message].split(/\s+/)[1])
20
+ else
21
+ fetch_random
22
+ end
23
+
24
+ msg.speak(BASE_URL + comic['src'])
25
+
26
+ end
27
+
28
+ private
29
+
30
+ def fetch_latest
31
+ fetch_comic
32
+ end
33
+
34
+ def fetch_random
35
+ fetch_comic(rand(number_of_comics))
36
+ end
37
+
38
+ def fetch_comic(id = nil)
39
+ # Rely on the comic being the last image on the page not nested
40
+ (Hpricot(open("#{BASE_URL}fast#{'/' + id_to_date(id) + '/' if id}"))/'//img').last
41
+ end
42
+
43
+ def id_to_date(id)
44
+ (START_DATE + id.days).to_date.to_s(:db)
45
+ end
46
+
47
+ def number_of_comics
48
+ (Date.today - START_DATE).to_i
49
+ end
50
+
51
+ end