xmpp4r-hipchat 0.0.4 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c86b726905b0644b457090253260866f05b4c2d6
4
- data.tar.gz: b7d3c6e6a93f9e683f0eaac4594dd326c02be89a
3
+ metadata.gz: dd02f79e1cc61f8bdd46302cbe12327ce2bbfeab
4
+ data.tar.gz: 965a232d7d9358dc15d7d2d50ba6ef4040491fca
5
5
  SHA512:
6
- metadata.gz: 40b8a1f78cd6e3cf843246b60277b959256d7688920843b968cdffb1860af209fb4745c41b211ce2941a59a221abda5c37882921fc4fa8da148f4a53bddc39c5
7
- data.tar.gz: 49ffa540d99c34c845df79542c80d4f97c46ede1737dffb88d1c199dcd0a2df60b89de01576eed92dc4f3d0c33c4ad467c689197498a229b5f4886308a2328c1
6
+ metadata.gz: '0196c7eb22c8717f3455c233cd2561870dc4e959e3d608a2d84be9525c85c5557ecf6784274c9cc281cead2f0e2712cee7a923922920143759d5f1b04ae0eb58'
7
+ data.tar.gz: 2fdb5fac322cc4fcd6d501c3d57be8c95ab3f0f51a01e2ceb5f2272326e2eb64edd7d66235728250202ee4b8a948a134bb1fdf63f4c05b7401957781d6cece52
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .ruby-version
6
+ .yardoc
7
+ _yardoc
8
+ coverage
9
+ coverage/*
10
+ doc/
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Xmpp4r::Hipchat
1
+ # XMPP4R-Hipchat
2
2
 
3
- TODO: Write a gem description
3
+ This is a HipChat / Slack XMPP adapter using XMPP4R lib.
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,7 +18,7 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- TODO: Write usage instructions here
21
+ Please refer to [Hipbot](https://github.com/pewniak747/hipbot) source code for up-to-date usage example. This gem is still in development.
22
22
 
23
23
  ## Contributing
24
24
 
@@ -6,4 +6,16 @@ require 'xmpp4r/muc/iq/mucadmin'
6
6
  require 'xmpp4r/dataforms'
7
7
  require 'xmpp4r/roster'
8
8
  require 'xmpp4r/vcard'
9
+
10
+ require 'xmpp4r/muc/hipchat/message'
11
+ require 'xmpp4r/muc/hipchat/kick_message'
12
+
13
+ require 'xmpp4r/muc/hipchat/presence'
14
+
15
+ require 'xmpp4r/muc/hipchat/received_stanza'
16
+ require 'xmpp4r/muc/hipchat/received_presence'
17
+ require 'xmpp4r/muc/hipchat/received_message'
18
+ require 'xmpp4r/muc/hipchat/room_data'
19
+ require 'xmpp4r/muc/hipchat/vcard'
20
+ require 'xmpp4r/muc/hipchat/user_data'
9
21
  require 'xmpp4r/muc/helper/hipchat_client'
@@ -1,5 +1,5 @@
1
1
  module XMPP4R
2
2
  module HipChat
3
- VERSION = '0.0.4'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
@@ -1,224 +1,217 @@
1
1
  module Jabber
2
2
  module MUC
3
3
  class HipchatClient
4
- attr_accessor :my_jid, :chat_domain, :conference_domain, :stream
4
+ attr_reader :my_jid
5
5
 
6
- def initialize(jid)
7
- self.my_jid = JID.new(jid)
8
- self.stream = Client.new(my_jid.strip) # TODO: Error Handling
9
- Jabber::debuglog "Stream initialized"
10
- self.chat_domain = my_jid.domain
6
+ def initialize jid, conference_host = nil
7
+ @my_jid = JID.new(jid)
11
8
 
12
- @callbacks = Hash.new { |hash, key| hash[key] = CallbackList.new }
9
+ @presence = HipChat::Presence.new(my_jid)
10
+ @message = HipChat::Message.new(my_jid)
11
+
12
+ @conference_host = conference_host
13
13
  end
14
14
 
15
- def join(jid, password = nil, opts = { history: false })
16
- room_jid = JID.new(jid)
17
- xmuc = XMUC.new
18
- xmuc.password = password
15
+ def name
16
+ my_jid.resource
17
+ end
19
18
 
20
- if !opts[:history]
21
- history = REXML::Element.new('history').tap{ |h| h.add_attribute('maxstanzas','0') }
22
- xmuc.add_element history
23
- end
19
+ def name= resource
20
+ my_jid.resource = resource
21
+ end
24
22
 
25
- room_jid.resource = name
26
- set_presence(:available, room_jid, nil, xmuc) # TODO: Handle all join responses
23
+ ## Actions
24
+
25
+ def join room_id, fetch_history = false
26
+ jid = JID.new(room_id, conference_host)
27
+ Jabber::debuglog "Joining #{jid}"
28
+ @presence.get_join(jid, fetch_history).send_to(stream)
27
29
  end
28
30
 
29
- def exit(jid, reason = nil)
30
- room_jid = JID.new(jid)
31
+ def exit room_id, reason = nil
32
+ jid = JID.new(room_id, conference_host)
31
33
  Jabber::debuglog "Exiting #{jid}"
32
- set_presence(:unavailable, room_jid, reason)
34
+ @presence.get_leave(jid, reason).send_to(stream)
33
35
  end
34
36
 
35
- def keep_alive password
36
- if stream.is_disconnected?
37
- connect(password)
38
- end
37
+ def set_presence status = nil, type = :available, room_id = nil
38
+ room_jid = room_id ? JID.new(room_id, conference_host) : nil
39
+ Jabber::debuglog "Setting presence to #{type} in #{room_jid} with #{status}"
40
+ @presence.get_status(type, room_jid, status).send_to(stream)
39
41
  end
40
42
 
41
- def name
42
- my_jid.resource
43
+ def kick user_ids, room_id
44
+ room_jid = JID.new(room_id, conference_host)
45
+ user_jids = user_ids.map{ |id| JID.new(id, chat_host) }
46
+ Jabber::debuglog "Kicking #{user_jids} from #{room_jid}"
47
+ HipChat::KickMessage.new(my_jid).make(room_jid, user_jids).send_to(stream)
43
48
  end
44
49
 
45
- def name= resource
46
- my_jid.resource = resource
50
+ def invite user_ids, room_id
51
+ room_jid = JID.new(room_id, conference_host)
52
+ user_jids = user_ids.map{ |id| JID.new(id, chat_host) }
53
+ Jabber::debuglog "Inviting #{user_jids} to #{room_jid}"
54
+ @message.get_invite(room_jid, user_jids).send_to(stream)
47
55
  end
48
56
 
49
- %w(lobby_presence room_presence room_message private_message invite).each do |callback_name|
50
- define_method("on_#{callback_name}") do |prio = 0, ref = nil, &block|
51
- @callbacks[callback_name.to_sym].add(prio, ref) do |*args|
52
- block.call(*args)
53
- end
54
- end
57
+ def send_message type, recipient_id, text, subject = nil
58
+ jid = JID.new(recipient_id, type == :chat ? chat_host : conference_host)
59
+ @message.get_text(type, jid, text, subject).send_to(stream)
55
60
  end
56
61
 
57
- def set_presence(type, to = nil, reason = nil, xmuc = nil, &block)
58
- pres = Presence.new(:chat, reason)
59
- pres.type = type
60
- pres.to = to if to
61
- pres.from = my_jid
62
- pres.add(xmuc) if xmuc
63
- stream.send(pres) { |r| block.call(r) }
64
- end
65
-
66
- def kick(recipients, room_jid)
67
- iq = Iq.new(:set, room_jid)
68
- iq.from = my_jid
69
- iq.add(IqQueryMUCAdmin.new)
70
- recipients.each do |recipient|
71
- item = IqQueryMUCAdminItem.new
72
- item.nick = recipient
73
- item.role = :none
74
- iq.query.add(item)
75
- end
76
- stream.send_with_id(iq)
77
- end
62
+ ## Fetching
78
63
 
79
- def invite(recipients, room_jid)
80
- msg = Message.new
81
- msg.from = my_jid
82
- msg.to = room_jid
83
- x = msg.add(XMUCUser.new)
84
- recipients.each do |jid|
85
- x.add(XMUCUserInvite.new(jid))
86
- end
87
- stream.send(msg)
64
+ def get_rooms
65
+ HipChat::RoomData.get_rooms_data(stream, conference_host)
88
66
  end
89
67
 
90
- def send_message(type, jid, text, subject = nil)
91
- message = Message.new(JID.new(jid), text.to_s)
92
- message.type = type
93
- message.from = my_jid
94
- message.subject = subject
68
+ def get_users
69
+ HipChat::UserData.get_users_data(stream)
70
+ end
95
71
 
96
- @send_thread.join if !@send_thread.nil? && @send_thread.alive?
97
- @send_thread = Thread.new do
98
- stream.send(message)
99
- sleep(0.2)
100
- end
72
+ def get_user_details user_id
73
+ HipChat::VCard.get_details(stream, user_id)
101
74
  end
102
75
 
76
+ ## Connection
77
+
103
78
  def connect password
104
79
  stream.connect
105
80
  Jabber::debuglog "Connected to stream"
106
81
  stream.auth(password)
107
82
  Jabber::debuglog "Authenticated"
108
- @muc_browser = MUCBrowser.new(stream)
109
- Jabber::debuglog "MUCBrowser initialized"
110
- self.conference_domain = @muc_browser.muc_rooms(chat_domain).keys.first
111
- Jabber::debuglog "No conference domain found" if conference_domain.nil?
112
- @roster = Roster::Helper.new(stream) # TODO: Error handling
113
- @vcard = Vcard::Helper.new(stream) # TODO: Error handling
114
83
  true
115
84
  end
116
85
 
117
- def activate_callbacks
118
- stream.add_presence_callback(150, self){ |presence| handle_presence(presence) }
119
- stream.add_message_callback(150, self){ |message| handle_message(message) }
120
- Jabber::debuglog "Callbacks activated"
86
+ def keep_alive password
87
+ if stream.is_disconnected?
88
+ Jabber::debuglog "Stream disconnected. Connecting again..."
89
+ connect(password)
90
+ end
121
91
  end
122
92
 
123
- def get_rooms
124
- iq = Iq.new(:get, conference_domain)
125
- iq.from = stream.jid
126
- iq.add(Discovery::IqQueryDiscoItems.new)
127
-
128
- rooms = []
129
- stream.send_with_id(iq) do |answer|
130
- answer.query.each_element('item') do |item|
131
- details = {}
132
- item.first.children.each{ |c| details[c.name] = c.text }
133
- rooms << {
134
- item: item,
135
- details: details
136
- }
93
+ ## Callbacks
94
+
95
+ CALLBACKS = %w(lobby_presence room_presence room_message private_message room_invite room_topic error)
96
+
97
+ CALLBACKS.each do |callback_name|
98
+ define_method("on_#{callback_name}") do |prio = 0, ref = nil, &block|
99
+ callbacks[callback_name.to_sym].add(prio, ref) do |*args|
100
+ block.call(*args)
137
101
  end
138
102
  end
139
- rooms
140
103
  end
141
104
 
142
- def get_users
143
- @roster.wait_for_roster
144
- @roster.items.map do |jid, item|
145
- {
146
- jid: item.jid.to_s,
147
- name: item.iname,
148
- mention: item.attributes['mention_name'],
149
- }
105
+ def activate_callbacks
106
+ stream.add_stanza_callback(0, self) do |stanza|
107
+ case stanza.name
108
+ when 'message'
109
+ handle_message(HipChat::ReceivedMessage.new(stanza))
110
+ when 'presence'
111
+ handle_presence(HipChat::ReceivedPresence.new(stanza, chat_host))
112
+ end
150
113
  end
151
- end
152
-
153
- def get_user_details user_jid
154
- vcard = @vcard.get(user_jid)
155
- {
156
- email: vcard['EMAIL/USERID'],
157
- title: vcard['TITLE'],
158
- photo: vcard['PHOTO'],
159
- }
114
+ Jabber::debuglog "Callbacks activated"
160
115
  end
161
116
 
162
117
  def deactivate_callbacks
163
- stream.delete_presence_callback(self)
164
- stream.delete_message_callback(self)
118
+ stream.delete_stanza_callback(self)
165
119
  Jabber::debuglog "Callbacks deactivated"
166
120
  end
167
121
 
168
122
  private
169
123
 
170
- def handle_presence(presence)
171
- from_jid = presence.from.strip.to_s
172
- presence_type = presence.type.to_s
173
- user_name = presence.from.resource
124
+ def chat_host
125
+ my_jid.domain
126
+ end
127
+
128
+ def conference_host
129
+ @conference_host ||= begin
130
+ MUCBrowser.new(stream).muc_rooms(chat_host).keys.first.domain
131
+ end
132
+ rescue => e
133
+ Jabber.logger.error("Conference host not found")
134
+ nil
135
+ end
136
+
137
+ def stream
138
+ @stream ||= Client.new(my_jid.strip) # TODO: Error Handling
139
+ end
140
+
141
+ def callbacks
142
+ @callbacks ||= Hash.new { |hash, key| hash[key] = CallbackList.new }
143
+ end
174
144
 
175
- if presence.from.domain == chat_domain
176
- @callbacks[:lobby_presence].process(from_jid, presence_type)
145
+ def handle_presence presence
146
+ if presence.lobby?
147
+ callbacks[:lobby_presence].process(
148
+ presence.sender_id,
149
+ presence.type
150
+ )
177
151
  else
178
- @callbacks[:room_presence].process(from_jid, user_name, presence_type)
152
+ callbacks[:room_presence].process(
153
+ presence.room_id,
154
+ # presence.user_id,
155
+ presence.sender_name,
156
+ presence.type,
157
+ presence.role,
158
+ )
179
159
  end
180
160
  end
181
161
 
182
- def handle_message(message)
183
- if is_invite?(message)
184
- handle_invite(message)
185
- elsif message.type == :chat
162
+ def handle_message message
163
+ case message.type
164
+ when :chat
186
165
  handle_private_message(message)
187
- elsif message.type == :groupchat
188
- handle_group_message(message)
189
- elsif message.type == :error
166
+ when :groupchat
167
+ if message.topic?
168
+ handle_room_topic(message)
169
+ else
170
+ handle_group_message(message)
171
+ end
172
+ when :error
190
173
  handle_error(message)
174
+ else
175
+ handle_invite(message)
191
176
  end
192
177
  end
193
178
 
194
- def handle_invite(message)
195
- room_name = message.children.last.first_element_text('name')
196
- topic = message.children.last.first_element_text('topic')
197
- room_jid = message.from.strip.to_s
198
- user_name = message.from.resource
199
- @callbacks[:invite].process(room_jid, user_name, room_name, topic)
179
+ def handle_invite message
180
+ callbacks[:room_invite].process(
181
+ message.room_id,
182
+ message.room_name,
183
+ )
200
184
  end
201
185
 
202
- def handle_private_message(message)
203
- user_jid = message.from.strip.to_s
204
- message_body = message.body.to_s
205
- @callbacks[:private_message].process(user_jid, message_body)
186
+ def handle_private_message message
187
+ callbacks[:private_message].process(
188
+ message.sender_id,
189
+ message.body,
190
+ )
206
191
  end
207
192
 
208
- def handle_group_message(message)
209
- room_jid = message.from.strip.to_s
210
- user_name = message.from.resource
211
- message_body = message.body.to_s
212
- topic = message.subject.to_s
213
- @callbacks[:room_message].process(room_jid, user_name, message_body, topic)
193
+ def handle_group_message message
194
+ callbacks[:room_message].process(
195
+ message.room_id,
196
+ message.sender_name,
197
+ message.body,
198
+ )
214
199
  end
215
200
 
216
- def handle_error(message)
217
- false
201
+ def handle_room_topic message
202
+ callbacks[:room_topic].process(
203
+ message.room_id,
204
+ message.topic,
205
+ )
218
206
  end
219
207
 
220
- def is_invite?(message)
221
- !message.x.nil? && message.x.kind_of?(XMUCUser) && message.x.first.kind_of?(XMUCUserInvite)
208
+ def handle_error message
209
+ callbacks[:error].process(
210
+ message.room_id,
211
+ message.user_id,
212
+ message.body,
213
+ message.topic,
214
+ )
222
215
  end
223
216
  end
224
217
  end
@@ -0,0 +1,32 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class KickMessage < Iq
5
+ def initialize my_jid
6
+ super(:set)
7
+ self.from = my_jid
8
+ self.add(IqQueryMUCAdmin.new)
9
+ end
10
+
11
+ def make room_jid, recipients
12
+ self.to = room_jid
13
+
14
+ recipients.each do |recipient|
15
+ add_recipient(recipient)
16
+ end
17
+ end
18
+
19
+ def add_recipient nick
20
+ item = IqQueryMUCAdminItem.new
21
+ item.nick = nick
22
+ item.role = :none
23
+ self.query.add(item)
24
+ end
25
+
26
+ def send_to(stream)
27
+ stream.send_with_id(self)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class Message < Jabber::Message
5
+ def initialize my_jid
6
+ super()
7
+ @my_jid = my_jid
8
+ self.from = my_jid
9
+ @semaphore = Mutex.new
10
+ end
11
+
12
+ def get_text type, jid, text, subject = nil
13
+ self.dup.tap do |d|
14
+ recipient = JID.new(jid)
15
+ recipient.resource ||= recipient.node
16
+
17
+ d.body = text.to_s
18
+ d.to = recipient
19
+ d.type = type
20
+ d.subject = subject if subject
21
+ end
22
+ end
23
+
24
+ def get_invite room_jid, recipient_jids
25
+ self.dup.tap do |d|
26
+ d.to = JID.new(room_jid)
27
+
28
+ recipient_jids.each do |recipient_jid|
29
+ d.add_recipient(recipient_jid)
30
+ end
31
+ end
32
+ end
33
+
34
+ def add_recipient jid
35
+ xmuc_user.add(XMUCUserInvite.new(jid))
36
+ end
37
+
38
+ def send_to stream, &block
39
+ Thread.new do
40
+ @semaphore.synchronize {
41
+ stream.send(self, &block)
42
+ sleep(0.2)
43
+ }
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def xmuc_user
50
+ @xmuc_user ||= add(XMUCUser.new)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,52 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class Presence < Jabber::Presence
5
+ def initialize my_jid
6
+ super(:chat)
7
+ @my_jid = my_jid
8
+ self.from = my_jid
9
+ end
10
+
11
+ def get_leave jid, reason = nil
12
+ get_status(:unavailable, room_jid(jid), reason)
13
+ end
14
+
15
+ def get_join jid, fetch_history = false
16
+ get_status(:available, room_jid(jid)) # TODO: Handle all join responses
17
+ end
18
+
19
+ def get_status type, to = nil, status = nil, fetch_history = false
20
+ self.dup.tap do |d|
21
+ d.set_status(status)
22
+ d.set_type(type)
23
+ d.no_history! unless fetch_history
24
+ d.to = to
25
+ end
26
+ end
27
+
28
+ def no_history!
29
+ element = REXML::Element.new('history').tap do |h|
30
+ h.add_attribute('maxstanzas', '0')
31
+ end
32
+
33
+ xmuc = XMUC.new
34
+ xmuc.add_element(element)
35
+ self.add(xmuc)
36
+ end
37
+
38
+ def send_to stream, &block
39
+ stream.send(self, &block)
40
+ end
41
+
42
+ private
43
+
44
+ def room_jid jid
45
+ JID.new(jid).tap do |j|
46
+ j.resource = @my_jid.resource
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,33 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class ReceivedMessage < ReceivedStanza
5
+ alias_method :recipient_id, :user_id
6
+
7
+ def topic
8
+ @stanza.subject.to_s
9
+ end
10
+
11
+ def body
12
+ @stanza.body.to_s
13
+ end
14
+
15
+ ## Invite
16
+
17
+ def topic?
18
+ @stanza.children.first.name == "subject"
19
+ end
20
+
21
+ def invite?
22
+ !@stanza.x.nil? &&
23
+ @stanza.x.kind_of?(XMUCUser) &&
24
+ @stanza.x.first.kind_of?(XMUCUserInvite)
25
+ end
26
+
27
+ def room_name
28
+ @stanza.children.last.first_element_text('name')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class ReceivedPresence < ReceivedStanza
5
+ def initialize stanza, chat_host
6
+ super stanza
7
+ @is_lobby = chat_host == host
8
+ end
9
+
10
+ def lobby?
11
+ @is_lobby
12
+ end
13
+
14
+ def type
15
+ super || @stanza.show || :available
16
+ end
17
+
18
+ ## Room presence
19
+
20
+ def role
21
+ item.affiliation if item
22
+ end
23
+
24
+ private
25
+
26
+ def host
27
+ @stanza.from.domain if @stanza.from
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class ReceivedStanza
5
+ def initialize stanza
6
+ @stanza = stanza
7
+ end
8
+
9
+ # Presence Types: :available, :unavailable, ...
10
+ # Message Types: :chat, :groupchat, :error, ...
11
+ def type
12
+ @stanza.type
13
+ end
14
+
15
+ # User ID is available in presences and private messages
16
+ def user_id
17
+ item.jid.node if item
18
+ end
19
+
20
+ def sender_id
21
+ @stanza.from.node
22
+ end
23
+ alias_method :room_id, :sender_id
24
+
25
+ # Used in room message or presence
26
+ def sender_name
27
+ @stanza.from.resource
28
+ end
29
+
30
+ private
31
+
32
+ def item
33
+ @item ||= begin
34
+ if @stanza.x.respond_to?(:items)
35
+ @stanza.x.items.first
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,46 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class RoomData
5
+ # ATTRIBUTES = [:id, :topic, :privacy, :is_archived, :guest_url, :owner, :last_active, :num_participants]
6
+ attr_accessor :attributes
7
+
8
+ def initialize room
9
+ @room = room
10
+ @attributes = {
11
+ "name" => name,
12
+ "id" => id,
13
+ }
14
+
15
+ room.first.children.each do |c|
16
+ @attributes[c.name] ||= c.text
17
+ end
18
+ end
19
+
20
+ def name
21
+ @room.iname
22
+ end
23
+
24
+ def id
25
+ @room.jid.node
26
+ end
27
+
28
+ class << self
29
+ def get_rooms_data stream, conference_host
30
+ iq = Iq.new(:get, conference_host)
31
+ iq.from = stream.jid
32
+ iq.add(Discovery::IqQueryDiscoItems.new)
33
+
34
+ rooms = []
35
+ stream.send_with_id(iq) do |answer|
36
+ answer.query.each_element('item') do |item|
37
+ rooms << self.new(item)
38
+ end
39
+ end
40
+ rooms
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,50 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class UserData
5
+ def initialize user
6
+ @user = user
7
+ end
8
+
9
+ def id
10
+ @user.jid.node
11
+ end
12
+
13
+ def name
14
+ @user.iname
15
+ end
16
+
17
+ def mention
18
+ @user.attributes['mention_name']
19
+ end
20
+
21
+ def attributes
22
+ {
23
+ name: name,
24
+ mention: mention,
25
+ }
26
+ end
27
+
28
+ class << self
29
+ def get_users_data stream
30
+ @stream ||= stream
31
+ @roster ||= Roster::Helper.new(stream, false) # TODO: Error handling
32
+
33
+ loop do
34
+ rosterget = Iq.new_rosterget
35
+ rosterget.id = "roster_1"
36
+ @stream.send(rosterget)
37
+ @roster.wait_for_roster
38
+ break if @roster.items.any?
39
+ sleep(2)
40
+ end
41
+
42
+ @roster.items.map do |_, item|
43
+ self.new(item)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ module Jabber
2
+ module MUC
3
+ module HipChat
4
+ class VCard
5
+ def initialize vcard
6
+ @vcard = vcard
7
+ end
8
+
9
+ def email
10
+ @vcard['EMAIL/USERID']
11
+ end
12
+
13
+ def title
14
+ @vcard['TITLE']
15
+ end
16
+
17
+ def photo
18
+ @vcard['PHOTO']
19
+ end
20
+
21
+ def attributes
22
+ {
23
+ email: email,
24
+ title: title,
25
+ photo: photo,
26
+ }
27
+ end
28
+
29
+ class << self
30
+ def get_details stream, user_id
31
+ @vcard_helper ||= Vcard::Helper.new(stream)
32
+ user_jid = JID.new(user_id, stream.host)
33
+ vcard = @vcard_helper.get(user_jid)
34
+ VCard.new(vcard)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,149 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ $:.unshift '../lib'
6
+ require 'xmpp4r'
7
+ require 'test/unit'
8
+ require 'socket'
9
+ require 'xmpp4r/semaphore'
10
+
11
+ # Jabber::debug = true
12
+ $ctdebug = false
13
+ # $ctdebug = true
14
+
15
+ # This is sane for tests:
16
+ Thread::abort_on_exception = true
17
+
18
+ # Turn $VERBOSE off to suppress warnings about redefinition
19
+ oldverbose = $VERBOSE
20
+ $VERBOSE = false
21
+
22
+ module Jabber
23
+ ##
24
+ # The ClientTester is a mix-in which provides a setup and teardown
25
+ # method to prepare a Stream object (@client) and the method
26
+ # interfacing as the "server side":
27
+ # * send(xml):: Send a stanza to @client
28
+ #
29
+ # The server side is a stream, too: add your callbacks to @server
30
+ #
31
+ # ClientTester is written to test complex helper classes.
32
+ module ClientTester
33
+ @@SOCKET_PORT = 65223
34
+
35
+ def setup
36
+ servlisten = TCPServer.new(@@SOCKET_PORT)
37
+ serverwait = Semaphore.new
38
+ stream = '<stream:stream xmlns:stream="http://etherx.jabber.org/streams">'
39
+
40
+ @state = 0
41
+ @states = []
42
+
43
+ Thread.new do
44
+ Thread.current.abort_on_exception = true
45
+ serversock = servlisten.accept
46
+ servlisten.close
47
+ serversock.sync = true
48
+ @server = Stream.new
49
+ @server.add_xml_callback do |xml|
50
+ if xml.prefix == 'stream' and xml.name == 'stream'
51
+ send(stream)
52
+ true
53
+ else
54
+ false
55
+ end
56
+ end
57
+ @server.start(serversock)
58
+
59
+ serverwait.run
60
+ end
61
+
62
+ clientsock = TCPSocket.new('localhost', @@SOCKET_PORT)
63
+ clientsock.sync = true
64
+ @client = Stream.new
65
+ #=begin
66
+ class << @client
67
+ def jid
68
+ begin
69
+ #raise
70
+ rescue
71
+ puts $!.backtrace.join("\n")
72
+ end
73
+ JID.new('test@test.com/test')
74
+ end
75
+ end
76
+ #=end
77
+ @client.start(clientsock)
78
+
79
+ @processdone_wait = Semaphore.new
80
+ @nextstate_wait = Semaphore.new
81
+ serverwait.wait
82
+ @server.add_stanza_callback { |stanza|
83
+ # Client prepares everything, then calls wait_state. Problem: because
84
+ # of a race condition, it is possible that we receive the stanza before
85
+ # what to do with it is defined. We busy-wait on @states here.
86
+ n = 0
87
+ while @state >= @states.size and n < 1000
88
+ Thread.pass
89
+ n += 1
90
+ end
91
+ if n == 1000
92
+ puts "Unmanaged stanza in state. Maybe processed by helper?" if $ctdebug
93
+ else
94
+ begin
95
+ puts "Calling #{@states[@state]} for #{stanza.to_s}" if $ctdebug
96
+ @states[@state].call(stanza)
97
+ rescue Exception => e
98
+ puts "Exception in state: #{e.class}: #{e}\n#{e.backtrace.join("\n")}"
99
+ end
100
+ @state += 1
101
+ @nextstate_wait.wait
102
+ @processdone_wait.run
103
+ end
104
+
105
+ false
106
+ }
107
+ @client.send(stream) { |reply| true }
108
+
109
+ end
110
+
111
+ def teardown
112
+ # In some cases, we might lost count of some stanzas
113
+ # (for example, if the handler raises an exception)
114
+ # so we can't block forever.
115
+ n = 0
116
+ while @client.processing > 0 and n < 1000
117
+ Thread::pass
118
+ n += 1
119
+ end
120
+ n = 0
121
+ while @server.processing > 0 and n < 1000
122
+ Thread::pass
123
+ n += 1
124
+ end
125
+ @client.close
126
+ @server.close
127
+ end
128
+
129
+ def send(xml)
130
+ @server.send(xml)
131
+ end
132
+
133
+ def state(&block)
134
+ @states << block
135
+ end
136
+
137
+ def wait_state
138
+ @nextstate_wait.run
139
+ @processdone_wait.wait
140
+ end
141
+
142
+ def skip_state
143
+ @nextstate_wait.run
144
+ end
145
+ end
146
+ end
147
+
148
+ # Restore the old $VERBOSE setting
149
+ $VERBOSE = oldverbose
@@ -0,0 +1,110 @@
1
+ require 'pry'
2
+ require 'test/unit'
3
+ require 'xmpp4r/../../test/lib/clienttester'
4
+ require 'xmpp4r/muc'
5
+ require 'xmpp4r/semaphore'
6
+ # require_relative '../lib/xmpp4r/muc/hipchat_client'
7
+ include Jabber
8
+
9
+ class HipchatClientTest < Test::Unit::TestCase
10
+ include ClientTester
11
+
12
+ def test_new1
13
+ m = MUC::HipchatClient.new(@client.jid)
14
+ assert_equal(@client.jid, m.my_jid)
15
+ assert_equal(@client.jid.domain, m.chat_domain)
16
+ assert_equal(nil, m.conference_domain)
17
+ end
18
+
19
+ def test_complex
20
+ # Jabber.debug = true
21
+ m = MUC::HipchatClient.new(@client.jid)
22
+ m.stream = @client
23
+
24
+ block_args = []
25
+ wait = Semaphore.new
26
+ block = lambda { |*a| block_args = a; wait.run }
27
+ m.on_message(&block)
28
+ m.on_private_message(&block)
29
+ m.on_presence(&block)
30
+ m.on_invite(&block)
31
+ # m.on_leave(&block)
32
+ # m.on_self_leave(&block)
33
+
34
+ state { |pres|
35
+ assert_kind_of(Presence, pres)
36
+ assert_equal(@client.jid, pres.from)
37
+ assert_equal(JID.new('darkcave@macbeth.shakespeare.lit/test'), pres.to)
38
+ send("<presence from='darkcave@macbeth.shakespeare.lit/firstwitch' to='hag66@shakespeare.lit/pda'>" +
39
+ "<x xmlns='http://jabber.org/protocol/muc#user'><item affiliation='owner' role='moderator'/></x>" +
40
+ "</presence>" +
41
+ "<presence from='darkcave@macbeth.shakespeare.lit/secondwitch' to='hag66@shakespeare.lit/pda'>" +
42
+ "<x xmlns='http://jabber.org/protocol/muc#user'><item affiliation='admin' role='moderator'/></x>" +
43
+ "</presence>" +
44
+ "<presence from='darkcave@macbeth.shakespeare.lit/thirdwitch' to='hag66@shakespeare.lit/pda'>" +
45
+ "<x xmlns='http://jabber.org/protocol/muc#user'><item affiliation='member' role='participant'/></x>" +
46
+ "</presence>")
47
+ }
48
+ # m.my_jid = JID.new('hag66@shakespeare.lit/pda')
49
+ assert_equal(m, m.join('darkcave@macbeth.shakespeare.lit/thirdwitch'))
50
+ wait_state
51
+
52
+ state { |msg|
53
+ assert_kind_of(Message, msg)
54
+ assert_equal(:groupchat, msg.type)
55
+ assert_equal(JID.new('hag66@shakespeare.lit/pda'), msg.from)
56
+ assert_equal(JID.new('darkcave@macbeth.shakespeare.lit'), msg.to)
57
+ # assert_equal('TestCasing room', msg.subject)
58
+ assert_nil(msg.body)
59
+ send(msg.set_from('darkcave@macbeth.shakespeare.lit/thirdwitch').set_to('hag66@shakespeare.lit/pda'))
60
+ }
61
+ # assert_nil(m.subject)
62
+ # wait.wait
63
+ # m.subject = 'TestCasing room'
64
+ # wait_state
65
+ # wait.wait
66
+
67
+ # FIXME : **Intermittently** failing (especially during RCOV run) at this line with:
68
+ # 1) Failure:
69
+ # test_complex(HipchatClientTest) [./test/muc/tc_muc_HipchatClient.rb:71]:
70
+ # <[nil, "thirdwitch", "TestCasing room"]> expected but was
71
+ # <[nil, "secondwitch"]>.
72
+ #
73
+ #assert_equal([nil, 'thirdwitch', 'TestCasing room'], block_args)
74
+
75
+ # FIXME : **Intermittently** failing (especially during RCOV run) at this line with:
76
+ # 1) Failure:
77
+ # test_complex(HipchatClientTest) [./test/muc/tc_muc_HipchatClient.rb:80]:
78
+ # <"TestCasing room"> expected but was
79
+ # <nil>.
80
+ #
81
+ #assert_equal('TestCasing room', m.subject)
82
+
83
+ end
84
+
85
+ def test_kick
86
+ m = MUC::HipchatClient.new(@client.jid)
87
+ m.stream = @client
88
+
89
+ state { |presence|
90
+ send("<presence from='test@test/test'/>")
91
+ }
92
+ m.join('test@test/test')
93
+ wait_state
94
+
95
+ state { |iq|
96
+ assert_kind_of(Iq, iq)
97
+ assert_equal('http://jabber.org/protocol/muc#admin', iq.queryns)
98
+ assert_kind_of(MUC::IqQueryMUCAdmin, iq.query)
99
+ assert_equal(1, iq.query.items.size)
100
+ assert_equal('pistol', iq.query.items[0].nick)
101
+ assert_equal(:none, iq.query.items[0].role)
102
+ assert_equal('Avaunt, you cullion!', iq.query.items[0].reason)
103
+ a = iq.answer(false)
104
+ a.type = :result
105
+ send(a)
106
+ }
107
+ m.kick('pistol', 'Avaunt, you cullion!')
108
+ wait_state
109
+ end
110
+ end
@@ -0,0 +1,8 @@
1
+ require_relative '../lib/xmpp4r-hipchat'
2
+
3
+ RSpec.configure do |config|
4
+ config.mock_with :rspec
5
+
6
+ config.before(:all) do
7
+ end
8
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ module Jabber
4
+ module MUC
5
+ describe HipchatClient do
6
+ let(:client_jid){ '00000_000000@chat.hipchat.com' }
7
+ let(:room_jid){ '00000_room@conf.hipchat.com' }
8
+
9
+ subject{ described_class.new(client_jid) }
10
+
11
+ describe '#initialize' do
12
+ it 'assigns JID' do
13
+ expect(subject.my_jid.to_s).to eq(client_jid)
14
+ end
15
+
16
+ it 'assigns chat host' do
17
+ expect(subject.chat_host).to eq('chat.hipchat.com')
18
+ end
19
+
20
+ it 'creates new stream client' do
21
+ expect(subject.stream).to be_instance_of(Jabber::Client)
22
+ end
23
+ end
24
+
25
+ describe '#exit' do
26
+ it 'sets unavailable presence' do
27
+ expect(subject).to receive(:set_presence).with(:unavailable, anything, anything)
28
+ subject.exit(room_jid)
29
+ end
30
+ end
31
+
32
+ describe '#join' do
33
+ it 'sets available presence' do
34
+ expect(subject).to receive(:set_presence).with(:available, anything, anything, anything)
35
+ subject.join(room_jid)
36
+ end
37
+ end
38
+
39
+ describe '#name' do
40
+ it 'returns JID resource' do
41
+ subject.name = 'client name'
42
+ expect(subject.name).to eq('client name')
43
+ end
44
+ end
45
+
46
+ describe '#set_presence' do
47
+ it 'sends a new presence to the stream' do
48
+ expect(subject.stream).to receive(:send).with(kind_of(Jabber::Presence))
49
+ subject.set_presence(:type)
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -5,9 +5,8 @@ Gem::Specification.new do |spec|
5
5
  spec.name = 'xmpp4r-hipchat'
6
6
  spec.version = XMPP4R::HipChat::VERSION
7
7
  spec.authors = ['Bartosz Kopiński']
8
- spec.email = ['bartosz.kopinski@gmail.com']
9
- spec.description = 'HipChat client extension to XMPP4R'
10
- spec.summary = 'HipChat client extension to XMPP4R'
8
+ spec.email = ['bartosz@kopinski.pl']
9
+ spec.summary = 'HipChat / Slack XMPP Client'
11
10
  spec.homepage = 'https://github.com/bartoszkopinski/xmpp4r-hipchat'
12
11
  spec.license = 'MIT'
13
12
 
@@ -15,5 +14,6 @@ Gem::Specification.new do |spec|
15
14
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
15
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
16
  spec.require_paths = ['lib']
18
- spec.add_runtime_dependency 'xmpp4r', ['~> 0.5']
17
+ spec.add_runtime_dependency 'xmpp4r', ['~> 0.5.6']
18
+ spec.add_development_dependency 'rspec', ['>= 2.13.0']
19
19
  end
metadata CHANGED
@@ -1,36 +1,51 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xmpp4r-hipchat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bartosz Kopiński
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-19 00:00:00.000000000 Z
11
+ date: 2017-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xmpp4r
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.5'
19
+ version: 0.5.6
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.5'
27
- description: HipChat client extension to XMPP4R
26
+ version: 0.5.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.13.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.13.0
41
+ description:
28
42
  email:
29
- - bartosz.kopinski@gmail.com
43
+ - bartosz@kopinski.pl
30
44
  executables: []
31
45
  extensions: []
32
46
  extra_rdoc_files: []
33
47
  files:
48
+ - ".gitignore"
34
49
  - Gemfile
35
50
  - LICENSE
36
51
  - LICENSE.txt
@@ -39,6 +54,19 @@ files:
39
54
  - lib/xmpp4r-hipchat.rb
40
55
  - lib/xmpp4r/hipchat/version.rb
41
56
  - lib/xmpp4r/muc/helper/hipchat_client.rb
57
+ - lib/xmpp4r/muc/hipchat/kick_message.rb
58
+ - lib/xmpp4r/muc/hipchat/message.rb
59
+ - lib/xmpp4r/muc/hipchat/presence.rb
60
+ - lib/xmpp4r/muc/hipchat/received_message.rb
61
+ - lib/xmpp4r/muc/hipchat/received_presence.rb
62
+ - lib/xmpp4r/muc/hipchat/received_stanza.rb
63
+ - lib/xmpp4r/muc/hipchat/room_data.rb
64
+ - lib/xmpp4r/muc/hipchat/user_data.rb
65
+ - lib/xmpp4r/muc/hipchat/vcard.rb
66
+ - spec/integration/muc/clienttester.rb
67
+ - spec/integration/muc/tc_muc_hipchat_client.rb
68
+ - spec/spec_helper.rb
69
+ - spec/unit/hipchat_client_spec.rb
42
70
  - xmpp4r-hipchat.gemspec
43
71
  homepage: https://github.com/bartoszkopinski/xmpp4r-hipchat
44
72
  licenses:
@@ -50,18 +78,22 @@ require_paths:
50
78
  - lib
51
79
  required_ruby_version: !ruby/object:Gem::Requirement
52
80
  requirements:
53
- - - '>='
81
+ - - ">="
54
82
  - !ruby/object:Gem::Version
55
83
  version: '0'
56
84
  required_rubygems_version: !ruby/object:Gem::Requirement
57
85
  requirements:
58
- - - '>='
86
+ - - ">="
59
87
  - !ruby/object:Gem::Version
60
88
  version: '0'
61
89
  requirements: []
62
90
  rubyforge_project:
63
- rubygems_version: 2.0.3
91
+ rubygems_version: 2.6.13
64
92
  signing_key:
65
93
  specification_version: 4
66
- summary: HipChat client extension to XMPP4R
67
- test_files: []
94
+ summary: HipChat / Slack XMPP Client
95
+ test_files:
96
+ - spec/integration/muc/clienttester.rb
97
+ - spec/integration/muc/tc_muc_hipchat_client.rb
98
+ - spec/spec_helper.rb
99
+ - spec/unit/hipchat_client_spec.rb