robut 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +11 -0
- data/Gemfile +2 -3
- data/README.rdoc +38 -19
- data/examples/Chatfile +10 -4
- data/lib/robut.rb +3 -0
- data/lib/robut/connection.rb +10 -72
- data/lib/robut/plugin.rb +94 -13
- data/lib/robut/plugin/echo.rb +3 -13
- data/lib/robut/plugin/google_images.rb +17 -0
- data/lib/robut/plugin/help.rb +7 -15
- data/lib/robut/plugin/meme.rb +22 -53
- data/lib/robut/plugin/pick.rb +18 -0
- data/lib/robut/plugin/ping.rb +3 -9
- data/lib/robut/plugin/quips.rb +60 -0
- data/lib/robut/plugin/say.rb +6 -12
- data/lib/robut/plugin/stock.rb +45 -0
- data/lib/robut/pm.rb +40 -0
- data/lib/robut/presence.rb +35 -0
- data/lib/robut/room.rb +30 -0
- data/lib/robut/version.rb +1 -1
- data/test/mocks/connection_mock.rb +2 -18
- data/test/mocks/presence_mock.rb +25 -0
- data/test/test_helper.rb +3 -1
- data/test/unit/connection_test.rb +58 -25
- data/test/unit/plugin/alias_test.rb +5 -4
- data/test/unit/plugin/echo_test.rb +5 -4
- data/test/unit/plugin/help_test.rb +4 -3
- data/test/unit/plugin/later_test.rb +7 -6
- data/test/unit/plugin/lunch_test.rb +6 -5
- data/test/unit/plugin/pick_test.rb +35 -0
- data/test/unit/plugin/ping_test.rb +4 -3
- data/test/unit/plugin/quips_test.rb +58 -0
- data/test/unit/plugin/say_test.rb +4 -3
- data/test/unit/plugin/weather_test.rb +15 -14
- data/test/unit/plugin_test.rb +62 -1
- data/test/unit/room_test.rb +51 -0
- metadata +28 -20
- data/lib/robut/plugin/rdio.rb +0 -96
- data/lib/robut/plugin/rdio/public/css/rdio.css +0 -141
- data/lib/robut/plugin/rdio/public/css/style.css +0 -79
- data/lib/robut/plugin/rdio/public/css/style_after.css +0 -42
- data/lib/robut/plugin/rdio/public/images/background.png +0 -0
- data/lib/robut/plugin/rdio/public/images/no-album.png +0 -0
- data/lib/robut/plugin/rdio/public/index.html +0 -43
- data/lib/robut/plugin/rdio/public/js/libs/dd_belatedpng.js +0 -13
- data/lib/robut/plugin/rdio/public/js/libs/jquery-1.5.1.min.js +0 -16
- data/lib/robut/plugin/rdio/public/js/libs/modernizr-1.7.min.js +0 -2
- data/lib/robut/plugin/rdio/public/js/rdio.js +0 -129
- data/lib/robut/plugin/rdio/public/js/script.js +0 -3
- data/lib/robut/plugin/rdio/server.rb +0 -57
data/lib/robut/plugin/echo.rb
CHANGED
@@ -2,18 +2,8 @@
|
|
2
2
|
class Robut::Plugin::Echo
|
3
3
|
include Robut::Plugin
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
if sent_to_me?(message) && words.first == 'echo'
|
9
|
-
phrase = words[1..-1].join(' ')
|
10
|
-
reply(phrase) unless phrase.empty?
|
11
|
-
end
|
5
|
+
desc "echo <message> - replies to the channel with <message>"
|
6
|
+
match /^echo (.*)/, :sent_to_me => true do |phrase|
|
7
|
+
reply(phrase) unless phrase.empty?
|
12
8
|
end
|
13
|
-
|
14
|
-
# Returns a description of how to use this plugin
|
15
|
-
def usage
|
16
|
-
"#{at_nick} echo <message> - replies to the channel with <message>"
|
17
|
-
end
|
18
|
-
|
19
9
|
end
|
@@ -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
|
data/lib/robut/plugin/help.rb
CHANGED
@@ -3,22 +3,14 @@
|
|
3
3
|
class Robut::Plugin::Help
|
4
4
|
include Robut::Plugin
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
Array(plugin_instance.usage).each do |command_usage|
|
14
|
-
reply(command_usage)
|
15
|
-
end
|
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)
|
16
13
|
end
|
17
14
|
end
|
18
15
|
end
|
19
|
-
|
20
|
-
# Returns a description of how to use this plugin
|
21
|
-
def usage
|
22
|
-
"#{at_nick} help - displays this message"
|
23
|
-
end
|
24
16
|
end
|
data/lib/robut/plugin/meme.rb
CHANGED
@@ -1,63 +1,32 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
|
3
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.
|
4
16
|
class Robut::Plugin::Meme
|
5
17
|
include Robut::Plugin
|
6
18
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
'scumbag_steve' => 'http://memecaptain.com/scumbag_steve.jpg',
|
13
|
-
'town_crier' => 'http://memecaptain.com/town_crier.jpg',
|
14
|
-
'troll_face' => 'http://memecaptain.com/troll_face.jpg',
|
15
|
-
'y_u_no' => 'http://memecaptain.com/y_u_no.jpg',
|
16
|
-
'yao_ming' => 'http://memecaptain.com/yao_ming.jpg',
|
17
|
-
'business_cat' => 'http://memecaptain.com/business_cat.jpg',
|
18
|
-
'all_the_things' => 'http://memecaptain.com/all_the_things.jpg',
|
19
|
-
'fry' => 'http://memecaptain.com/fry.png',
|
20
|
-
'sap' => 'http://memecaptain.com/sap.jpg'
|
21
|
-
}
|
22
|
-
|
23
|
-
# Returns a description of how to use this plugin
|
24
|
-
def usage
|
25
|
-
[
|
26
|
-
"#{at_nick} meme list - lists all the memes that #{nick} knows about",
|
27
|
-
"#{at_nick} meme <meme> <line1>;<line2> - responds with a link to a generated <meme> image using <line1> and <line2>"
|
28
|
-
]
|
29
|
-
end
|
30
|
-
|
31
|
-
# This plugin is activated when robut is sent a message starting
|
32
|
-
# with the name of a meme. The list of generators can be discovered
|
33
|
-
# by running
|
34
|
-
#
|
35
|
-
# @robut meme list
|
36
|
-
#
|
37
|
-
# from the command line. Example:
|
38
|
-
#
|
39
|
-
# @robut meme all_the_things write; all the plugins
|
40
|
-
#
|
41
|
-
# Send message to the specified meme generator. If the meme requires
|
42
|
-
# more than one line of text, lines should be separated with a semicolon.
|
43
|
-
def handle(time, sender_nick, message)
|
44
|
-
return unless sent_to_me?(message)
|
45
|
-
words = words(message)
|
46
|
-
command = words.shift.downcase
|
47
|
-
return unless command == 'meme'
|
48
|
-
meme = words.shift
|
49
|
-
|
50
|
-
if meme == 'list'
|
51
|
-
reply("Memes available: #{MEMES.keys.join(', ')}")
|
52
|
-
elsif MEMES[meme]
|
53
|
-
url = CGI.escape(MEMES[meme])
|
54
|
-
line1, line2 = words.join(' ').split(';').map { |line| CGI.escape(line.strip)}
|
55
|
-
meme_url = "http://memecaptain.com/i?u=#{url}&tt=#{line1}"
|
56
|
-
meme_url += "&tb=#{line2}" if line2
|
57
|
-
reply(meme_url)
|
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
|
58
24
|
else
|
59
|
-
|
25
|
+
url = "http://memecaptain.com/#{meme}.jpg"
|
60
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)
|
61
31
|
end
|
62
|
-
|
63
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
|
data/lib/robut/plugin/ping.rb
CHANGED
@@ -2,14 +2,8 @@
|
|
2
2
|
class Robut::Plugin::Ping
|
3
3
|
include Robut::Plugin
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
desc "ping - responds 'pong'"
|
6
|
+
match /^ping$/, :sent_to_me => true do
|
7
|
+
reply("pong")
|
7
8
|
end
|
8
|
-
|
9
|
-
# Responds "pong" if +message+ is "ping"
|
10
|
-
def handle(time, sender_nick, message)
|
11
|
-
words = words(message)
|
12
|
-
reply("pong") if sent_to_me?(message) && words.length == 1 && words.first.downcase == 'ping'
|
13
|
-
end
|
14
|
-
|
15
9
|
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
|
data/lib/robut/plugin/say.rb
CHANGED
@@ -8,20 +8,14 @@
|
|
8
8
|
class Robut::Plugin::Say
|
9
9
|
include Robut::Plugin
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# Pipes +message+ through the +say+ command
|
17
|
-
def handle(time, sender_nick, message)
|
18
|
-
words = words(message)
|
19
|
-
if sent_to_me?(message) && words.first == "say"
|
20
|
-
phrase = clean(words[1..-1].join(' '))
|
21
|
-
system("say #{phrase}")
|
22
|
-
end
|
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}")
|
23
15
|
end
|
24
16
|
|
17
|
+
private
|
18
|
+
|
25
19
|
def clean(str)
|
26
20
|
str.gsub("'", "").gsub(/[^A-Za-z0-9\s]+/, " ").gsub(/\s+/, ' ').strip
|
27
21
|
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
|
data/lib/robut/pm.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
class Robut::PM < Robut::Presence
|
2
|
+
|
3
|
+
def initialize(connection, rooms)
|
4
|
+
# Add the callback from direct messages. Turns out the
|
5
|
+
# on_private_message callback doesn't do what it sounds like, so I
|
6
|
+
# have to go a little deeper into xmpp4r to get this working.
|
7
|
+
self.connection = connection
|
8
|
+
connection.client.add_message_callback(200, self) do |message|
|
9
|
+
from_room = rooms.any? {|room| room.muc.from_room?(message.from)}
|
10
|
+
if !from_room && message.type == :chat && message.body
|
11
|
+
time = Time.now # TODO: get real timestamp? Doesn't seem like
|
12
|
+
# jabber gives it to us
|
13
|
+
sender_jid = message.from
|
14
|
+
plugins = Robut::Plugin.plugins.map { |p| p.new(self, sender_jid) }
|
15
|
+
handle_message(plugins, time, connection.roster[sender_jid].iname, message.body)
|
16
|
+
true
|
17
|
+
else
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def reply(message, to)
|
24
|
+
unless to.kind_of?(Jabber::JID)
|
25
|
+
to = find_jid_by_name(to)
|
26
|
+
end
|
27
|
+
|
28
|
+
msg = Jabber::Message.new(to, message)
|
29
|
+
msg.type = :chat
|
30
|
+
connection.client.send(msg)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Find a jid in the roster with the given name, case-insensitively
|
36
|
+
def find_jid_by_name(name)
|
37
|
+
name = name.downcase
|
38
|
+
connection.roster.items.detect {|jid, item| item.iname.downcase == name}.first
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Robut::Presence
|
2
|
+
|
3
|
+
# The Robut::Connection that has all the connection info.
|
4
|
+
attr_accessor :connection
|
5
|
+
|
6
|
+
def initialize(connection)
|
7
|
+
self.connection = connection
|
8
|
+
end
|
9
|
+
|
10
|
+
# Sends the chat message +message+ through +plugins+.
|
11
|
+
def handle_message(plugins, time, nick, message)
|
12
|
+
# ignore all messages sent by robut. If you really want robut to
|
13
|
+
# reply to itself, you can use +fake_message+.
|
14
|
+
return if nick == connection.config.nick
|
15
|
+
|
16
|
+
plugins.each do |plugin|
|
17
|
+
begin
|
18
|
+
rsp = plugin.handle(time, nick, message)
|
19
|
+
break if rsp == true
|
20
|
+
rescue => e
|
21
|
+
error = "UH OH! #{plugin.class.name} just crashed!"
|
22
|
+
|
23
|
+
if nick
|
24
|
+
reply(error, nick) # Connection#reply
|
25
|
+
else
|
26
|
+
reply(error) # Room#reply
|
27
|
+
end
|
28
|
+
if connection.config.logger
|
29
|
+
connection.config.logger.error e
|
30
|
+
connection.config.logger.error e.backtrace.join("\n")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/robut/room.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Handles connections and responses to different rooms.
|
2
|
+
class Robut::Room < Robut::Presence
|
3
|
+
|
4
|
+
# The MUC that wraps the Jabber Chat protocol.
|
5
|
+
attr_accessor :muc
|
6
|
+
|
7
|
+
# The room jid
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def initialize(connection, room_name)
|
11
|
+
self.muc = Jabber::MUC::SimpleMUCClient.new(connection.client)
|
12
|
+
self.connection = connection
|
13
|
+
self.name = room_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def join
|
17
|
+
# Add the callback from messages that occur inside the room
|
18
|
+
muc.on_message do |time, nick, message|
|
19
|
+
plugins = Robut::Plugin.plugins.map { |p| p.new(self) }
|
20
|
+
handle_message(plugins, time, nick, message)
|
21
|
+
end
|
22
|
+
|
23
|
+
muc.join(self.name + '/' + connection.config.nick)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Send +message+ to the room we're currently connected to
|
27
|
+
def reply(message, to)
|
28
|
+
muc.send(Jabber::Message.new(muc.room, message))
|
29
|
+
end
|
30
|
+
end
|
data/lib/robut/version.rb
CHANGED