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/lolcats.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class LolCats < CampfireBot::Plugin
|
6
|
+
on_command 'lolcat', :lolcats
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@log = Logging.logger["CampfireBot::Plugin::Lolcat"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def lolcats(msg)
|
13
|
+
# Scrape random lolcat
|
14
|
+
lolcat = (Hpricot(open('http://icanhascheezburger.com/?random#top'))/'div.entry img').first['src']
|
15
|
+
msg.speak(lolcat)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# Remember quotes and quips added upon request. Rather than pulling
|
4
|
+
# public quoes form the internet, the idea is that you can maintain
|
5
|
+
# your own list of quotes said by people at your campfire.
|
6
|
+
#
|
7
|
+
# Since this plugin maintains long-term persistent data, not just
|
8
|
+
# temporary state, its data file lives under var/ instead of tmp/.
|
9
|
+
#
|
10
|
+
# You can import from the eggdrop QuoteBot.log with this, which will
|
11
|
+
# overwrite any existing campfire-bot quotes:
|
12
|
+
#
|
13
|
+
# ruby -e '
|
14
|
+
# require "yaml"
|
15
|
+
# log = File.readlines("QuoteBot.log").collect { |l|
|
16
|
+
# action, scope, quoter, room, id, quote = l.split(" ", 6)
|
17
|
+
# quote.gsub!(/\x03\d\d/, "") # remove IRC color codes
|
18
|
+
# [action, quoter, room, id.sub(/^[(]#/, "").sub(/[)]$/, "").to_i,
|
19
|
+
# nil, quote]
|
20
|
+
# }
|
21
|
+
# File.open("var/quote-log.yml", "w") do |out|
|
22
|
+
# YAML.dump(log, out)
|
23
|
+
# end
|
24
|
+
# puts "wrote #{log.size} log entries"
|
25
|
+
# '
|
26
|
+
#
|
27
|
+
# Issues:
|
28
|
+
#
|
29
|
+
# * The entire log is rewritten after each add/del, which takes 1.2
|
30
|
+
# sec for my 3k quotes. It's the YAML.dump call that's slow. An
|
31
|
+
# alternative would be to store each log entry as a document and only
|
32
|
+
# append to the file. However, an incomplete write would corrupt the
|
33
|
+
# file. Maybe there's a way to discard the last, corrupt yaml
|
34
|
+
# document? For now, it's fast enough.
|
35
|
+
#
|
36
|
+
# * Would be nice when there are multiple matches to iterate through
|
37
|
+
# them when the same query is repeated.
|
38
|
+
#
|
39
|
+
# * There is another quote plugin in quote.rb whose model is to pull
|
40
|
+
# quotes from the internet. The upside is you have a list of quotes
|
41
|
+
# to start with. The downside is that it doesn't foster much sense
|
42
|
+
# of community nor develop common culture. To avoid a command name
|
43
|
+
# collision, the quote recall command of this plugin may be
|
44
|
+
# configured. The default commant is "!ourquote", but "!quote" is
|
45
|
+
# more natural.
|
46
|
+
#
|
47
|
+
class OurQuotes < CampfireBot::Plugin
|
48
|
+
on_command 'addquote', :addquote
|
49
|
+
on_command 'rmquote', :rmquote
|
50
|
+
# Configure with "our_quote_recall_command: quote"
|
51
|
+
#on_command 'quote', :quote
|
52
|
+
|
53
|
+
config_var :data_file, File.join(BOT_ROOT, 'var', 'quote-log.yml')
|
54
|
+
config_var :recall_command, "ourquote"
|
55
|
+
config_var :debug_enabled, false
|
56
|
+
|
57
|
+
attr_reader :use_htmlentities
|
58
|
+
|
59
|
+
def initialize()
|
60
|
+
super
|
61
|
+
|
62
|
+
self.class.on_command recall_command.to_s, :quote
|
63
|
+
|
64
|
+
# Put this in the constructor so we don't fail to find htmlentities
|
65
|
+
# every time we fetch a bug title.
|
66
|
+
begin
|
67
|
+
require 'htmlentities'
|
68
|
+
@use_htmlentities = true
|
69
|
+
rescue LoadError
|
70
|
+
debug "Falling back to 'cgi', install 'htmlentities' better unescaping"
|
71
|
+
require 'cgi'
|
72
|
+
end
|
73
|
+
|
74
|
+
@log = begin
|
75
|
+
YAML::load(File.read(@data_file))
|
76
|
+
rescue Errno::ENOENT => e
|
77
|
+
debug e
|
78
|
+
[]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def debug(spew)
|
83
|
+
$stderr.puts "#{self.class.name}: #{spew}" if debug_enabled
|
84
|
+
end
|
85
|
+
|
86
|
+
def addquote(msg)
|
87
|
+
debug "Addquote requested: #{msg}"
|
88
|
+
if msg[:message].empty?
|
89
|
+
msg.speak "Please include the text of the quote and attribution."
|
90
|
+
return
|
91
|
+
end
|
92
|
+
append_add(msg[:person], msg[:room].name, msg[:message])
|
93
|
+
# Show the users quote numberes that start with 1 not 0
|
94
|
+
msg.speak "Added quote ##{quotes.length}."
|
95
|
+
end
|
96
|
+
|
97
|
+
def append_add(quoter, room, quote)
|
98
|
+
debug "ADD: #{quotes.length + 1} #{quote}"
|
99
|
+
@log.push ["ADD", quoter, room, quotes.length + 1, Time.now, decode(quote)]
|
100
|
+
write_log
|
101
|
+
end
|
102
|
+
|
103
|
+
def decode(str)
|
104
|
+
# Unicode decode: The "&" of "<" appear as "\\u0026".
|
105
|
+
html = str.gsub(/\\u([0-9a-f]{4})/i) { eval "0x#{$1}.chr" }
|
106
|
+
html_decode(html)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the non-HTML version of the given string using
|
110
|
+
# htmlentities if available, or else unescapeHTML
|
111
|
+
def html_decode(html)
|
112
|
+
if use_htmlentities
|
113
|
+
HTMLEntities.new.decode(html)
|
114
|
+
else
|
115
|
+
CGI.unescapeHTML(html)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def quote(msg)
|
120
|
+
q = msg[:message]
|
121
|
+
id = q.to_i
|
122
|
+
# If numeric
|
123
|
+
if id.to_s == q
|
124
|
+
msg.speak quotes[id - 1] ? format_quote(id) : "No quote ##{id}."
|
125
|
+
else
|
126
|
+
matches = select_quote_ids {|quote| quote.include?(q) }
|
127
|
+
msg.speak matches.empty? ?
|
128
|
+
"No matching quotes." :
|
129
|
+
format_quote(matches[rand(matches.length)]) +
|
130
|
+
(q == "" ? "" :
|
131
|
+
" (#{matches.size} match#{"es" if matches.size != 1})")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def format_quote(id)
|
136
|
+
"##{id} #{quotes[id - 1]}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def select_quote_ids
|
140
|
+
returning([]) {|ids|
|
141
|
+
quotes.each_with_index {|quote,idx|
|
142
|
+
if !quote.nil? && yield(quote)
|
143
|
+
ids.push(idx + 1)
|
144
|
+
end
|
145
|
+
}
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
def rmquote(msg)
|
150
|
+
q = msg[:message]
|
151
|
+
id = q.to_i
|
152
|
+
# If numeric
|
153
|
+
if id.to_s != q || !quotes[id - 1]
|
154
|
+
msg.speak "No quote ##{q}."
|
155
|
+
return
|
156
|
+
end
|
157
|
+
append_del(msg[:person], msg[:room].name, id)
|
158
|
+
msg.speak "Deleted quote ##{id}."
|
159
|
+
end
|
160
|
+
|
161
|
+
def append_del(rmer, room, id)
|
162
|
+
debug "DEL: #{quotes.length} #{id}"
|
163
|
+
@log.push ["DEL", rmer, room, id, Time.now, nil]
|
164
|
+
write_log
|
165
|
+
end
|
166
|
+
|
167
|
+
def quotes
|
168
|
+
return @quotes if @quotes
|
169
|
+
debug "Rebuilding @quotes from @log of length #{@log.size}"
|
170
|
+
@quotes = []
|
171
|
+
@log.each { |action, quoter, room, id, ts, quote|
|
172
|
+
case action
|
173
|
+
when "ADD" then
|
174
|
+
raise "Bad ID: #{id}" if id - 1 != @quotes.length
|
175
|
+
@quotes.push quote
|
176
|
+
when "DEL" then
|
177
|
+
@quotes[id - 1] = nil
|
178
|
+
# Allow any trailing IDs with nil quotes to be reused
|
179
|
+
@quotes.pop while @quotes.last.nil? && !@quotes.empty?
|
180
|
+
else raise "Unknown log action #{action}"
|
181
|
+
end
|
182
|
+
}
|
183
|
+
@quotes
|
184
|
+
end
|
185
|
+
|
186
|
+
def write_log
|
187
|
+
debug "Writing #{@log.length} log entries"
|
188
|
+
File.open("#{data_file}.tmp", 'w') do |out|
|
189
|
+
YAML.dump(@log, out)
|
190
|
+
end
|
191
|
+
debug "Renaming .tmp file to #{data_file}"
|
192
|
+
File.rename("#{data_file}.tmp", data_file)
|
193
|
+
@quotes = nil
|
194
|
+
end
|
195
|
+
end
|
data/plugins/quote.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'hpricot'
|
3
|
+
|
4
|
+
class Quote < CampfireBot::Plugin
|
5
|
+
on_command 'quote', :quote
|
6
|
+
|
7
|
+
def quote(msg)
|
8
|
+
# Get our quotes from the web
|
9
|
+
url = "http://quotes4all.net/rss/000010110/quotes.xml"
|
10
|
+
response = ''
|
11
|
+
|
12
|
+
begin
|
13
|
+
# open-uri RDoc: http://stdlib.rubyonrails.org/libdoc/open-uri/rdoc/index.html
|
14
|
+
open(url, "User-Agent" => "Ruby/#{RUBY_VERSION}",
|
15
|
+
"From" => "Campfire") { |f|
|
16
|
+
|
17
|
+
# Save the response body
|
18
|
+
response = f.read
|
19
|
+
}
|
20
|
+
|
21
|
+
# HPricot RDoc: http://code.whytheluckystiff.net/hpricot/
|
22
|
+
doc = Hpricot(response)
|
23
|
+
|
24
|
+
msg.speak((doc/"*/item/description").inner_html.gsub(/<\/?[^>]*>/,"").gsub(/\s+/," ").gsub(/\"e;/,"'").gsub(/\&[\#|\w]\w+\;/,"").gsub(/\#39\;/,"'"))
|
25
|
+
msg.speak((doc/"*/item/title").inner_html)
|
26
|
+
|
27
|
+
rescue Exception => e
|
28
|
+
msg.speak(e, "\n")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/plugins/schneier.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'hpricot'
|
3
|
+
|
4
|
+
class Schneier < CampfireBot::Plugin
|
5
|
+
BASE_URL = 'http://geekz.co.uk/schneierfacts/'
|
6
|
+
|
7
|
+
on_command 'schneier', :schneier
|
8
|
+
|
9
|
+
def schneier(msg)
|
10
|
+
quote = case msg[:message].split(/\s+/)[0]
|
11
|
+
when 'latest'
|
12
|
+
fetch_quote(true)
|
13
|
+
when 'random'
|
14
|
+
fetch_quote
|
15
|
+
else
|
16
|
+
fetch_quote
|
17
|
+
end
|
18
|
+
msg.speak quote
|
19
|
+
rescue => e
|
20
|
+
msg.speak e
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def fetch_quote(latest = false)
|
26
|
+
CGI::unescapeHTML((Hpricot(open("#{BASE_URL}#{'fact/latest' if latest}"))).search('p .fact').html)
|
27
|
+
end
|
28
|
+
end
|
data/plugins/seen.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Courtesy of joshwand (http://github.com/joshwand)
|
2
|
+
class Seen < CampfireBot::Plugin
|
3
|
+
ACTIVITY_REGEXP = /^(.*)$/
|
4
|
+
SEEN_REGEXP = /([^\?]+)(?=\?)*/
|
5
|
+
|
6
|
+
on_message Regexp.new("#{ACTIVITY_REGEXP.source}", Regexp::IGNORECASE), :update
|
7
|
+
on_command 'seen', :seen
|
8
|
+
on_command 'reload_seen', :reload
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@data_file = File.join(BOT_ROOT, 'tmp', "seen-#{BOT_ENVIRONMENT}.yml")
|
12
|
+
@seen = YAML::load(File.read(@data_file)) rescue {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(msg)
|
16
|
+
left_room = (msg[:message] == "has left the room " ? true : false)
|
17
|
+
@seen[msg[:person]] = {:time => Time.now, :left => left_room}
|
18
|
+
|
19
|
+
File.open(@data_file, 'w') do |out|
|
20
|
+
YAML.dump(@seen, out)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def seen(msg)
|
25
|
+
found = false
|
26
|
+
puts msg[:message]
|
27
|
+
puts msg[:message] =~ SEEN_REGEXP
|
28
|
+
|
29
|
+
if !$1.nil?
|
30
|
+
first_name = $1.match("[A-Za-z]+")[0]
|
31
|
+
|
32
|
+
@seen.each do |person, seenat|
|
33
|
+
if person.downcase.include?(first_name.downcase)
|
34
|
+
time_ago = time_ago_in_words(seenat[:time])
|
35
|
+
left = seenat[:left] ? "leaving the room " : ""
|
36
|
+
msg.speak("#{person} was last seen #{left}#{time_ago} ago")
|
37
|
+
found = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if !found
|
42
|
+
msg.speak("Sorry, I haven't seen #{first_name}.")
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def reload(msg)
|
49
|
+
@seen = {}
|
50
|
+
msg.speak("ok, reloaded seen db")
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def time_ago_in_words(from_time, include_seconds = false)
|
56
|
+
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
57
|
+
end
|
58
|
+
|
59
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
60
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
61
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
62
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
63
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
64
|
+
|
65
|
+
case distance_in_minutes
|
66
|
+
when 0..1
|
67
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
68
|
+
case distance_in_seconds
|
69
|
+
when 0..4 then 'less than 5 seconds'
|
70
|
+
when 5..9 then 'less than 10 seconds'
|
71
|
+
when 10..19 then 'less than 20 seconds'
|
72
|
+
when 20..39 then 'half a minute'
|
73
|
+
when 40..59 then 'less than a minute'
|
74
|
+
else '1 minute'
|
75
|
+
end
|
76
|
+
|
77
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
78
|
+
when 45..89 then 'about 1 hour'
|
79
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
80
|
+
when 1440..2879 then '1 day'
|
81
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
82
|
+
when 43200..86399 then 'about 1 month'
|
83
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
84
|
+
when 525600..1051199 then 'about 1 year'
|
85
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class SignalFilter < CampfireBot::Plugin
|
2
|
+
on_message /^\[.*?\]/i, :echo_signal
|
3
|
+
|
4
|
+
def echo_signal(msg)
|
5
|
+
unless msg[:room] == bot.config['signal_target_room']
|
6
|
+
bot.rooms[bot.config['signal_target_room']].speak("#{msg[:person]} #{msg[:message]}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
data/plugins/svn.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class Svn < CampfireBot::Plugin
|
6
|
+
|
7
|
+
at_interval 20.minutes, :check_svn
|
8
|
+
on_command 'svn', :checksvn_command
|
9
|
+
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
# log "initializing... "
|
13
|
+
@data_file = File.join(BOT_ROOT, 'tmp', "svn-#{BOT_ENVIRONMENT}-#{bot.config['room']}.yml")
|
14
|
+
@cached_revisions = YAML::load(File.read(@data_file)) rescue {}
|
15
|
+
@last_checked ||= 10.minutes.ago
|
16
|
+
@urls = bot.config['svn_urls']
|
17
|
+
@log = Logging.logger["CampfireBot::Plugin::Svn"]
|
18
|
+
end
|
19
|
+
|
20
|
+
# respond to checkjira command-- same as interval except we answer with 'no issues found' if
|
21
|
+
def checksvn_command(msg)
|
22
|
+
msg.speak "no new commits since I last checked #{@lastlast} ago" if !check_svn(msg)
|
23
|
+
end
|
24
|
+
|
25
|
+
def check_svn(msg)
|
26
|
+
|
27
|
+
saw_a_commit = false
|
28
|
+
old_cache = Marshal::load(Marshal.dump(@cached_revisions)) # since ruby doesn't have deep copy
|
29
|
+
|
30
|
+
@lastlast = time_ago_in_words(@last_checked)
|
31
|
+
commits = fetch_svn_urls
|
32
|
+
|
33
|
+
commits.each do |commit|
|
34
|
+
# p commit
|
35
|
+
if new?(commit, old_cache)
|
36
|
+
saw_an_issue = true
|
37
|
+
|
38
|
+
@cached_revisions = update_cache(commit, @cached_revisions)
|
39
|
+
flush_cache(@cached_revisions)
|
40
|
+
|
41
|
+
messagetext = "#{commit[:author]} committed revision #{commit[:revision]} #{time_ago_in_words(commit[:date])} ago on #{commit[:url]}:\n"
|
42
|
+
|
43
|
+
messagetext += "\n#{commit[:message]}\n"
|
44
|
+
messagetext += "----\n"
|
45
|
+
commit[:paths].each do |path|
|
46
|
+
messagetext += path[:action] + " " + path[:path] + "\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
msg.paste(messagetext)
|
50
|
+
@log.info messagetext
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
@last_checked = Time.now
|
56
|
+
@log.info "no new commits." if !saw_a_commit
|
57
|
+
|
58
|
+
saw_a_commit
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
# fetch jira url and return a list of commit Hashes
|
64
|
+
def fetch_svn_urls()
|
65
|
+
urls = bot.config['svn_urls']
|
66
|
+
commits = []
|
67
|
+
urls.each do |url|
|
68
|
+
begin
|
69
|
+
@log.info "checking #{url} for new commits..."
|
70
|
+
xmldata = `svn log --xml -v --limit 15 #{url}`
|
71
|
+
doc = REXML::Document.new(xmldata)
|
72
|
+
|
73
|
+
doc.elements.inject('log/logentry', commits) do |commits, element|
|
74
|
+
commits.push({:url => url}.merge(parse_entry_info(element)))
|
75
|
+
end
|
76
|
+
|
77
|
+
rescue Exception => e
|
78
|
+
@log.error "error connecting to svn: #{e.message}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return commits
|
82
|
+
end
|
83
|
+
|
84
|
+
# extract commit hash from indivrevisionual xml element
|
85
|
+
def parse_entry_info(xml_element)
|
86
|
+
|
87
|
+
revision = xml_element.attributes['revision']
|
88
|
+
author = xml_element.elements['author'].text
|
89
|
+
date = DateTime.parse(xml_element.elements['date'].text)
|
90
|
+
message = xml_element.elements['msg'].text
|
91
|
+
|
92
|
+
paths = xml_element.elements.collect('paths/path') do |e|
|
93
|
+
{
|
94
|
+
:action => e.attributes['action'],
|
95
|
+
:path => e.text
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
return {
|
100
|
+
:revision => revision,
|
101
|
+
:author => author,
|
102
|
+
:message => message,
|
103
|
+
:date => date,
|
104
|
+
:paths => paths
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
# has this commit been seen before this run?
|
109
|
+
def new?(commit, old_cache)
|
110
|
+
!old_cache.key?(commit[:url]) or old_cache[commit[:url]] < commit[:revision].to_i
|
111
|
+
end
|
112
|
+
|
113
|
+
# only update the cached highest revision if it is in fact the highest revision
|
114
|
+
def update_cache(commit, cache)
|
115
|
+
cache[commit[:url]] = commit[:revision].to_i if new?(commit, cache)
|
116
|
+
cache
|
117
|
+
end
|
118
|
+
|
119
|
+
# write the cache to disk
|
120
|
+
def flush_cache(cache)
|
121
|
+
File.open(@data_file, 'w') do |out|
|
122
|
+
YAML.dump(cache, out)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
#
|
129
|
+
# time/utility functions
|
130
|
+
#
|
131
|
+
|
132
|
+
|
133
|
+
def time_ago_in_words(from_time, include_seconds = false)
|
134
|
+
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
135
|
+
end
|
136
|
+
|
137
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
138
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
139
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
140
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
141
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
142
|
+
|
143
|
+
case distance_in_minutes
|
144
|
+
when 0..1
|
145
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
146
|
+
case distance_in_seconds
|
147
|
+
when 0..4 then 'less than 5 seconds'
|
148
|
+
when 5..9 then 'less than 10 seconds'
|
149
|
+
when 10..19 then 'less than 20 seconds'
|
150
|
+
when 20..39 then 'half a minute'
|
151
|
+
when 40..59 then 'less than a minute'
|
152
|
+
else '1 minute'
|
153
|
+
end
|
154
|
+
|
155
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
156
|
+
when 45..89 then 'about 1 hour'
|
157
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
158
|
+
when 1440..2879 then '1 day'
|
159
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
160
|
+
when 43200..86399 then 'about 1 month'
|
161
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
162
|
+
when 525600..1051199 then 'about 1 year'
|
163
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|