shingara-blather 0.4.9 → 0.4.14

Sign up to get free protection for your applications and to get access to all the features.
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