campfire-bot 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/plugins/fail.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class Fail < CampfireBot::Plugin
|
6
|
+
on_command 'fail', :fail
|
7
|
+
|
8
|
+
def fail(msg)
|
9
|
+
# Scrape random fail
|
10
|
+
fail = (Hpricot(open('http://failblog.org/?random#top'))/'div.entry img').first
|
11
|
+
|
12
|
+
msg.speak(fail['src'])
|
13
|
+
rescue => e
|
14
|
+
msg.speak e
|
15
|
+
end
|
16
|
+
end
|
data/plugins/figlet.rb
ADDED
data/plugins/fun.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
class Fun < CampfireBot::Plugin
|
2
|
+
on_command 'say', :say
|
3
|
+
on_message Regexp.new("^#{bot.config['nickname']},\\s+(should|can|will|shall) (i|he|she|we|they) do it\\?", Regexp::IGNORECASE), :do_or_do_not
|
4
|
+
on_message Regexp.new("^(good morning|morning|m0ink|hello|hi|hey|whassup|what's up|yo|hola|ola|'sup|sup)(,)*\\s*(#{bot.config['nickname']}).*$", Regexp::IGNORECASE), :greet
|
5
|
+
on_message /(how's it|how are|how're) (ya |you )*(going|doing|doin).*/, :howareya
|
6
|
+
on_command "blame", :blame
|
7
|
+
on_command "trout", :trout
|
8
|
+
on_command "slap", :trout
|
9
|
+
on_command "troutslap", :trout
|
10
|
+
# on_speaker 'Tim R.', :agree_with_tim
|
11
|
+
# on_message /undo it/i, :do_it
|
12
|
+
# on_message /(^|\s)do it/i, :undo_it
|
13
|
+
# at_time 1.minute.from_now, :do_it
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@last_agreed = 20.minutes.ago
|
17
|
+
@log = Logging.logger["CampfireBot::Plugin::Fun"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def say(m)
|
21
|
+
m.speak(m[:message])
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_it(m = nil)
|
25
|
+
m.speak('Do it!')
|
26
|
+
end
|
27
|
+
|
28
|
+
def undo_it(m)
|
29
|
+
m.speak('Undo it!')
|
30
|
+
end
|
31
|
+
|
32
|
+
def do_or_do_not(m)
|
33
|
+
responses = ['Do it!', 'Don\'t do it!', 'Undo it!']
|
34
|
+
m.speak(responses.choice)
|
35
|
+
end
|
36
|
+
|
37
|
+
def agree_with_tim(m)
|
38
|
+
m.speak('I agree with Tim.') unless @last_agreed > 15.minutes.ago
|
39
|
+
@last_agreed = Time.now
|
40
|
+
end
|
41
|
+
|
42
|
+
def greet(m)
|
43
|
+
messages = ['Howdy', 'Wassup', 'Greets', 'Hello', 'Hey there', "It's a", 'Good day']
|
44
|
+
m.speak("#{messages.choice} #{m[:person].split(' ')[0]}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def howareya(m)
|
48
|
+
messages = ["just great", "peachy", "mas o menos",
|
49
|
+
"you know how it is", "eh, ok", "pretty good. how about you?"]
|
50
|
+
m.speak(messages[rand(messages.size)])
|
51
|
+
end
|
52
|
+
|
53
|
+
def blame(m)
|
54
|
+
# TODO: capture user-submitted entries to a yaml file and regurgitate them
|
55
|
+
# TODO: put all the default ones in a separate yaml
|
56
|
+
if m[:message].strip.length > 0
|
57
|
+
blamed = m[:message].strip
|
58
|
+
else
|
59
|
+
users = m[:room].users.delete_if {|u| u[:name] == bot.campfire.me[:name]}.map {|u| u[:name]}
|
60
|
+
others = ["nobody", "my", "Microsoft", "Steve Jobs", "the terrorists", "your",
|
61
|
+
"Project Management", "Development", "Management", "Corporate", "Cartman", "the user",
|
62
|
+
"the liberal media", "Wall Street"]
|
63
|
+
|
64
|
+
# mostly blame the other users
|
65
|
+
if rand(10) >= 2
|
66
|
+
blamed = users.choice
|
67
|
+
else
|
68
|
+
blamed = others.choice
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
case blamed
|
74
|
+
when "nobody"
|
75
|
+
blamestring = "It's nobody's fault"
|
76
|
+
when "your", "my"
|
77
|
+
blamestring = "It's all #{blamed} fault"
|
78
|
+
else
|
79
|
+
blamestring = "It's all #{blamed}'s fault"
|
80
|
+
blamestring = "It's all #{blamed}' fault" if blamed[-1].chr == "s"
|
81
|
+
end
|
82
|
+
|
83
|
+
m.speak blamestring
|
84
|
+
end
|
85
|
+
|
86
|
+
def trout(m)
|
87
|
+
if m[:message].strip.length > 0
|
88
|
+
selected_user_name = m[:message].strip
|
89
|
+
else
|
90
|
+
users = m[:room].users.map{|u| u[:name] }
|
91
|
+
selected_user_name = users.choice
|
92
|
+
end
|
93
|
+
m.speak("#{m[:person]} slaps #{selected_user_name} #{["upside the head", "in the face", "on the rear", "where it counts", "in the knees", "ineffectually", "in the elbow", "on the funny bone", "in the ear", "on the nose", "in the teeth"].choice} with a #{%w(good-sized large decaying moldy spiked sabre-toothed surprised disappointed dramatic enraged rabid bug-eyed rotten foul-smelling demonic cluestick-holding).choice} trout")
|
94
|
+
end
|
95
|
+
end
|
data/plugins/garfield.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class Garfield < CampfireBot::Plugin
|
6
|
+
BASE_URL = 'http://images.ucomics.com/comics/ga'
|
7
|
+
START_DATE = Date.parse('1978-06-19')
|
8
|
+
END_DATE = Date.today
|
9
|
+
|
10
|
+
on_command 'garfield', :garfield
|
11
|
+
|
12
|
+
def garfield(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')}/ga#{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,66 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Search sites like Google and Wikipedia
|
6
|
+
#
|
7
|
+
# You configure the command name and URL pattern. Given a search
|
8
|
+
# term, it attempts to respond with the URL of the first search
|
9
|
+
# result. It does so by simply inserting the term into the URL at the
|
10
|
+
# '%s'. If the url redirects, it responds with the redirect target
|
11
|
+
# instead, so you get a hint about what you'll see. Otherwise, you
|
12
|
+
# get just the expanded url pattern.
|
13
|
+
#
|
14
|
+
# This is useful for a wide range of sites. Sample config:
|
15
|
+
#
|
16
|
+
# generic_search_commands:
|
17
|
+
# wikipedia: "http://en.wikipedia.org/wiki/Special:Search?search=%s&go=Go"
|
18
|
+
# google: "http://www.google.com/search?hl=en&q=%s&btnI=I'm+Feeling+Lucky&aq=f&oq="
|
19
|
+
# php: "http://us3.php.net/manual-lookup.php?pattern=%s&lang=en"
|
20
|
+
# letmegooglethatforyou: "http://letmegooglethatforyou.com/?q=%s"
|
21
|
+
#
|
22
|
+
# Note that the last site never redirects, which is fine.
|
23
|
+
#
|
24
|
+
class GenericSearch < CampfireBot::Plugin
|
25
|
+
attr_reader :commands
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@commands = bot.config["generic_search_commands"] || {}
|
29
|
+
commands.each { |c, s|
|
30
|
+
method = "do_#{c}_command".to_sym
|
31
|
+
self.class.send(:define_method, method) {|msg|
|
32
|
+
msg.speak(http_peek(sprintf(s, CGI.escape(msg[:message]))))
|
33
|
+
}
|
34
|
+
self.class.on_command(c, method)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
# Follow the url just enough to see if it redirects. If so,
|
41
|
+
# return the redirect. Otherwise, return the original.
|
42
|
+
def http_peek(url)
|
43
|
+
uri = URI.parse url
|
44
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
45
|
+
|
46
|
+
# Unfortunately the net/http(s) API can't seem to do this for us,
|
47
|
+
# even if we require net/https from the beginning (ruby 1.8)
|
48
|
+
if uri.scheme == "https"
|
49
|
+
require 'net/https'
|
50
|
+
http.use_ssl = true
|
51
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
52
|
+
end
|
53
|
+
|
54
|
+
res = http.start { |http|
|
55
|
+
req = Net::HTTP::Get.new uri.request_uri
|
56
|
+
#req.basic_auth u, p
|
57
|
+
response = http.request req
|
58
|
+
}
|
59
|
+
case res
|
60
|
+
when Net::HTTPRedirection
|
61
|
+
uri.merge res.header['Location']
|
62
|
+
else # Net::HTTPSuccess or error
|
63
|
+
url
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/plugins/help.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Rudimentary help system. Worth exploring further, though I am not sure how much access to the
|
2
|
+
# rest of the sytem plugins should be allowed. Should they only be allowed to operate in their own
|
3
|
+
# sandbox, or reach into the list of registered plugins like this one does?
|
4
|
+
|
5
|
+
class Help < CampfireBot::Plugin
|
6
|
+
on_command 'help', :help
|
7
|
+
|
8
|
+
def help(msg)
|
9
|
+
commands = CampfireBot::Plugin.registered_commands.map { |command| command.matcher.to_s + " " }
|
10
|
+
msg.speak("To address me, type \"#{bot.config['nickname']},\" and a command, or just !command. \n
|
11
|
+
Available commands: #{commands}")
|
12
|
+
end
|
13
|
+
end
|
data/plugins/infobot.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class Infobot < CampfireBot::Plugin
|
4
|
+
|
5
|
+
Infobot::DEFINE_REGEXP = /(no, )*(.+) is ([^\?]+)(?!\?)$/
|
6
|
+
Infobot::RESPOND_REGEXP = /(what's|what is|who is|who's|where|where's|how's|how is) ([^\?]+)(?=\?)*/
|
7
|
+
|
8
|
+
# if BOT_ENVIRONMENT == 'development'
|
9
|
+
on_message Regexp.new("^#{bot.config['nickname']},\\s+#{RESPOND_REGEXP.source}", Regexp::IGNORECASE), :respond
|
10
|
+
on_message Regexp.new("^#{bot.config['nickname']},\\s+#{DEFINE_REGEXP.source}", Regexp::IGNORECASE), :define
|
11
|
+
on_command 'reload_facts', :reload
|
12
|
+
# end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
# puts "entering initialize()"
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def respond(msg)
|
20
|
+
# puts "entering respond()"
|
21
|
+
@facts = init()
|
22
|
+
puts msg[:message]
|
23
|
+
puts msg[:message] =~ RESPOND_REGEXP # Regexp.new("^#{Bot.instance.config['nickname']},\\s+#{RESPOND_REGEXP.source}", Regexp::IGNORECASE)
|
24
|
+
puts $1, $2, $3
|
25
|
+
if !@facts.has_key?($2.downcase)
|
26
|
+
msg.speak("Sorry, I don't know what #{$2} is.")
|
27
|
+
else
|
28
|
+
fact = @facts[$2.downcase]
|
29
|
+
msg.speak("#{msg[:person].split(" ")[0]}, #{$2} is #{fact}.")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def define(msg)
|
34
|
+
# puts 'entering define()'
|
35
|
+
@facts = init()
|
36
|
+
# puts @facts
|
37
|
+
# puts msg[:message]
|
38
|
+
puts msg[:message] =~ Regexp.new("^#{bot.config['nickname']},\\s+#{DEFINE_REGEXP.source}", Regexp::IGNORECASE)
|
39
|
+
# puts @define_regexp
|
40
|
+
# puts $1, $2, $3
|
41
|
+
@facts[$2.downcase] = $3
|
42
|
+
msg.speak("Okay, #{$2} is now #{$3}")
|
43
|
+
File.open(File.join(File.dirname(__FILE__), 'infobot.yml'), 'w') do |out|
|
44
|
+
YAML.dump(@facts, out)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def init
|
49
|
+
# puts "entering init()"
|
50
|
+
YAML::load(File.read(File.join(BOT_ROOT, 'tmp', 'infobot.yml')))
|
51
|
+
end
|
52
|
+
|
53
|
+
def reload(msg)
|
54
|
+
@facts = init()
|
55
|
+
msg.speak("ok, reloaded #{@facts.size} facts")
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/plugins/insult.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
class Insult < CampfireBot::Plugin
|
2
|
+
|
3
|
+
on_command 'insult', :insult
|
4
|
+
|
5
|
+
def insult(msg)
|
6
|
+
adj1 = adjective()
|
7
|
+
adj2 = adjective()
|
8
|
+
amount = amount()
|
9
|
+
noun = noun()
|
10
|
+
article = "a"
|
11
|
+
article = "an" if (adj1[0] =~ /^[aeiou]/)
|
12
|
+
out = "#{msg[:message]}, you are nothing but a #{adj1} #{amount} of #{adj2} #{noun}"
|
13
|
+
|
14
|
+
|
15
|
+
msg.speak(out)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def adjective()
|
22
|
+
adjectives = %w(acidic antique contemptible culturally-unsound despicable evil fermented
|
23
|
+
festering foul fulminating humid impure inept inferior industrial
|
24
|
+
left-over low-quality malodorous off-color penguin-molesting
|
25
|
+
petrified pointy-nosed salty sausage-snorfling tastless tempestuous
|
26
|
+
tepid tofu-nibbling unintelligent unoriginal uninspiring weasel-smelling
|
27
|
+
wretched spam-sucking egg-sucking decayed halfbaked infected squishy
|
28
|
+
porous pickled coughed-up thick vapid hacked-up
|
29
|
+
unmuzzled bawdy vain lumpish churlish fobbing rank craven puking
|
30
|
+
jarring fly-bitten pox-marked fen-sucked spongy droning gleeking warped
|
31
|
+
currish milk-livered surly mammering ill-borne beef-witted tickle-brained
|
32
|
+
half-faced headless wayward rump-fed onion-eyed beslubbering villainous
|
33
|
+
lewd-minded cockered full-gorged rude-snouted crook-pated pribbling
|
34
|
+
dread-bolted fool-born puny fawning sheep-biting dankish goatish
|
35
|
+
weather-bitten knotty-pated malt-wormy saucyspleened motley-mind
|
36
|
+
it-fowling vassal-willed loggerheaded clapper-clawed frothy ruttish
|
37
|
+
clouted common-kissing pignutted folly-fallen plume-plucked flap-mouthed
|
38
|
+
swag-bellied dizzy-eyed gorbellied weedy reeky measled spur-galled mangled
|
39
|
+
impertinent bootless toad-spotted hasty-witted horn-beat yeasty
|
40
|
+
imp-bladdereddle-headed boil-brained tottering hedge-born hugger-muggered
|
41
|
+
elf-skinned
|
42
|
+
|
43
|
+
artless bawdy beslubbering bootless churlish cockered clouted craven currish dankish
|
44
|
+
dissembling droning errant fawning fobbing froward gleeking goatish gorbellied
|
45
|
+
impertinent infectious jarring loggerheaded lumpish mammering mangled mewling paunchy
|
46
|
+
pribbling puking puny quailing rank reeky roguish ruttish saucy spleeny spongy surly
|
47
|
+
tottering unmuzzled vain venomed villainous warped wayward weedy yeasty base-court
|
48
|
+
bat-fowling beef-witted beetle-headed boil-brained clapper-clawed clay-brained
|
49
|
+
common-kissing crook-pated dismal-dreaming dizzy-eyed doghearted dread-bolted
|
50
|
+
earth-vexing elf-skinned fat-kidneyed flap-mouthed fly-bitten folly-fallen fool-born
|
51
|
+
full-gorged guts-griping half-faced hasty-witted hedge-born hell-hated idle-headed
|
52
|
+
ill-breeding ill-nurtured knotty-pated milk-livered motley-minded onion-eyed
|
53
|
+
plume-plucked pottle-deep pox-marked reeling-ripe rough-hewn rude-growing rump-fed
|
54
|
+
shard-borne sheep-biting spur-galled swag-bellied tardy-gaited tickle-brained
|
55
|
+
toad-spotted urchin-snouted weather-bitten)
|
56
|
+
|
57
|
+
adjectives[rand(adjectives.size)]
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def amount()
|
62
|
+
amounts = %w(accumulation bucket coagulation enema-bucketful gob half-mouthful
|
63
|
+
heap mass mound petrification pile puddle stack thimbleful tongueful
|
64
|
+
ooze quart bag plate ass-full assload load)
|
65
|
+
|
66
|
+
amounts[rand(amounts.size)]
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
def noun()
|
71
|
+
nouns = %w(bat|toenails bug|spit cat|hair chicken|piss dog|vomit dung
|
72
|
+
fat-woman's|stomach-bile fish|heads guano gunk pond|scum rat|retch
|
73
|
+
red|dye|number-9 Sun|IPC|manuals waffle-house|grits yoo-hoo
|
74
|
+
dog|balls seagull|puke cat|bladders pus urine|samples
|
75
|
+
squirrel|guts snake|rectums snake|bait buzzard|gizzards
|
76
|
+
cat-hair-balls rat-farts pods armadillo|snouts entrails
|
77
|
+
snake|snot eel|ooze slurpee-backwash toxic|waste Stimpy-drool
|
78
|
+
poopy poop craptacular|carpet|droppings cold|sores warts)
|
79
|
+
|
80
|
+
nouns[rand(nouns.size)].gsub("|", " ")
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
data/plugins/jira.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'rexml/document'
|
5
|
+
##
|
6
|
+
# JIRA plugin
|
7
|
+
#
|
8
|
+
# checks JIRA for new issues and posts them to the room
|
9
|
+
#
|
10
|
+
# in your config.yml you can either specify a single URL or a list of URLs, e.g.
|
11
|
+
#
|
12
|
+
# jira_url: http://your_jira_url
|
13
|
+
# # OR
|
14
|
+
# jira_url:
|
15
|
+
# - http://your_jira_url
|
16
|
+
# - http://your_jira_url2
|
17
|
+
#
|
18
|
+
|
19
|
+
|
20
|
+
class Jira < CampfireBot::Plugin
|
21
|
+
|
22
|
+
at_interval 3.minutes, :check_jira
|
23
|
+
on_command 'checkjira', :checkjira_command
|
24
|
+
on_command 'jira', :checkjira_command
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
# log "initializing... "
|
28
|
+
@data_file = File.join(BOT_ROOT, 'tmp', "jira-#{BOT_ENVIRONMENT}.yml")
|
29
|
+
@cached_ids = YAML::load(File.read(@data_file)) || {}
|
30
|
+
@last_checked = @cached_ids[:last_checked] || 10.minutes.ago
|
31
|
+
@log = Logging.logger["CampfireBot::Plugin::Jira"]
|
32
|
+
end
|
33
|
+
|
34
|
+
# respond to checkjira command-- same as interval except we answer with 'no issues found' if there are no issues
|
35
|
+
def checkjira_command(msg)
|
36
|
+
begin
|
37
|
+
msg.speak "no new issues since I last checked #{@lastlast} ago" if !check_jira(msg)
|
38
|
+
rescue
|
39
|
+
msg.speak "sorry, we had trouble connecting to JIRA."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_jira(msg)
|
44
|
+
|
45
|
+
saw_an_issue = false
|
46
|
+
old_cache = Marshal::load(Marshal.dump(@cached_ids)) # since ruby doesn't have deep copy
|
47
|
+
|
48
|
+
|
49
|
+
@lastlast = time_ago_in_words(@last_checked)
|
50
|
+
@last_checked = Time.now
|
51
|
+
|
52
|
+
|
53
|
+
tix = fetch_jira_url
|
54
|
+
raise if tix.nil?
|
55
|
+
|
56
|
+
tix.each do |ticket|
|
57
|
+
if seen?(ticket, old_cache)
|
58
|
+
saw_an_issue = true
|
59
|
+
|
60
|
+
@cached_ids = update_cache(ticket, @cached_ids)
|
61
|
+
|
62
|
+
|
63
|
+
messagetext = "#{ticket[:type]} - #{ticket[:title]} - #{ticket[:link]} - reported by #{ticket[:reporter]} - #{ticket[:priority]}"
|
64
|
+
msg.speak(messagetext)
|
65
|
+
msg.play("vuvuzela") if ticket[:priority] == "Blocker"
|
66
|
+
@log.info messagetext
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
flush_cache(@cached_ids)
|
72
|
+
@log.info "no new issues." if !saw_an_issue
|
73
|
+
|
74
|
+
saw_an_issue
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
# fetch jira url and return a list of ticket Hashes
|
80
|
+
def fetch_jira_url()
|
81
|
+
|
82
|
+
jiraconfig = bot.config['jira_url']
|
83
|
+
|
84
|
+
if jiraconfig.is_a?(Array)
|
85
|
+
searchurls = jiraconfig
|
86
|
+
else
|
87
|
+
searchurls = [jiraconfig]
|
88
|
+
end
|
89
|
+
|
90
|
+
tix = []
|
91
|
+
|
92
|
+
searchurls.each do |searchurl|
|
93
|
+
begin
|
94
|
+
@log.info "checking jira for new issues... #{searchurl}"
|
95
|
+
xmldata = open(searchurl).read
|
96
|
+
doc = REXML::Document.new(xmldata)
|
97
|
+
raise Exception.new("response had no content") if doc.nil?
|
98
|
+
doc.elements.inject('rss/channel/item', tix) do |tix, element|
|
99
|
+
tix.push(parse_ticket_info(element))
|
100
|
+
end
|
101
|
+
rescue Exception => e
|
102
|
+
@log.error "error connecting to jira: #{e.message}"
|
103
|
+
# @log.error "#{e.backtrace}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return tix
|
107
|
+
end
|
108
|
+
|
109
|
+
# extract ticket hash from individual xml element
|
110
|
+
def parse_ticket_info(xml_element)
|
111
|
+
id = xml_element.elements['key'].text rescue ""
|
112
|
+
id, spacekey = split_spacekey_and_id(id) rescue ""
|
113
|
+
|
114
|
+
link = xml_element.elements['link'].text rescue ""
|
115
|
+
title = xml_element.elements['title'].text rescue ""
|
116
|
+
reporter = xml_element.elements['reporter'].text rescue ""
|
117
|
+
type = xml_element.elements['type'].text rescue ""
|
118
|
+
priority = xml_element.elements['priority'].text rescue ""
|
119
|
+
|
120
|
+
return {
|
121
|
+
:spacekey => spacekey,
|
122
|
+
:id => id,
|
123
|
+
:link => link,
|
124
|
+
:title => title,
|
125
|
+
:reporter => reporter,
|
126
|
+
:type => type,
|
127
|
+
:priority => priority
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
# extract the spacekey and id from the ticket id
|
132
|
+
def split_spacekey_and_id(key)
|
133
|
+
spacekey = key.scan(/^([A-Z]+)/).to_s
|
134
|
+
id = key.scan(/([0-9]+)$/)[0].to_s.to_i
|
135
|
+
return id, spacekey
|
136
|
+
end
|
137
|
+
|
138
|
+
# has this ticket been seen before this run?
|
139
|
+
def seen?(ticket, old_cache)
|
140
|
+
!old_cache.key?(ticket[:spacekey]) or old_cache[ticket[:spacekey]] < ticket[:id]
|
141
|
+
end
|
142
|
+
|
143
|
+
# only update the cached highest ID if it is in fact the highest ID
|
144
|
+
def update_cache(ticket, cache)
|
145
|
+
cache[ticket[:spacekey]] = ticket[:id] if seen?(ticket, cache)
|
146
|
+
cache
|
147
|
+
end
|
148
|
+
|
149
|
+
# write the cache to disk
|
150
|
+
def flush_cache(cache)
|
151
|
+
cache[:last_checked] = @last_checked
|
152
|
+
File.open(@data_file, 'w') do |out|
|
153
|
+
YAML.dump(cache, out)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
#
|
159
|
+
# time/utility functions
|
160
|
+
#
|
161
|
+
|
162
|
+
|
163
|
+
def time_ago_in_words(from_time, include_seconds = false)
|
164
|
+
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
165
|
+
end
|
166
|
+
|
167
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
168
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
169
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
170
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
171
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
172
|
+
|
173
|
+
case distance_in_minutes
|
174
|
+
when 0..1
|
175
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
176
|
+
case distance_in_seconds
|
177
|
+
when 0..4 then 'less than 5 seconds'
|
178
|
+
when 5..9 then 'less than 10 seconds'
|
179
|
+
when 10..19 then 'less than 20 seconds'
|
180
|
+
when 20..39 then 'half a minute'
|
181
|
+
when 40..59 then 'less than a minute'
|
182
|
+
else '1 minute'
|
183
|
+
end
|
184
|
+
|
185
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
186
|
+
when 45..89 then 'about 1 hour'
|
187
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
188
|
+
when 1440..2879 then '1 day'
|
189
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
190
|
+
when 43200..86399 then 'about 1 month'
|
191
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
192
|
+
when 525600..1051199 then 'about 1 year'
|
193
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|