em-xmpp 0.0.10 → 0.0.11
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.
- data/.gitignore +1 -0
- data/README.md +157 -18
- data/bin/xmig +1099 -0
- data/lib/em-xmpp/connection.rb +1 -0
- data/lib/em-xmpp/context.rb +444 -38
- data/lib/em-xmpp/conversation.rb +105 -0
- data/lib/em-xmpp/entity.rb +759 -31
- data/lib/em-xmpp/handler.rb +16 -0
- data/lib/em-xmpp/helpers.rb +207 -0
- data/lib/em-xmpp/jid.rb +2 -1
- data/lib/em-xmpp/namespaces.rb +25 -0
- data/lib/em-xmpp/version.rb +1 -1
- data/samples/hello.rb +25 -4
- metadata +12 -9
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
require 'fiber'
|
3
|
+
module EM::Xmpp
|
4
|
+
class Conversation
|
5
|
+
|
6
|
+
class CallbackSet
|
7
|
+
def initialize
|
8
|
+
@callbacks = {}
|
9
|
+
end
|
10
|
+
def on(key,&blk)
|
11
|
+
@callbacks[key] = blk
|
12
|
+
end
|
13
|
+
def cb(key)
|
14
|
+
@callbacks[key]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Continue = Struct.new(:type, :ctx) do
|
19
|
+
def timeout?
|
20
|
+
type == :timeout
|
21
|
+
end
|
22
|
+
def interrupted?
|
23
|
+
not timeout?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
Timeout = Continue.new(:timeout, nil)
|
27
|
+
|
28
|
+
def self.start(ctx,state)
|
29
|
+
fib = Fiber.new do
|
30
|
+
conv = Fiber.yield
|
31
|
+
yield conv
|
32
|
+
end
|
33
|
+
fib.resume #first resume starts the fiber
|
34
|
+
obj = self.new(ctx,state,fib)
|
35
|
+
fib.resume obj #second resume injects the conversation to call block
|
36
|
+
obj
|
37
|
+
end
|
38
|
+
|
39
|
+
# A list of callbacks
|
40
|
+
attr_reader :callbacks
|
41
|
+
|
42
|
+
# A user-provided state
|
43
|
+
attr_reader :state
|
44
|
+
|
45
|
+
def initialize(ctx,state,fiber=Fiber.current)
|
46
|
+
@connection = ctx.connection
|
47
|
+
@fiber = fiber
|
48
|
+
@state = state
|
49
|
+
@callbacks = CallbackSet.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepare_callbacks(*keys)
|
53
|
+
yield callbacks
|
54
|
+
expect_callbacks *keys
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def expect_callbacks(*keys)
|
59
|
+
missing = keys.reject {|k| callbacks.cb(k) }.map(&:to_s)
|
60
|
+
raise RuntimeError, "missing callbacks #{missing.join(' ')}" unless missing.empty?
|
61
|
+
end
|
62
|
+
public
|
63
|
+
|
64
|
+
def callback(key,&blk)
|
65
|
+
cb = callbacks.cb(key)
|
66
|
+
raise RuntimeError, "no such callback #{key}" unless cb
|
67
|
+
cb.call state, &blk
|
68
|
+
end
|
69
|
+
|
70
|
+
def start_timeout(seconds=:forever)
|
71
|
+
timer = nil
|
72
|
+
unless seconds == :forever
|
73
|
+
timer = EM::Timer.new(seconds) do
|
74
|
+
wake_up Timeout if @fiber
|
75
|
+
end
|
76
|
+
end
|
77
|
+
timer
|
78
|
+
end
|
79
|
+
|
80
|
+
def delay(seconds=:forever)
|
81
|
+
timer = start_timeout seconds
|
82
|
+
ret = Fiber.yield
|
83
|
+
timer.cancel if timer
|
84
|
+
ret
|
85
|
+
end
|
86
|
+
|
87
|
+
def resume(ctx)
|
88
|
+
wake_up Continue.new(:resumed, ctx)
|
89
|
+
end
|
90
|
+
|
91
|
+
def wake_up(obj)
|
92
|
+
@fiber.resume obj
|
93
|
+
end
|
94
|
+
|
95
|
+
def send_stanza(stanza,seconds=:forever)
|
96
|
+
@connection.send_stanza(stanza) do |response|
|
97
|
+
resume response
|
98
|
+
end
|
99
|
+
timer = start_timeout seconds if seconds
|
100
|
+
ret = Fiber.yield unless seconds == :no_response
|
101
|
+
timer.cancel if timer
|
102
|
+
ret
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/em-xmpp/entity.rb
CHANGED
@@ -14,43 +14,73 @@ module EM::Xmpp
|
|
14
14
|
yield self if block_given?
|
15
15
|
end
|
16
16
|
|
17
|
+
# returns the domain entity of this entity
|
17
18
|
def domain
|
18
19
|
Entity.new(connection, jid.domain)
|
19
20
|
end
|
20
21
|
|
22
|
+
# returns the bare entity of this entity
|
21
23
|
def bare
|
22
24
|
Entity.new(connection, jid.bare)
|
23
25
|
end
|
24
26
|
|
27
|
+
# returns the full jid of this entity
|
25
28
|
def full
|
26
29
|
jid.full
|
27
30
|
end
|
28
31
|
|
32
|
+
# to_s is defined as the jid.to_s
|
33
|
+
# so that you can just pass the entity when building stanzas
|
34
|
+
# to refer to the entity (e.g., message_stanza('to' => some_entity) )
|
29
35
|
def to_s
|
30
36
|
jid.to_s
|
31
37
|
end
|
32
38
|
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
x.body body
|
38
|
-
end
|
39
|
-
connection.send_stanza msg
|
39
|
+
# sends a subscription request to the bare entity
|
40
|
+
def subscribe(&blk)
|
41
|
+
pres = connection.presence_stanza('to'=>jid.bare, 'type' => 'subscribe')
|
42
|
+
connection.send_stanza pres, &blk
|
40
43
|
end
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
connection.
|
45
|
+
# send a subscription stanza to accept an incoming subscription request
|
46
|
+
def accept_subscription(&blk)
|
47
|
+
pres = connection.presence_stanza('to'=>jid.bare, 'type' => 'subscribed')
|
48
|
+
connection.send_stanza pres, &blk
|
45
49
|
end
|
46
50
|
|
47
|
-
|
51
|
+
# unsubscribes from from the bare entity
|
52
|
+
def unsubscribe(&blk)
|
48
53
|
pres = connection.presence_stanza('to'=>jid.bare, 'type' => 'unsubscribe')
|
49
|
-
connection.send_stanza pres
|
54
|
+
connection.send_stanza pres, &blk
|
50
55
|
end
|
51
56
|
|
52
|
-
|
57
|
+
# sends some plain message to the entity (use type = 'chat')
|
58
|
+
def say(body, type='chat', xmlproc=nil, &blk)
|
59
|
+
msg = connection.message_stanza(:to => jid, :type => type) do |xml|
|
60
|
+
xml.body body
|
61
|
+
xmlproc.call xml if xmlproc
|
62
|
+
end
|
63
|
+
connection.send_stanza msg, &blk
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def send_iq_stanza_fibered(iq)
|
53
69
|
f = Fiber.current
|
70
|
+
connection.send_stanza(iq) do |ctx|
|
71
|
+
f.resume ctx
|
72
|
+
end
|
73
|
+
Fiber.yield
|
74
|
+
end
|
75
|
+
|
76
|
+
public
|
77
|
+
|
78
|
+
# add the entity (bare) to the roster
|
79
|
+
# optional parameters can set the display name (or friendly name)
|
80
|
+
# for the roster entity
|
81
|
+
#
|
82
|
+
# similarly, you can attach the entity to one or multiple groups
|
83
|
+
def add_to_roster(display_name=nil,groups=[])
|
54
84
|
item_fields = {:jid => jid.bare}
|
55
85
|
item_fields[:name] = display_name if display_name
|
56
86
|
|
@@ -63,14 +93,11 @@ module EM::Xmpp
|
|
63
93
|
end
|
64
94
|
end
|
65
95
|
|
66
|
-
|
67
|
-
f.resume ctx
|
68
|
-
end
|
69
|
-
Fiber.yield
|
96
|
+
send_iq_stanza_fibered query
|
70
97
|
end
|
71
98
|
|
99
|
+
# removes an entity (bare) from the roster
|
72
100
|
def remove_from_roster
|
73
|
-
f = Fiber.current
|
74
101
|
item_fields = {:jid => jid.bare, :subscription => 'remove'}
|
75
102
|
|
76
103
|
query = connection.iq_stanza(:type => 'set') do |iq|
|
@@ -78,34 +105,735 @@ module EM::Xmpp
|
|
78
105
|
q.item item_fields
|
79
106
|
end
|
80
107
|
end
|
81
|
-
|
82
|
-
|
83
|
-
end
|
84
|
-
Fiber.yield
|
108
|
+
|
109
|
+
send_iq_stanza_fibered query
|
85
110
|
end
|
86
111
|
|
112
|
+
# discovers infos (disco#infos) about an entity
|
113
|
+
# can optionally specify a node of the entity
|
87
114
|
def discover_infos(node=nil)
|
88
|
-
f = Fiber.current
|
89
115
|
hash = {'xmlns' => Namespaces::DiscoverInfos}
|
90
116
|
hash['node'] = node if node
|
91
117
|
iq = connection.iq_stanza('to'=>jid) do |xml|
|
92
118
|
xml.query(hash)
|
93
119
|
end
|
94
|
-
|
95
|
-
f.resume ctx
|
96
|
-
end
|
97
|
-
Fiber.yield
|
120
|
+
send_iq_stanza_fibered iq
|
98
121
|
end
|
99
122
|
|
123
|
+
# discovers items (disco#items) of an entity
|
124
|
+
# can optionally specify a node to discover
|
100
125
|
def discover_items(node=nil)
|
101
|
-
f = Fiber.current
|
102
126
|
iq = connection.iq_stanza('to'=>jid.to_s) do |xml|
|
103
127
|
xml.query('xmlns' => Namespaces::DiscoverItems)
|
104
128
|
end
|
105
|
-
|
106
|
-
|
129
|
+
send_iq_stanza_fibered iq
|
130
|
+
end
|
131
|
+
|
132
|
+
# returns a PubSub entity with same bare jid
|
133
|
+
# accepts an optional node-id
|
134
|
+
def pubsub(nid=nil)
|
135
|
+
node_jid = if nid
|
136
|
+
JID.new(jid.node, jid.domain, nid)
|
137
|
+
else
|
138
|
+
jid.to_s
|
139
|
+
end
|
140
|
+
PubSub.new(connection, node_jid)
|
141
|
+
end
|
142
|
+
|
143
|
+
# returns a (file-)Transfer entity with same jid
|
144
|
+
def transfer
|
145
|
+
Transfer.new(connection, jid)
|
146
|
+
end
|
147
|
+
|
148
|
+
# returns an entity to communicate with the Avatar service
|
149
|
+
def avatar
|
150
|
+
Avatar.new(connection, jid.bare)
|
151
|
+
end
|
152
|
+
|
153
|
+
class Transfer < Entity
|
154
|
+
def self.describe_file(path)
|
155
|
+
ret = {}
|
156
|
+
ret[:name] = File.basename path
|
157
|
+
ret[:size] = File.read(path).size #FIXME use file stats
|
158
|
+
ret[:mime] = 'text/plain' #FIXME
|
159
|
+
ret[:hash] = nil #TODO
|
160
|
+
ret[:date] = nil #TODO
|
161
|
+
ret
|
107
162
|
end
|
108
|
-
|
163
|
+
|
164
|
+
def negotiation_request(filedesc,sid,form)
|
165
|
+
si_args = {'profile' => EM::Xmpp::Namespaces::FileTransfer,
|
166
|
+
'mime-type' => filedesc[:mime]
|
167
|
+
}
|
168
|
+
file_args = {'name' => filedesc[:name],
|
169
|
+
'size' => filedesc[:size],
|
170
|
+
'hash' => filedesc[:md5],
|
171
|
+
'date' => filedesc[:date]
|
172
|
+
}
|
173
|
+
iq = connection.iq_stanza('to'=>jid,'type'=>'set') do |xml|
|
174
|
+
xml.si({:xmlns => EM::Xmpp::Namespaces::StreamInitiation, :id => sid}.merge(si_args)) do |si|
|
175
|
+
si.file({:xmlns => EM::Xmpp::Namespaces::FileTransfer}.merge(file_args)) do |file|
|
176
|
+
file.desc filedesc[:description]
|
177
|
+
end
|
178
|
+
si.feature(:xmlns => EM::Xmpp::Namespaces::FeatureNeg) do |feat|
|
179
|
+
connection.build_submit_form(feat,form)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
send_iq_stanza_fibered iq
|
184
|
+
end
|
185
|
+
|
186
|
+
def negotiation_reply(reply_id,form)
|
187
|
+
iq = connection.iq_stanza('to'=>jid,'type'=>'result','id'=>reply_id) do |xml|
|
188
|
+
xml.si(:xmlns => EM::Xmpp::Namespaces::StreamInitiation) do |si|
|
189
|
+
si.feature(:xmlns => EM::Xmpp::Namespaces::FeatureNeg) do |feat|
|
190
|
+
connection.build_submit_form(feat,form)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
connection.send_stanza iq
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class Avatar < Entity
|
199
|
+
Item = Struct.new(:sha1, :data, :width, :height, :mime) do
|
200
|
+
def id
|
201
|
+
sha1 || Digest::SHA1.hexdigest(data)
|
202
|
+
end
|
203
|
+
def b64
|
204
|
+
Base64.strict_encode64 data
|
205
|
+
end
|
206
|
+
def bytes
|
207
|
+
data.size
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def publish(item)
|
212
|
+
publish_data item
|
213
|
+
publish_metadata item
|
214
|
+
end
|
215
|
+
|
216
|
+
def publish_data(item)
|
217
|
+
iq = connection.iq_stanza('type' => 'set','to' => jid) do |xml|
|
218
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
219
|
+
pubsub.publish(:node => EM::Xmpp::Namespaces::AvatarData) do |pub|
|
220
|
+
pub.item(:id => item.id) do |i|
|
221
|
+
i.data({:xmnls => EM::Xmpp::Namespaces::AvatarData}, item.b64)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
send_iq_stanza_fibered iq
|
227
|
+
end
|
228
|
+
|
229
|
+
def publish_metadata(item)
|
230
|
+
iq = connection.iq_stanza('type' => 'set','to' => jid) do |xml|
|
231
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
232
|
+
pubsub.publish(:node => EM::Xmpp::Namespaces::AvatarMetaData) do |pub|
|
233
|
+
pub.item(:id => item.id) do |i|
|
234
|
+
i.metadata({:xmnls => EM::Xmpp::Namespaces::AvatarMetaData}) do |md|
|
235
|
+
md.info(:width => item.width, :height => item.height, :bytes => item.bytes, :type => item.mime, :id => item.id)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
send_iq_stanza_fibered iq
|
242
|
+
end
|
243
|
+
|
244
|
+
def remove
|
245
|
+
iq = connection.iq_stanza('type' => 'set','to' => jid) do |xml|
|
246
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
247
|
+
pubsub.publish(:node => EM::Xmpp::Namespaces::AvatarMetaData) do |pub|
|
248
|
+
pub.item(:id => "current") do |i|
|
249
|
+
i.metadata(:xmlns => EM::Xmpp::Namespaces::AvatarMetaData)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
send_iq_stanza_fibered iq
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
class PubSub < Entity
|
259
|
+
# returns the pubsub entity for a specific node_id of this entity
|
260
|
+
def node(node_id)
|
261
|
+
pubsub(node_id)
|
262
|
+
end
|
263
|
+
|
264
|
+
# returns the node_id of this pubsub entity
|
265
|
+
def node_id
|
266
|
+
jid.resource
|
267
|
+
end
|
268
|
+
|
269
|
+
# requests the list of subscriptions on this PubSub service
|
270
|
+
# returns the iq context for the answer
|
271
|
+
def service_subscriptions
|
272
|
+
iq = connection.iq_stanza('to'=>jid.bare) do |xml|
|
273
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
274
|
+
pubsub.subscriptions
|
275
|
+
end
|
276
|
+
end
|
277
|
+
send_iq_stanza_fibered iq
|
278
|
+
end
|
279
|
+
|
280
|
+
# requests the list of affiliations for this PubSub service
|
281
|
+
# returns the iq context for the answer
|
282
|
+
def service_affiliations
|
283
|
+
iq = connection.iq_stanza('to'=>jid.bare) do |xml|
|
284
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
285
|
+
pubsub.affiliations
|
286
|
+
end
|
287
|
+
end
|
288
|
+
send_iq_stanza_fibered iq
|
289
|
+
end
|
290
|
+
|
291
|
+
# list the subscription on that node
|
292
|
+
# returns the iq context for the answer
|
293
|
+
def subscription_options(subscription_id=nil)
|
294
|
+
params = {}
|
295
|
+
params['subid'] = subscription_id if subscription_id
|
296
|
+
subscribee = connection.jid.bare
|
297
|
+
|
298
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'get') do |xml|
|
299
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |sub|
|
300
|
+
sub.options({'node' => node_id, 'jid'=>subscribee}.merge(params))
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
send_iq_stanza_fibered iq
|
305
|
+
end
|
306
|
+
|
307
|
+
# sets configuration options on this entity
|
308
|
+
# uses a DataForms form
|
309
|
+
# returns the iq context for the answer
|
310
|
+
def configure_subscription(form)
|
311
|
+
subscribee = connection.jid.bare
|
312
|
+
|
313
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
314
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |sub|
|
315
|
+
sub.options({'node' => node_id, 'jid'=>subscribee}) do |options|
|
316
|
+
connection.build_submit_form(options,form)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
send_iq_stanza_fibered iq
|
322
|
+
end
|
323
|
+
|
324
|
+
# retrieve default configuration of this entity
|
325
|
+
# returns the iq context for the answer
|
326
|
+
def default_subscription_configuration
|
327
|
+
subscribee = connection.jid.bare
|
328
|
+
args = {'node' => node_id} if node_id
|
329
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'get') do |xml|
|
330
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |sub|
|
331
|
+
sub.default(args)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
send_iq_stanza_fibered iq
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
# subscribe to this entity
|
340
|
+
# returns the iq context for the answer
|
341
|
+
def subscribe
|
342
|
+
subscribee = connection.jid.bare
|
343
|
+
|
344
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
345
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |sub|
|
346
|
+
sub.subscribe('node' => node_id, 'jid'=>subscribee)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
send_iq_stanza_fibered iq
|
351
|
+
end
|
352
|
+
|
353
|
+
# subscribe and configure this entity at the same time
|
354
|
+
# see subscribe and configure
|
355
|
+
# returns the iq context for the answer
|
356
|
+
def subscribe_and_configure(form)
|
357
|
+
subscribee = connection.jid.bare
|
358
|
+
|
359
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
360
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |sub|
|
361
|
+
sub.subscribe('node' => node_id, 'jid'=>subscribee)
|
362
|
+
sub.options do |options|
|
363
|
+
connection.build_submit_form(options,form)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
send_iq_stanza_fibered iq
|
369
|
+
end
|
370
|
+
|
371
|
+
# unsubscribes from this entity.
|
372
|
+
# One must provide a subscription-id if there
|
373
|
+
# are multiple subscriptions to this node.
|
374
|
+
# returns the iq context for the answer
|
375
|
+
def unsubscribe(subscription_id=nil)
|
376
|
+
params = {}
|
377
|
+
params['subid'] = subscription_id if subscription_id
|
378
|
+
subscribee = connection.jid.bare
|
379
|
+
|
380
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
381
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |sub|
|
382
|
+
sub.unsubscribe({'node' => node_id, 'jid'=>subscribee}.merge(params))
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
send_iq_stanza_fibered iq
|
387
|
+
end
|
388
|
+
|
389
|
+
# list items of this pubsub node
|
390
|
+
# max_items is the maximum number of answers to return in the answer
|
391
|
+
# item_ids is the list of IDs to select from the pubsub node
|
392
|
+
#
|
393
|
+
# returns the iq context for the answer
|
394
|
+
def items(max_items=nil,item_ids=nil)
|
395
|
+
params = {}
|
396
|
+
params['max_items'] = max_items if max_items
|
397
|
+
iq = connection.iq_stanza('to'=>jid.bare) do |xml|
|
398
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |retrieve|
|
399
|
+
retrieve.items({'node' => node_id}.merge(params)) do |items|
|
400
|
+
if item_ids.respond_to?(:each)
|
401
|
+
item_ids.each do |item_id|
|
402
|
+
items('id' => item_id)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
send_iq_stanza_fibered iq
|
410
|
+
end
|
411
|
+
|
412
|
+
# publishes a payload to the pubsub node
|
413
|
+
# if the item_payload responds to :call (e.g., a proc)
|
414
|
+
# then the item_payload will receive :call method with, as unique parameter,
|
415
|
+
# the xml node of the xml builder. this method call should append an entry
|
416
|
+
# node with the payload
|
417
|
+
# otherwise it is just serialized in an entry node
|
418
|
+
#
|
419
|
+
# item_id is an optional ID to identify the payload, otherwise, the
|
420
|
+
# pubsub service will attribute an ID
|
421
|
+
#
|
422
|
+
# returns the iq context for the answer
|
423
|
+
def publish(item_payload,item_id=nil)
|
424
|
+
params = {}
|
425
|
+
params['id'] = item_id if item_id
|
426
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
427
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
428
|
+
pubsub.publish(:node => node_id) do |publish|
|
429
|
+
if item_payload.respond_to?(:call)
|
430
|
+
publish.item(params) { |payload| item_payload.call payload }
|
431
|
+
else
|
432
|
+
publish.item(params) do |item|
|
433
|
+
item.entry(item_payload)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
send_iq_stanza_fibered iq
|
442
|
+
end
|
443
|
+
|
444
|
+
# Retracts an item on a PubSub node given it's item_id.
|
445
|
+
#
|
446
|
+
# returns the iq context for the answer
|
447
|
+
def retract(item_id=nil)
|
448
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
449
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
450
|
+
pubsub.retract(:node => node_id) do |retract|
|
451
|
+
retract.item(:id => item_id)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
send_iq_stanza_fibered iq
|
457
|
+
end
|
458
|
+
|
459
|
+
# Creates the PubSub node.
|
460
|
+
#
|
461
|
+
# returns the iq context for the answer
|
462
|
+
def create
|
463
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
464
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |pubsub|
|
465
|
+
pubsub.create(:node => node_id)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
send_iq_stanza_fibered iq
|
470
|
+
end
|
471
|
+
|
472
|
+
|
473
|
+
# Purges the PubSub node.
|
474
|
+
#
|
475
|
+
# returns the iq context for the answer
|
476
|
+
def purge
|
477
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
478
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |pubsub|
|
479
|
+
pubsub.purge(:node => node_id)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
send_iq_stanza_fibered iq
|
484
|
+
end
|
485
|
+
|
486
|
+
# requests the list of subscriptions on this pubsub node (for the owner)
|
487
|
+
# returns the iq context for the answer
|
488
|
+
def subscriptions
|
489
|
+
iq = connection.iq_stanza('to'=>jid.bare) do |xml|
|
490
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |pubsub|
|
491
|
+
pubsub.subscriptions(:node => node_id)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
send_iq_stanza_fibered iq
|
495
|
+
end
|
496
|
+
|
497
|
+
# requests the list of affiliations on this pubsub node (for the owner)
|
498
|
+
# returns the iq context for the answer
|
499
|
+
def affiliations
|
500
|
+
iq = connection.iq_stanza('to'=>jid.bare) do |xml|
|
501
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |pubsub|
|
502
|
+
pubsub.affiliations(:node => node_id)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
send_iq_stanza_fibered iq
|
506
|
+
end
|
507
|
+
|
508
|
+
# changes the subscription status of a pubsub node (for the owner)
|
509
|
+
# returns the iq context for the answer
|
510
|
+
def modify_subscriptions(subs)
|
511
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
512
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |pubsub|
|
513
|
+
pubsub.subscriptions(:node => node_id) do |node|
|
514
|
+
subs.each do |s|
|
515
|
+
node.subscription(:jid => s.jid, :subscription => s.subscription, :subid => s.sub_id)
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
send_iq_stanza_fibered iq
|
521
|
+
end
|
522
|
+
|
523
|
+
# changes the affiliation status of a pubsub node (for the owner)
|
524
|
+
# returns the iq context for the answer
|
525
|
+
def modify_affiliations(affs)
|
526
|
+
affs = [affs].flatten
|
527
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
528
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |pubsub|
|
529
|
+
pubsub.affiliations(:node => node_id) do |node|
|
530
|
+
affs.each do |s|
|
531
|
+
node.affiliation(:jid => s.jid, :affiliation => s.affiliation)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
send_iq_stanza_fibered iq
|
537
|
+
end
|
538
|
+
|
539
|
+
# deletes the subscription of one or multiple subscribees of a pubsub node (for the owner)
|
540
|
+
# returns the iq context for the answer
|
541
|
+
def delete_subscriptions(jids,subids=nil)
|
542
|
+
jids = [jids].flatten
|
543
|
+
subids = [subids].flatten
|
544
|
+
subs = jids.zip(subids).map{|jid,subid| EM::Xmpp::Context::Contexts::PubsubMain::Subscription.new(jid, nil, 'none', subid, nil)}
|
545
|
+
modify_subscriptions subs
|
546
|
+
end
|
547
|
+
|
548
|
+
# deletes the affiliation of one or multiple subscribees of a pubsub node (for the owner)
|
549
|
+
# returns the iq context for the answer
|
550
|
+
def delete_affiliations(jids)
|
551
|
+
jids = [jids].flatten
|
552
|
+
affs = jids.map{|jid| EM::Xmpp::Context::Contexts::PubsubMain::Affiliation.new(jid, node_id, 'none')}
|
553
|
+
modify_affiliations affs
|
554
|
+
end
|
555
|
+
|
556
|
+
# Deletes the PubSub node.
|
557
|
+
# Optionnaly redirects the node.
|
558
|
+
#
|
559
|
+
# returns the iq context for the answer
|
560
|
+
def delete(redirect_uri=nil)
|
561
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
562
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |pubsub|
|
563
|
+
pubsub.delete(:node => node_id) do |del|
|
564
|
+
del.redirect(:uri => redirect_uri) if redirect_uri
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
send_iq_stanza_fibered iq
|
570
|
+
end
|
571
|
+
|
572
|
+
# Creates the PubSub node with a non-default configuration.
|
573
|
+
#
|
574
|
+
# returns the iq context for the answer
|
575
|
+
def create_and_configure(form)
|
576
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
577
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSub) do |node|
|
578
|
+
node.create('node' => node_id)
|
579
|
+
node.configure do |options|
|
580
|
+
connection.build_submit_form(options,form)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
send_iq_stanza_fibered iq
|
586
|
+
end
|
587
|
+
|
588
|
+
|
589
|
+
# requests the node configuration (for owners)
|
590
|
+
#
|
591
|
+
# returns the iq context for the answer
|
592
|
+
def configuration_options
|
593
|
+
iq = connection.iq_stanza('to'=>jid.bare) do |xml|
|
594
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |node|
|
595
|
+
node.configure('node' => node_id)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
send_iq_stanza_fibered iq
|
600
|
+
end
|
601
|
+
|
602
|
+
# configures the node (for owners)
|
603
|
+
#
|
604
|
+
# returns the iq context for the answer
|
605
|
+
def configure(form)
|
606
|
+
iq = connection.iq_stanza('to'=>jid.bare,'type'=>'set') do |xml|
|
607
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |node|
|
608
|
+
node.configure('node' => node_id) do |config|
|
609
|
+
connection.build_submit_form(config,form)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
send_iq_stanza_fibered iq
|
615
|
+
end
|
616
|
+
|
617
|
+
# retrieve default configuration of this pubsub service
|
618
|
+
#
|
619
|
+
# returns the iq context for the answer
|
620
|
+
def default_configuration
|
621
|
+
iq = connection.iq_stanza('to'=>jid.bare) do |xml|
|
622
|
+
xml.pubsub(:xmlns => EM::Xmpp::Namespaces::PubSubOwner) do |sub|
|
623
|
+
sub.default
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
send_iq_stanza_fibered iq
|
628
|
+
end
|
629
|
+
|
630
|
+
|
631
|
+
end
|
632
|
+
|
633
|
+
# Generates a MUC entity from this entity.
|
634
|
+
# If the nick argument is null then the entity is the MUC itself.
|
635
|
+
# If the nick argument is present, then the entity is the user with
|
636
|
+
# the corresponding nickname.
|
637
|
+
def muc(nick=nil)
|
638
|
+
muc_jid = JID.new jid.node, jid.domain, nick
|
639
|
+
Muc.new(connection, muc_jid)
|
109
640
|
end
|
641
|
+
|
642
|
+
class Muc < Entity
|
643
|
+
# The room corresponding to this entity.
|
644
|
+
def room
|
645
|
+
muc(nil)
|
646
|
+
end
|
647
|
+
|
648
|
+
# Join a MUC.
|
649
|
+
def join(nick,pass=nil,historysize=0,&blk)
|
650
|
+
pres = connection.presence_stanza('to'=> muc(nick).to_s) do |xml|
|
651
|
+
xml.password pass if pass
|
652
|
+
xml.x('xmlns' => Namespaces::Muc) do |x|
|
653
|
+
x.history('maxchars' => historysize.to_s)
|
654
|
+
end
|
655
|
+
end
|
656
|
+
connection.send_stanza pres, &blk
|
657
|
+
end
|
658
|
+
|
659
|
+
# Leave a MUC.
|
660
|
+
def part(nick,msg=nil)
|
661
|
+
pres = connection.presence_stanza('to'=> muc(nick).to_s,'type'=>'unavailable') do |xml|
|
662
|
+
xml.status msg if msg
|
663
|
+
end
|
664
|
+
connection.send_stanza pres
|
665
|
+
end
|
666
|
+
|
667
|
+
# Changes nickname in this room.
|
668
|
+
def change_nick(nick)
|
669
|
+
join(nick)
|
670
|
+
end
|
671
|
+
|
672
|
+
# Say some message in the muc.
|
673
|
+
def say(body, xmlproc=nil, &blk)
|
674
|
+
msg = connection.message_stanza(:to => jid, :type => 'groupchat') do |xml|
|
675
|
+
xml.body body
|
676
|
+
xmlproc.call xml if xmlproc
|
677
|
+
end
|
678
|
+
connection.send_stanza msg, &blk
|
679
|
+
end
|
680
|
+
|
681
|
+
private
|
682
|
+
|
683
|
+
def set_role(role,nick,reason=nil,&blk)
|
684
|
+
iq = connection.iq_stanza(:to => jid,:type => 'set') do |xml|
|
685
|
+
xml.query('xmlns' => Namespaces::MucAdmin) do |q|
|
686
|
+
q.item('nick' => nick, 'role' => role) do |r|
|
687
|
+
r.reason reason if reason
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
send_iq_stanza_fibered iq
|
692
|
+
end
|
693
|
+
|
694
|
+
def set_affiliation(affiliation,affiliated_jid,reason=nil,&blk)
|
695
|
+
iq = connection.iq_stanza(:to => jid,:type => 'set') do |xml|
|
696
|
+
xml.query('xmlns' => Namespaces::MucAdmin) do |q|
|
697
|
+
q.item('affiliation' => affiliation, 'jid' => affiliated_jid) do |r|
|
698
|
+
r.reason reason if reason
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end
|
702
|
+
send_iq_stanza_fibered iq
|
703
|
+
end
|
704
|
+
|
705
|
+
public
|
706
|
+
|
707
|
+
# kick a user (based on his nickname) from the channel
|
708
|
+
def kick(nick,reason="no reason",&blk)
|
709
|
+
set_role 'none', nick, reason, &blk
|
710
|
+
end
|
711
|
+
|
712
|
+
# voice a user (based on his nickname) in a channel
|
713
|
+
def voice(nick,reason=nil,&blk)
|
714
|
+
set_role 'participant', nick, reason, &blk
|
715
|
+
end
|
716
|
+
|
717
|
+
# remove voice flag from a user (based on his nickname) in a channel
|
718
|
+
def unvoice(nick,reason=nil,&blk)
|
719
|
+
set_role 'visitor', nick, reason, &blk
|
720
|
+
end
|
721
|
+
|
722
|
+
# set a ban on a user (from his bare JID) from the channel
|
723
|
+
def ban(jid,reason="no reason",&blk)
|
724
|
+
set_affiliation 'outcast', jid, reason, &blk
|
725
|
+
end
|
726
|
+
|
727
|
+
# lifts the ban on a user (from his bare JID) from the channel
|
728
|
+
def unban(jid,reason=nil,&blk)
|
729
|
+
set_affiliation 'none', jid, reason, &blk
|
730
|
+
end
|
731
|
+
|
732
|
+
# sets membership to the room
|
733
|
+
def member(jid,reason=nil,&blk)
|
734
|
+
set_affiliation 'member', jid, reason, &blk
|
735
|
+
end
|
736
|
+
|
737
|
+
# removes membership to the room
|
738
|
+
def unmember(jid,reason=nil,&blk)
|
739
|
+
set_affiliation 'none', jid, reason, &blk
|
740
|
+
end
|
741
|
+
|
742
|
+
# sets moderator status
|
743
|
+
def moderator(jid,reason=nil,&blk)
|
744
|
+
set_role 'moderator', jid, reason, &blk
|
745
|
+
end
|
746
|
+
|
747
|
+
# removes moderator status
|
748
|
+
def unmoderator(jid,reason=nil,&blk)
|
749
|
+
set_role 'participant', jid, reason, &blk
|
750
|
+
end
|
751
|
+
|
752
|
+
# gives ownership of the room
|
753
|
+
def owner(jid,reason=nil,&blk)
|
754
|
+
set_affiliation 'owner', jid, reason, &blk
|
755
|
+
end
|
756
|
+
|
757
|
+
# removes membership of the room
|
758
|
+
def unowner(jid,reason=nil,&blk)
|
759
|
+
set_affiliation 'admin', jid, reason, &blk
|
760
|
+
end
|
761
|
+
|
762
|
+
# gives admin rights on the room
|
763
|
+
def admin(jid,reason=nil,&blk)
|
764
|
+
set_affiliation 'admin', jid, reason, &blk
|
765
|
+
end
|
766
|
+
|
767
|
+
# removes admin rights on of the room
|
768
|
+
def unadmin(jid,reason=nil,&blk)
|
769
|
+
set_affiliation 'member', jid, reason, &blk
|
770
|
+
end
|
771
|
+
|
772
|
+
#TODO:
|
773
|
+
# get configure-form
|
774
|
+
# configure max users
|
775
|
+
# configure as reserved
|
776
|
+
# configure public jids
|
777
|
+
# create/destroy a room
|
778
|
+
|
779
|
+
# asks for a nickname registration
|
780
|
+
def register_nickname(nick)
|
781
|
+
#TODO: fiber blocks on getting the registration form
|
782
|
+
# user fills-in the form and submit
|
783
|
+
# rooms returns result
|
784
|
+
raise NotImplementedError
|
785
|
+
end
|
786
|
+
|
787
|
+
# request voice to the moderators
|
788
|
+
def request_voice(nick)
|
789
|
+
#TODO: fiber blocks on getting the registration form
|
790
|
+
# user fills-in the form and submit
|
791
|
+
# rooms returns result
|
792
|
+
raise NotImplementedError
|
793
|
+
end
|
794
|
+
|
795
|
+
# gets the list of registered nicknames for that list
|
796
|
+
def registered_nicknames
|
797
|
+
raise NotImplementedError
|
798
|
+
end
|
799
|
+
|
800
|
+
def voice_list
|
801
|
+
raise NotImplementedError
|
802
|
+
end
|
803
|
+
|
804
|
+
def banned_list
|
805
|
+
raise NotImplementedError
|
806
|
+
end
|
807
|
+
|
808
|
+
def owner_list
|
809
|
+
raise NotImplementedError
|
810
|
+
end
|
811
|
+
|
812
|
+
def admin_list
|
813
|
+
raise NotImplementedError
|
814
|
+
end
|
815
|
+
|
816
|
+
# sets the room subject (Message Of The Day)
|
817
|
+
def motd(subject,&blk)
|
818
|
+
msg = connection.message_stanza(:to => jid) do |xml|
|
819
|
+
xml.subject subject
|
820
|
+
end
|
821
|
+
connection.send_stanza msg, &blk
|
822
|
+
end
|
823
|
+
|
824
|
+
# invites someone (based on his jid) to the MUC
|
825
|
+
def invite(invited_jid,reason="no reason",&blk)
|
826
|
+
msg = connection.message_stanza(:to => jid) do |xml|
|
827
|
+
xml.x('xmlns' => Namespaces::MucUser) do |x|
|
828
|
+
x.invite('to' => invited_jid.to_s) do |invite|
|
829
|
+
invite.reason reason
|
830
|
+
end
|
831
|
+
end
|
832
|
+
end
|
833
|
+
connection.send_stanza msg, &blk
|
834
|
+
end
|
835
|
+
|
836
|
+
end
|
837
|
+
|
110
838
|
end
|
111
839
|
end
|