shingara-blather 0.4.8
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/LICENSE +22 -0
- data/README.md +162 -0
- data/examples/echo.rb +18 -0
- data/examples/execute.rb +16 -0
- data/examples/ping_pong.rb +37 -0
- data/examples/print_hierarchy.rb +76 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/stream_only.rb +27 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/lib/blather/client/client.rb +310 -0
- data/lib/blather/client/dsl/pubsub.rb +170 -0
- data/lib/blather/client/dsl.rb +264 -0
- data/lib/blather/client.rb +87 -0
- data/lib/blather/core_ext/nokogiri.rb +40 -0
- data/lib/blather/errors/sasl_error.rb +43 -0
- data/lib/blather/errors/stanza_error.rb +107 -0
- data/lib/blather/errors/stream_error.rb +82 -0
- data/lib/blather/errors.rb +69 -0
- data/lib/blather/jid.rb +142 -0
- data/lib/blather/roster.rb +111 -0
- data/lib/blather/roster_item.rb +122 -0
- data/lib/blather/stanza/disco/disco_info.rb +176 -0
- data/lib/blather/stanza/disco/disco_items.rb +132 -0
- data/lib/blather/stanza/disco.rb +25 -0
- data/lib/blather/stanza/iq/query.rb +53 -0
- data/lib/blather/stanza/iq/roster.rb +179 -0
- data/lib/blather/stanza/iq.rb +138 -0
- data/lib/blather/stanza/message.rb +332 -0
- data/lib/blather/stanza/presence/status.rb +212 -0
- data/lib/blather/stanza/presence/subscription.rb +101 -0
- data/lib/blather/stanza/presence.rb +163 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
- data/lib/blather/stanza/pubsub/create.rb +65 -0
- data/lib/blather/stanza/pubsub/errors.rb +18 -0
- data/lib/blather/stanza/pubsub/event.rb +123 -0
- data/lib/blather/stanza/pubsub/items.rb +103 -0
- data/lib/blather/stanza/pubsub/publish.rb +103 -0
- data/lib/blather/stanza/pubsub/retract.rb +92 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
- data/lib/blather/stanza/pubsub/subscription.rb +134 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
- data/lib/blather/stanza/pubsub.rb +129 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
- data/lib/blather/stanza/pubsub_owner.rb +51 -0
- data/lib/blather/stanza.rb +149 -0
- data/lib/blather/stream/client.rb +31 -0
- data/lib/blather/stream/component.rb +38 -0
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/features/sasl.rb +187 -0
- data/lib/blather/stream/features/session.rb +44 -0
- data/lib/blather/stream/features/tls.rb +28 -0
- data/lib/blather/stream/features.rb +53 -0
- data/lib/blather/stream/parser.rb +102 -0
- data/lib/blather/stream.rb +231 -0
- data/lib/blather/xmpp_node.rb +218 -0
- data/lib/blather.rb +78 -0
- data/spec/blather/client/client_spec.rb +559 -0
- data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
- data/spec/blather/client/dsl_spec.rb +143 -0
- data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
- data/spec/blather/errors/sasl_error_spec.rb +33 -0
- data/spec/blather/errors/stanza_error_spec.rb +129 -0
- data/spec/blather/errors/stream_error_spec.rb +108 -0
- data/spec/blather/errors_spec.rb +33 -0
- data/spec/blather/jid_spec.rb +87 -0
- data/spec/blather/roster_item_spec.rb +96 -0
- data/spec/blather/roster_spec.rb +103 -0
- data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
- data/spec/blather/stanza/iq/query_spec.rb +64 -0
- data/spec/blather/stanza/iq/roster_spec.rb +140 -0
- data/spec/blather/stanza/iq_spec.rb +45 -0
- data/spec/blather/stanza/message_spec.rb +132 -0
- data/spec/blather/stanza/presence/status_spec.rb +132 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
- data/spec/blather/stanza/presence_spec.rb +66 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +67 -0
- data/spec/blather/stanza_spec.rb +116 -0
- data/spec/blather/stream/client_spec.rb +1011 -0
- data/spec/blather/stream/component_spec.rb +95 -0
- data/spec/blather/stream/parser_spec.rb +145 -0
- data/spec/blather/xmpp_node_spec.rb +231 -0
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +43 -0
- metadata +249 -0
data/lib/blather/jid.rb
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
|
|
3
|
+
# Jabber ID or JID
|
|
4
|
+
#
|
|
5
|
+
# See [RFC 3920 Section 3 - Addressing](http://xmpp.org/rfcs/rfc3920.html#addressing)
|
|
6
|
+
#
|
|
7
|
+
# An entity is anything that can be considered a network endpoint (i.e., an
|
|
8
|
+
# ID on the network) and that can communicate using XMPP. All such entities
|
|
9
|
+
# are uniquely addressable in a form that is consistent with RFC 2396 [URI].
|
|
10
|
+
# For historical reasons, the address of an XMPP entity is called a Jabber
|
|
11
|
+
# Identifier or JID. A valid JID contains a set of ordered elements formed
|
|
12
|
+
# of a domain identifier, node identifier, and resource identifier.
|
|
13
|
+
#
|
|
14
|
+
# The syntax for a JID is defined below using the Augmented Backus-Naur Form
|
|
15
|
+
# as defined in [ABNF]. (The IPv4address and IPv6address rules are defined
|
|
16
|
+
# in Appendix B of [IPv6]; the allowable character sequences that conform to
|
|
17
|
+
# the node rule are defined by the Nodeprep profile of [STRINGPREP] as
|
|
18
|
+
# documented in Appendix A of this memo; the allowable character sequences
|
|
19
|
+
# that conform to the resource rule are defined by the Resourceprep profile
|
|
20
|
+
# of [STRINGPREP] as documented in Appendix B of this memo; and the
|
|
21
|
+
# sub-domain rule makes reference to the concept of an internationalized
|
|
22
|
+
# domain label as described in [IDNA].)
|
|
23
|
+
#
|
|
24
|
+
# jid = [ node "@" ] domain [ "/" resource ]
|
|
25
|
+
# domain = fqdn / address-literal
|
|
26
|
+
# fqdn = (sub-domain 1*("." sub-domain))
|
|
27
|
+
# sub-domain = (internationalized domain label)
|
|
28
|
+
# address-literal = IPv4address / IPv6address
|
|
29
|
+
#
|
|
30
|
+
# All JIDs are based on the foregoing structure. The most common use of this
|
|
31
|
+
# structure is to identify an instant messaging user, the server to which
|
|
32
|
+
# the user connects, and the user's connected resource (e.g., a specific
|
|
33
|
+
# client) in the form of <user@host/resource>. However, node types other
|
|
34
|
+
# than clients are possible; for example, a specific chat room offered by a
|
|
35
|
+
# multi-user chat service could be addressed as <room@service> (where "room"
|
|
36
|
+
# is the name of the chat room and "service" is the hostname of the
|
|
37
|
+
# multi-user chat service) and a specific occupant of such a room could be
|
|
38
|
+
# addressed as <room@service/nick> (where "nick" is the occupant's room
|
|
39
|
+
# nickname). Many other JID types are possible (e.g., <domain/resource>
|
|
40
|
+
# could be a server-side script or service).
|
|
41
|
+
#
|
|
42
|
+
# Each allowable portion of a JID (node identifier, domain identifier, and
|
|
43
|
+
# resource identifier) MUST NOT be more than 1023 bytes in length, resulting
|
|
44
|
+
# in a maximum total size (including the '@' and '/' separators) of 3071
|
|
45
|
+
# bytes.
|
|
46
|
+
class JID
|
|
47
|
+
include Comparable
|
|
48
|
+
|
|
49
|
+
PATTERN = /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.freeze
|
|
50
|
+
|
|
51
|
+
attr_reader :node,
|
|
52
|
+
:domain,
|
|
53
|
+
:resource
|
|
54
|
+
|
|
55
|
+
def self.new(node, domain = nil, resource = nil)
|
|
56
|
+
node.is_a?(JID) ? node : super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Create a new JID object
|
|
60
|
+
#
|
|
61
|
+
# @overload initialize(jid)
|
|
62
|
+
# Passes the jid object right back out
|
|
63
|
+
# @param [Blather::JID] jid a jid object
|
|
64
|
+
# @overload initialize(jid)
|
|
65
|
+
# Creates a new JID parsed out of the provided jid
|
|
66
|
+
# @param [String] jid a jid in the standard format
|
|
67
|
+
# ("node@domain/resource")
|
|
68
|
+
# @overload initialize(node, domain = nil, resource = nil)
|
|
69
|
+
# Creates a new JID
|
|
70
|
+
# @param [String] node the node of the JID
|
|
71
|
+
# @param [String, nil] domian the domain of the JID
|
|
72
|
+
# @param [String, nil] resource the resource of the JID
|
|
73
|
+
# @raise [ArgumentError] if the parts of the JID are too large (1023 bytes)
|
|
74
|
+
# @return [Blather::JID] a new jid object
|
|
75
|
+
def initialize(node, domain = nil, resource = nil)
|
|
76
|
+
@resource = resource
|
|
77
|
+
@domain = domain
|
|
78
|
+
@node = node
|
|
79
|
+
|
|
80
|
+
if @domain.nil? && @resource.nil?
|
|
81
|
+
@node, @domain, @resource = @node.to_s.scan(PATTERN).first
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
@node.downcase! if @node
|
|
85
|
+
@domain.downcase! if @domain
|
|
86
|
+
|
|
87
|
+
raise ArgumentError, 'Node too long' if (@node || '').length > 1023
|
|
88
|
+
raise ArgumentError, 'Domain too long' if (@domain || '').length > 1023
|
|
89
|
+
raise ArgumentError, 'Resource too long' if (@resource || '').length > 1023
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Turn the JID into a string
|
|
93
|
+
#
|
|
94
|
+
# * ""
|
|
95
|
+
# * "domain"
|
|
96
|
+
# * "node@domain"
|
|
97
|
+
# * "domain/resource"
|
|
98
|
+
# * "node@domain/resource"
|
|
99
|
+
#
|
|
100
|
+
# @return [String] the JID as a string
|
|
101
|
+
def to_s
|
|
102
|
+
s = @domain
|
|
103
|
+
s = "#{@node}@#{s}" if @node
|
|
104
|
+
s = "#{s}/#{@resource}" if @resource
|
|
105
|
+
s
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Returns a new JID with resource removed.
|
|
109
|
+
#
|
|
110
|
+
# @return [Blather::JID] a new JID without a resource
|
|
111
|
+
def stripped
|
|
112
|
+
dup.strip!
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Removes the resource (sets it to nil)
|
|
116
|
+
#
|
|
117
|
+
# @return [Blather::JID] the JID without a resource
|
|
118
|
+
def strip!
|
|
119
|
+
@resource = nil
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Compare two JIDs, helpful for sorting etc.
|
|
124
|
+
#
|
|
125
|
+
# String representations are compared, see JID#to_s
|
|
126
|
+
#
|
|
127
|
+
# @param [#to_s] other a JID to comare against
|
|
128
|
+
# @return [Fixnum<-1, 0, 1>]
|
|
129
|
+
def <=>(other)
|
|
130
|
+
to_s <=> other.to_s
|
|
131
|
+
end
|
|
132
|
+
alias_method :eql?, :==
|
|
133
|
+
|
|
134
|
+
# Test if JID is stripped
|
|
135
|
+
#
|
|
136
|
+
# @return [true, false]
|
|
137
|
+
def stripped?
|
|
138
|
+
@resource.nil?
|
|
139
|
+
end
|
|
140
|
+
end # JID
|
|
141
|
+
|
|
142
|
+
end # Blather
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
|
|
3
|
+
# Local Roster
|
|
4
|
+
# Takes care of adding/removing JIDs through the stream
|
|
5
|
+
class Roster
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
# Create a new roster
|
|
9
|
+
#
|
|
10
|
+
# @param [Blather::Stream] stream the stream the roster should use to
|
|
11
|
+
# update roster entries
|
|
12
|
+
# @param [Blather::Stanza::Roster] stanza a roster stanza used to preload
|
|
13
|
+
# the roster
|
|
14
|
+
# @return [Blather::Roster]
|
|
15
|
+
def initialize(stream, stanza = nil)
|
|
16
|
+
@stream = stream
|
|
17
|
+
@items = {}
|
|
18
|
+
stanza.items.each { |i| push i, false } if stanza
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Process any incoming stanzas and either adds or removes the
|
|
22
|
+
# corresponding RosterItem
|
|
23
|
+
#
|
|
24
|
+
# @param [Blather::Stanza::Roster] stanza a roster stanza
|
|
25
|
+
def process(stanza)
|
|
26
|
+
stanza.items.each do |i|
|
|
27
|
+
case i.subscription
|
|
28
|
+
when :remove then @items.delete(key(i.jid))
|
|
29
|
+
else @items[key(i.jid)] = RosterItem.new(i)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Pushes a JID into the roster
|
|
35
|
+
#
|
|
36
|
+
# @param [String, Blather::JID, #jid] elem a JID to add to the roster
|
|
37
|
+
# @return [self]
|
|
38
|
+
# @see #push
|
|
39
|
+
def <<(elem)
|
|
40
|
+
push elem
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Push a JID into the roster and update the server
|
|
45
|
+
#
|
|
46
|
+
# @param [String, Blather::JID, #jid] elem a jid to add to the roster
|
|
47
|
+
# @param [true, false] send send the update over the wire
|
|
48
|
+
# @see Blather::JID
|
|
49
|
+
def push(elem, send = true)
|
|
50
|
+
jid = elem.respond_to?(:jid) ? elem.jid : JID.new(elem)
|
|
51
|
+
@items[key(jid)] = node = RosterItem.new(elem)
|
|
52
|
+
|
|
53
|
+
@stream.write(node.to_stanza(:set)) if send
|
|
54
|
+
end
|
|
55
|
+
alias_method :add, :push
|
|
56
|
+
|
|
57
|
+
# Remove a JID from the roster and update the server
|
|
58
|
+
#
|
|
59
|
+
# @param [String, Blather::JID] jid the JID to remove from the roster
|
|
60
|
+
def delete(jid)
|
|
61
|
+
@items.delete key(jid)
|
|
62
|
+
item = Stanza::Iq::Roster::RosterItem.new(jid, nil, :remove)
|
|
63
|
+
@stream.write Stanza::Iq::Roster.new(:set, item)
|
|
64
|
+
end
|
|
65
|
+
alias_method :remove, :delete
|
|
66
|
+
|
|
67
|
+
# Get a RosterItem by JID
|
|
68
|
+
#
|
|
69
|
+
# @param [String, Blather::JID] jid the jid of the item to return
|
|
70
|
+
# @return [Blather::RosterItem, nil] the associated RosterItem
|
|
71
|
+
def [](jid)
|
|
72
|
+
items[key(jid)]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Iterate over all RosterItems
|
|
76
|
+
#
|
|
77
|
+
# @yield [Blather::RosterItem] yields each RosterItem
|
|
78
|
+
def each(&block)
|
|
79
|
+
items.each &block
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get a duplicate of all RosterItems
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<Blather::RosterItem>] a duplicate of all RosterItems
|
|
85
|
+
def items
|
|
86
|
+
@items.dup
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# A hash of items keyed by group
|
|
90
|
+
#
|
|
91
|
+
# @return [Hash<group => Array<RosterItem>>]
|
|
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] }
|
|
95
|
+
hash
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
# Creates a stripped jid
|
|
101
|
+
def self.key(jid)
|
|
102
|
+
JID.new(jid).stripped.to_s
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Instance method to wrap around the class method
|
|
106
|
+
def key(jid)
|
|
107
|
+
self.class.key(jid)
|
|
108
|
+
end
|
|
109
|
+
end # Roster
|
|
110
|
+
|
|
111
|
+
end # Blather
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
|
|
3
|
+
# RosterItems hold internal representations of the user's roster
|
|
4
|
+
# including each JID's status.
|
|
5
|
+
class RosterItem
|
|
6
|
+
VALID_SUBSCRIPTION_TYPES = [:both, :from, :none, :remove, :to].freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :jid,
|
|
9
|
+
:ask,
|
|
10
|
+
:statuses
|
|
11
|
+
|
|
12
|
+
attr_accessor :name,
|
|
13
|
+
:groups
|
|
14
|
+
|
|
15
|
+
def self.new(item)
|
|
16
|
+
return item if item.is_a?(self)
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Create a new RosterItem
|
|
21
|
+
#
|
|
22
|
+
# @overload initialize(jid)
|
|
23
|
+
# Create a new RosterItem based on a JID
|
|
24
|
+
# @param [Blather::JID] jid the JID object
|
|
25
|
+
# @overload initialize(jid)
|
|
26
|
+
# Create a new RosterItem based on a JID string
|
|
27
|
+
# @param [String] jid a JID string
|
|
28
|
+
# @overload initialize(node)
|
|
29
|
+
# Create a new RosterItem based on a stanza
|
|
30
|
+
# @param [Blather::Stanza::Iq::Roster::RosterItem] node a RosterItem
|
|
31
|
+
# stanza
|
|
32
|
+
# @return [Blather::RosterItem] the new RosterItem
|
|
33
|
+
def initialize(item)
|
|
34
|
+
@statuses = []
|
|
35
|
+
@groups = []
|
|
36
|
+
|
|
37
|
+
case item
|
|
38
|
+
when JID
|
|
39
|
+
self.jid = item.stripped
|
|
40
|
+
when String
|
|
41
|
+
self.jid = JID.new(item).stripped
|
|
42
|
+
when XMPPNode
|
|
43
|
+
self.jid = JID.new(item[:jid]).stripped
|
|
44
|
+
self.name = item[:name]
|
|
45
|
+
self.subscription = item[:subscription]
|
|
46
|
+
self.ask = item[:ask]
|
|
47
|
+
item.groups.each { |g| @groups << g }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@groups = [nil] if @groups.empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Set the jid
|
|
54
|
+
#
|
|
55
|
+
# @param [String, Blather::JID] jid the new jid
|
|
56
|
+
# @see Blather::JID
|
|
57
|
+
def jid=(jid)
|
|
58
|
+
@jid = JID.new(jid).stripped
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Set the subscription
|
|
62
|
+
# Ensures it is one of VALID_SUBSCRIPTION_TYPES
|
|
63
|
+
#
|
|
64
|
+
# @param [#to_sym] sub the new subscription
|
|
65
|
+
def subscription=(sub)
|
|
66
|
+
if sub && !VALID_SUBSCRIPTION_TYPES.include?(sub = sub.to_sym)
|
|
67
|
+
raise ArgumentError, "Invalid Type (#{sub}), use: #{VALID_SUBSCRIPTION_TYPES*' '}"
|
|
68
|
+
end
|
|
69
|
+
@subscription = sub ? sub : :none
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get the current subscription
|
|
73
|
+
#
|
|
74
|
+
# @return [:both, :from, :none, :remove, :to]
|
|
75
|
+
def subscription
|
|
76
|
+
@subscription || :none
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Set the ask value
|
|
80
|
+
#
|
|
81
|
+
# @param [nil, :subscribe] ask the new ask
|
|
82
|
+
def ask=(ask)
|
|
83
|
+
if ask && (ask = ask.to_sym) != :subscribe
|
|
84
|
+
raise ArgumentError, "Invalid Type (#{ask}), can only be :subscribe"
|
|
85
|
+
end
|
|
86
|
+
@ask = ask ? ask : nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Set the status then sorts them according to priority
|
|
90
|
+
#
|
|
91
|
+
# @param [Blather::Stanza::Status] the new status
|
|
92
|
+
def status=(presence)
|
|
93
|
+
@statuses.delete_if { |s| s.from == presence.from }
|
|
94
|
+
@statuses << presence
|
|
95
|
+
@statuses.sort!
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The status with the highest priority
|
|
99
|
+
#
|
|
100
|
+
# @param [String, nil] resource the resource to get the status of
|
|
101
|
+
def status(resource = nil)
|
|
102
|
+
top = if resource
|
|
103
|
+
@statuses.detect { |s| s.from.resource == resource }
|
|
104
|
+
else
|
|
105
|
+
@statuses.first
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Translate the RosterItem into a proper stanza that can be sent over the
|
|
110
|
+
# stream
|
|
111
|
+
#
|
|
112
|
+
# @return [Blather::Stanza::Iq::Roster]
|
|
113
|
+
def to_stanza(type = nil)
|
|
114
|
+
r = Stanza::Iq::Roster.new type
|
|
115
|
+
n = Stanza::Iq::Roster::RosterItem.new jid, name, subscription, ask
|
|
116
|
+
r.query << n
|
|
117
|
+
n.groups = groups
|
|
118
|
+
r
|
|
119
|
+
end
|
|
120
|
+
end #RosterItem
|
|
121
|
+
|
|
122
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
class Stanza
|
|
3
|
+
|
|
4
|
+
# # DiscoInfo Stanza
|
|
5
|
+
#
|
|
6
|
+
# [XEP-0030 Disco Info](http://xmpp.org/extensions/xep-0030.html#info)
|
|
7
|
+
#
|
|
8
|
+
# Disco Info node that provides or retreives information about a jabber entity
|
|
9
|
+
#
|
|
10
|
+
# @handler :disco_info
|
|
11
|
+
class DiscoInfo < Disco
|
|
12
|
+
register :disco_info, nil, 'http://jabber.org/protocol/disco#info'
|
|
13
|
+
|
|
14
|
+
# Create a new DiscoInfo stanza
|
|
15
|
+
# @param [:get, :set, :result, :error, nil] type the Iq stanza type
|
|
16
|
+
# @param [String, nil] node the name of the node the info belongs to
|
|
17
|
+
# @param [Array<Array, DiscoInfo::Identity>, nil] identities a list of
|
|
18
|
+
# identities. these are passed directly to DiscoInfo::Identity.new
|
|
19
|
+
# @param [Array<Array, DiscoInfo::Identity>, nil] features a list of
|
|
20
|
+
# features. these are passed directly to DiscoInfo::Feature.new
|
|
21
|
+
# @return [DiscoInfo] a new DiscoInfo stanza
|
|
22
|
+
def self.new(type = nil, node = nil, identities = [], features = [])
|
|
23
|
+
new_node = super type
|
|
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) }
|
|
27
|
+
new_node
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# List of identity objects
|
|
31
|
+
def identities
|
|
32
|
+
query.find('//ns:identity', :ns => self.class.registered_ns).map do |i|
|
|
33
|
+
Identity.new i
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# List of feature objects
|
|
38
|
+
def features
|
|
39
|
+
query.find('//ns:feature', :ns => self.class.registered_ns).map do |f|
|
|
40
|
+
Feature.new f
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Identity < XMPPNode
|
|
45
|
+
# Create a new DiscoInfo Identity
|
|
46
|
+
# @overload new(node)
|
|
47
|
+
# Imports the XML::Node to create a Identity object
|
|
48
|
+
# @param [XML::Node] node the node object to import
|
|
49
|
+
# @overload new(opts = {})
|
|
50
|
+
# Creates a new Identity using a hash of options
|
|
51
|
+
# @param [Hash] opts a hash of options
|
|
52
|
+
# @option opts [String] :name the name of the identity
|
|
53
|
+
# @option opts [String] :type the type of the identity
|
|
54
|
+
# @option opts [String] :category the category of the identity
|
|
55
|
+
# @overload new(name, type = nil, category = nil)
|
|
56
|
+
# Create a new Identity by name
|
|
57
|
+
# @param [String] name the name of the Identity
|
|
58
|
+
# @param [String, nil] type the type of the Identity
|
|
59
|
+
# @param [String, nil] category the category of the Identity
|
|
60
|
+
def self.new(name, type = nil, category = nil)
|
|
61
|
+
new_node = super :identity
|
|
62
|
+
|
|
63
|
+
case name
|
|
64
|
+
when Nokogiri::XML::Node
|
|
65
|
+
new_node.inherit name
|
|
66
|
+
when Hash
|
|
67
|
+
new_node.name = name[:name]
|
|
68
|
+
new_node.type = name[:type]
|
|
69
|
+
new_node.category = name[:category]
|
|
70
|
+
else
|
|
71
|
+
new_node.name = name
|
|
72
|
+
new_node.type = type
|
|
73
|
+
new_node.category = category
|
|
74
|
+
end
|
|
75
|
+
new_node
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The Identity's category
|
|
79
|
+
# @return [Symbol, nil]
|
|
80
|
+
def category
|
|
81
|
+
read_attr :category, :to_sym
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Set the Identity's category
|
|
85
|
+
# @param [String, Symbol] category the new category
|
|
86
|
+
def category=(category)
|
|
87
|
+
write_attr :category, category
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The Identity's type
|
|
91
|
+
# @return [Symbol, nil]
|
|
92
|
+
def type
|
|
93
|
+
read_attr :type, :to_sym
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Set the Identity's type
|
|
97
|
+
# @param [String, Symbol] type the new category
|
|
98
|
+
def type=(type)
|
|
99
|
+
write_attr :type, type
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The Identity's name
|
|
103
|
+
# @return [String]
|
|
104
|
+
def name
|
|
105
|
+
read_attr :name
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Set the Identity's name
|
|
109
|
+
# @param [String] name the new name for the identity
|
|
110
|
+
def name=(name)
|
|
111
|
+
write_attr :name, name
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Compare two Identity objects by name, type and category
|
|
115
|
+
# @param [DiscoInfo::Identity] o the Identity object to compare against
|
|
116
|
+
# @return [true, false]
|
|
117
|
+
def eql?(o)
|
|
118
|
+
unless o.is_a?(self.class)
|
|
119
|
+
raise "Cannot compare #{self.class} with #{o.class}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
o.name == self.name &&
|
|
123
|
+
o.type == self.type &&
|
|
124
|
+
o.category == self.category
|
|
125
|
+
end
|
|
126
|
+
alias_method :==, :eql?
|
|
127
|
+
end # Identity
|
|
128
|
+
|
|
129
|
+
class Feature < XMPPNode
|
|
130
|
+
# Create a new DiscoInfo::Feature object
|
|
131
|
+
# @overload new(node)
|
|
132
|
+
# Create a new Feature by importing an XML::Node
|
|
133
|
+
# @param [XML::Node] node an XML::Node to import
|
|
134
|
+
# @overload new(var)
|
|
135
|
+
# Create a new feature by var
|
|
136
|
+
# @param [String] var a the Feautre's var
|
|
137
|
+
# @return [DiscoInfo::Feature]
|
|
138
|
+
def self.new(var)
|
|
139
|
+
new_node = super :feature
|
|
140
|
+
case var
|
|
141
|
+
when Nokogiri::XML::Node
|
|
142
|
+
new_node.inherit var
|
|
143
|
+
else
|
|
144
|
+
new_node.var = var
|
|
145
|
+
end
|
|
146
|
+
new_node
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The Feature's var
|
|
150
|
+
# @return [String]
|
|
151
|
+
def var
|
|
152
|
+
read_attr :var
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Set the Feature's var
|
|
156
|
+
# @param [String] var the new var
|
|
157
|
+
def var=(var)
|
|
158
|
+
write_attr :var, var
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Compare two Feature objects by var
|
|
162
|
+
# @param [DiscoInfo::Feature] o the Feature object to compare against
|
|
163
|
+
# @return [true, false]
|
|
164
|
+
def eql?(o)
|
|
165
|
+
unless o.is_a?(self.class)
|
|
166
|
+
raise "Cannot compare #{self.class} with #{o.class}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
o.var == self.var
|
|
170
|
+
end
|
|
171
|
+
alias_method :==, :eql?
|
|
172
|
+
end
|
|
173
|
+
end # Feature
|
|
174
|
+
|
|
175
|
+
end # Stanza
|
|
176
|
+
end # Blather
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
class Stanza
|
|
3
|
+
|
|
4
|
+
# # DiscoItems Stanza
|
|
5
|
+
#
|
|
6
|
+
# [XEP-0030 Disco Info](http://xmpp.org/extensions/xep-0030.html#items)
|
|
7
|
+
#
|
|
8
|
+
# Disco Items node that provides or retreives items associated with a
|
|
9
|
+
# jabbery entity
|
|
10
|
+
#
|
|
11
|
+
# @handler :disco_items
|
|
12
|
+
class DiscoItems < Disco
|
|
13
|
+
register :disco_items, nil, 'http://jabber.org/protocol/disco#items'
|
|
14
|
+
|
|
15
|
+
# Create a new DiscoItems node
|
|
16
|
+
#
|
|
17
|
+
# @param [#to_s] type the IQ type
|
|
18
|
+
# @param [#to_s] node the node the items are associated with
|
|
19
|
+
# @param [Array<Blather::XMPPNode>] items an array of Disco::Items
|
|
20
|
+
# @return [Blather::Stanza::DiscoItems]
|
|
21
|
+
def self.new(type = nil, node = nil, items = [])
|
|
22
|
+
new_node = super type
|
|
23
|
+
new_node.node = node
|
|
24
|
+
[items].flatten.each { |item| new_node.query << Item.new(item) }
|
|
25
|
+
new_node
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Set of items associated with the node
|
|
29
|
+
#
|
|
30
|
+
# @return [Array<Blather::Stanza::DiscoItems::Item>]
|
|
31
|
+
def items
|
|
32
|
+
query.find('//ns:item', :ns => self.class.registered_ns).map do |i|
|
|
33
|
+
Item.new i
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# An individual Disco Item
|
|
38
|
+
class Item < XMPPNode
|
|
39
|
+
# Create a new Blather::Stanza::DiscoItems::Item
|
|
40
|
+
#
|
|
41
|
+
# @overload new(node)
|
|
42
|
+
# Create a new Item by inheriting an existing node
|
|
43
|
+
# @param [XML::Node] node an XML::Node to inherit from
|
|
44
|
+
# @overload new(opts)
|
|
45
|
+
# Create a new Item through a hash of options
|
|
46
|
+
# @param [Hash] opts a hash options
|
|
47
|
+
# @option opts [Blather::JID, String] :jid the JID to attach to the item
|
|
48
|
+
# @option opts [#to_s] :node the node the item is attached to
|
|
49
|
+
# @option opts [#to_S] :name the name of the Item
|
|
50
|
+
# @overload new(jid, node = nil, name = nil)
|
|
51
|
+
# Create a new Item
|
|
52
|
+
# @param [Blather::JID, String] jid the JID to attach to the item
|
|
53
|
+
# @param [#to_s] node the node the item is attached to
|
|
54
|
+
# @param [#to_s] name the name of the Item
|
|
55
|
+
def self.new(jid, node = nil, name = nil)
|
|
56
|
+
new_node = super :item
|
|
57
|
+
|
|
58
|
+
case jid
|
|
59
|
+
when Nokogiri::XML::Node
|
|
60
|
+
new_node.inherit jid
|
|
61
|
+
when Hash
|
|
62
|
+
new_node.jid = jid[:jid]
|
|
63
|
+
new_node.node = jid[:node]
|
|
64
|
+
new_node.name = jid[:name]
|
|
65
|
+
else
|
|
66
|
+
new_node.jid = jid
|
|
67
|
+
new_node.node = node
|
|
68
|
+
new_node.name = name
|
|
69
|
+
end
|
|
70
|
+
new_node
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Get the JID attached to the node
|
|
74
|
+
#
|
|
75
|
+
# @return [Blather::JID, nil]
|
|
76
|
+
def jid
|
|
77
|
+
(j = self[:jid]) ? JID.new(j) : nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Set the JID of the node
|
|
81
|
+
#
|
|
82
|
+
# @param [Blather::JID, String, nil] jid the new JID
|
|
83
|
+
def jid=(jid)
|
|
84
|
+
write_attr :jid, jid
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get the name of the node
|
|
88
|
+
#
|
|
89
|
+
# @return [String, nil]
|
|
90
|
+
def node
|
|
91
|
+
read_attr :node
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Set the name of the node
|
|
95
|
+
#
|
|
96
|
+
# @param [String, nil] node the new node name
|
|
97
|
+
def node=(node)
|
|
98
|
+
write_attr :node, node
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get the Item name
|
|
102
|
+
#
|
|
103
|
+
# @return [String, nil]
|
|
104
|
+
def name
|
|
105
|
+
read_attr :name
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Set the Item name
|
|
109
|
+
#
|
|
110
|
+
# @param [#to_s] name the Item name
|
|
111
|
+
def name=(name)
|
|
112
|
+
write_attr :name, name
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check for equality based on jid, node, and name
|
|
116
|
+
#
|
|
117
|
+
# @param [Blather::Stanza::DiscoItems::Item] o the other Item
|
|
118
|
+
def eql?(o)
|
|
119
|
+
unless o.is_a?(self.class)
|
|
120
|
+
raise "Cannot compare #{self.class} with #{o.class}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
o.jid == self.jid &&
|
|
124
|
+
o.node == self.node &&
|
|
125
|
+
o.name == self.name
|
|
126
|
+
end
|
|
127
|
+
alias_method :==, :eql?
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end #Stanza
|
|
132
|
+
end #Blather
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
class Stanza
|
|
3
|
+
|
|
4
|
+
# # Disco Base class
|
|
5
|
+
#
|
|
6
|
+
# Use Blather::Stanza::DiscoInfo or Blather::Stanza::DiscoItems
|
|
7
|
+
class Disco < Iq::Query
|
|
8
|
+
|
|
9
|
+
# Get the name of the node
|
|
10
|
+
#
|
|
11
|
+
# @return [String] the node name
|
|
12
|
+
def node
|
|
13
|
+
query[:node]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Set the name of the node
|
|
17
|
+
#
|
|
18
|
+
# @param [#to_s] node the new node name
|
|
19
|
+
def node=(node)
|
|
20
|
+
query[:node] = node
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end # Stanza
|
|
25
|
+
end # Blather
|