campfire-bot 0.0.1

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 (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