campfire-bot 0.1.0 → 1.0.0

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.
@@ -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