hipbot 1.0.0.rc2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +6 -0
  4. data/Gemfile +1 -2
  5. data/README.md +482 -88
  6. data/Rakefile +1 -1
  7. data/bin/hipbot +4 -4
  8. data/hipbot.gemspec +9 -9
  9. data/lib/hipbot.rb +28 -11
  10. data/lib/hipbot/adapter.rb +80 -0
  11. data/lib/hipbot/adapters/hipchat.rb +53 -0
  12. data/lib/hipbot/adapters/hipchat/initializer.rb +83 -0
  13. data/lib/hipbot/adapters/shell.rb +37 -0
  14. data/lib/hipbot/adapters/telnet.rb +41 -0
  15. data/lib/hipbot/bot.rb +15 -52
  16. data/lib/hipbot/cache.rb +23 -0
  17. data/lib/hipbot/callbacks/base.rb +15 -0
  18. data/lib/hipbot/callbacks/invite.rb +11 -0
  19. data/lib/hipbot/callbacks/lobby_presence.rb +13 -0
  20. data/lib/hipbot/callbacks/message.rb +11 -0
  21. data/lib/hipbot/callbacks/presence.rb +17 -0
  22. data/lib/hipbot/callbacks/private_message.rb +12 -0
  23. data/lib/hipbot/callbacks/room_message.rb +21 -0
  24. data/lib/hipbot/callbacks/room_presence.rb +28 -0
  25. data/lib/hipbot/configurable.rb +22 -0
  26. data/lib/hipbot/configuration.rb +9 -3
  27. data/lib/hipbot/helpers.rb +4 -40
  28. data/lib/hipbot/http.rb +65 -0
  29. data/lib/hipbot/match.rb +21 -9
  30. data/lib/hipbot/matchable.rb +38 -0
  31. data/lib/hipbot/message.rb +24 -9
  32. data/lib/hipbot/plugin.rb +3 -3
  33. data/lib/hipbot/reactable.rb +14 -21
  34. data/lib/hipbot/reaction.rb +27 -15
  35. data/lib/hipbot/reaction_factory.rb +38 -0
  36. data/lib/hipbot/response.rb +18 -10
  37. data/lib/hipbot/room.rb +34 -2
  38. data/lib/hipbot/storages/base.rb +78 -0
  39. data/lib/hipbot/storages/hash.rb +89 -0
  40. data/lib/hipbot/storages/mongoid.rb +18 -0
  41. data/lib/hipbot/user.rb +8 -2
  42. data/lib/hipbot/version.rb +1 -1
  43. data/spec/integration/my_hipbot.rb +24 -4
  44. data/spec/integration/{hipbot_spec.rb → my_hipbot_spec.rb} +41 -23
  45. data/spec/spec_helper.rb +14 -2
  46. data/spec/unit/adapters/hipchat_spec.rb +5 -0
  47. data/spec/unit/{hipbot_spec.rb → bot_spec.rb} +13 -12
  48. data/spec/unit/match_spec.rb +148 -0
  49. data/spec/unit/message_spec.rb +14 -7
  50. data/spec/unit/reaction_factory_spec.rb +54 -0
  51. data/spec/unit/reaction_spec.rb +99 -0
  52. data/spec/unit/storages/hash_spec.rb +75 -0
  53. data/spec/unit/user_spec.rb +0 -2
  54. metadata +64 -54
  55. data/Gemfile.lock +0 -90
  56. data/examples/cleverbot.rb +0 -23
  57. data/examples/google_images.rb +0 -35
  58. data/lib/hipbot/adapters/hipchat/connection.rb +0 -166
  59. data/lib/hipbot/adapters/hipchat/hipchat.rb +0 -13
  60. data/lib/hipbot/adapters/telnet/connection.rb +0 -17
  61. data/lib/hipbot/adapters/telnet/telnet.rb +0 -14
  62. data/lib/hipbot/collection.rb +0 -72
  63. data/lib/hipbot/patches/hipchat_client.rb +0 -230
@@ -0,0 +1,23 @@
1
+ module Hipbot
2
+ module Cache
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend ClassMethods
7
+ end
8
+
9
+ def _cache
10
+ @_cache ||= {}
11
+ end
12
+
13
+ module ClassMethods
14
+ def attr_cache *attributes, &block
15
+ attributes.each do |attr_name|
16
+ define_method(attr_name) do
17
+ _cache[attr_name] ||= block_given? ? instance_eval(&block) : []
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class Base
4
+ protected
5
+
6
+ def with_room params
7
+ yield Room.find_or_create_by(params)
8
+ end
9
+
10
+ def with_user params
11
+ yield User.find_or_create_by(params)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class Invite < Base
4
+ def initialize room_id, user_name, room_name, topic
5
+ with_room(id: room_id, name: room_name, topic: topic) do |room|
6
+ Hipbot.join_room(room)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class LobbyPresence < Presence
4
+ def initialize user_id, presence
5
+ self.presence = presence
6
+
7
+ with_user(id: user_id) do |user|
8
+ user.update_attribute(:is_online, online_presence?)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class Message < Base
4
+ protected
5
+
6
+ def ignore_message? sender, message_body
7
+ message_body.empty? || sender.myself?
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class Presence < Base
4
+ attr_accessor :presence
5
+
6
+ protected
7
+
8
+ def online_presence?
9
+ presence.empty?
10
+ end
11
+
12
+ def offline_presence?
13
+ presence == 'unavailable'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class PrivateMessage < Message
4
+ def initialize user_id, message_body
5
+ with_user(id: user_id) do |user|
6
+ return if ignore_message?(user, message_body)
7
+ Hipbot.react(user, nil, message_body)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class RoomMessage < Message
4
+ def initialize room_id, user_name, message_body, topic
5
+ with_room(id: room_id) do |room|
6
+ update_topic(room, topic)
7
+ with_user(name: user_name) do |user|
8
+ return if ignore_message?(user, message_body)
9
+ Hipbot.react(user, room, message_body)
10
+ end
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def update_topic room, topic
17
+ room.update_attribute(:topic, topic) unless topic.empty?
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ module Hipbot
2
+ module Callbacks
3
+ class RoomPresence < Presence
4
+ attr_accessor :room_id, :user_name
5
+
6
+ def initialize room_id, user_name, presence
7
+ self.presence = presence
8
+ self.user_name = user_name
9
+ self.room_id = room_id
10
+ handle_room_presence
11
+ end
12
+
13
+ protected
14
+
15
+ def handle_room_presence
16
+ with_room(id: room_id) do |room|
17
+ with_user(name: user_name) do |user|
18
+ if offline_presence?
19
+ room.on_leave(user)
20
+ elsif online_presence? && !room.users.include?(user)
21
+ room.on_join(user)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module Hipbot
2
+ module Configurable
3
+ extend ActiveSupport::Concern
4
+ attr_accessor :configuration
5
+
6
+ delegate *Configuration::OPTIONS, to: :configuration
7
+
8
+ included do
9
+ extend ClassMethods
10
+ end
11
+
12
+ def initialize
13
+ self.configuration ||= Configuration.new
14
+ end
15
+
16
+ module ClassMethods
17
+ def configure &block
18
+ instance.configuration = Configuration.new.tap(&block)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,18 +1,24 @@
1
1
  module Hipbot
2
2
  class Configuration
3
- attr_accessor *Bot::CONFIGURABLE_OPTIONS
3
+ OPTIONS = [
4
+ :adapter, :exception_handler, :helpers, :jid, :join, :logger, :password,
5
+ :plugins, :preloader, :rooms, :status, :storage, :teams, :user
6
+ ]
7
+ attr_accessor *OPTIONS
4
8
 
5
9
  def initialize
6
10
  self.adapter = Adapters::Hipchat
7
- self.error_handler = Proc.new{}
11
+ self.exception_handler = Proc.new{ |e| Hipbot.logger.error(e) }
8
12
  self.helpers = Module.new
9
13
  self.jid = ''
14
+ self.join = :all
10
15
  self.logger = Logger.new($stdout)
11
16
  self.password = ''
12
17
  self.plugins = Hipbot.plugins
13
18
  self.preloader = Proc.new{}
14
19
  self.rooms = {}
15
- self.storage = Collection
20
+ self.status = ''
21
+ self.storage = Storages::Hash
16
22
  self.teams = {}
17
23
  end
18
24
 
@@ -1,46 +1,10 @@
1
1
  module Hipbot
2
2
  module Helpers
3
- [:get, :post, :put, :delete].each do |http_verb|
4
- define_method http_verb do |url, query = {}, &block|
5
- Hipbot.logger.info("HTTP-REQUEST: #{url} #{query}")
6
- query.merge!({ :head => {'accept-encoding' => 'gzip, compressed'} })
7
- conn = ::EM::HttpRequest.new(url, :connect_timeout => 5, :inactivity_timeout => 10)
8
- http = conn.send(http_verb, :query => query)
9
- http.callback do
10
- begin
11
- response = HttpResponse.new(http)
12
- Hipbot.logger.info("HTTP-RESPONSE: #{response}")
13
- block.call(response)
14
- rescue => e
15
- Hipbot.logger.error(e)
16
- instance_exec(e, &Hipbot.error_handler)
17
- end
18
- end if block.present?
19
-
20
- http.errback do
21
- Hipbot.logger.error("HTTP-RESPONSE-ERROR: #{url}")
22
- end
23
- end
24
-
25
- module_function http_verb
26
- end
27
-
28
- class HttpResponse < Struct.new(:raw_response)
29
- def body
30
- raw_response.response
31
- end
32
-
33
- def code
34
- raw_response.response_header.status
35
- end
36
-
37
- def headers
38
- raw_response.response_header
39
- end
40
-
41
- def json
42
- @json ||= JSON.parse(body)
3
+ [:get, :post, :put, :delete].each do |method|
4
+ define_method method do |url, query = {}, &block|
5
+ Http::Request.new(url, query, method).(&block)
43
6
  end
7
+ module_function method
44
8
  end
45
9
  end
46
10
  end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ module Hipbot
3
+ module Http
4
+ class Request < Struct.new(:url, :params, :method)
5
+ DEFAULT_HEADERS = { 'accept-encoding' => 'gzip, compressed' }.freeze
6
+ CONNECTION_SETTINGS = { connect_timeout: 5, inactivity_timeout: 10 }.freeze
7
+ ERROR_CALLBACK = ->(error){ Hipbot.logger.error(error) }
8
+
9
+ def initialize *args
10
+ super
11
+ self.params ||= {}
12
+ self.params = params.has_key?(:query) ? params : { query: params }
13
+ self.params = { head: DEFAULT_HEADERS }.merge(params)
14
+ Hipbot.logger.info("HTTP-REQUEST: #{url} #{params}")
15
+ end
16
+
17
+ def call &success_block
18
+ http.errback(&ERROR_CALLBACK)
19
+ http.callback do
20
+ success(&success_block)
21
+ end unless success_block.nil?
22
+ end
23
+
24
+ protected
25
+
26
+ def success
27
+ yield Http::Response.new(http)
28
+ rescue => e
29
+ instance_exec(e, &Hipbot.exception_handler)
30
+ end
31
+
32
+ def http options = {}
33
+ @http ||= connection.send(method, params.merge(options))
34
+ end
35
+
36
+ def connection
37
+ @connection ||= EM::HttpRequest.new(url, CONNECTION_SETTINGS)
38
+ end
39
+ end
40
+
41
+ class Response < Struct.new(:raw_response)
42
+ def initialize *args
43
+ super
44
+ Hipbot.logger.debug("HTTP-RESPONSE: #{body}")
45
+ end
46
+
47
+ def body
48
+ raw_response.response
49
+ end
50
+
51
+ def code
52
+ raw_response.response_header.status
53
+ end
54
+
55
+ def headers
56
+ raw_response.response_header
57
+ end
58
+
59
+ def json
60
+ binding.pry
61
+ @json ||= JSON.parse(body) || {}
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,25 +1,27 @@
1
1
  module Hipbot
2
2
  class Match < Struct.new(:reaction, :message)
3
+ include Cache
4
+
3
5
  def matches?
4
- matches_regexp? && matches_scope? && matches_sender? && matches_place?
6
+ matches_scope? && matches_place? && matches_regexp? && matches_sender? && matches_condition?
5
7
  end
6
8
 
7
9
  def invoke
8
- Response.new(reaction, message).invoke(params)
10
+ Response.new(reaction, message).invoke(reaction_parameters)
9
11
  end
10
12
 
11
13
  protected
12
14
 
13
- def params
14
- reaction.anything? ? [message.body] : regexp_match[1..-1]
15
+ def reaction_parameters
16
+ reaction.to_anything? ? [message.body] : match_data[1..-1]
15
17
  end
16
18
 
17
19
  def matches_regexp?
18
- reaction.anything? || regexp_match.present?
20
+ reaction.to_anything? || !match_data.nil? || reaction.regexps.empty?
19
21
  end
20
22
 
21
- def regexp_match
22
- @regexp_match ||= reaction.regexps.inject(nil) do |result, regexp|
23
+ attr_cache :match_data do
24
+ reaction.regexps.inject(nil) do |result, regexp|
23
25
  break result if result
24
26
  message_text.match(regexp)
25
27
  end
@@ -30,19 +32,29 @@ module Hipbot
30
32
  end
31
33
 
32
34
  def matches_place?
33
- reaction.anywhere? || (message.room.present? ? matches_room? : reaction.private_message_only?)
35
+ reaction.from_anywhere? || (message.room.nil? ? reaction.to_private_message? : matches_room?)
34
36
  end
35
37
 
36
38
  def matches_room?
37
- reaction.any_room? || reaction.rooms.include?(message.room.name)
39
+ reaction.in_any_room? || reaction.rooms.include?(message.room.name)
38
40
  end
39
41
 
40
42
  def matches_sender?
41
43
  reaction.from_all? || reaction.users.include?(message.sender.name)
42
44
  end
43
45
 
46
+ def matches_condition?
47
+ reaction.condition.(*reaction.condition.parameters.map{ |parameter| message.send(parameter.last) })
48
+ end
49
+
44
50
  def message_text
45
51
  reaction.global? ? message.raw_body : message.body
46
52
  end
53
+
54
+ class << self
55
+ def invoke_all matches
56
+ matches.each(&:invoke)
57
+ end
58
+ end
47
59
  end
48
60
  end
@@ -0,0 +1,38 @@
1
+ module Hipbot
2
+ module Matchable
3
+ def react sender, room, body
4
+ message = Message.new(body, room, sender)
5
+ matches = message_matches(message)
6
+ Match.invoke_all(matches)
7
+ end
8
+
9
+ def reactions
10
+ reaction_sets.flatten
11
+ end
12
+
13
+ protected
14
+
15
+ def reactables
16
+ plugins.map(&:class)
17
+ end
18
+
19
+ def reaction_sets
20
+ reactables.each_with_object([]) do |reactable, array|
21
+ array.unshift(reactable.reactions)
22
+ array.push(reactable.default_reactions)
23
+ end
24
+ end
25
+
26
+ def message_matches message
27
+ reaction_sets.each do |reactions|
28
+ matches = reactions_matches(message, reactions)
29
+ return matches if matches.any?
30
+ end
31
+ []
32
+ end
33
+
34
+ def reactions_matches message, reactions
35
+ reactions.map{ |reaction| reaction.match_with(message) }.select(&:matches?)
36
+ end
37
+ end
38
+ end
@@ -1,28 +1,43 @@
1
+ # encoding: utf-8
1
2
  module Hipbot
2
3
  class Message < Struct.new(:raw_body, :room, :sender)
3
- attr_accessor :body, :recipients
4
+ include Cache
5
+
6
+ attr_accessor :body
7
+
8
+ MENTION_REGEXP = /@(\p{Word}++)/.freeze
4
9
 
5
10
  def initialize *args
6
11
  super
7
12
  Hipbot.logger.info("MESSAGE from #{sender} in #{room}")
8
- self.body = strip_recipient(raw_body)
9
- self.recipients = raw_body.scan(/@(\p{Word}++)/).flatten.compact.uniq
13
+ self.raw_body = raw_body.force_encoding('UTF-8')
14
+ self.body = strip_bot_mention
10
15
  end
11
16
 
12
- def for? recipient
13
- recipients.include? recipient.mention
17
+ def for? user
18
+ recipients.include? user.mention
14
19
  end
15
20
 
16
- def strip_recipient body
17
- body.gsub(/^@\p{Word}++[^\p{Word}]*/, '').strip
21
+ attr_cache :recipients do
22
+ raw_body.scan(MENTION_REGEXP).flatten.compact.uniq
18
23
  end
19
24
 
20
- def mentions
21
- recipients[1..-1] || [] # TODO: Fix global message case
25
+ attr_cache :mentions do
26
+ recipients.tap{ |r| r.delete(bot_mention) }
22
27
  end
23
28
 
24
29
  def private?
25
30
  room.nil?
26
31
  end
32
+
33
+ protected
34
+
35
+ def bot_mention
36
+ Hipbot.user.mention
37
+ end
38
+
39
+ def strip_bot_mention
40
+ raw_body.gsub(/^@#{bot_mention}[^\p{Word}]*/, '')
41
+ end
27
42
  end
28
43
  end