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.
Files changed (100) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +162 -0
  3. data/examples/echo.rb +18 -0
  4. data/examples/execute.rb +16 -0
  5. data/examples/ping_pong.rb +37 -0
  6. data/examples/print_hierarchy.rb +76 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/stream_only.rb +27 -0
  9. data/examples/xmpp4r/echo.rb +35 -0
  10. data/lib/blather/client/client.rb +310 -0
  11. data/lib/blather/client/dsl/pubsub.rb +170 -0
  12. data/lib/blather/client/dsl.rb +264 -0
  13. data/lib/blather/client.rb +87 -0
  14. data/lib/blather/core_ext/nokogiri.rb +40 -0
  15. data/lib/blather/errors/sasl_error.rb +43 -0
  16. data/lib/blather/errors/stanza_error.rb +107 -0
  17. data/lib/blather/errors/stream_error.rb +82 -0
  18. data/lib/blather/errors.rb +69 -0
  19. data/lib/blather/jid.rb +142 -0
  20. data/lib/blather/roster.rb +111 -0
  21. data/lib/blather/roster_item.rb +122 -0
  22. data/lib/blather/stanza/disco/disco_info.rb +176 -0
  23. data/lib/blather/stanza/disco/disco_items.rb +132 -0
  24. data/lib/blather/stanza/disco.rb +25 -0
  25. data/lib/blather/stanza/iq/query.rb +53 -0
  26. data/lib/blather/stanza/iq/roster.rb +179 -0
  27. data/lib/blather/stanza/iq.rb +138 -0
  28. data/lib/blather/stanza/message.rb +332 -0
  29. data/lib/blather/stanza/presence/status.rb +212 -0
  30. data/lib/blather/stanza/presence/subscription.rb +101 -0
  31. data/lib/blather/stanza/presence.rb +163 -0
  32. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  33. data/lib/blather/stanza/pubsub/create.rb +65 -0
  34. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  35. data/lib/blather/stanza/pubsub/event.rb +123 -0
  36. data/lib/blather/stanza/pubsub/items.rb +103 -0
  37. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  38. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  39. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  40. data/lib/blather/stanza/pubsub/subscription.rb +134 -0
  41. data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
  42. data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
  43. data/lib/blather/stanza/pubsub.rb +129 -0
  44. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  45. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  46. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  47. data/lib/blather/stanza.rb +149 -0
  48. data/lib/blather/stream/client.rb +31 -0
  49. data/lib/blather/stream/component.rb +38 -0
  50. data/lib/blather/stream/features/resource.rb +63 -0
  51. data/lib/blather/stream/features/sasl.rb +187 -0
  52. data/lib/blather/stream/features/session.rb +44 -0
  53. data/lib/blather/stream/features/tls.rb +28 -0
  54. data/lib/blather/stream/features.rb +53 -0
  55. data/lib/blather/stream/parser.rb +102 -0
  56. data/lib/blather/stream.rb +231 -0
  57. data/lib/blather/xmpp_node.rb +218 -0
  58. data/lib/blather.rb +78 -0
  59. data/spec/blather/client/client_spec.rb +559 -0
  60. data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
  61. data/spec/blather/client/dsl_spec.rb +143 -0
  62. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  63. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  64. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  65. data/spec/blather/errors/stream_error_spec.rb +108 -0
  66. data/spec/blather/errors_spec.rb +33 -0
  67. data/spec/blather/jid_spec.rb +87 -0
  68. data/spec/blather/roster_item_spec.rb +96 -0
  69. data/spec/blather/roster_spec.rb +103 -0
  70. data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
  71. data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
  72. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  73. data/spec/blather/stanza/iq/roster_spec.rb +140 -0
  74. data/spec/blather/stanza/iq_spec.rb +45 -0
  75. data/spec/blather/stanza/message_spec.rb +132 -0
  76. data/spec/blather/stanza/presence/status_spec.rb +132 -0
  77. data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
  78. data/spec/blather/stanza/presence_spec.rb +66 -0
  79. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  80. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  81. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  82. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  83. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  84. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  85. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  86. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  87. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  88. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  89. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  90. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  92. data/spec/blather/stanza/pubsub_spec.rb +67 -0
  93. data/spec/blather/stanza_spec.rb +116 -0
  94. data/spec/blather/stream/client_spec.rb +1011 -0
  95. data/spec/blather/stream/component_spec.rb +95 -0
  96. data/spec/blather/stream/parser_spec.rb +145 -0
  97. data/spec/blather/xmpp_node_spec.rb +231 -0
  98. data/spec/fixtures/pubsub.rb +311 -0
  99. data/spec/spec_helper.rb +43 -0
  100. metadata +249 -0
@@ -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