sclemmer-robut 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.travis.yml +11 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +65 -0
- data/README.rdoc +199 -0
- data/Rakefile +27 -0
- data/bin/robut +10 -0
- data/examples/Chatfile +31 -0
- data/examples/config.ru +13 -0
- data/lib/rexml_patches.rb +26 -0
- data/lib/robut/connection.rb +124 -0
- data/lib/robut/plugin/alias.rb +109 -0
- data/lib/robut/plugin/calc.rb +26 -0
- data/lib/robut/plugin/echo.rb +9 -0
- data/lib/robut/plugin/google_images.rb +17 -0
- data/lib/robut/plugin/help.rb +16 -0
- data/lib/robut/plugin/later.rb +81 -0
- data/lib/robut/plugin/lunch.rb +76 -0
- data/lib/robut/plugin/meme.rb +32 -0
- data/lib/robut/plugin/pick.rb +18 -0
- data/lib/robut/plugin/ping.rb +9 -0
- data/lib/robut/plugin/quips.rb +60 -0
- data/lib/robut/plugin/say.rb +23 -0
- data/lib/robut/plugin/sayings.rb +37 -0
- data/lib/robut/plugin/stock.rb +45 -0
- data/lib/robut/plugin/twss.rb +19 -0
- data/lib/robut/plugin/weather.rb +126 -0
- data/lib/robut/plugin.rb +201 -0
- data/lib/robut/pm.rb +40 -0
- data/lib/robut/presence.rb +39 -0
- data/lib/robut/room.rb +30 -0
- data/lib/robut/storage/base.rb +21 -0
- data/lib/robut/storage/hash_store.rb +26 -0
- data/lib/robut/storage/yaml_store.rb +57 -0
- data/lib/robut/storage.rb +3 -0
- data/lib/robut/version.rb +4 -0
- data/lib/robut/web.rb +26 -0
- data/lib/robut.rb +9 -0
- data/sclemmer-robut.gemspec +24 -0
- data/test/fixtures/bad_location.xml +1 -0
- data/test/fixtures/las_vegas.xml +1 -0
- data/test/fixtures/seattle.xml +1 -0
- data/test/fixtures/tacoma.xml +1 -0
- data/test/mocks/connection_mock.rb +22 -0
- data/test/mocks/presence_mock.rb +25 -0
- data/test/simplecov_helper.rb +2 -0
- data/test/test_helper.rb +9 -0
- data/test/unit/connection_test.rb +161 -0
- data/test/unit/plugin/alias_test.rb +76 -0
- data/test/unit/plugin/echo_test.rb +27 -0
- data/test/unit/plugin/help_test.rb +46 -0
- data/test/unit/plugin/later_test.rb +40 -0
- data/test/unit/plugin/lunch_test.rb +36 -0
- data/test/unit/plugin/pick_test.rb +35 -0
- data/test/unit/plugin/ping_test.rb +22 -0
- data/test/unit/plugin/quips_test.rb +58 -0
- data/test/unit/plugin/say_test.rb +34 -0
- data/test/unit/plugin/weather_test.rb +101 -0
- data/test/unit/plugin_test.rb +91 -0
- data/test/unit/room_test.rb +51 -0
- data/test/unit/storage/hash_store_test.rb +15 -0
- data/test/unit/storage/yaml_store_test.rb +47 -0
- data/test/unit/storage/yaml_test.yml +1 -0
- data/test/unit/web_test.rb +46 -0
- metadata +162 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'google-search'
|
2
|
+
|
3
|
+
# Responds with the first google image search result matching a query.
|
4
|
+
class Robut::Plugin::GoogleImages
|
5
|
+
include Robut::Plugin
|
6
|
+
|
7
|
+
desc "image <query> - responds with the first image from a Google Images search for <query>"
|
8
|
+
match /^image (.*)/, :sent_to_me => true do |query|
|
9
|
+
image = Google::Search::Image.new(:query => query).first
|
10
|
+
|
11
|
+
if image
|
12
|
+
reply image.uri
|
13
|
+
else
|
14
|
+
reply "Couldn't find an image"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# When asked for help, responds with a list of commands supported by
|
2
|
+
# all loaded plugins
|
3
|
+
class Robut::Plugin::Help
|
4
|
+
include Robut::Plugin
|
5
|
+
|
6
|
+
desc "help - displays this message"
|
7
|
+
match /^help$/, :sent_to_me => true do
|
8
|
+
reply("Supported commands:")
|
9
|
+
Robut::Plugin.plugins.each do |plugin|
|
10
|
+
plugin_instance = plugin.new(reply_to, private_sender)
|
11
|
+
Array(plugin_instance.usage).each do |command_usage|
|
12
|
+
reply(command_usage)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# The Later plugin allows you to send messages/commands to robut based on
|
2
|
+
# a time delay. Like so:
|
3
|
+
#
|
4
|
+
# @robut in 5 minutes lunch?
|
5
|
+
# @robut in 1 hr echo @justin wake up!
|
6
|
+
#
|
7
|
+
# @robut will respond effectively the same as he would if someone had told
|
8
|
+
# him "lunch?" 5 minutes from now. In the case of the Lunch plugin, he
|
9
|
+
# would repond with a lunch suggestion. The Later plugin works with all other
|
10
|
+
# plugins as you follow this syntax:
|
11
|
+
#
|
12
|
+
# @robut in <number> <mins|hrs|secs> [command]
|
13
|
+
#
|
14
|
+
# Where command is the message you want to send to @robut in the future. For
|
15
|
+
# the time denominations it also recognizes minute, minutes, hour, hours,
|
16
|
+
# second, seconds.
|
17
|
+
#
|
18
|
+
class Robut::Plugin::Later
|
19
|
+
include Robut::Plugin
|
20
|
+
|
21
|
+
# Returns a description of how to use this plugin
|
22
|
+
def usage
|
23
|
+
"#{at_nick} in <number> <mins|hrs|secs> <command> - sends <command> to #{nick} after the specified interval"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Passes +message+ back through the plugin chain if we've been given
|
27
|
+
# a time to execute it later.
|
28
|
+
def handle(time, sender_nick, message)
|
29
|
+
if sent_to_me?(message)
|
30
|
+
phrase = words(message).join(' ')
|
31
|
+
if phrase =~ timer_regex
|
32
|
+
count = $1.to_i
|
33
|
+
scale = $2
|
34
|
+
future_message = at_nick + ' ' + $3
|
35
|
+
|
36
|
+
sleep_time = count * scale_multiplier(scale)
|
37
|
+
|
38
|
+
if sleep_time >= 3600
|
39
|
+
reply "Too far into the future--ain't no body got (timeforthat)"
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
reply("Ok, see you in #{count} #{scale}")
|
44
|
+
connection = self.connection
|
45
|
+
threader do
|
46
|
+
sleep sleep_time
|
47
|
+
fake_message(Time.now, sender_nick, future_message)
|
48
|
+
end
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# A regex that detects a time.
|
57
|
+
def timer_regex
|
58
|
+
/in (.*) (sec|secs|second|seconds|min|mins|minute|minutes|hr|hrs|hour|hours) (.*)$/
|
59
|
+
end
|
60
|
+
|
61
|
+
# Takes a time scale (secs, mins, hours, etc.) and returns the
|
62
|
+
# factor to convert it into seconds.
|
63
|
+
def scale_multiplier(time_scale)
|
64
|
+
case time_scale
|
65
|
+
when /sec(s|ond|onds)?/
|
66
|
+
1
|
67
|
+
when /min(s|ute|utes)?/
|
68
|
+
60
|
69
|
+
when /(hr|hrs|hour|hours)/
|
70
|
+
60 * 60
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Asynchronously runs the given block.
|
75
|
+
def threader
|
76
|
+
Thread.new do
|
77
|
+
yield
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Where should we go to lunch today?
|
2
|
+
class Robut::Plugin::Lunch
|
3
|
+
include Robut::Plugin
|
4
|
+
|
5
|
+
# Returns a description of how to use this plugin
|
6
|
+
def usage
|
7
|
+
[
|
8
|
+
"lunch? / food? - #{nick} will suggest a place to go eat",
|
9
|
+
"#{at_nick} lunch places - lists all the lunch places #{nick} knows about",
|
10
|
+
"#{at_nick} new lunch place <place> - tells #{nick} about a new place to eat",
|
11
|
+
"#{at_nick} remove lunch place <place> - tells #{nick} not to suggest <place> anymore"
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Replies with a random string selected from +places+.
|
16
|
+
def handle(time, sender_nick, message)
|
17
|
+
words = words(message)
|
18
|
+
phrase = words.join(' ')
|
19
|
+
# lunch?
|
20
|
+
if phrase =~ /^(lunch|food)\?$/i
|
21
|
+
if places.empty?
|
22
|
+
reply "I don't know about any lunch places"
|
23
|
+
else
|
24
|
+
$stderr.puts "*************************** -> #{sender_nick}"
|
25
|
+
reply choose_place(sender_nick) + "!"
|
26
|
+
end
|
27
|
+
# @robut lunch places
|
28
|
+
elsif phrase == "lunch places" && sent_to_me?(message)
|
29
|
+
if places.empty?
|
30
|
+
reply "I don't know about any lunch places"
|
31
|
+
else
|
32
|
+
reply places.join(', ')
|
33
|
+
end
|
34
|
+
# @robut new lunch place Green Leaf
|
35
|
+
elsif phrase =~ /new lunch place (.*)/i && sent_to_me?(message)
|
36
|
+
place = $1
|
37
|
+
new_place(place)
|
38
|
+
reply "Ok, I'll add \"#{place}\" to the the list of lunch places"
|
39
|
+
# @robut remove luynch place Green Leaf
|
40
|
+
elsif phrase =~ /remove lunch place (.*)/i && sent_to_me?(message)
|
41
|
+
place = $1
|
42
|
+
remove_place(place)
|
43
|
+
reply "I removed \"#{place}\" from the list of lunch places"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def choose_place(sender_nick)
|
48
|
+
excluded = sender_nick =~ /\bchu\b/i ? /\bchu\b/i : nil
|
49
|
+
# excluded = sender_nick =~ /\bclemmer\b/i ? /^[^g]/i : nil
|
50
|
+
valid_places = places
|
51
|
+
valid_places = places.reject { |p| p =~ excluded } unless excluded.nil?
|
52
|
+
return valid_places[rand(valid_places.length)]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Stores +place+ as a new lunch place.
|
56
|
+
def new_place(place)
|
57
|
+
store["lunch_places"] ||= []
|
58
|
+
store["lunch_places"] = (store["lunch_places"] + Array(place)).uniq
|
59
|
+
end
|
60
|
+
|
61
|
+
# Removes +place+ from the list of lunch places.
|
62
|
+
def remove_place(place)
|
63
|
+
store["lunch_places"] ||= []
|
64
|
+
store["lunch_places"] = store["lunch_places"] - Array(place)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the list of lunch places we know about.
|
68
|
+
def places
|
69
|
+
store["lunch_places"] ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
# Sets the list of lunch places to +v+
|
73
|
+
def places=(v)
|
74
|
+
store["lunch_places"] = v
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
# A simple plugin that wraps memecaptain.
|
4
|
+
# This plugin is activated when robut is sent a message starting
|
5
|
+
# with the name of a meme. The list of generators can be discovered
|
6
|
+
# by running
|
7
|
+
#
|
8
|
+
# @robut meme list
|
9
|
+
#
|
10
|
+
# from the command line. Example:
|
11
|
+
#
|
12
|
+
# @robut meme all_the_things write; all the plugins
|
13
|
+
#
|
14
|
+
# Send message to the specified meme generator. If the meme requires
|
15
|
+
# more than one line of text, lines should be separated with a semicolon.
|
16
|
+
class Robut::Plugin::Meme
|
17
|
+
include Robut::Plugin
|
18
|
+
|
19
|
+
desc "meme <meme> <line1>;<line2> - responds with a link to a generated <meme> image using <line1> and <line2>. " +
|
20
|
+
"See http://memecaptain.com/ for a list of memes. You can also pass a link to your own image as the meme."
|
21
|
+
match /^meme (\S+) (.*)$/, :sent_to_me => true do |meme, text|
|
22
|
+
if meme.include?("://")
|
23
|
+
url = meme
|
24
|
+
else
|
25
|
+
url = "http://memecaptain.com/#{meme}.jpg"
|
26
|
+
end
|
27
|
+
line1, line2 = text.split(';').map { |line| CGI.escape(line.strip)}
|
28
|
+
meme_url = "http://memecaptain.com/i?u=#{url}&tt=#{line1}"
|
29
|
+
meme_url += "&tb=#{line2}" if line2
|
30
|
+
reply(meme_url)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'calc'
|
2
|
+
|
3
|
+
# Let fate decide!
|
4
|
+
class Robut::Plugin::Pick
|
5
|
+
include Robut::Plugin
|
6
|
+
|
7
|
+
desc "pick <a>, <b>, <c>, ... - randomly selects one of the options"
|
8
|
+
match /^pick (.*)/, :sent_to_me => true do |message|
|
9
|
+
options = message.split(',').map { |s| s.strip }
|
10
|
+
rsp = options[random(options.length)]
|
11
|
+
reply("And the winner is... #{rsp}") if rsp
|
12
|
+
end
|
13
|
+
|
14
|
+
def random(c)
|
15
|
+
rand(c)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Stores quotes and replies with a random stored quote.
|
2
|
+
class Robut::Plugin::Quips
|
3
|
+
include Robut::Plugin
|
4
|
+
|
5
|
+
desc "add quip <text> - adds a quip to the quip database"
|
6
|
+
match /^add quip (.+)/, :sent_to_me => true do |new_quip|
|
7
|
+
if add_quip(new_quip)
|
8
|
+
reply "I added the quip to the quip database"
|
9
|
+
else
|
10
|
+
reply "I didn't add the quip, since it was already added"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "remove quip <text> - removes a quip from the quip database"
|
15
|
+
match /^remove quip (.+)/, :sent_to_me => true do |quip|
|
16
|
+
if remove_quip(quip)
|
17
|
+
reply "I removed the quip from the quip database"
|
18
|
+
else
|
19
|
+
reply "I couldn't remove the quip, since it wasn't in the quip database"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "quip - returns a random quip"
|
24
|
+
match /^quip$/, :sent_to_me => true do
|
25
|
+
quip = random_quip
|
26
|
+
if quip
|
27
|
+
reply quip
|
28
|
+
else
|
29
|
+
reply "I don't know any quips"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# The list of quips stored in the quip database
|
34
|
+
def quips
|
35
|
+
# I'd love to use a set here, but it doesn't serialize right to yaml
|
36
|
+
store["quips"] ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Update the list of quips stored in the quip database
|
40
|
+
def quips=(new_quips)
|
41
|
+
# I'd love to use a set here, but it doesn't serialize right to yaml
|
42
|
+
store["quips"] = new_quips
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds +quip+ to the quip database
|
46
|
+
def add_quip(quip)
|
47
|
+
self.quips = (quips + Array(quip)) unless quips.include?(quip)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Removes +quip+ from the quip database
|
51
|
+
def remove_quip(quip)
|
52
|
+
self.quips = (quips - Array(quip)) if quips.include?(quip)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a random quip
|
56
|
+
def random_quip
|
57
|
+
quips[rand(quips.length)] unless quips.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# This is a simple plugin the envokes the "say" command on whatever is passed
|
2
|
+
# Example:
|
3
|
+
#
|
4
|
+
# @robut say that was awesome!
|
5
|
+
#
|
6
|
+
# *Requires that the "say" command is installed and in the path
|
7
|
+
#
|
8
|
+
class Robut::Plugin::Say
|
9
|
+
include Robut::Plugin
|
10
|
+
|
11
|
+
desc "say <words> - uses Mac OS X's 'say' command to speak <words>"
|
12
|
+
match "^say (.*)$", :sent_to_me => true do |phrase|
|
13
|
+
phrase = clean(phrase)
|
14
|
+
system("say #{phrase}")
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def clean(str)
|
20
|
+
str.gsub("'", "").gsub(/[^A-Za-z0-9\s]+/, " ").gsub(/\s+/, ' ').strip
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# A simple regex => response plugin.
|
2
|
+
class Robut::Plugin::Sayings
|
3
|
+
include Robut::Plugin
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# A list of arrays. The first element is a regex, the second is
|
7
|
+
# the reply sent if the regex matches. After the first match, we
|
8
|
+
# don't try to match any other sayings. Configuration looks like the following:
|
9
|
+
#
|
10
|
+
# [["you're the worst", "I know."], ["sucks", "You know something, you suck!" ]]
|
11
|
+
#
|
12
|
+
# All regex matches are case-insensitive.
|
13
|
+
attr_accessor :sayings
|
14
|
+
end
|
15
|
+
self.sayings = []
|
16
|
+
|
17
|
+
# Returns a description of how to use this plugin
|
18
|
+
def usage
|
19
|
+
"#{at_nick} <words> - if <words> matches a regular expression defined in the Chatfile, responds with the specified response"
|
20
|
+
end
|
21
|
+
|
22
|
+
# For each element in sayings, creates a regex out of the first
|
23
|
+
# element, tries to match +message+ to it, and replies with the
|
24
|
+
# second element if it found a match. Robut::Plugin::Sayings will
|
25
|
+
# only respond once to each message, with the first match.
|
26
|
+
def handle(time, sender_nick, message)
|
27
|
+
# Tries to respond to any message sent to robut.
|
28
|
+
if sent_to_me?(message)
|
29
|
+
self.class.sayings.each do |saying|
|
30
|
+
if words(message).join(' ').match(/#{saying.first}/i)
|
31
|
+
reply(saying.last)
|
32
|
+
return
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
begin
|
2
|
+
require 'yahoofinance' # require yahoofinance gem
|
3
|
+
rescue LoadError
|
4
|
+
puts "You must install the yahoofinance gem in order to use the Stock plugin"
|
5
|
+
raise $!
|
6
|
+
end
|
7
|
+
|
8
|
+
class Robut::Plugin::Stock
|
9
|
+
include Robut::Plugin
|
10
|
+
|
11
|
+
desc "stock <symbol> - Returns a stock data from Yahoo Finance"
|
12
|
+
match /^stock (.*)/, :sent_to_me => true do |phrase|
|
13
|
+
stock_data = get_stock_data(format_stock_symbols(phrase))
|
14
|
+
reply format_reply(stock_data)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def format_reply(stock_data)
|
20
|
+
r = []
|
21
|
+
stock_data.keys.sort.each do |sym|
|
22
|
+
sd = stock_data[sym]
|
23
|
+
r << "#{sym}: #{format_number(sd.changePoints)} / #{format_number(sd.changePercent)}%,\tbid: #{pad_number(sd.bid)},\task: #{pad_number(sd.ask)},\tprevious close: #{pad_number(sd.previousClose)}"
|
24
|
+
end
|
25
|
+
r.join("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
def format_number(n)
|
29
|
+
n > 0 ? "+" + pad_number(n) : pad_number(n)
|
30
|
+
end
|
31
|
+
|
32
|
+
def pad_number(n)
|
33
|
+
sprintf("%.2f", n)
|
34
|
+
end
|
35
|
+
|
36
|
+
def format_stock_symbols(phrase)
|
37
|
+
phrase.downcase.split(/[\s,;]+/).join(',')
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_stock_data(symbols)
|
41
|
+
YahooFinance::get_quotes(YahooFinance::StandardQuote, symbols)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'twss-classifier'
|
2
|
+
|
3
|
+
# A simple plugin that feeds everything said in the room through the
|
4
|
+
# twss gem. Requires the 'twss' gem, obviously.
|
5
|
+
class Robut::Plugin::TWSS
|
6
|
+
include Robut::Plugin
|
7
|
+
|
8
|
+
# Returns a description of how to use this plugin
|
9
|
+
def usage
|
10
|
+
"<words> - responds with \"That's what she said!\" if #{nick} thinks <words> is a valid TWSS"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Responds "That's what she said!" if the TWSS gem returns true for
|
14
|
+
# +message+. Strips out any reference to our nick in +message+
|
15
|
+
# before it stuffs +message+ into the gem.
|
16
|
+
def handle(time, sender_nick, message)
|
17
|
+
reply("That's what she said!") if TWSSClassifier.is_twss?(words(message).join(" "))
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
# What's the current weather forecast?
|
5
|
+
class Robut::Plugin::Weather
|
6
|
+
include Robut::Plugin
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :default_location
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns a description of how to use this plugin
|
13
|
+
def usage
|
14
|
+
[
|
15
|
+
"#{at_nick} weather - returns the weather in the default location for today",
|
16
|
+
"#{at_nick} weather tomorrow - returns the weather in the default location for tomorrow",
|
17
|
+
"#{at_nick} weather <location> - returns the weather for <location> today",
|
18
|
+
"#{at_nick} weather <location> Tuesday - returns the weather for <location> Tuesday"
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle(time, sender_nick, message)
|
23
|
+
# ignore messages that don't end in ?
|
24
|
+
return unless message[message.length - 1, 1] == "?"
|
25
|
+
message = message[0..message.length - 2]
|
26
|
+
|
27
|
+
words = words(message)
|
28
|
+
i = words.index("weather")
|
29
|
+
|
30
|
+
# ignore messages that don't have "weather" in them
|
31
|
+
return if i.nil?
|
32
|
+
|
33
|
+
location = i.zero? ? self.class.default_location : words[0..i-1].join(" ")
|
34
|
+
if location.nil?
|
35
|
+
reply "I don't have a default location!"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
day_of_week = nil
|
40
|
+
day_string = words[i+1..-1].join(" ").downcase
|
41
|
+
if day_string != ""
|
42
|
+
day_of_week = parse_day(day_string)
|
43
|
+
if day_of_week.nil?
|
44
|
+
reply "I don't recognize the date: \"#{day_string}\""
|
45
|
+
return
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if bad_location?(location)
|
50
|
+
reply "I don't recognize the location: \"#{location}\""
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
if day_of_week
|
55
|
+
reply forecast(location, day_of_week)
|
56
|
+
else
|
57
|
+
reply current_conditions(location)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_day(day)
|
62
|
+
day_map = {
|
63
|
+
"monday" => "Mon",
|
64
|
+
"mon" => "Mon",
|
65
|
+
"tuesday" => "Tue",
|
66
|
+
"tue" => "Tue",
|
67
|
+
"tues" => "Tue",
|
68
|
+
"wed" => "Wed",
|
69
|
+
"wednesday" => "Wed",
|
70
|
+
"thurs" => "Thu",
|
71
|
+
"thu" => "Thu",
|
72
|
+
"thursday" => "Thu",
|
73
|
+
"friday" => "Fri",
|
74
|
+
"fri" => "Fri",
|
75
|
+
"saturday" => "Sat",
|
76
|
+
"sat" => "Sat",
|
77
|
+
"sunday" => "Sun",
|
78
|
+
"sun" => "Sun",
|
79
|
+
}
|
80
|
+
if day_map.has_key?(day)
|
81
|
+
return day_map[day]
|
82
|
+
end
|
83
|
+
|
84
|
+
if day == "tomorrow"
|
85
|
+
return (Time.now + 60*60*24).strftime("%a")
|
86
|
+
end
|
87
|
+
|
88
|
+
if day == "today"
|
89
|
+
return Time.now.strftime("%a")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def current_conditions(location)
|
94
|
+
doc = weather_data(location)
|
95
|
+
condition = doc.search("current_conditions condition").first["data"]
|
96
|
+
temperature = doc.search("current_conditions temp_f").first["data"]
|
97
|
+
normalized_location = doc.search("forecast_information city").first["data"]
|
98
|
+
"Weather for #{normalized_location}: #{condition}, #{temperature}F"
|
99
|
+
end
|
100
|
+
|
101
|
+
def forecast(location, day_of_week)
|
102
|
+
doc = weather_data(location)
|
103
|
+
forecast = doc.search("forecast_conditions").detect{|el| c = el.children.detect{|c| c.name == "day_of_week"}; c && c["data"] == day_of_week}
|
104
|
+
return "Can't find a forecast for #{day_of_week}" if forecast.nil?
|
105
|
+
|
106
|
+
condition = forecast.children.detect{|c| c.name == "condition"}["data"]
|
107
|
+
high = forecast.children.detect{|c| c.name == "high"}["data"]
|
108
|
+
low = forecast.children.detect{|c| c.name == "low"}["data"]
|
109
|
+
normalized_location = doc.search("forecast_information city").first["data"]
|
110
|
+
"Forecast for #{normalized_location} on #{day_of_week}: #{condition}, High: #{high}F, Low: #{low}F"
|
111
|
+
end
|
112
|
+
|
113
|
+
def weather_data(location = "")
|
114
|
+
@weather_data ||= {}
|
115
|
+
@weather_data[location] ||= begin
|
116
|
+
url = "http://www.google.com/ig/api?weather=#{URI.escape(location)}"
|
117
|
+
Nokogiri::XML(open(url))
|
118
|
+
end
|
119
|
+
@weather_data[location]
|
120
|
+
end
|
121
|
+
|
122
|
+
def bad_location?(location = "")
|
123
|
+
weather_data(location).search("forecast_information").empty?
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|