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