robut 0.3.0 → 0.4.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.
- 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