xmpp4r-hipchat 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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