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 +15 -1
- data/lib/blather.rb +5 -0
- data/lib/blather/core_ext/nokogiri.rb +2 -0
- data/lib/blather/roster.rb +3 -3
- data/lib/blather/roster_item.rb +14 -3
- data/lib/blather/stanza/disco/disco_info.rb +20 -2
- data/lib/blather/stanza/disco/disco_items.rb +10 -1
- data/lib/blather/stanza/iq/command.rb +324 -0
- data/lib/blather/stanza/iq/vcard.rb +147 -0
- data/lib/blather/stanza/message.rb +49 -5
- data/lib/blather/stanza/presence/status.rb +11 -3
- data/lib/blather/stanza/pubsub.rb +9 -21
- data/lib/blather/stanza/x.rb +398 -0
- data/lib/blather/stream.rb +3 -2
- data/lib/blather/stream/parser.rb +6 -5
- data/lib/blather/xmpp_node.rb +1 -1
- data/spec/blather/roster_item_spec.rb +39 -1
- data/spec/blather/stanza/discos/disco_info_spec.rb +23 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +11 -0
- data/spec/blather/stanza/iq/command_spec.rb +206 -0
- data/spec/blather/stanza/iq/vcard_query_spec.rb +96 -0
- data/spec/blather/stanza/message_spec.rb +89 -14
- data/spec/blather/stanza/pubsub/retract_spec.rb +1 -1
- data/spec/blather/stanza/pubsub_spec.rb +0 -5
- data/spec/blather/stanza/x_spec.rb +235 -0
- data/spec/blather/stream/client_spec.rb +14 -1
- data/spec/blather/stream/parser_spec.rb +6 -0
- data/spec/blather/xmpp_node_spec.rb +4 -3
- data/spec/spec_helper.rb +28 -0
- metadata +35 -5
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
|
|
data/lib/blather.rb
CHANGED
@@ -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
|
data/lib/blather/roster.rb
CHANGED
@@ -90,8 +90,8 @@ module Blather
|
|
90
90
|
#
|
91
91
|
# @return [Hash<group => Array<RosterItem>>]
|
92
92
|
def grouped
|
93
|
-
|
94
|
-
item
|
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
|
data/lib/blather/roster_item.rb
CHANGED
@@ -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.
|
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
|
-
|
26
|
-
|
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
|
-
|
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
|