robut 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.travis.yml +11 -0
  2. data/Gemfile +2 -3
  3. data/README.rdoc +38 -19
  4. data/examples/Chatfile +10 -4
  5. data/lib/robut.rb +3 -0
  6. data/lib/robut/connection.rb +10 -72
  7. data/lib/robut/plugin.rb +94 -13
  8. data/lib/robut/plugin/echo.rb +3 -13
  9. data/lib/robut/plugin/google_images.rb +17 -0
  10. data/lib/robut/plugin/help.rb +7 -15
  11. data/lib/robut/plugin/meme.rb +22 -53
  12. data/lib/robut/plugin/pick.rb +18 -0
  13. data/lib/robut/plugin/ping.rb +3 -9
  14. data/lib/robut/plugin/quips.rb +60 -0
  15. data/lib/robut/plugin/say.rb +6 -12
  16. data/lib/robut/plugin/stock.rb +45 -0
  17. data/lib/robut/pm.rb +40 -0
  18. data/lib/robut/presence.rb +35 -0
  19. data/lib/robut/room.rb +30 -0
  20. data/lib/robut/version.rb +1 -1
  21. data/test/mocks/connection_mock.rb +2 -18
  22. data/test/mocks/presence_mock.rb +25 -0
  23. data/test/test_helper.rb +3 -1
  24. data/test/unit/connection_test.rb +58 -25
  25. data/test/unit/plugin/alias_test.rb +5 -4
  26. data/test/unit/plugin/echo_test.rb +5 -4
  27. data/test/unit/plugin/help_test.rb +4 -3
  28. data/test/unit/plugin/later_test.rb +7 -6
  29. data/test/unit/plugin/lunch_test.rb +6 -5
  30. data/test/unit/plugin/pick_test.rb +35 -0
  31. data/test/unit/plugin/ping_test.rb +4 -3
  32. data/test/unit/plugin/quips_test.rb +58 -0
  33. data/test/unit/plugin/say_test.rb +4 -3
  34. data/test/unit/plugin/weather_test.rb +15 -14
  35. data/test/unit/plugin_test.rb +62 -1
  36. data/test/unit/room_test.rb +51 -0
  37. metadata +28 -20
  38. data/lib/robut/plugin/rdio.rb +0 -96
  39. data/lib/robut/plugin/rdio/public/css/rdio.css +0 -141
  40. data/lib/robut/plugin/rdio/public/css/style.css +0 -79
  41. data/lib/robut/plugin/rdio/public/css/style_after.css +0 -42
  42. data/lib/robut/plugin/rdio/public/images/background.png +0 -0
  43. data/lib/robut/plugin/rdio/public/images/no-album.png +0 -0
  44. data/lib/robut/plugin/rdio/public/index.html +0 -43
  45. data/lib/robut/plugin/rdio/public/js/libs/dd_belatedpng.js +0 -13
  46. data/lib/robut/plugin/rdio/public/js/libs/jquery-1.5.1.min.js +0 -16
  47. data/lib/robut/plugin/rdio/public/js/libs/modernizr-1.7.min.js +0 -2
  48. data/lib/robut/plugin/rdio/public/js/rdio.js +0 -129
  49. data/lib/robut/plugin/rdio/public/js/script.js +0 -3
  50. data/lib/robut/plugin/rdio/server.rb +0 -57
@@ -2,18 +2,8 @@
2
2
  class Robut::Plugin::Echo
3
3
  include Robut::Plugin
4
4
 
5
- # Responds with +message+ if the command sent to robut is 'echo'.
6
- def handle(time, sender_nick, message)
7
- words = words(message)
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
@@ -3,22 +3,14 @@
3
3
  class Robut::Plugin::Help
4
4
  include Robut::Plugin
5
5
 
6
- # Responds with a list of commands supported by all loaded plugins.
7
- def handle(time, sender_nick, message)
8
- words = words(message)
9
- if sent_to_me?(message) && words.first == 'help'
10
- reply("Supported commands:")
11
- Robut::Plugin.plugins.each do |plugin|
12
- plugin_instance = plugin.new(connection, private_sender)
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
@@ -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
- MEMES = {
8
- 'bear_grylls' => 'http://memecaptain.com/bear_grylls.jpg',
9
- 'insanity_wolf' => 'http://memecaptain.com/insanity_wolf.jpg',
10
- 'most_interesting' => 'http://memecaptain.com/most_interesting.jpg',
11
- 'philosoraptor' => 'http://memecaptain.com/philosoraptor.jpg',
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
- reply("Meme not found: #{meme}")
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
@@ -2,14 +2,8 @@
2
2
  class Robut::Plugin::Ping
3
3
  include Robut::Plugin
4
4
 
5
- def usage
6
- "#{at_nick} ping - responds 'pong'"
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
@@ -8,20 +8,14 @@
8
8
  class Robut::Plugin::Say
9
9
  include Robut::Plugin
10
10
 
11
- # Returns a description of how to use this plugin
12
- def usage
13
- "#{at_nick} say <words> - uses Mac OS X's 'say' command to speak <words>"
14
- end
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
@@ -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
@@ -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
@@ -1,4 +1,4 @@
1
1
  module Robut # :nodoc:
2
2
  # Robut's version number.
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
4
4
  end