shingara-blather 0.4.8

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