hipbot 0.0.5 → 0.1.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.
@@ -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