jubjub 0.0.4 → 0.0.5

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/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