hipbot 1.0.0.rc2 → 1.0.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 (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