hipbot 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,9 @@
1
1
  module Hipbot
2
2
  class Bot
3
3
  attr_accessor :reactions, :configuration, :connection
4
- CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter, :helpers]
4
+ cattr_accessor :default_reaction
5
+
6
+ CONFIGURABLE_OPTIONS = [:name, :jid, :password, :adapter, :helpers, :teams, :rooms]
5
7
  delegate *CONFIGURABLE_OPTIONS, to: :configuration
6
8
  alias_method :to_s, :name
7
9
 
@@ -11,6 +13,7 @@ module Hipbot
11
13
  self.class.reactions.each do |opts|
12
14
  on(*opts[0], &opts[-1])
13
15
  end
16
+ on(*default_reaction[0], &default_reaction[-1]) if default_reaction.present?
14
17
  extend(self.adapter || ::Hipbot::Adapters::Hipchat)
15
18
  end
16
19
 
@@ -19,25 +22,21 @@ module Hipbot
19
22
  self.reactions << Reaction.new(self, regexps, options, block)
20
23
  end
21
24
 
22
- def tell sender, room, message
23
- return if sender == name
25
+ def react sender, room, message
24
26
  matches = matching_reactions(sender, room, message)
25
- if matches.size > 0
26
- matches.first.invoke(sender, room, message)
27
- end
28
- end
29
-
30
- def reactions_list
31
- self.reactions.regexp
27
+ matches.first.invoke(sender, room, message) if matches.size > 0
32
28
  end
33
29
 
34
30
  class << self
35
-
36
31
  def on *regexps, &block
37
32
  @reactions ||= []
38
33
  @reactions << [regexps, block]
39
34
  end
40
35
 
36
+ def default &block
37
+ @@default_reaction = [[/(.*)/], block]
38
+ end
39
+
41
40
  def configure &block
42
41
  @configuration = block
43
42
  end
@@ -53,20 +52,12 @@ module Hipbot
53
52
  def start!
54
53
  new.start!
55
54
  end
56
-
57
55
  end
58
56
 
59
57
  private
60
58
 
61
59
  def matching_reactions sender, room, message
62
- all_reactions = reactions + [default_reaction]
63
- all_reactions.select { |r| r.match?(sender, room, message) }
64
- end
65
-
66
- def default_reaction
67
- @default_reaction ||= Reaction.new(self, [/.*/], {}, Proc.new {
68
- reply("I don't understand \"#{message.body}\"")
69
- })
60
+ self.reactions.select { |r| r.match?(sender, room, message) }
70
61
  end
71
62
 
72
63
  end
@@ -0,0 +1,48 @@
1
+ module Hipbot
2
+ class Collection < Struct.new(:id, :name, :params)
3
+ private_class_method :new
4
+ alias_method :to_s, :name
5
+
6
+ def initialize *args
7
+ super
8
+ self.params = OpenStruct.new(params)
9
+ end
10
+
11
+ def delete
12
+ self.class.collection.delete(self.id)
13
+ end
14
+
15
+ class << self
16
+ attr_accessor :bot
17
+
18
+ def create *args, &block
19
+ collection[args[0]] = new(*args, &block)
20
+ end
21
+
22
+ def collection
23
+ @collection ||= {}
24
+ end
25
+
26
+ def [] *items
27
+ items.first.is_a?(Array) ? find_many(*items) : find_one(items.first)
28
+ end
29
+
30
+ def find_one item
31
+ collection[item] || collection.find{ |_, i| i.name == item }.try(:last)
32
+ end
33
+
34
+ def find_many *items
35
+ items.flatten!
36
+ items.map{ |i| find_one(i) }.compact.uniq
37
+ end
38
+
39
+ protected
40
+
41
+ def method_missing name, *args, &block
42
+ return collection.public_send(name, *args, &block) if collection.respond_to?(name)
43
+ super
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -3,9 +3,11 @@ module Hipbot
3
3
  attr_accessor *Bot::CONFIGURABLE_OPTIONS
4
4
 
5
5
  def initialize
6
- self.name = 'robot'
7
- self.jid = 'changeme'
6
+ self.name = 'robot'
7
+ self.jid = 'changeme'
8
8
  self.password = 'changeme'
9
+ self.teams = {}
10
+ self.rooms = {}
9
11
  end
10
12
 
11
13
  def helpers
@@ -1,10 +1,11 @@
1
1
  module Hipbot
2
2
  class Message
3
- attr_accessor :body, :sender, :raw_body
3
+ attr_reader :body, :sender, :raw_body
4
+
4
5
  def initialize body, sender
5
- self.raw_body = body
6
- self.body = strip_recipient(body)
7
- self.sender = sender
6
+ @raw_body = body
7
+ @body = strip_recipient(body)
8
+ @sender = sender
8
9
  end
9
10
 
10
11
  def recipients
@@ -20,12 +21,8 @@ module Hipbot
20
21
  body.gsub(/^@\w+\W*/, '')
21
22
  end
22
23
 
23
- def sender_name
24
- sender.split.first
25
- end
26
-
27
24
  def mentions
28
- recipients[1..-1]
25
+ recipients[1..-1] # TODO: Fix global message case
29
26
  end
30
27
  end
31
28
  end
@@ -0,0 +1,192 @@
1
+ require 'xmpp4r/muc/x/muc'
2
+ require 'xmpp4r/muc/iq/mucowner'
3
+ require 'xmpp4r/muc/iq/mucadmin'
4
+ require 'xmpp4r/dataforms'
5
+ require 'xmpp4r/roster'
6
+ require 'xmpp4r/vcard'
7
+
8
+ module Jabber
9
+ module MUC
10
+ class HipchatClient
11
+
12
+ def initialize(jid)
13
+ @my_jid = JID.new(jid)
14
+
15
+ @stream = Client.new(@my_jid.strip) # TODO: Error Handling
16
+ Jabber::debuglog "Stream initialized"
17
+ @chat_domain = @my_jid.domain
18
+
19
+ @presence_cbs = CallbackList.new
20
+ @message_cbs = CallbackList.new
21
+ @private_message_cbs = CallbackList.new
22
+ @invite_cbs = CallbackList.new
23
+ end
24
+
25
+ def join(jid, password = nil, opts = { :history => false })
26
+ room_jid = JID.new(jid)
27
+ xmuc = XMUC.new
28
+ xmuc.password = password
29
+
30
+ if !opts[:history]
31
+ history = REXML::Element.new('history').tap{ |h| h.add_attribute('maxstanzas','0') }
32
+ xmuc.add_element history
33
+ end
34
+
35
+ room_jid.resource = name
36
+ set_presence(:available, room_jid, nil, xmuc) # TODO: Handle all join responses
37
+ end
38
+
39
+ def exit(jid, reason = nil)
40
+ room_jid = JID.new(jid)
41
+ Jabber::debuglog "Exiting #{jid}"
42
+ set_presence(:unavailable, room_jid, reason)
43
+ end
44
+
45
+ def keep_alive password
46
+ if @stream.is_disconnected?
47
+ connect(password)
48
+ end
49
+ end
50
+
51
+ def name
52
+ @my_jid.resource
53
+ end
54
+
55
+ def on_presence(prio = 0, ref = nil, &block)
56
+ @presence_cbs.add(prio, ref) do |room_jid, user_name, pres_type|
57
+ block.call(room_jid, user_name, pres_type)
58
+ false
59
+ end
60
+ end
61
+
62
+ def on_message(prio = 0, ref = nil, &block)
63
+ @message_cbs.add(prio, ref) do |room_jid, user_name, message|
64
+ block.call(room_jid, user_name, message)
65
+ false
66
+ end
67
+ end
68
+
69
+ def on_private_message(prio = 0, ref = nil, &block)
70
+ @private_message_cbs.add(prio, ref) do |user_jid, message|
71
+ block.call(user_jid, message)
72
+ false
73
+ end
74
+ end
75
+
76
+ def on_invite(prio = 0, ref = nil, &block)
77
+ @invite_cbs.add(prio, ref) do |room_jid, user_name, room_name, topic|
78
+ block.call(room_jid, user_name, room_name, topic)
79
+ false
80
+ end
81
+ end
82
+
83
+ def set_presence(type, to = nil, reason = nil, xmuc = nil, &block)
84
+ pres = Presence.new(:chat, reason)
85
+ pres.type = type
86
+ pres.to = to if to
87
+ pres.from = @my_jid
88
+ pres.add(xmuc) if xmuc
89
+ @stream.send(pres) { |r| block.call(r) }
90
+ end
91
+
92
+ def send_message(type, jid, text, subject = nil)
93
+ message = Message.new(JID.new(jid), text)
94
+ message.type = type
95
+ message.from = @my_jid
96
+ message.subject = subject
97
+
98
+ @send_thread.join if @send_thread.present? && @send_thread.alive?
99
+ @send_thread = Thread.new {
100
+ @stream.send(message)
101
+ sleep(0.2)
102
+ }
103
+ end
104
+
105
+ def connect password
106
+ begin
107
+ @stream.connect
108
+ Jabber::debuglog "Connected to stream"
109
+ @stream.auth(password)
110
+ Jabber::debuglog "Authenticated"
111
+ @muc_browser = MUCBrowser.new(@stream)
112
+ Jabber::debuglog "MUCBrowser initialized"
113
+ @conference_domain = @muc_browser.muc_rooms(@chat_domain).keys.first
114
+ Jabber::debuglog "No conference domain found" if !@conference_domain.present?
115
+ true
116
+ rescue => e
117
+ Jabber::debuglog "Connection failed"
118
+ false
119
+ end
120
+ end
121
+
122
+ def activate_callbacks
123
+ @stream.add_presence_callback(150, self) { |presence|
124
+ @presence_cbs.process(presence.from.strip, presence.from.resource, presence.type.to_s)
125
+ }
126
+
127
+ @stream.add_message_callback(150, self) { |message|
128
+ handle_message(message)
129
+ }
130
+ Jabber::debuglog "Callbacks activated"
131
+ end
132
+
133
+ def get_rooms
134
+ iq = Iq.new(:get, @conference_domain)
135
+ iq.from = @stream.jid
136
+ iq.add(Discovery::IqQueryDiscoItems.new)
137
+
138
+ rooms = []
139
+ @stream.send_with_id(iq) do |answer|
140
+ answer.query.each_element('item') do |item|
141
+ details = {}
142
+ item.first.children.each{ |c| details[c.name] = c.text }
143
+ rooms << {
144
+ :item => item,
145
+ :details => details
146
+ }
147
+ end
148
+ end
149
+ rooms
150
+ end
151
+
152
+ 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|
157
+ {
158
+ :item => item,
159
+ :vcard => vcard.get(jid)
160
+ }
161
+ end.compact
162
+ end
163
+
164
+ def deactivate_callbacks
165
+ @stream.delete_presence_callback(self)
166
+ @stream.delete_message_callback(self)
167
+ Jabber::debuglog "Callbacks deactivated"
168
+ end
169
+
170
+ private
171
+
172
+ def handle_message(message)
173
+ if is_invite?(message)
174
+ room_name = message.children.last.first_element_text('name')
175
+ topic = message.children.last.first_element_text('topic')
176
+ @invite_cbs.process(message.from.strip, message.from.resource, room_name, topic)
177
+ elsif message.type == :chat
178
+ @private_message_cbs.process(message.from.strip, message)
179
+ elsif message.type == :groupchat
180
+ @message_cbs.process(message.from.strip, message.from.resource, message)
181
+ elsif message.type == :error
182
+ false
183
+ end
184
+ end
185
+
186
+ def is_invite?(message)
187
+ !message.x.nil? && message.x.kind_of?(XMUCUser) && message.x.first.kind_of?(XMUCUserInvite)
188
+ end
189
+
190
+ end
191
+ end
192
+ end
@@ -1,54 +1,61 @@
1
1
  module Hipbot
2
- class Reaction < Struct.new(:robot, :regexps, :options, :block)
2
+ class Reaction < Struct.new(:bot, :regexps, :options, :block)
3
3
 
4
4
  def invoke sender, room, message
5
- message = message_for(message, sender)
5
+ message = message_for(message, sender)
6
6
  arguments = arguments_for(message)
7
- Response.new(robot, self, room, message).invoke(arguments)
7
+ Response.new(bot, self, room, message).invoke(arguments)
8
8
  end
9
9
 
10
10
  def match? sender, room, message
11
11
  message = message_for(message, sender)
12
- matches_regexp?(message) && matches_scope?(message) && matches_sender?(message) && matches_room?(room)
12
+ matches_regexp?(message) && matches_scope?(room, message) && matches_sender?(message) && matches_room?(room)
13
13
  end
14
14
 
15
- private
15
+ protected
16
16
 
17
- # TODO: this is pointless since we can get actual message object from xmpp4r
18
17
  def message_for message, sender
19
18
  Message.new(message, sender)
20
19
  end
21
20
 
22
21
  def arguments_for message
23
- message.body.match(matching_regexp(message))[1..-1]
22
+ (global? ? message.raw_body : message.body).match(matching_regexp(message))[1..-1]
24
23
  end
25
24
 
26
25
  def matches_regexp?(message)
27
26
  matching_regexp(message).present?
28
27
  end
29
28
 
30
- def matches_room?(room)
31
- !options[:room] || Array(options[:room]).include?(room.name)
29
+ def matching_regexp(message)
30
+ regexps.find{ |regexp| regexp =~ (global? ? message.raw_body : message.body) }
32
31
  end
33
32
 
34
- def matches_scope?(message)
35
- global? || message.for?(robot)
33
+ def matches_scope?(room, message)
34
+ global? || message.for?(bot) || room.nil?
36
35
  end
37
36
 
38
- def matches_sender?(message)
39
- from_all? || Array(options[:from]).include?(message.sender)
37
+ def matches_room?(room)
38
+ !options[:room] || rooms.include?(room.name) || room.nil?
40
39
  end
41
40
 
42
- def matching_regexp(message)
43
- regexps.find { |regexp| regexp =~ message.body }
41
+ def matches_sender?(message)
42
+ from_all? || users.include?(message.sender.name)
44
43
  end
45
44
 
46
45
  def global?
47
- options[:global]
46
+ !!options[:global]
48
47
  end
49
48
 
50
49
  def from_all?
51
- !options[:from]
50
+ options[:from].blank?
51
+ end
52
+
53
+ def rooms
54
+ Array(options[:room]).flat_map{ |v| bot.rooms[v].presence || [v] }
55
+ end
56
+
57
+ def users
58
+ Array(options[:from]).flat_map{ |v| bot.teams[v].presence || [v] }
52
59
  end
53
60
  end
54
61
  end
@@ -14,15 +14,15 @@ module Hipbot
14
14
  private
15
15
 
16
16
  def reply string, room = self.room
17
- bot.reply(room, string)
17
+ return bot.send_to_user(sender, string) if room.nil?
18
+ bot.send_to_room(room, string)
18
19
  end
19
20
 
20
21
  [:get, :post, :put, :delete].each do |http_verb|
21
- define_method http_verb do |url, query={}, &block|
22
+ define_method http_verb do |url, query = {}, &block|
22
23
  http = ::EM::HttpRequest.new(url).send(http_verb, :query => query)
23
24
  http.callback { block.call(::Hipbot::HttpResponse.new(http)) if block }
24
25
  end
25
26
  end
26
-
27
27
  end
28
28
  end