shingara-blather 0.4.9 → 0.4.14

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.md CHANGED
@@ -148,6 +148,17 @@ Command line options:
148
148
  -h, --help Show this message
149
149
  -v, --version Show version
150
150
 
151
+ ## Health warning:
152
+
153
+ Some parts of Blather will allow you to do stupid things that don't conform to XMPP
154
+ spec. You should exercise caution and read the relevant specifications (indicated in
155
+ the preamble to most relevant classes).
156
+
157
+ # Contributions
158
+
159
+ All contributions are welcome, even encouraged. However, contributions must be
160
+ well tested. If you send me a branch name to merge that'll get my attention faster
161
+ than a change set made directly on master.
151
162
 
152
163
  # Author
153
164
 
@@ -155,7 +166,10 @@ Command line options:
155
166
 
156
167
  ### Contributors
157
168
 
158
- [Nolan Darilek](http://github.com/thewordnerd)
169
+ * [Nolan Darilek](http://github.com/thewordnerd)
170
+ * [Tim Carey-Smith](http://github.com/halorgium)
171
+ * [Ben Langfeld](http://github.com/benlangfeld)
172
+ * [Anton Mironov](http://github.com/mironov)
159
173
 
160
174
  # Copyright
161
175
 
@@ -4,6 +4,7 @@
4
4
  eventmachine
5
5
  nokogiri
6
6
  digest/md5
7
+ digest/sha1
7
8
  logger
8
9
  active_support/core_ext/class/inheritable_attributes
9
10
  active_support/core_ext/object/blank
@@ -22,7 +23,9 @@
22
23
  blather/stanza
23
24
  blather/stanza/iq
24
25
  blather/stanza/iq/query
26
+ blather/stanza/iq/command
25
27
  blather/stanza/iq/roster
28
+ blather/stanza/iq/vcard
26
29
  blather/stanza/disco
27
30
  blather/stanza/disco/disco_info
28
31
  blather/stanza/disco/disco_items
@@ -47,6 +50,8 @@
47
50
  blather/stanza/pubsub_owner/delete
48
51
  blather/stanza/pubsub_owner/purge
49
52
 
53
+ blather/stanza/x
54
+
50
55
  blather/stream
51
56
  blather/stream/client
52
57
  blather/stream/component
@@ -22,9 +22,11 @@ module XML
22
22
  # and namespace designation
23
23
  def xpath(*paths)
24
24
  paths[0] = paths[0].to_s
25
+
25
26
  if paths.size > 1 && (namespaces = paths.pop).is_a?(Hash)
26
27
  paths << namespaces.inject({}) { |h,v| h[v[0].to_s] = v[1]; h }
27
28
  end
29
+
28
30
  nokogiri_xpath *paths
29
31
  end
30
32
  alias_method :find, :xpath
@@ -90,8 +90,8 @@ module Blather
90
90
  #
91
91
  # @return [Hash<group => Array<RosterItem>>]
92
92
  def grouped
93
- self.inject(Hash.new{|h,k|h[k]=[]}) do |hash, item|
94
- item[1].groups.each { |group| hash[group] << item[1] }
93
+ @items.values.sort.inject(Hash.new{|h,k|h[k]=[]}) do |hash, item|
94
+ item.groups.each { |group| hash[group] << item }
95
95
  hash
96
96
  end
97
97
  end
@@ -108,4 +108,4 @@ module Blather
108
108
  end
109
109
  end # Roster
110
110
 
111
- end # Blather
111
+ end # Blather
@@ -90,7 +90,7 @@ module Blather
90
90
  #
91
91
  # @param [Blather::Stanza::Status] the new status
92
92
  def status=(presence)
93
- @statuses.delete_if { |s| s.from == presence.from }
93
+ @statuses.delete_if { |s| s.from == presence.from || s.state == :unavailable }
94
94
  @statuses << presence
95
95
  @statuses.sort!
96
96
  end
@@ -102,7 +102,7 @@ module Blather
102
102
  top = if resource
103
103
  @statuses.detect { |s| s.from.resource == resource }
104
104
  else
105
- @statuses.first
105
+ @statuses.last
106
106
  end
107
107
  end
108
108
 
@@ -117,6 +117,17 @@ module Blather
117
117
  n.groups = groups
118
118
  r
119
119
  end
120
+
121
+ def <=>(o)
122
+ self.jid.to_s <=> o.jid.to_s
123
+ end
124
+
125
+ def eql?(o)
126
+ o.is_a?(RosterItem) &&
127
+ o.jid == self.jid &&
128
+ o.groups == self.groups
129
+ end
130
+ alias_method :==, :eql?
120
131
  end #RosterItem
121
132
 
122
- end
133
+ end
@@ -22,8 +22,8 @@ class Stanza
22
22
  def self.new(type = nil, node = nil, identities = [], features = [])
23
23
  new_node = super type
24
24
  new_node.node = node
25
- [identities].flatten.each { |i| new_node.query << Identity.new(i) }
26
- [features].flatten.each { |f| new_node.query << Feature.new(f) }
25
+ new_node.identities = [identities]
26
+ new_node.features = [features]
27
27
  new_node
28
28
  end
29
29
 
@@ -34,6 +34,15 @@ class Stanza
34
34
  end
35
35
  end
36
36
 
37
+ # Add an array of identities
38
+ # @param identities the array of identities, passed directly to Identity.new
39
+ def identities=(identities)
40
+ query.find('//ns:identity', :ns => self.class.registered_ns).each &:remove
41
+ if identities
42
+ [identities].flatten.each { |i| self.query << Identity.new(i) }
43
+ end
44
+ end
45
+
37
46
  # List of feature objects
38
47
  def features
39
48
  query.find('//ns:feature', :ns => self.class.registered_ns).map do |f|
@@ -41,6 +50,15 @@ class Stanza
41
50
  end
42
51
  end
43
52
 
53
+ # Add an array of features
54
+ # @param features the array of features, passed directly to Feature.new
55
+ def features=(features)
56
+ query.find('//ns:feature', :ns => self.class.registered_ns).each &:remove
57
+ if features
58
+ [features].flatten.each { |f| self.query << Feature.new(f) }
59
+ end
60
+ end
61
+
44
62
  class Identity < XMPPNode
45
63
  # Create a new DiscoInfo Identity
46
64
  # @overload new(node)
@@ -21,7 +21,7 @@ class Stanza
21
21
  def self.new(type = nil, node = nil, items = [])
22
22
  new_node = super type
23
23
  new_node.node = node
24
- [items].flatten.each { |item| new_node.query << Item.new(item) }
24
+ new_node.items = [items]
25
25
  new_node
26
26
  end
27
27
 
@@ -34,6 +34,15 @@ class Stanza
34
34
  end
35
35
  end
36
36
 
37
+ # Add an array of items
38
+ # @param items the array of items, passed directly to Item.new
39
+ def items=(items)
40
+ query.find('//ns:item', :ns => self.class.registered_ns).each &:remove
41
+ if items
42
+ [items].flatten.each { |i| self.query << Item.new(i) }
43
+ end
44
+ end
45
+
37
46
  # An individual Disco Item
38
47
  class Item < XMPPNode
39
48
  # Create a new Blather::Stanza::DiscoItems::Item
@@ -0,0 +1,324 @@
1
+ module Blather
2
+ class Stanza
3
+ class Iq
4
+
5
+ # # Command Stanza
6
+ #
7
+ # [XEP-0050 Ad-Hoc Commands](http://xmpp.org/extensions/xep-0050.html)
8
+ #
9
+ # This is a base class for any command based Iq stanzas. It provides a base set
10
+ # of methods for working with command stanzas
11
+ #
12
+ # @handler :command
13
+ class Command < Iq
14
+ VALID_ACTIONS = [:cancel, :execute, :complete, :next, :prev].freeze
15
+ VALID_STATUS = [:executing, :completed, :canceled].freeze
16
+ VALID_NOTE_TYPES = [:info, :warn, :error].freeze
17
+
18
+ register :command, :command, 'http://jabber.org/protocol/commands'
19
+
20
+ # Overrides the parent method to ensure a command node is created
21
+ #
22
+ # @param [:get, :set, :result, :error, nil] type the IQ type
23
+ # @param [String] node the name of the node
24
+ # @param [:cancel, :execute, :complete, :next, :prev, nil] action the command's action
25
+ # @return [Command] a new Command stanza
26
+ def self.new(type = :set, node = nil, action = :execute)
27
+ new_node = super type
28
+ new_node.command
29
+ new_node.node = node
30
+ new_node.action = action
31
+ new_node
32
+ end
33
+
34
+ # Overrides the parent method to ensure the current command node is destroyed
35
+ # and the action is set to execute if no action provided
36
+ #
37
+ # @see Blather::Stanza::Iq#inherit
38
+ def inherit(node)
39
+ command.remove
40
+ super
41
+ self.action = :execute unless self.action
42
+ self
43
+ end
44
+
45
+ # Overrides the parent method to ensure the reply has no action
46
+ #
47
+ # @return [self]
48
+ def reply!
49
+ super
50
+ self.action = nil
51
+ self
52
+ end
53
+
54
+ # Command node accessor
55
+ # If a command node exists it will be returned.
56
+ # Otherwise a new node will be created and returned
57
+ #
58
+ # @return [Blather::XMPPNode]
59
+ def command
60
+ c = if self.class.registered_ns
61
+ find_first('ns:command', :ns => self.class.registered_ns)
62
+ else
63
+ find_first('command')
64
+ end
65
+
66
+ unless c
67
+ (self << (c = XMPPNode.new('command', self.document)))
68
+ c.namespace = self.class.registered_ns
69
+ end
70
+ c
71
+ end
72
+
73
+ # Get the name of the node
74
+ #
75
+ # @return [String, nil]
76
+ def node
77
+ command[:node]
78
+ end
79
+
80
+ # Set the name of the node
81
+ #
82
+ # @param [String, nil] node the new node name
83
+ def node=(node)
84
+ command[:node] = node
85
+ end
86
+
87
+ # Get the sessionid of the command
88
+ #
89
+ # @return [String, nil]
90
+ def sessionid
91
+ command[:sessionid]
92
+ end
93
+
94
+ # Check if there is a sessionid set
95
+ #
96
+ # @return [true, false]
97
+ def sessionid?
98
+ !sessionid.nil?
99
+ end
100
+
101
+ # Set the sessionid of the command
102
+ #
103
+ # @param [String, nil] sessionid the new sessionid
104
+ def sessionid=(sessionid)
105
+ command[:sessionid] = Digest::SHA1.hexdigest(sessionid)
106
+ end
107
+
108
+ # Generate a new session ID (SHA-1 hash)
109
+ def new_sessionid!
110
+ self.sessionid = "commandsession-#{id}"
111
+ end
112
+
113
+ # Get the action of the command
114
+ #
115
+ # @return [Symbol, nil]
116
+ def action
117
+ (val = command[:action]) && val.to_sym
118
+ end
119
+
120
+ # Check if the command action is :cancel
121
+ #
122
+ # @return [true, false]
123
+ def cancel?
124
+ self.action == :cancel
125
+ end
126
+
127
+ # Check if the command action is :execute
128
+ #
129
+ # @return [true, false]
130
+ def execute?
131
+ self.action == :execute
132
+ end
133
+
134
+ # Check if the command action is :complete
135
+ #
136
+ # @return [true, false]
137
+ def complete?
138
+ self.action == :complete
139
+ end
140
+
141
+ # Check if the command action is :next
142
+ #
143
+ # @return [true, false]
144
+ def next?
145
+ self.action == :next
146
+ end
147
+
148
+ # Check if the command action is :prev
149
+ #
150
+ # @return [true, false]
151
+ def prev?
152
+ self.action == :prev
153
+ end
154
+
155
+ # Set the action of the command
156
+ #
157
+ # @param [:cancel, :execute, :complete, :next, :prev] action the new action
158
+ def action=(action)
159
+ if action && !VALID_ACTIONS.include?(action.to_sym)
160
+ raise ArgumentError, "Invalid Action (#{action}), use: #{VALID_ACTIONS*' '}"
161
+ end
162
+ command[:action] = action
163
+ end
164
+
165
+ # Get the status of the command
166
+ #
167
+ # @return [Symbol, nil]
168
+ def status
169
+ ((val = command[:status]) && val.to_sym) || :executing
170
+ end
171
+
172
+ # Check if the command status is :executing
173
+ #
174
+ # @return [true, false]
175
+ def executing?
176
+ self.status == :executing
177
+ end
178
+
179
+ # Check if the command status is :completed
180
+ #
181
+ # @return [true, false]
182
+ def completed?
183
+ self.status == :completed
184
+ end
185
+
186
+ # Check if the command status is :canceled
187
+ #
188
+ # @return [true, false]
189
+ def canceled?
190
+ self.status == :canceled
191
+ end
192
+
193
+ # Set the status of the command
194
+ #
195
+ # @param [:executing, :completed, :canceled] status the new status
196
+ def status=(status)
197
+ if status && !VALID_STATUS.include?(status.to_sym)
198
+ raise ArgumentError, "Invalid Action (#{status}), use: #{VALID_STATUS*' '}"
199
+ end
200
+ command[:status] = status
201
+ end
202
+
203
+ # Command actions accessor
204
+ # If a command actions element exists it will be returned.
205
+ # Otherwise a new actions element will be created and returned
206
+ #
207
+ # @return [Blather::XMPPNode]
208
+ def actions
209
+ unless a = self.command.find_first('ns:actions', :ns => self.class.registered_ns)
210
+ (self.command << (a = XMPPNode.new('actions', self.document)))
211
+ a.namespace = self.command.namespace
212
+ end
213
+ a
214
+ end
215
+
216
+ # Get the command's allowed actions
217
+ #
218
+ # @return [[Symbol]]
219
+ def allowed_actions
220
+ ([:execute] + actions.children.map { |action| action.name.to_sym }).uniq
221
+ end
222
+
223
+ def primary_allowed_action
224
+ (actions[:execute] || :execute).to_sym
225
+ end
226
+
227
+ def primary_allowed_action=(a)
228
+ if a && ![:prev, :next, :complete, :execute].include?(a.to_sym)
229
+ raise ArgumentError, "Invalid Action (#{a}), use: #{[:prev, :next, :complete, :execute]*' '}"
230
+ end
231
+ actions[:execute] = a
232
+ end
233
+
234
+ # Add allowed actions to the command
235
+ #
236
+ # @param [[:prev, :next, :complete]] allowed_actions the new allowed actions
237
+ def allowed_actions=(allowed_actions)
238
+ allowed_actions = ([allowed_actions].flatten.map(&:to_sym) + [:execute]).uniq
239
+ if (invalid_actions = allowed_actions - VALID_ACTIONS).size > 0
240
+ raise ArgumentError, "Invalid Action(s) (#{invalid_actions*' '}), use: #{VALID_ACTIONS*' '}"
241
+ end
242
+ actions.children.map(&:remove)
243
+ allowed_actions.each { |action| actions << XMPPNode.new(action.to_s) }
244
+ end
245
+
246
+ # Remove allowed actions from the command
247
+ #
248
+ # @param [[:prev, :next, :complete]] disallowed_actions the allowed actions to remove
249
+ def remove_allowed_actions!
250
+ actions.remove
251
+ end
252
+
253
+ # Command note accessor
254
+ # If a command note exists it will be returned.
255
+ # Otherwise a new note will be created and returned
256
+ #
257
+ # @return [Blather::XMPPNode]
258
+ def note
259
+ unless n = self.command.find_first('ns:note', :ns => self.class.registered_ns)
260
+ (self.command << (n = XMPPNode.new('note', self.document)))
261
+ n.namespace = self.command.namespace
262
+ end
263
+ n
264
+ end
265
+
266
+ # Get the note_type of the command
267
+ #
268
+ # @return [Symbol, nil]
269
+ def note_type
270
+ (val = note[:type]) && val.to_sym
271
+ end
272
+
273
+ # Check if the command status is :info
274
+ #
275
+ # @return [true, false]
276
+ def info?
277
+ self.note_type == :info
278
+ end
279
+
280
+ # Check if the command status is :warn
281
+ #
282
+ # @return [true, false]
283
+ def warn?
284
+ self.status == :warn
285
+ end
286
+
287
+ # Check if the command status is :error
288
+ #
289
+ # @return [true, false]
290
+ def error?
291
+ self.status == :error
292
+ end
293
+
294
+ # Set the note_type of the command
295
+ #
296
+ # @param [:executing, :completed, :canceled] note_type the new note_type
297
+ def note_type=(note_type)
298
+ if note_type && !VALID_NOTE_TYPES.include?(note_type.to_sym)
299
+ raise ArgumentError, "Invalid Action (#{note_type}), use: #{VALID_NOTE_TYPES*' '}"
300
+ end
301
+ note[:type] = note_type
302
+ end
303
+
304
+ # Get the text of the command's note
305
+ def note_text
306
+ content_from :note
307
+ end
308
+
309
+ # Set the command's note text
310
+ #
311
+ # @param [String] note_text the command's new note text
312
+ def note_text=(note_text)
313
+ set_content_for :note, note_text
314
+ end
315
+
316
+ # Returns the command's x:data form child
317
+ def form
318
+ X.find_or_create command
319
+ end
320
+ end #Command
321
+
322
+ end #Iq
323
+ end #Stanza
324
+ end #Blather