tp-blather 0.8.2

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 (150) hide show
  1. data/.autotest +13 -0
  2. data/.gemtest +0 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +249 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +413 -0
  11. data/Rakefile +20 -0
  12. data/TODO.md +2 -0
  13. data/blather.gemspec +51 -0
  14. data/examples/certs/README +20 -0
  15. data/examples/certs/ca-bundle.crt +3987 -0
  16. data/examples/echo.rb +19 -0
  17. data/examples/execute.rb +17 -0
  18. data/examples/ping_pong.rb +38 -0
  19. data/examples/print_hierarchy.rb +77 -0
  20. data/examples/rosterprint.rb +15 -0
  21. data/examples/stream_only.rb +28 -0
  22. data/examples/trusted_echo.rb +21 -0
  23. data/examples/xmpp4r/echo.rb +36 -0
  24. data/lib/blather.rb +112 -0
  25. data/lib/blather/cert_store.rb +53 -0
  26. data/lib/blather/client.rb +95 -0
  27. data/lib/blather/client/client.rb +345 -0
  28. data/lib/blather/client/dsl.rb +320 -0
  29. data/lib/blather/client/dsl/pubsub.rb +174 -0
  30. data/lib/blather/core_ext/eventmachine.rb +125 -0
  31. data/lib/blather/core_ext/ipaddr.rb +20 -0
  32. data/lib/blather/errors.rb +69 -0
  33. data/lib/blather/errors/sasl_error.rb +44 -0
  34. data/lib/blather/errors/stanza_error.rb +110 -0
  35. data/lib/blather/errors/stream_error.rb +84 -0
  36. data/lib/blather/file_transfer.rb +107 -0
  37. data/lib/blather/file_transfer/ibb.rb +68 -0
  38. data/lib/blather/file_transfer/s5b.rb +114 -0
  39. data/lib/blather/jid.rb +141 -0
  40. data/lib/blather/roster.rb +118 -0
  41. data/lib/blather/roster_item.rb +146 -0
  42. data/lib/blather/stanza.rb +167 -0
  43. data/lib/blather/stanza/disco.rb +32 -0
  44. data/lib/blather/stanza/disco/capabilities.rb +161 -0
  45. data/lib/blather/stanza/disco/disco_info.rb +205 -0
  46. data/lib/blather/stanza/disco/disco_items.rb +134 -0
  47. data/lib/blather/stanza/iq.rb +144 -0
  48. data/lib/blather/stanza/iq/command.rb +339 -0
  49. data/lib/blather/stanza/iq/ibb.rb +86 -0
  50. data/lib/blather/stanza/iq/ping.rb +50 -0
  51. data/lib/blather/stanza/iq/query.rb +53 -0
  52. data/lib/blather/stanza/iq/roster.rb +185 -0
  53. data/lib/blather/stanza/iq/s5b.rb +208 -0
  54. data/lib/blather/stanza/iq/si.rb +415 -0
  55. data/lib/blather/stanza/iq/vcard.rb +149 -0
  56. data/lib/blather/stanza/message.rb +428 -0
  57. data/lib/blather/stanza/message/muc_user.rb +119 -0
  58. data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
  59. data/lib/blather/stanza/presence.rb +172 -0
  60. data/lib/blather/stanza/presence/c.rb +100 -0
  61. data/lib/blather/stanza/presence/muc.rb +35 -0
  62. data/lib/blather/stanza/presence/muc_user.rb +147 -0
  63. data/lib/blather/stanza/presence/status.rb +218 -0
  64. data/lib/blather/stanza/presence/subscription.rb +100 -0
  65. data/lib/blather/stanza/pubsub.rb +119 -0
  66. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  67. data/lib/blather/stanza/pubsub/create.rb +65 -0
  68. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  69. data/lib/blather/stanza/pubsub/event.rb +139 -0
  70. data/lib/blather/stanza/pubsub/items.rb +103 -0
  71. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  72. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  73. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  74. data/lib/blather/stanza/pubsub/subscription.rb +135 -0
  75. data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
  76. data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
  77. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  78. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  79. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  80. data/lib/blather/stanza/x.rb +416 -0
  81. data/lib/blather/stream.rb +266 -0
  82. data/lib/blather/stream/client.rb +32 -0
  83. data/lib/blather/stream/component.rb +39 -0
  84. data/lib/blather/stream/features.rb +70 -0
  85. data/lib/blather/stream/features/register.rb +38 -0
  86. data/lib/blather/stream/features/resource.rb +63 -0
  87. data/lib/blather/stream/features/sasl.rb +190 -0
  88. data/lib/blather/stream/features/session.rb +45 -0
  89. data/lib/blather/stream/features/tls.rb +29 -0
  90. data/lib/blather/stream/parser.rb +102 -0
  91. data/lib/blather/version.rb +3 -0
  92. data/lib/blather/xmpp_node.rb +94 -0
  93. data/spec/blather/client/client_spec.rb +687 -0
  94. data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
  95. data/spec/blather/client/dsl_spec.rb +266 -0
  96. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  97. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  98. data/spec/blather/errors/stream_error_spec.rb +108 -0
  99. data/spec/blather/errors_spec.rb +33 -0
  100. data/spec/blather/file_transfer_spec.rb +135 -0
  101. data/spec/blather/jid_spec.rb +87 -0
  102. data/spec/blather/roster_item_spec.rb +134 -0
  103. data/spec/blather/roster_spec.rb +107 -0
  104. data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
  105. data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
  106. data/spec/blather/stanza/iq/command_spec.rb +206 -0
  107. data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
  108. data/spec/blather/stanza/iq/ping_spec.rb +45 -0
  109. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  110. data/spec/blather/stanza/iq/roster_spec.rb +139 -0
  111. data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
  112. data/spec/blather/stanza/iq/si_spec.rb +98 -0
  113. data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
  114. data/spec/blather/stanza/iq_spec.rb +61 -0
  115. data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
  116. data/spec/blather/stanza/message_spec.rb +282 -0
  117. data/spec/blather/stanza/presence/c_spec.rb +56 -0
  118. data/spec/blather/stanza/presence/muc_spec.rb +37 -0
  119. data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
  120. data/spec/blather/stanza/presence/status_spec.rb +144 -0
  121. data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
  122. data/spec/blather/stanza/presence_spec.rb +125 -0
  123. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  124. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  125. data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
  126. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  127. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  128. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  129. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  130. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  131. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  132. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
  133. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  134. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  135. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  136. data/spec/blather/stanza/pubsub_spec.rb +68 -0
  137. data/spec/blather/stanza/x_spec.rb +231 -0
  138. data/spec/blather/stanza_spec.rb +134 -0
  139. data/spec/blather/stream/client_spec.rb +1090 -0
  140. data/spec/blather/stream/component_spec.rb +108 -0
  141. data/spec/blather/stream/parser_spec.rb +152 -0
  142. data/spec/blather/stream/ssl_spec.rb +32 -0
  143. data/spec/blather/xmpp_node_spec.rb +47 -0
  144. data/spec/blather_spec.rb +34 -0
  145. data/spec/fixtures/pubsub.rb +311 -0
  146. data/spec/spec_helper.rb +17 -0
  147. data/yard/templates/default/class/html/handlers.erb +18 -0
  148. data/yard/templates/default/class/setup.rb +10 -0
  149. data/yard/templates/default/class/text/handlers.erb +1 -0
  150. metadata +459 -0
@@ -0,0 +1,54 @@
1
+ module Blather
2
+ class Stanza
3
+ class MUC
4
+
5
+ module MUCUserBase
6
+ MUC_USER_NAMESPACE = "http://jabber.org/protocol/muc#user"
7
+
8
+ def self.included(klass)
9
+ klass.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+ def new(*args)
14
+ super.tap { |e| e.muc_user }
15
+ end
16
+ end
17
+
18
+ def inherit(node)
19
+ muc_user.remove
20
+ super
21
+ self
22
+ end
23
+
24
+ def password
25
+ find_password_node && password_node.content
26
+ end
27
+
28
+ def password=(var)
29
+ password_node.content = var
30
+ end
31
+
32
+ def muc_user
33
+ unless muc_user = find_first('ns:x', :ns => MUC_USER_NAMESPACE)
34
+ self << (muc_user = XMPPNode.new('x', self.document))
35
+ muc_user.namespace = self.class.registered_ns
36
+ end
37
+ muc_user
38
+ end
39
+
40
+ def password_node
41
+ unless pw = find_password_node
42
+ muc_user << (pw = XMPPNode.new('password', self.document))
43
+ end
44
+ pw
45
+ end
46
+
47
+ def find_password_node
48
+ muc_user.find_first 'ns:password', :ns => MUC_USER_NAMESPACE
49
+ end
50
+ end # MUCUserBase
51
+
52
+ end # MUC
53
+ end # Stanza
54
+ end # Blather
@@ -0,0 +1,172 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ # # Presence Stanza
5
+ #
6
+ # [RFC 3921 Section 2.2 - Presence Syntax](http://xmpp.org/rfcs/rfc3921.html#stanzas-presence)
7
+ #
8
+ # Within Blather most of the interaction with Presence stanzas will be
9
+ # through one of its child classes: Status or Subscription.
10
+ #
11
+ # Presence stanzas are used to express an entity's current network
12
+ # availability (offline or online, along with various sub-states of the
13
+ # latter and optional user-defined descriptive text), and to notify other
14
+ # entities of that availability. Presence stanzas are also used to negotiate
15
+ # and manage subscriptions to the presence of other entities.
16
+ #
17
+ # ## "Type" Attribute
18
+ #
19
+ # The `type` attribute of a presence stanza is optional. A presence stanza
20
+ # that does not possess a `type` attribute is used to signal to the server
21
+ # that the sender is online and available for communication. If included,
22
+ # the `type` attribute specifies a lack of availability, a request to manage
23
+ # a subscription to another entity's presence, a request for another
24
+ # entity's current presence, or an error related to a previously-sent
25
+ # presence stanza. If included, the `type` attribute must have one of the
26
+ # following values:
27
+ #
28
+ # * `:unavailable` -- Signals that the entity is no longer available for
29
+ # communication
30
+ #
31
+ # * `:subscribe` -- The sender wishes to subscribe to the recipient's
32
+ # presence.
33
+ #
34
+ # * `:subscribed` -- The sender has allowed the recipient to receive their
35
+ # presence.
36
+ #
37
+ # * `:unsubscribe` -- The sender is unsubscribing from another entity's
38
+ # presence.
39
+ #
40
+ # * `:unsubscribed` -- The subscription request has been denied or a
41
+ # previously-granted subscription has been cancelled.
42
+ #
43
+ # * `:probe` -- A request for an entity's current presence; should be
44
+ # generated only by a server on behalf of a user.
45
+ #
46
+ # * `:error` -- An error has occurred regarding processing or delivery of a
47
+ # previously-sent presence stanza.
48
+ #
49
+ # Blather provides a helper for each possible type:
50
+ #
51
+ # Presence#unavailabe?
52
+ # Presence#unavailable?
53
+ # Presence#subscribe?
54
+ # Presence#subscribed?
55
+ # Presence#unsubscribe?
56
+ # Presence#unsubscribed?
57
+ # Presence#probe?
58
+ # Presence#error?
59
+ #
60
+ # Blather treats the `type` attribute like a normal ruby object attribute
61
+ # providing a getter and setter. The default `type` is nil.
62
+ #
63
+ # presence = Presence.new
64
+ # presence.type # => nil
65
+ # presence.type = :unavailable
66
+ # presence.unavailable? # => true
67
+ # presence.error? # => false
68
+ #
69
+ # presence.type = :invalid # => RuntimeError
70
+ #
71
+ # @handler :presence
72
+ class Presence < Stanza
73
+ # @private
74
+ VALID_TYPES = [ :unavailable,
75
+ :subscribe,
76
+ :subscribed,
77
+ :unsubscribe,
78
+ :unsubscribed,
79
+ :probe,
80
+ :error].freeze
81
+
82
+ register :presence
83
+
84
+ # Creates a class based on the presence type
85
+ # either a Status or Subscription object is created based
86
+ # on the type attribute.
87
+ # If neither is found it instantiates a Presence object
88
+ def self.import(node, *decorators) # :nodoc:
89
+ node.children.each do |e|
90
+ ns = e.namespace ? e.namespace.href : nil
91
+ klass = class_from_registration e.element_name, ns
92
+ decorators << klass if klass
93
+ end
94
+
95
+ case node['type']
96
+ when nil, 'unavailable'
97
+ decorators << Status
98
+ when /subscribe/
99
+ decorators << Subscription
100
+ end
101
+
102
+ super node, *decorators
103
+ end
104
+
105
+ # Ensure element_name is "presence" for all subclasses
106
+ def self.new(*args)
107
+ super :presence
108
+ end
109
+
110
+ # Check if the IQ is of type :unavailable
111
+ #
112
+ # @return [true, false]
113
+ def unavailable?
114
+ self.type == :unavailable
115
+ end
116
+
117
+ # Check if the IQ is of type :subscribe
118
+ #
119
+ # @return [true, false]
120
+ def subscribe?
121
+ self.type == :subscribe
122
+ end
123
+
124
+ # Check if the IQ is of type :subscribed
125
+ #
126
+ # @return [true, false]
127
+ def subscribed?
128
+ self.type == :subscribed
129
+ end
130
+
131
+ # Check if the IQ is of type :unsubscribe
132
+ #
133
+ # @return [true, false]
134
+ def unsubscribe?
135
+ self.type == :unsubscribe
136
+ end
137
+
138
+ # Check if the IQ is of type :unsubscribed
139
+ #
140
+ # @return [true, false]
141
+ def unsubscribed?
142
+ self.type == :unsubscribed
143
+ end
144
+
145
+ # Check if the IQ is of type :probe
146
+ #
147
+ # @return [true, false]
148
+ def probe?
149
+ self.type == :probe
150
+ end
151
+
152
+ # Check if the IQ is of type :error
153
+ #
154
+ # @return [true, false]
155
+ def error?
156
+ self.type == :error
157
+ end
158
+
159
+ # Ensures type is one of Blather::Stanza::Presence::VALID_TYPES
160
+ #
161
+ # @param [#to_sym] type the Presence type. Must be one of VALID_TYPES
162
+ def type=(type)
163
+ if type && !VALID_TYPES.include?(type.to_sym)
164
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
165
+ end
166
+ super
167
+ end
168
+
169
+ end
170
+
171
+ end #Stanza
172
+ end
@@ -0,0 +1,100 @@
1
+ module Blather
2
+ class Stanza
3
+ class Presence
4
+
5
+ # # Entity Capabilities Stanza
6
+ #
7
+ # [XEP-0115 - Entity Capabilities](http://http://xmpp.org/extensions/xep-0115.html)
8
+ #
9
+ # Blather handles c nodes through this class. It provides a set of helper methods
10
+ # to quickly deal with capabilites presence stanzas.
11
+ #
12
+ # @handler :c
13
+ class C < Presence
14
+ register :c, :c, 'http://jabber.org/protocol/caps'
15
+
16
+ # @private
17
+ VALID_HASH_TYPES = %w[md2 md5 sha-1 sha-224 sha-256 sha-384 sha-512].freeze
18
+
19
+ def self.new(node = nil, ver = nil, hash = 'sha-1')
20
+ new_node = super()
21
+ new_node.c
22
+ new_node.hash = hash
23
+ new_node.node = node
24
+ new_node.ver = ver
25
+ parse new_node.to_xml
26
+ end
27
+
28
+ module InstanceMethods
29
+
30
+ # @private
31
+ def inherit(node)
32
+ c.remove
33
+ super
34
+ self
35
+ end
36
+
37
+ # Get the name of the node
38
+ #
39
+ # @return [String, nil]
40
+ def node
41
+ c[:node]
42
+ end
43
+
44
+ # Set the name of the node
45
+ #
46
+ # @param [String, nil] node the new node name
47
+ def node=(node)
48
+ c[:node] = node
49
+ end
50
+
51
+ # Get the name of the hash
52
+ #
53
+ # @return [Symbol, nil]
54
+ def hash
55
+ c[:hash] && c[:hash].to_sym
56
+ end
57
+
58
+ # Set the name of the hash
59
+ #
60
+ # @param [String, nil] hash the new hash name
61
+ def hash=(hash)
62
+ if hash && !VALID_HASH_TYPES.include?(hash.to_s)
63
+ raise ArgumentError, "Invalid Hash Type (#{hash}), use: #{VALID_HASH_TYPES*' '}"
64
+ end
65
+ c[:hash] = hash
66
+ end
67
+
68
+ # Get the ver
69
+ #
70
+ # @return [String, nil]
71
+ def ver
72
+ c[:ver]
73
+ end
74
+
75
+ # Set the ver
76
+ #
77
+ # @param [String, nil] ver the new ver
78
+ def ver=(ver)
79
+ c[:ver] = ver
80
+ end
81
+
82
+ # C node accessor
83
+ # If a c node exists it will be returned.
84
+ # Otherwise a new node will be created and returned
85
+ #
86
+ # @return [Blather::XMPPNode]
87
+ def c
88
+ unless c = find_first('ns:c', :ns => C.registered_ns)
89
+ self << (c = XMPPNode.new('c', self.document))
90
+ c.namespace = self.class.registered_ns
91
+ end
92
+ c
93
+ end
94
+ end
95
+
96
+ include InstanceMethods
97
+ end # C
98
+ end #Presence
99
+ end #Stanza
100
+ end
@@ -0,0 +1,35 @@
1
+ module Blather
2
+ class Stanza
3
+ class Presence
4
+
5
+ class MUC < Status
6
+ register :muc_join, :x, "http://jabber.org/protocol/muc"
7
+
8
+ def self.new(*args)
9
+ new_node = super
10
+ new_node.muc
11
+ new_node
12
+ end
13
+
14
+ module InstanceMethods
15
+ def inherit(node)
16
+ muc.remove
17
+ super
18
+ self
19
+ end
20
+
21
+ def muc
22
+ unless muc = find_first('ns:x', :ns => MUC.registered_ns)
23
+ self << (muc = XMPPNode.new('x', self.document))
24
+ muc.namespace = self.class.registered_ns
25
+ end
26
+ muc
27
+ end
28
+ end
29
+
30
+ include InstanceMethods
31
+ end # MUC
32
+
33
+ end # Presence
34
+ end # Stanza
35
+ end # Blather
@@ -0,0 +1,147 @@
1
+ require 'blather/stanza/muc/muc_user_base'
2
+
3
+ module Blather
4
+ class Stanza
5
+ class Presence
6
+
7
+ class MUCUser < Presence
8
+ include Blather::Stanza::MUC::MUCUserBase
9
+
10
+ def self.decorator_modules
11
+ super + [Blather::Stanza::MUC::MUCUserBase]
12
+ end
13
+
14
+ register :muc_user_presence, :x, MUC_USER_NAMESPACE
15
+
16
+ module InstanceMethods
17
+
18
+ def affiliation
19
+ item.affiliation
20
+ end
21
+
22
+ def affiliation=(val)
23
+ item.affiliation = val
24
+ end
25
+
26
+ def role
27
+ item.role
28
+ end
29
+
30
+ def role=(val)
31
+ item.role = val
32
+ end
33
+
34
+ def jid
35
+ item.jid
36
+ end
37
+
38
+ def jid=(val)
39
+ item.jid = val
40
+ end
41
+
42
+ def status_codes
43
+ status.map &:code
44
+ end
45
+
46
+ def status_codes=(val)
47
+ muc_user.remove_children :status
48
+ val.each do |code|
49
+ muc_user << Status.new(code)
50
+ end
51
+ end
52
+
53
+ def item
54
+ if item = muc_user.find_first('ns:item', :ns => MUCUser.registered_ns)
55
+ Item.new item
56
+ else
57
+ muc_user << (item = Item.new nil, nil, nil, self.document)
58
+ item
59
+ end
60
+ end
61
+
62
+ def status
63
+ muc_user.find('ns:status', :ns => MUCUser.registered_ns).map do |status|
64
+ Status.new status
65
+ end
66
+ end
67
+ end
68
+
69
+ include InstanceMethods
70
+
71
+ class Item < XMPPNode
72
+ def self.new(affiliation = nil, role = nil, jid = nil, document = nil)
73
+ new_node = super :item, document
74
+
75
+ case affiliation
76
+ when self
77
+ affiliation.document ||= document
78
+ return affiliation
79
+ when Nokogiri::XML::Node
80
+ new_node.inherit affiliation
81
+ when Hash
82
+ new_node.affiliation = affiliation[:affiliation]
83
+ new_node.role = affiliation[:role]
84
+ new_node.jid = affiliation[:jid]
85
+ else
86
+ new_node.affiliation = affiliation
87
+ new_node.role = role
88
+ new_node.jid = jid
89
+ end
90
+ new_node
91
+ end
92
+
93
+ def affiliation
94
+ read_attr :affiliation, :to_sym
95
+ end
96
+
97
+ def affiliation=(val)
98
+ write_attr :affiliation, val
99
+ end
100
+
101
+ def role
102
+ read_attr :role, :to_sym
103
+ end
104
+
105
+ def role=(val)
106
+ write_attr :role, val
107
+ end
108
+
109
+ def jid
110
+ read_attr :jid
111
+ end
112
+
113
+ def jid=(val)
114
+ write_attr :jid, val
115
+ end
116
+ end
117
+
118
+ class Status < XMPPNode
119
+ def self.new(code = nil)
120
+ new_node = super :status
121
+
122
+ case code
123
+ when self.class
124
+ return code
125
+ when Nokogiri::XML::Node
126
+ new_node.inherit code
127
+ when Hash
128
+ new_node.code = code[:code]
129
+ else
130
+ new_node.code = code
131
+ end
132
+ new_node
133
+ end
134
+
135
+ def code
136
+ read_attr :code, :to_i
137
+ end
138
+
139
+ def code=(val)
140
+ write_attr :code, val
141
+ end
142
+ end
143
+ end # MUC
144
+
145
+ end # Presence
146
+ end # Stanza
147
+ end # Blather