jubjub 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.mdown CHANGED
@@ -50,6 +50,10 @@ Examples
50
50
  #<Jubjub::Muc:0x10166dcb0 @jid="think_linux@conference.jabber.ru" @name="think_linux (n/a)">
51
51
  ...
52
52
 
53
+ # Find a room
54
+ u.mucs('conference.jabber.ru')['tota-room']
55
+ => #<Jubjub::Muc:0x10166e930 @jid="tota-room@conference.jabber.ru" @name="tota-room (5)">
56
+
53
57
  # Create a room
54
58
  room = u.mucs.create('jubjub')
55
59
  => #<Jubjub::Muc:0x101532f58 @jid="jubjub@conference.jabber.org" @name=nil>
@@ -79,6 +83,10 @@ Examples
79
83
  #<Jubjub::Pubsub:0x101f23a30 @service="pubsub.jabber.org" @node="news">
80
84
  ]
81
85
 
86
+ # Find a pubsub node
87
+ u.pubsub['facts']
88
+ => #<Jubjub::Pubsub:0x101f23c88 @service="pubsub.jabber.org" @node="facts">
89
+
82
90
  # List from a different service?
83
91
  u.pubsub('pubsub.jabber.ru')
84
92
  => [
@@ -97,6 +105,29 @@ Examples
97
105
  u.pubsub.subscribe('new_thing')
98
106
  => #<Jubjub::PubsubSubscription:0x101effd60 @service="pubsub.jabber.org" @node="new_thing" @subscriber="theozaurus@jabber.org" @subid="5129CD7935528" @subscription="subscribed">
99
107
 
108
+ # Publish to a node
109
+ item = u.pubsub['node_1'].publish(Jubjub::DataForm.new({:foo => {:type => 'boolean', :value => 'bar'}}))
110
+ => #<Jubjub::PubsubItem:0x101f2e9f8 @jid="pubsub.jabber.org" @node="node_1" @item_id="519DCAA72FFD6" @data="<x xmlns=\"jabber:x:data\" type=\"submit\">\n <field var=\"foo\">\n <value>false</value>\n </field>\n</x>">
111
+
112
+ # Retract an item from a node
113
+ item.retract
114
+ => true
115
+
116
+ # Or
117
+ u.pubsub['node_1'].retract('abc')
118
+ => true
119
+
120
+ # List items on a node
121
+ u.pubsub['node_1'].items
122
+ => [
123
+ #<Jubjub::PubsubItem:0x101f7bd48 @jid="pubsub.jabber.org" @node="node_1" @item_id="519DCAA72FFD6" @data="...">,
124
+ ...
125
+ ]
126
+
127
+ # Retrieve an item from a node
128
+ u.pubsub['node_1'].items['519DCAA72FFD6']
129
+ => #<Jubjub::PubsubItem:0x101f7bd48 @jid="pubsub.jabber.org" @node="node_1" @item_id="519DCAA72FFD6" @data="...">
130
+
100
131
  # Unsubscribe
101
132
  subscription.unsubscribe
102
133
  => true
@@ -118,7 +149,7 @@ TODO
118
149
 
119
150
  - Error handling
120
151
  - MUC user role and affiliation control
152
+ - Pubsub configuration
121
153
  - Service discovery
122
- - Pubsub / PEP
123
154
  - Operations that are not IQ based, such as rosters and two way messaging
124
155
  - Other backends (for servers that are evented)
@@ -33,21 +33,13 @@ module Jubjub
33
33
  # to='crone1@shakespeare.lit/desktop'
34
34
  # type='result'/>
35
35
  #
36
- def create(full_jid, configuration = nil)
36
+ def create(full_jid, configuration = Jubjub::MucConfiguration.new)
37
37
  room_jid = Jubjub::Jid.new full_jid.node, full_jid.domain
38
38
 
39
39
  request = Nokogiri::XML::Builder.new do |xml|
40
40
  xml.iq_(:type => 'set', :to => room_jid) {
41
41
  xml.query_('xmlns' => 'http://jabber.org/protocol/muc#owner'){
42
- xml.x_('xmlns' => 'jabber:x:data', :type => 'submit') {
43
- configuration.settings.each{|name,values|
44
- xml.field_('var' => name){
45
- values.each {|v|
46
- xml.value_ v
47
- }
48
- }
49
- } if configuration
50
- }
42
+ configuration.to_builder(xml.parent)
51
43
  }
52
44
  }
53
45
  end
@@ -217,7 +217,159 @@ module Jubjub
217
217
  '//iq[@type="result"]'
218
218
  ).any?
219
219
  end
220
-
220
+
221
+ # http://xmpp.org/extensions/xep-0060.html#publisher-publish
222
+ # <iq type='set'
223
+ # from='hamlet@denmark.lit/blogbot'
224
+ # to='pubsub.shakespeare.lit'
225
+ # id='publish1'>
226
+ # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
227
+ # <publish node='princely_musings'>
228
+ # <item id='ae890ac52d0df67ed7cfdf51b644e901'>
229
+ # ...
230
+ # </item>
231
+ # </publish>
232
+ # </pubsub>
233
+ # </iq>
234
+ #
235
+ # Expected
236
+ # <iq type='result'
237
+ # from='pubsub.shakespeare.lit'
238
+ # to='hamlet@denmark.lit/blogbot'
239
+ # id='publish1'>
240
+ # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
241
+ # <publish node='princely_musings'>
242
+ # <item id='ae890ac52d0df67ed7cfdf51b644e901'/>
243
+ # </publish>
244
+ # </pubsub>
245
+ # </iq>
246
+ def publish(jid, node, data, item_id = nil)
247
+ item_options = {}
248
+ item_options[:id] = item_id if item_id
249
+
250
+ request = Nokogiri::XML::Builder.new do |xml|
251
+ xml.iq_(:to => jid, :type => 'set') {
252
+ xml.pubsub_('xmlns' => namespaces['pubsub']) {
253
+ xml.publish_(:node => node){
254
+ xml.item_(item_options){
255
+ if data.respond_to?( :to_builder )
256
+ data.to_builder(xml.parent)
257
+ else
258
+ xml << data
259
+ end
260
+ }
261
+ }
262
+ }
263
+ }
264
+ end
265
+
266
+ result = write(
267
+ # Generate stanza
268
+ request.to_xml
269
+ ).xpath(
270
+ # Pull out required parts
271
+ '//iq[@type="result"]/pubsub:pubsub/pubsub:publish/pubsub:item',
272
+ namespaces
273
+ )
274
+ if result.any?
275
+ item_id = result.first.attr('id')
276
+ data = request.doc.xpath("//pubsub:item/*", namespaces).to_s
277
+ Jubjub::PubsubItem.new jid, node, item_id, data, @connection
278
+ end
279
+ end
280
+
281
+ # http://xmpp.org/extensions/xep-0060.html#publisher-delete
282
+ # <iq type='set'
283
+ # from='hamlet@denmark.lit/elsinore'
284
+ # to='pubsub.shakespeare.lit'
285
+ # id='retract1'>
286
+ # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
287
+ # <retract node='princely_musings'>
288
+ # <item id='ae890ac52d0df67ed7cfdf51b644e901'/>
289
+ # </retract>
290
+ # </pubsub>
291
+ # </iq>
292
+ #
293
+ # Expected
294
+ # <iq type='result'
295
+ # from='pubsub.shakespeare.lit'
296
+ # to='hamlet@denmark.lit/elsinore'
297
+ # id='retract1'/>
298
+ def retract(jid, node, item_id)
299
+ request = Nokogiri::XML::Builder.new do |xml|
300
+ xml.iq_(:to => jid, :type => 'set') {
301
+ xml.pubsub_('xmlns' => namespaces['pubsub']) {
302
+ xml.retract_(:node => node){
303
+ xml.item_ :id => item_id
304
+ }
305
+ }
306
+ }
307
+ end
308
+
309
+ write(
310
+ # Generate stanza
311
+ request.to_xml
312
+ ).xpath('//iq[@type="result"]').any?
313
+ end
314
+
315
+ # http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve
316
+ # <iq type='get'
317
+ # from='francisco@denmark.lit/barracks'
318
+ # to='pubsub.shakespeare.lit'
319
+ # id='items1'>
320
+ # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
321
+ # <items node='princely_musings'/>
322
+ # </pubsub>
323
+ # </iq>
324
+ #
325
+ # Expected
326
+ # <iq type='result'
327
+ # from='pubsub.shakespeare.lit'
328
+ # to='francisco@denmark.lit/barracks'
329
+ # id='items1'>
330
+ # <pubsub xmlns='http://jabber.org/protocol/pubsub'>
331
+ # <items node='princely_musings'>
332
+ # <item id='368866411b877c30064a5f62b917cffe'>
333
+ # <entry xmlns='http://www.w3.org/2005/Atom'>
334
+ # <title>The Uses of This World</title>
335
+ # <summary>
336
+ # O, that this too too solid flesh would melt
337
+ # Thaw and resolve itself into a dew!
338
+ # </summary>
339
+ # <link rel='alternate' type='text/html'
340
+ # href='http://denmark.lit/2003/12/13/atom03'/>
341
+ # <id>tag:denmark.lit,2003:entry-32396</id>
342
+ # <published>2003-12-12T17:47:23Z</published>
343
+ # <updated>2003-12-12T17:47:23Z</updated>
344
+ # </entry>
345
+ # </item>
346
+ # ...
347
+ # </items>
348
+ # </pubsub>
349
+ # </iq>
350
+ def retrieve_items(jid, node)
351
+ request = Nokogiri::XML::Builder.new do |xml|
352
+ xml.iq_(:to => jid, :type => 'get') {
353
+ xml.pubsub_(:xmlns => namespaces['pubsub']){
354
+ xml.items_(:node => node)
355
+ }
356
+ }
357
+ end
358
+
359
+ write(
360
+ # Generate stanza
361
+ request.to_xml
362
+ ).xpath(
363
+ # Pull out required parts
364
+ '//iq[@type="result"]/pubsub:pubsub/pubsub:items/pubsub:item',
365
+ namespaces
366
+ ).map{|item|
367
+ item_id = item.attr('id')
368
+ data = item.xpath('./*').to_xml
369
+ Jubjub::PubsubItem.new jid, node, item_id, data, @connection
370
+ }
371
+ end
372
+
221
373
  private
222
374
 
223
375
  def subscriber
@@ -0,0 +1,91 @@
1
+ require "nokogiri"
2
+
3
+ module Jubjub
4
+ class DataForm
5
+
6
+ attr_reader :fields
7
+
8
+ def initialize(config={})
9
+ check_config config
10
+
11
+ @fields = config
12
+ end
13
+
14
+ def [](arg)
15
+ if fields.has_key? arg
16
+ case fields[arg][:type]
17
+ when 'boolean'
18
+ fields[arg][:value] == '1' || fields[arg][:value] == 'true' || fields[arg][:value] == true ? true : false
19
+ when 'jid-multi'
20
+ fields[arg][:value].map{|j| Jubjub::Jid.new j }
21
+ when 'jid-single'
22
+ Jubjub::Jid.new fields[arg][:value]
23
+ else
24
+ fields[arg][:value]
25
+ end
26
+ end
27
+ end
28
+
29
+ def []=(arg,value)
30
+ if fields.has_key? arg
31
+ check_options arg, value if fields[arg].has_key?(:options)
32
+ value = Array[value].flatten if fields[arg][:type].match /-multi$/
33
+ fields[arg][:value] = value
34
+ else
35
+ raise Jubjub::ArgumentError.new("#{arg} is not a valid field")
36
+ end
37
+ end
38
+
39
+ def settings
40
+ Hash[fields.keys.map{|field|
41
+ [field, Array[self[field]].flatten]
42
+ }]
43
+ end
44
+
45
+ def ==(thing)
46
+ thing.is_a?( self.class ) && thing.fields == self.fields
47
+ end
48
+
49
+ def to_builder(root_doc=Nokogiri::XML.parse(""))
50
+ Nokogiri::XML::Builder.with(root_doc) do |xml|
51
+ xml.x_('xmlns' => 'jabber:x:data', :type => 'submit') {
52
+ settings.each{|name,values|
53
+ xml.field_('var' => name){
54
+ values.each {|v|
55
+ xml.value_ v
56
+ }
57
+ }
58
+ }
59
+ }
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def check_options(name, value)
66
+ valid_options = fields[name][:options].map{|o| o[:value].to_s }
67
+ invalid_options = Array[value].flatten - valid_options
68
+ raise Jubjub::ArgumentError.new(
69
+ "#{invalid_options.join(', ')} is not an accepted value please choose from #{valid_options.join(', ')}"
70
+ ) if invalid_options.any?
71
+ end
72
+
73
+ def check_config(config)
74
+ required = [:type]
75
+ understood = required + [:label, :options, :value]
76
+
77
+ raise Jubjub::ArgumentError.new("please initialize with a hash of the format { 'foo' => {:type => 'boolean', :value => false, :label => 'Fooey'} }") unless config.is_a? Hash
78
+
79
+ config.each do |name,argument|
80
+ required.each{|r| raise Jubjub::ArgumentError.new(":#{r} is missing for #{name}") unless argument.keys.include? r }
81
+
82
+ mystery_arguments = (argument.keys - understood)
83
+ raise Jubjub::ArgumentError.new(
84
+ ":#{mystery_arguments.join(', :')} is not a recognised option for #{name}"
85
+ ) if mystery_arguments.any?
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ end
data/lib/jubjub/muc.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'jubjub/data_form'
2
+
1
3
  module Jubjub
2
4
  class Muc
3
5
 
@@ -55,6 +57,18 @@ module Jubjub
55
57
 
56
58
  end
57
59
 
60
+ def [](jid_node_num)
61
+ case jid_node_num
62
+ when Fixnum
63
+ list[jid_node_num]
64
+ when Jubjub::Jid
65
+ list.find{|m| m.jid == jid_node_num }
66
+ else
67
+ j = Jubjub::Jid.new jid_node_num, jid.domain
68
+ list.find{|m| m.jid == j }
69
+ end
70
+ end
71
+
58
72
  # Hint that methods are actually applied to list using method_missing
59
73
  def inspect
60
74
  list.inspect
@@ -72,76 +86,7 @@ module Jubjub
72
86
 
73
87
  end
74
88
 
75
- class MucConfiguration
76
-
77
- attr_reader :fields
78
-
79
- def initialize(config)
80
- check_config config
81
-
82
- @fields = config
83
- end
84
-
85
- def [](arg)
86
- if fields.has_key? arg
87
- case fields[arg][:type]
88
- when 'boolean'
89
- fields[arg][:value] == '1' || fields[arg][:value] == 'true' || fields[arg][:value] == true ? true : false
90
- when 'jid-multi'
91
- fields[arg][:value].map{|j| Jubjub::Jid.new j }
92
- when 'jid-single'
93
- Jubjub::Jid.new fields[arg][:value]
94
- else
95
- fields[arg][:value]
96
- end
97
- end
98
- end
99
-
100
- def []=(arg,value)
101
- if fields.has_key? arg
102
- check_options arg, value if fields[arg].has_key?(:options)
103
- value = Array[value].flatten if fields[arg][:type].match /-multi$/
104
- fields[arg][:value] = value
105
- else
106
- raise Jubjub::ArgumentError.new("#{arg} is not a valid field")
107
- end
108
- end
109
-
110
- def settings
111
- Hash[fields.keys.map{|field|
112
- [field, Array[self[field]].flatten]
113
- }]
114
- end
115
-
116
- def ==(thing)
117
- thing.is_a?( self.class ) && thing.fields == self.fields
118
- end
119
-
120
- private
121
-
122
- def check_options(name, value)
123
- valid_options = fields[name][:options].map{|o| o[:value].to_s }
124
- invalid_options = Array[value].flatten - valid_options
125
- raise Jubjub::ArgumentError.new(
126
- "#{invalid_options.join(', ')} is not an accepted value please choose from #{valid_options.join(', ')}"
127
- ) if invalid_options.any?
128
- end
129
-
130
- def check_config(config)
131
- required = [:type]
132
- understood = required + [:label, :options, :value]
133
-
134
- raise Jubjub::ArgumentError.new("please initialize with a hash of the format { 'foo' => {:type => 'boolean', :value => false, :label => 'Fooey'} }") unless config.is_a? Hash
135
-
136
- config.each do |name,argument|
137
- required.each{|r| raise Jubjub::ArgumentError.new(":#{r} is missing for #{name}") unless argument.keys.include? r }
138
-
139
- mystery_arguments = (argument.keys - understood)
140
- raise Jubjub::ArgumentError.new(
141
- ":#{mystery_arguments.join(', :')} is not a recognised option for #{name}"
142
- ) if mystery_arguments.any?
143
- end
144
- end
89
+ class MucConfiguration < DataForm
145
90
 
146
91
  end
147
92
 
data/lib/jubjub/pubsub.rb CHANGED
@@ -25,12 +25,98 @@ module Jubjub
25
25
  @connection.pubsub.unsubscribe jid, node, subid
26
26
  end
27
27
 
28
+ def publish(data, item_id = nil)
29
+ @connection.pubsub.publish jid, node, data, item_id
30
+ end
31
+
32
+ def retract(item_id)
33
+ @connection.pubsub.retract jid, node, item_id
34
+ end
35
+
28
36
  # Hide the connection details and show jid as string for compactness
29
37
  def inspect
30
38
  obj_id = "%x" % (object_id << 1)
31
39
  "#<#{self.class}:0x#{obj_id} @jid=\"#{jid}\" @node=#{node.inspect}>"
32
40
  end
33
41
 
42
+ def items
43
+ PubsubItemCollection.new jid, node, @connection
44
+ end
45
+
46
+ private
47
+
48
+ def method_missing(name, *args, &block)
49
+ items.send(name, *args, &block)
50
+ end
51
+
52
+ end
53
+
54
+ class PubsubItemCollection
55
+
56
+ attr_reader :jid, :node
57
+
58
+ def initialize(jid,node,connection)
59
+ @jid = Jubjub::Jid.new jid
60
+ @node = node
61
+ @connection = connection
62
+ end
63
+
64
+ def [](item_num)
65
+ case item_num
66
+ when Fixnum
67
+ items[item_num]
68
+ else
69
+ items.find{|i| i.item_id == item_num }
70
+ end
71
+ end
72
+
73
+ # Hint that methods are actually applied to list using method_missing
74
+ def inspect
75
+ items.inspect
76
+ end
77
+
78
+ private
79
+
80
+ def method_missing(name, *args, &block)
81
+ items.send(name, *args, &block)
82
+ end
83
+
84
+ def items
85
+ @items ||= @connection.pubsub.retrieve_items( @jid, @node )
86
+ end
87
+
88
+ end
89
+
90
+ class PubsubItem
91
+
92
+ attr_reader :jid, :node, :item_id, :data
93
+
94
+ def initialize(jid, node, item_id, data, connection)
95
+ @jid = Jubjub::Jid.new jid
96
+ @node = node
97
+ @item_id = item_id
98
+ @data = data
99
+ @connection = connection
100
+ end
101
+
102
+ # Hide the connection details and show jid as string for compactness
103
+ def inspect
104
+ obj_id = "%x" % (object_id << 1)
105
+ "#<#{self.class}:0x#{obj_id} @jid=\"#{jid}\" @node=#{node.inspect} @item_id=#{item_id.inspect} @data=#{data.inspect}>"
106
+ end
107
+
108
+ def retract
109
+ @connection.pubsub.retract jid, node, item_id
110
+ end
111
+
112
+ def ==(other)
113
+ other.is_a?( self.class ) &&
114
+ other.jid == self.jid &&
115
+ other.node == self.node &&
116
+ other.item_id == self.item_id &&
117
+ other.data == self.data
118
+ end
119
+
34
120
  end
35
121
 
36
122
  class PubsubSubscription
@@ -87,6 +173,15 @@ module Jubjub
87
173
  @connection.pubsub.unsubscribe jid, node, subid
88
174
  end
89
175
 
176
+ def [](node_num)
177
+ case node_num
178
+ when Fixnum
179
+ list[node_num]
180
+ else
181
+ list.find{|p| p.node == node_num }
182
+ end
183
+ end
184
+
90
185
  # Hint that methods are actually applied to list using method_missing
91
186
  def inspect
92
187
  list.inspect