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.
- data/.autotest +11 -0
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +57 -0
- data/README.textile +52 -0
- data/TODO +72 -0
- data/bin/bot +13 -0
- data/campfire-bot.gemspec +27 -0
- data/cfbot-stop.sh +8 -0
- data/config.example.yml +31 -0
- data/lib/bot.rb +194 -0
- data/lib/event.rb +114 -0
- data/lib/message.rb +30 -0
- data/lib/plugin.rb +77 -0
- data/lib/version.rb +3 -0
- data/plugins/accountability.rb +45 -0
- data/plugins/austin.rb +29 -0
- data/plugins/basecamp.rb +48 -0
- data/plugins/beer.rb +214 -0
- data/plugins/beijing_tally.rb +30 -0
- data/plugins/boop.rb +127 -0
- data/plugins/bruce.rb +15 -0
- data/plugins/bugzilla.rb +198 -0
- data/plugins/calvin.rb +43 -0
- data/plugins/chuck.rb +23 -0
- data/plugins/dilbert.rb +51 -0
- data/plugins/excuse.rb +478 -0
- data/plugins/fail.rb +16 -0
- data/plugins/figlet.rb +10 -0
- data/plugins/fun.rb +95 -0
- data/plugins/garfield.rb +43 -0
- data/plugins/generic_search.rb +66 -0
- data/plugins/help.rb +13 -0
- data/plugins/infobot.rb +58 -0
- data/plugins/insult.rb +87 -0
- data/plugins/jira.rb +197 -0
- data/plugins/lolcats.rb +17 -0
- data/plugins/our_quotes.rb +195 -0
- data/plugins/quote.rb +31 -0
- data/plugins/schneier.rb +28 -0
- data/plugins/seen.rb +88 -0
- data/plugins/signal_filter.rb +9 -0
- data/plugins/svn.rb +167 -0
- data/plugins/twitter_echo.rb +54 -0
- data/plugins/unfuddle.rb +69 -0
- data/plugins/weather.rb +25 -0
- data/plugins/xkcd.rb +43 -0
- data/spec/beer_spec.rb +224 -0
- data/spec/bugzilla_spec.rb +90 -0
- data/spec/command_spec.rb +96 -0
- data/spec/jira_spec.rb +264 -0
- data/spec/our_quotes_spec.rb +186 -0
- data/spec/plugin_spec.rb +43 -0
- data/spec/spec.opts +1 -0
- data/vendor/escape/ChangeLog +30 -0
- data/vendor/escape/Makefile +5 -0
- data/vendor/escape/README +81 -0
- data/vendor/escape/VERSION +1 -0
- data/vendor/escape/escape.rb +302 -0
- data/vendor/escape/install.rb +109 -0
- data/vendor/escape/misc/README.erb +85 -0
- data/vendor/escape/rdoc/classes/Escape.html +427 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000022.html +19 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000023.html +32 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000024.html +24 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000025.html +19 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000026.html +48 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000027.html +19 -0
- data/vendor/escape/rdoc/classes/Escape.src/M000028.html +19 -0
- data/vendor/escape/rdoc/classes/Escape/HTMLAttrValue.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/HTMLEscaped.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/PercentEncoded.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/ShellEscaped.html +113 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.html +242 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000029.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000030.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000031.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000032.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000033.html +18 -0
- data/vendor/escape/rdoc/classes/Escape/StringWrapper.src/M000035.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.html +182 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000008.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000009.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000010.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeHTML.src/M000011.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.html +182 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000012.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000013.html +19 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000014.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapePercentEncoded.src/M000015.html +22 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.html +167 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000016.html +18 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000017.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapeShellEscaped.src/M000018.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.html +167 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000019.html +20 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000020.html +24 -0
- data/vendor/escape/rdoc/classes/TestEscapeStringWrapper.src/M000021.html +22 -0
- data/vendor/escape/rdoc/files/escape_rb.html +136 -0
- data/vendor/escape/rdoc/files/install_rb.html +250 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000001.html +23 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000002.html +31 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000003.html +27 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000004.html +27 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000005.html +21 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000006.html +23 -0
- data/vendor/escape/rdoc/files/install_rb.src/M000007.html +21 -0
- data/vendor/escape/rdoc/files/test/test-escape_rb.html +109 -0
- data/vendor/escape/rdoc/fr_class_index.html +36 -0
- data/vendor/escape/rdoc/fr_file_index.html +29 -0
- data/vendor/escape/rdoc/fr_method_index.html +61 -0
- data/vendor/escape/rdoc/index.html +24 -0
- data/vendor/escape/rdoc/rdoc-style.css +208 -0
- data/vendor/escape/test/test-escape.rb +90 -0
- 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
|
data/plugins/boop.rb
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
#
|
|
3
|
+
# - convert/filter out HTML entities like "
|
|
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
|
data/plugins/bruce.rb
ADDED
|
@@ -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
|
data/plugins/bugzilla.rb
ADDED
|
@@ -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 (–) 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
|
data/plugins/calvin.rb
ADDED
|
@@ -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
|
data/plugins/chuck.rb
ADDED
|
@@ -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
|
data/plugins/dilbert.rb
ADDED
|
@@ -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
|