campfire-bot 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,17 +0,0 @@
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
@@ -1,195 +0,0 @@
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 "&lt;" 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
@@ -1,31 +0,0 @@
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(/\&quote;/,"'").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
@@ -1,28 +0,0 @@
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
@@ -1,88 +0,0 @@
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
@@ -1,9 +0,0 @@
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
@@ -1,167 +0,0 @@
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