hipbot 0.2.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,81 +1,94 @@
1
1
  module Hipbot
2
- class Bot < Reactable
2
+ class << self
3
+ attr_accessor :bot, :plugins
4
+ delegate :name, to: :bot
5
+
6
+ def plugins
7
+ @plugins ||= []
8
+ end
9
+
10
+ def method_missing name, *params, &block
11
+ bot.send(name, *params, &block)
12
+ end
13
+ end
14
+
15
+ class Bot
16
+ extend Reactable
3
17
  include Singleton
4
- attr_accessor :configuration, :connection, :error_handler
5
18
 
6
- CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter, :helpers, :plugins, :teams, :rooms, :logger]
19
+ attr_accessor :configuration, :connection
20
+
21
+ CONFIGURABLE_OPTIONS = [:adapter, :error_handler, :helpers, :jid, :logger, :password, :plugins, :preloader, :rooms, :storage, :teams, :user]
7
22
  delegate *CONFIGURABLE_OPTIONS, to: :configuration
23
+ delegate :name, to: :user
8
24
  alias_method :to_s, :name
9
25
 
10
26
  def initialize
11
- super
12
- self.configuration = Configuration.new.tap(&self.class.configuration)
13
- self.error_handler = self.class.error_handler
14
- extend(self.adapter || Adapters::Hipchat)
27
+ self.configuration ||= Configuration.new
15
28
  end
16
29
 
17
- def reactions
18
- defined_reactions + plugin_reactions + default_reactions
30
+ def react sender, room, message
31
+ message = Message.new(message, room, sender)
32
+ matching_reactions(message, sender.reactions, plugin_reactions, default_reactions).each(&:invoke)
19
33
  end
20
34
 
21
- def react sender, room, message
22
- Hipbot.logger.info("MESSAGE from #{sender} in #{room}")
23
- matching_reactions(sender, room, message) do |matches|
24
- Hipbot.logger.info("REACTION #{matches.first.inspect}")
25
- matches.first.invoke(sender, room, message)
26
- end
35
+ def setup
36
+ extend adapter
37
+ Hipbot.bot = self
38
+
39
+ User.send(:include, storage)
40
+ Room.send(:include, storage)
41
+ Response.send(:include, helpers)
42
+
43
+ helpers.module_exec(&preloader)
44
+ plugins.append(self)
45
+ Jabber.debug = true
46
+ Jabber.logger = logger
47
+ end
48
+
49
+ def plugin_reactions
50
+ plugins.flat_map{ |p| p.class.reactions }
51
+ end
52
+
53
+ def default_reactions
54
+ plugins.flat_map{ |p| p.class.default_reactions }
27
55
  end
28
56
 
29
57
  class << self
30
- ACCESSORS = { configure: :configuration, on_preload: :preloader, on_error: :error_handler }
58
+ def configure &block
59
+ instance.configuration = Configuration.new.tap(&block)
60
+ end
31
61
 
32
- ACCESSORS.each do |setter, getter|
33
- define_method(setter) do |&block|
34
- instance_variable_set("@#{getter}", block)
35
- end
62
+ def on_preload &block
63
+ instance.configuration.preloader = block
64
+ end
36
65
 
37
- define_method(getter) do
38
- instance_variable_get("@#{getter}") || Proc.new{}
39
- end
66
+ def on_error &block
67
+ instance.configuration.error_handler = block
40
68
  end
41
69
 
42
70
  def start!
43
71
  ::EM::run do
44
- Jabber.debug = true
45
- Jabber.logger = Hipbot::Bot.instance.logger
46
- Helpers.module_exec(&preloader)
72
+ instance.setup
47
73
  instance.start!
48
74
  end
49
75
  end
50
76
  end
51
77
 
52
- def plugin_for(reaction)
53
- included_plugins.find { |p| p.defined_reactions.include?(reaction) }
54
- end
78
+ protected
55
79
 
56
- private
57
-
58
- def plugin_reactions
59
- included_plugins.map(&:defined_reactions).flatten
60
- end
61
-
62
- def included_plugins
63
- @included_plugins ||= begin
64
- Array(plugins).map do |object|
65
- plugin = object.kind_of?(Plugin) ? object : object.new
66
- plugin.bot = self
67
- plugin
68
- end
80
+ def matching_reactions message, *reaction_sets
81
+ reaction_sets.each do |reactions|
82
+ matches = reactions.map{ |reaction| matching_rection(message, reaction) }.compact
83
+ return matches if matches.any?
69
84
  end
85
+ []
70
86
  end
71
87
 
72
- def default_reactions
73
- super + included_plugins.flat_map(&:default_reactions)
88
+ def matching_rection message, reaction
89
+ match = reaction.match_with(message)
90
+ match.matches? ? match : nil
74
91
  end
75
92
 
76
- def matching_reactions sender, room, message
77
- matches = reactions.select{ |r| r.match?(sender, room, message) }
78
- yield matches if matches.any?
79
- end
80
93
  end
81
94
  end
@@ -1,20 +1,41 @@
1
1
  module Hipbot
2
- class Collection < Struct.new(:id, :name, :params)
3
- private_class_method :new
4
- alias_method :to_s, :name
2
+ module Collection
3
+ extend ActiveSupport::Concern
5
4
 
6
- def initialize *args
7
- super
8
- self.params = OpenStruct.new(params)
5
+ included do
6
+ extend ClassMethods
7
+
8
+ attr_accessor :id, :name, :attributes
9
+ alias_method :to_s, :name
10
+ end
11
+
12
+ def initialize params
13
+ self.id = params.delete(:id)
14
+ self.name = params.delete(:name)
15
+ self.attributes = params
16
+ end
17
+
18
+ def update_attribute key, value
19
+ if key == :name
20
+ self.name = value
21
+ else
22
+ self.attributes[key] = value
23
+ end
24
+ end
25
+
26
+ def update_attributes hash
27
+ hash.each do |k, v|
28
+ update_attribute k, v
29
+ end
9
30
  end
10
31
 
11
32
  def delete
12
33
  self.class.collection.delete(self.id)
13
34
  end
14
35
 
15
- class << self
16
- def create *args, &block
17
- collection[args[0]] = new(*args, &block)
36
+ module ClassMethods
37
+ def create params, &block
38
+ collection[params[:id]] = new(params, &block)
18
39
  end
19
40
 
20
41
  def collection
@@ -34,6 +55,10 @@ module Hipbot
34
55
  items.map{ |i| find_one(i) }.compact.uniq
35
56
  end
36
57
 
58
+ def find_or_create_by params
59
+ find_one(params[:id] || params[:name]) || create(params)
60
+ end
61
+
37
62
  protected
38
63
 
39
64
  def method_missing name, *args, &block
@@ -3,13 +3,21 @@ module Hipbot
3
3
  attr_accessor *Bot::CONFIGURABLE_OPTIONS
4
4
 
5
5
  def initialize
6
- self.name = 'robot'
7
- self.jid = 'changeme'
8
- self.password = 'changeme'
9
- self.teams = {}
10
- self.rooms = {}
11
- self.helpers = Module.new
12
- self.logger = Hipbot::Logger.new($stdout)
6
+ self.adapter = Adapters::Hipchat
7
+ self.error_handler = Proc.new{}
8
+ self.helpers = Module.new
9
+ self.jid = ''
10
+ self.logger = Logger.new($stdout)
11
+ self.password = ''
12
+ self.plugins = Hipbot.plugins
13
+ self.preloader = Proc.new{}
14
+ self.rooms = {}
15
+ self.storage = Collection
16
+ self.teams = {}
17
+ end
18
+
19
+ def user
20
+ @user ||= User.new(name: 'robot')
13
21
  end
14
22
  end
15
23
  end
@@ -7,10 +7,16 @@ module Hipbot
7
7
  conn = ::EM::HttpRequest.new(url, :connect_timeout => 5, :inactivity_timeout => 10)
8
8
  http = conn.send(http_verb, :query => query)
9
9
  http.callback do
10
- response = HttpResponse.new(http)
11
- Hipbot.logger.info("HTTP-RESPONSE: #{response}")
12
- block.call(response)
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
13
18
  end if block.present?
19
+
14
20
  http.errback do
15
21
  Hipbot.logger.error("HTTP-RESPONSE-ERROR: #{url}")
16
22
  end
@@ -33,7 +39,7 @@ module Hipbot
33
39
  end
34
40
 
35
41
  def json
36
- @json ||= JSON.parse(body) rescue {}
42
+ @json ||= JSON.parse(body)
37
43
  end
38
44
  end
39
45
  end
@@ -8,8 +8,4 @@ module Hipbot
8
8
  super(severity, "[#{severity_name}][#{Time.now}] #{msg}")
9
9
  end
10
10
  end
11
-
12
- def self.logger
13
- Hipbot::Bot.instance.logger
14
- end
15
11
  end
@@ -0,0 +1,48 @@
1
+ module Hipbot
2
+ class Match < Struct.new(:reaction, :message)
3
+ def matches?
4
+ matches_regexp? && matches_scope? && matches_sender? && matches_place?
5
+ end
6
+
7
+ def invoke
8
+ Response.new(reaction, message).invoke(params)
9
+ end
10
+
11
+ protected
12
+
13
+ def params
14
+ reaction.anything? ? [message.body] : regexp_match[1..-1]
15
+ end
16
+
17
+ def matches_regexp?
18
+ reaction.anything? || regexp_match.present?
19
+ end
20
+
21
+ def regexp_match
22
+ @regexp_match ||= reaction.regexps.inject(nil) do |result, regexp|
23
+ break result if result
24
+ message_text.match(regexp)
25
+ end
26
+ end
27
+
28
+ def matches_scope?
29
+ reaction.global? || message.for?(Hipbot.user) || message.private?
30
+ end
31
+
32
+ def matches_place?
33
+ reaction.anywhere? || (message.room.present? ? matches_room? : reaction.private_message_only?)
34
+ end
35
+
36
+ def matches_room?
37
+ reaction.any_room? || reaction.rooms.include?(message.room.name)
38
+ end
39
+
40
+ def matches_sender?
41
+ reaction.from_all? || reaction.users.include?(message.sender.name)
42
+ end
43
+
44
+ def message_text
45
+ reaction.global? ? message.raw_body : message.body
46
+ end
47
+ end
48
+ end
@@ -1,28 +1,28 @@
1
1
  module Hipbot
2
- class Message
3
- attr_reader :body, :sender, :raw_body
2
+ class Message < Struct.new(:raw_body, :room, :sender)
3
+ attr_accessor :body, :recipients
4
4
 
5
- def initialize body, sender
6
- @raw_body = body
7
- @body = strip_recipient(body)
8
- @sender = sender
9
- end
10
-
11
- def recipients
12
- results = raw_body.scan(/@(\w+)/) + raw_body.scan(/@"(.*)"/)
13
- results.flatten.uniq
5
+ def initialize *args
6
+ super
7
+ 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
14
10
  end
15
11
 
16
12
  def for? recipient
17
- recipients.include? recipient.to_s.gsub(/\s+/, '')
13
+ recipients.include? recipient.mention
18
14
  end
19
15
 
20
16
  def strip_recipient body
21
- body.gsub(/^@\w+\W*/, '')
17
+ body.gsub(/^@\p{Word}++[^\p{Word}]*/, '').strip
22
18
  end
23
19
 
24
20
  def mentions
25
21
  recipients[1..-1] || [] # TODO: Fix global message case
26
22
  end
23
+
24
+ def private?
25
+ room.nil?
26
+ end
27
27
  end
28
28
  end
@@ -52,6 +52,10 @@ module Jabber
52
52
  @my_jid.resource
53
53
  end
54
54
 
55
+ def name= resource
56
+ @my_jid.resource = resource
57
+ end
58
+
55
59
  def on_presence(prio = 0, ref = nil, &block)
56
60
  @presence_cbs.add(prio, ref) do |room_jid, user_name, pres_type|
57
61
  block.call(room_jid, user_name, pres_type)
@@ -89,6 +93,30 @@ module Jabber
89
93
  @stream.send(pres) { |r| block.call(r) }
90
94
  end
91
95
 
96
+ def kick(recipients, room_jid)
97
+ iq = Iq.new(:set, room_jid)
98
+ iq.from = @my_jid
99
+ iq.add(IqQueryMUCAdmin.new)
100
+ recipients.each do |recipient|
101
+ item = IqQueryMUCAdminItem.new
102
+ item.nick = recipient
103
+ item.role = :none
104
+ iq.query.add(item)
105
+ end
106
+ @stream.send_with_id(iq)
107
+ end
108
+
109
+ def invite(recipients, room_jid)
110
+ msg = Message.new
111
+ msg.from = @my_jid
112
+ msg.to = room_jid
113
+ x = msg.add(XMUCUser.new)
114
+ recipients.each do |jid|
115
+ x.add(XMUCUserInvite.new(jid))
116
+ end
117
+ @stream.send(msg)
118
+ end
119
+
92
120
  def send_message(type, jid, text, subject = nil)
93
121
  message = Message.new(JID.new(jid), text.to_s)
94
122
  message.type = type
@@ -112,6 +140,8 @@ module Jabber
112
140
  Jabber::debuglog "MUCBrowser initialized"
113
141
  @conference_domain = @muc_browser.muc_rooms(@chat_domain).keys.first
114
142
  Jabber::debuglog "No conference domain found" if !@conference_domain.present?
143
+ @roster = Roster::Helper.new(@stream) # TODO: Error handling
144
+ @vcard = Vcard::Helper.new(@stream) # TODO: Error handling
115
145
  true
116
146
  rescue => e
117
147
  Jabber::debuglog "Connection failed"
@@ -121,7 +151,7 @@ module Jabber
121
151
 
122
152
  def activate_callbacks
123
153
  @stream.add_presence_callback(150, self) { |presence|
124
- @presence_cbs.process(presence.from.strip, presence.from.resource, presence.type.to_s)
154
+ @presence_cbs.process(presence.from.strip.to_s, presence.from.resource, presence.type.to_s)
125
155
  }
126
156
 
127
157
  @stream.add_message_callback(150, self) { |message|
@@ -141,7 +171,7 @@ module Jabber
141
171
  details = {}
142
172
  item.first.children.each{ |c| details[c.name] = c.text }
143
173
  rooms << {
144
- :item => item,
174
+ :item => item,
145
175
  :details => details
146
176
  }
147
177
  end
@@ -150,15 +180,23 @@ module Jabber
150
180
  end
151
181
 
152
182
  def get_users
153
- roster = Roster::Helper.new(@stream) # TODO: Error handling
154
- vcard = Vcard::Helper.new(@stream) # TODO: Error handling
155
- roster.wait_for_roster
156
- roster.items.map do |jid, item|
183
+ @roster.wait_for_roster
184
+ @roster.items.map do |jid, item|
157
185
  {
158
- :item => item,
159
- :vcard => vcard.get(jid)
186
+ jid: item.jid.to_s,
187
+ name: item.iname,
188
+ mention: item.attributes['mention_name'],
160
189
  }
161
- end.compact
190
+ end
191
+ end
192
+
193
+ def get_user_details user_jid
194
+ vcard = @vcard.get(user_jid)
195
+ {
196
+ email: vcard['EMAIL/USERID'],
197
+ title: vcard['TITLE'],
198
+ photo: vcard['PHOTO'],
199
+ }
162
200
  end
163
201
 
164
202
  def deactivate_callbacks
@@ -173,11 +211,11 @@ module Jabber
173
211
  if is_invite?(message)
174
212
  room_name = message.children.last.first_element_text('name')
175
213
  topic = message.children.last.first_element_text('topic')
176
- @invite_cbs.process(message.from.strip, message.from.resource, room_name, topic)
214
+ @invite_cbs.process(message.from.strip.to_s, message.from.resource, room_name, topic)
177
215
  elsif message.type == :chat
178
- @private_message_cbs.process(message.from.strip, message)
216
+ @private_message_cbs.process(message.from.strip.to_s, message)
179
217
  elsif message.type == :groupchat
180
- @message_cbs.process(message.from.strip, message.from.resource, message)
218
+ @message_cbs.process(message.from.strip.to_s, message.from.resource, message)
181
219
  elsif message.type == :error
182
220
  false
183
221
  end