em-xmpp 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
- #TODO: pub, sub, etc.
34
-
35
- def say(body)
36
- msg = connection.message_stanza(:to => jid) do |x|
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
- def subscribe
43
- pres = connection.presence_stanza('to'=>jid.bare, 'type' => 'subscribe')
44
- connection.send_stanza pres
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
- def unsubscribe
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
- def add_to_roster(display_name=nil,groups=[])
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
- connection.send_stanza(query) do |ctx|
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
- connection.send_stanza(query) do |ctx|
82
- f.resume ctx
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
- connection.send_stanza(iq) do |ctx|
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
- connection.send_stanza(iq) do |ctx|
106
- f.resume ctx
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
- Fiber.yield
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