sprsquish-blather 0.1 → 0.2.3

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 (73) hide show
  1. data/LICENSE +2 -0
  2. data/README.rdoc +100 -0
  3. data/Rakefile +110 -0
  4. data/examples/drb_client.rb +5 -0
  5. data/examples/echo.rb +18 -0
  6. data/ext/extconf.rb +65 -0
  7. data/ext/push_parser.c +231 -0
  8. data/lib/blather/client.rb +219 -44
  9. data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
  10. data/lib/blather/core_ext/libxml.rb +28 -0
  11. data/lib/blather/errors/sasl_error.rb +87 -0
  12. data/lib/blather/errors/stanza_error.rb +262 -0
  13. data/lib/blather/errors/stream_error.rb +253 -0
  14. data/lib/blather/errors.rb +48 -0
  15. data/lib/blather/{core/jid.rb → jid.rb} +15 -26
  16. data/lib/blather/{core/roster.rb → roster.rb} +22 -0
  17. data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
  18. data/lib/blather/stanza/iq/disco.rb +11 -0
  19. data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
  20. data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
  21. data/lib/blather/stanza/iq/query.rb +51 -0
  22. data/lib/blather/stanza/iq/roster.rb +90 -0
  23. data/lib/blather/stanza/iq.rb +38 -0
  24. data/lib/blather/stanza/message.rb +58 -0
  25. data/lib/blather/stanza/presence/status.rb +78 -0
  26. data/lib/blather/stanza/presence/subscription.rb +72 -0
  27. data/lib/blather/stanza/presence.rb +45 -0
  28. data/lib/blather/stanza.rb +101 -0
  29. data/lib/blather/stream/client.rb +26 -0
  30. data/lib/blather/stream/component.rb +34 -0
  31. data/lib/blather/stream/parser.rb +70 -0
  32. data/lib/blather/stream/resource.rb +48 -0
  33. data/lib/blather/stream/sasl.rb +173 -0
  34. data/lib/blather/stream/session.rb +36 -0
  35. data/lib/blather/stream/stream_handler.rb +39 -0
  36. data/lib/blather/stream/tls.rb +33 -0
  37. data/lib/blather/stream.rb +249 -0
  38. data/lib/blather/xmpp_node.rb +199 -0
  39. data/lib/blather.rb +40 -41
  40. data/spec/blather/core_ext/libxml_spec.rb +58 -0
  41. data/spec/blather/errors/sasl_error_spec.rb +56 -0
  42. data/spec/blather/errors/stanza_error_spec.rb +148 -0
  43. data/spec/blather/errors/stream_error_spec.rb +114 -0
  44. data/spec/blather/errors_spec.rb +40 -0
  45. data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
  46. data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
  47. data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
  48. data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
  49. data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
  50. data/spec/blather/stanza/iq/query_spec.rb +34 -0
  51. data/spec/blather/stanza/iq/roster_spec.rb +123 -0
  52. data/spec/blather/stanza/iq_spec.rb +40 -0
  53. data/spec/blather/stanza/message_spec.rb +52 -0
  54. data/spec/blather/stanza/presence/status_spec.rb +102 -0
  55. data/spec/blather/stanza/presence/subscription_spec.rb +85 -0
  56. data/spec/blather/stanza/presence_spec.rb +53 -0
  57. data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
  58. data/spec/blather/stream/client_spec.rb +787 -0
  59. data/spec/blather/stream/component_spec.rb +86 -0
  60. data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
  61. data/spec/build_safe.rb +20 -0
  62. data/spec/spec_helper.rb +7 -17
  63. metadata +79 -59
  64. data/CHANGELOG +0 -1
  65. data/blather.gemspec +0 -73
  66. data/lib/blather/callback.rb +0 -24
  67. data/lib/blather/core/errors.rb +0 -24
  68. data/lib/blather/core/stanza.rb +0 -90
  69. data/lib/blather/core/stream.rb +0 -179
  70. data/lib/blather/core/xmpp_node.rb +0 -95
  71. data/lib/blather/extensions/last_activity.rb +0 -57
  72. data/lib/blather/extensions/version.rb +0 -85
  73. data/spec/blather/core/stream_spec.rb +0 -263
@@ -1,5 +1,8 @@
1
1
  module Blather
2
2
 
3
+ ##
4
+ # Local Roster
5
+ # Takes care of adding/removing JIDs through the stream
3
6
  class Roster
4
7
  include Enumerable
5
8
 
@@ -9,6 +12,9 @@ module Blather
9
12
  stanza.items.each { |i| push i, false } if stanza
10
13
  end
11
14
 
15
+ ##
16
+ # Process any incoming stanzas adn either add or remove the
17
+ # corresponding RosterItem
12
18
  def process(stanza)
13
19
  stanza.items.each do |i|
14
20
  case i.subscription
@@ -18,11 +24,18 @@ module Blather
18
24
  end
19
25
  end
20
26
 
27
+ ##
28
+ # Pushes a JID into the roster
29
+ # then returns self to allow for chaining
21
30
  def <<(elem)
22
31
  push elem
23
32
  self
24
33
  end
25
34
 
35
+ ##
36
+ # Push a JID into the roster
37
+ # Will send the new item to the server
38
+ # unless overridden by calling #push(elem, false)
26
39
  def push(elem, send = true)
27
40
  jid = elem.respond_to?(:jid) ? elem.jid : JID.new(elem)
28
41
  @items[key(jid)] = node = RosterItem.new(elem)
@@ -31,20 +44,29 @@ module Blather
31
44
  end
32
45
  alias_method :add, :push
33
46
 
47
+ ##
48
+ # Remove a JID from the roster
49
+ # Sends a remove query stanza to the server
34
50
  def delete(jid)
35
51
  @items.delete key(jid)
36
52
  @stream.send_data Stanza::Iq::Roster.new(:set, Stanza::Iq::Roster::RosterItem.new(jid, nil, :remove))
37
53
  end
38
54
  alias_method :remove, :delete
39
55
 
56
+ ##
57
+ # Get a RosterItem by JID
40
58
  def [](jid)
41
59
  items[key(jid)]
42
60
  end
43
61
 
62
+ ##
63
+ # Iterate over all RosterItems
44
64
  def each(&block)
45
65
  items.each &block
46
66
  end
47
67
 
68
+ ##
69
+ # Returns a duplicate of all RosterItems
48
70
  def items
49
71
  @items.dup
50
72
  end
@@ -1,6 +1,11 @@
1
1
  module Blather
2
2
 
3
+ ##
4
+ # RosterItems hold internal representations of the user's roster
5
+ # including each JID's status.
3
6
  class RosterItem
7
+ VALID_SUBSCRIPTION_TYPES = [:both, :from, :none, :remove, :to]
8
+
4
9
  attr_reader :jid,
5
10
  :ask,
6
11
  :statuses
@@ -8,50 +13,76 @@ module Blather
8
13
  attr_accessor :name,
9
14
  :groups
10
15
 
16
+ ##
17
+ # item:: can be a JID, String (a@b) or a Stanza
11
18
  def initialize(item)
12
19
  @statuses = []
20
+ @groups = []
13
21
 
14
- if item.is_a?(JID)
22
+ case item
23
+ when JID
15
24
  self.jid = item.stripped
16
- elsif item.is_a?(XMPPNode)
17
- self.jid = JID.new(item['jid']).stripped
18
- self.name = item['name']
19
- self.subscription = item['subscription']
20
- self.ask = item['ask']
25
+ when String
26
+ self.jid = JID.new(item).stripped
27
+ when XMPPNode
28
+ self.jid = JID.new(item[:jid]).stripped
29
+ self.name = item[:name]
30
+ self.subscription = item[:subscription]
31
+ self.ask = item[:ask]
21
32
  item.groups.each { |g| self.groups << g }
22
33
  end
34
+
35
+ @groups = [nil] if @groups.empty?
23
36
  end
24
37
 
38
+ ##
39
+ # Set the jid
25
40
  def jid=(jid)
26
41
  @jid = JID.new(jid).stripped
27
42
  end
28
43
 
29
- VALID_SUBSCRIPTION_TYPES = [:both, :from, :none, :remove, :to].freeze
44
+ ##
45
+ # Set the subscription
46
+ # Ensures it is one of VALID_SUBSCRIPTION_TYPES
30
47
  def subscription=(sub)
31
48
  raise ArgumentError, "Invalid Type (#{sub}), use: #{VALID_SUBSCRIPTION_TYPES*' '}" if
32
49
  sub && !VALID_SUBSCRIPTION_TYPES.include?(sub = sub.to_sym)
33
50
  @subscription = sub ? sub : :none
34
51
  end
35
52
 
53
+ ##
54
+ # Get the current subscription
55
+ # returns:: :both, :from, :none, :remove, :to or :none
36
56
  def subscription
37
57
  @subscription || :none
38
58
  end
39
59
 
60
+ ##
61
+ # Set the ask value
62
+ # ask:: must only be nil or :subscribe
40
63
  def ask=(ask)
41
- raise ArgumentError, "Invalid Type (#{ask}), use: #{VALID_SUBSCRIPTION_TYPES*' '}" if ask && (ask = ask.to_sym) != :subscribe
64
+ raise ArgumentError, "Invalid Type (#{ask}), can only be :subscribe" if ask && (ask = ask.to_sym) != :subscribe
42
65
  @ask = ask ? ask : nil
43
66
  end
44
67
 
68
+ ##
69
+ # Set the status then sorts them according to priority
70
+ # presence:: Status
45
71
  def status=(presence)
46
72
  @statuses.delete_if { |s| s.from == presence.from }
47
73
  @statuses << presence
48
74
  @statuses.sort!
49
75
  end
50
76
 
77
+ ##
78
+ # Return the status with the highest priority
79
+ # if resource is set find the status of that specific resource
51
80
  def status(resource = nil)
52
81
  top = resource ? @statuses.detect { |s| s.from.resource == resource } : @statuses.first
53
82
  end
54
83
 
84
+ ##
85
+ # Translate the RosterItem into a proper stanza that can be sent over the stream
55
86
  def to_stanza(type = nil)
56
87
  r = Stanza::Iq::Roster.new type
57
88
  n = Stanza::Iq::Roster::RosterItem.new jid, name, subscription, ask
@@ -0,0 +1,11 @@
1
+ module Blather
2
+ class Stanza
3
+ class Iq
4
+
5
+ class Disco < Query
6
+ attribute_accessor :node, :to_sym => false
7
+ end
8
+
9
+ end #Iq
10
+ end #Stanza
11
+ end #Blather
@@ -0,0 +1,86 @@
1
+ module Blather
2
+ class Stanza
3
+ class Iq
4
+
5
+ ##
6
+ # DiscoInfo object ()
7
+ #
8
+ class DiscoInfo < Disco
9
+ register :disco_info, nil, 'http://jabber.org/protocol/disco#info'
10
+
11
+ def initialize(type = nil, identities = [], features = [], node = nil)
12
+ super type
13
+
14
+ [identities].flatten.each do |id|
15
+ query << (id.is_a?(Identity) ? id : Identity.new(id[:name], id[:type], id[:category]))
16
+ end
17
+
18
+ [features].flatten.each do |feature|
19
+ query << (feature.is_a?(Feature) ? feature : Feature.new(feature))
20
+ end
21
+
22
+ self.node = node
23
+ end
24
+
25
+ ##
26
+ # List of identity objects
27
+ def identities
28
+ identities = query.find('identity')
29
+ identities = query.find('query_ns:identity', :query_ns => self.class.ns) if identities.empty?
30
+ identities.map { |i| Identity.new i }
31
+ end
32
+
33
+ ##
34
+ # List of feature objects
35
+ def features
36
+ features = query.find('feature')
37
+ features = query.find('query_ns:feature', :query_ns => self.class.ns) if features.empty?
38
+ features.map { |i| Feature.new i }
39
+ end
40
+
41
+ class Identity < XMPPNode
42
+ attribute_accessor :category, :type
43
+ attribute_accessor :name, :to_sym => false
44
+
45
+ def initialize(name, type = nil, category = nil)
46
+ super :identity
47
+
48
+ if name.is_a?(XML::Node)
49
+ self.inherit name
50
+ else
51
+ self.name = name
52
+ self.type = type
53
+ self.category = category
54
+ end
55
+ end
56
+
57
+ def eql?(other)
58
+ other.kind_of?(self.class) &&
59
+ other.name == self.name &&
60
+ other.type == self.type &&
61
+ other.category == self.category
62
+ end
63
+ end
64
+
65
+ class Feature < XMPPNode
66
+ attribute_accessor :var, :to_sym => false
67
+
68
+ def initialize(var)
69
+ super :feature
70
+ if var.is_a?(XML::Node)
71
+ self.inherit var
72
+ else
73
+ self.var = var
74
+ end
75
+ end
76
+
77
+ def eql?(other)
78
+ other.kind_of?(self.class) &&
79
+ other.var == self.var
80
+ end
81
+ end
82
+ end
83
+
84
+ end #Iq
85
+ end #Stanza
86
+ end #Blather
@@ -0,0 +1,61 @@
1
+ module Blather
2
+ class Stanza
3
+ class Iq
4
+
5
+ class DiscoItems < Disco
6
+ register :disco_items, nil, 'http://jabber.org/protocol/disco#items'
7
+
8
+ def initialize(type = nil, node = nil, items = [])
9
+ super type
10
+ self.node = node
11
+ [items].flatten.each do |item|
12
+ query << (item.is_a?(Item) ? item : Item.new(item[:jid], item[:node], item[:name]))
13
+ end
14
+ end
15
+
16
+ def items
17
+ items = query.find('item')
18
+ items = query.find('query_ns:item', :query_ns => self.class.ns) if items.empty?
19
+ items.map { |i| Item.new i }
20
+ end
21
+
22
+ def node=(node)
23
+ query.attributes[:node] = node
24
+ end
25
+
26
+ def node
27
+ query.attributes[:node]
28
+ end
29
+
30
+ class Item < XMPPNode
31
+ def initialize(jid, node = nil, name = nil)
32
+ super :item
33
+
34
+ if jid.is_a?(XML::Node)
35
+ self.inherit jid
36
+ else
37
+ self.jid = jid
38
+ self.node = node
39
+ self.name = name
40
+ end
41
+ end
42
+
43
+ def jid
44
+ (j = attributes[:jid]) ? JID.new(j) : nil
45
+ end
46
+ attribute_writer :jid
47
+
48
+ attribute_accessor :node, :name, :to_sym => false
49
+ end
50
+
51
+ def eql?(other)
52
+ other.kind_of?(self.class) &&
53
+ other.jid == self.jid &&
54
+ other.node == self.node &&
55
+ other.name == self.name
56
+ end
57
+ end
58
+
59
+ end #Iq
60
+ end #Stanza
61
+ end #Blather
@@ -0,0 +1,51 @@
1
+ module Blather
2
+ class Stanza
3
+ class Iq
4
+
5
+ class Query < Iq
6
+ register :query, :query
7
+
8
+ ##
9
+ # Ensure the namespace is set to the query node
10
+ def initialize(type = nil)
11
+ super()
12
+ query.namespace = self.class.ns
13
+ end
14
+
15
+ ##
16
+ # Kill the query node before running inherit
17
+ def inherit(node)
18
+ query.remove!
19
+ super
20
+ end
21
+
22
+ ##
23
+ # Query node accessor
24
+ # This will ensure there actually is a query node
25
+ def query
26
+ q = find_first('query')
27
+ q = find_first('//query_ns:query', :query_ns => self.class.ns) if !q && self.class.ns
28
+ (self << (q = XMPPNode.new('query'))) unless q
29
+ q
30
+ end
31
+
32
+ ##
33
+ # A query reply should have type set to "result"
34
+ def reply
35
+ elem = super
36
+ elem.type = :result
37
+ elem
38
+ end
39
+
40
+ ##
41
+ # A query reply should have type set to "result"
42
+ def reply!
43
+ super
44
+ self.type = :result
45
+ self
46
+ end
47
+ end #Query
48
+
49
+ end #Iq
50
+ end #Stanza
51
+ end
@@ -0,0 +1,90 @@
1
+ module Blather
2
+ class Stanza
3
+ class Iq
4
+
5
+ class Roster < Query
6
+ register :roster, nil, 'jabber:iq:roster'
7
+
8
+ ##
9
+ # Any new items are added to the query
10
+ def initialize(type = nil, item = nil)
11
+ super type
12
+ query << item if item
13
+ end
14
+
15
+ ##
16
+ # Inherit the XMPPNode to create a proper Roster object.
17
+ # Creates RosterItem objects out of each roster item as well.
18
+ def inherit(node)
19
+ # remove the current set of nodes
20
+ items.each { |i| i.remove! }
21
+ super
22
+ # transmogrify nodes into RosterItems
23
+ items.each { |i| query << RosterItem.new(i); i.remove! }
24
+ self
25
+ end
26
+
27
+ ##
28
+ # Roster items
29
+ def items
30
+ items = query.find('item')
31
+ items = query.find('query_ns:item', :query_ns => self.class.ns) if items.empty?
32
+ items.map { |i| RosterItem.new(i) }
33
+ end
34
+
35
+ class RosterItem < XMPPNode
36
+ ##
37
+ # [jid] may be either a JID or XMPPNode.
38
+ # [name] name alias of the given JID
39
+ # [subscription] subscription type
40
+ # [ask] ask subscription sub-state
41
+ def initialize(jid = nil, name = nil, subscription = nil, ask = nil)
42
+ super :item
43
+
44
+ if jid.is_a?(XML::Node)
45
+ self.inherit jid
46
+ else
47
+ self.jid = jid
48
+ self.name = name
49
+ self.subscription = subscription
50
+ self.ask = ask
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Roster item's JID
56
+ def jid
57
+ (j = attributes[:jid]) ? JID.new(j) : nil
58
+ end
59
+ attribute_writer :jid
60
+
61
+ attribute_accessor :name, :to_sym => false
62
+
63
+ attribute_accessor :subscription, :ask
64
+
65
+ ##
66
+ # The groups roster item belongs to
67
+ def groups
68
+ find(:group).map { |g| g.content }
69
+ end
70
+
71
+ ##
72
+ # Set the roster item's groups
73
+ # must be an array
74
+ def groups=(new_groups)
75
+ find(:group).each { |g| g.remove! }
76
+ new_groups.uniq.each { |g| self << XMPPNode.new(:group, g) } if new_groups
77
+ end
78
+
79
+ ##
80
+ # Convert the roster item to a proper stanza all wrapped up
81
+ # This facilitates new subscriptions
82
+ def to_stanza
83
+ Roster.new(:set, self)
84
+ end
85
+ end #RosterItem
86
+ end #Roster
87
+
88
+ end #Iq
89
+ end #Stanza
90
+ end
@@ -0,0 +1,38 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ ##
5
+ # Base Iq stanza
6
+ class Iq < Stanza
7
+ VALID_TYPES = [:get, :set, :result, :error]
8
+
9
+ register :iq
10
+
11
+ def self.import(node)
12
+ raise(ArgumentError, "Import missmatch #{[node.element_name, self.name].inspect}") if node.element_name != self.name.to_s
13
+ klass = nil
14
+ node.children.each { |e| break if klass = class_from_registration(e.element_name, e.namespace) }
15
+ (klass || self).new(node.attributes[:type]).inherit(node)
16
+ end
17
+
18
+ def initialize(type = nil, to = nil, id = nil)
19
+ super :iq
20
+ self.type = type || :get
21
+ self.to = to
22
+ self.id = id if id
23
+ end
24
+
25
+ VALID_TYPES.each do |valid_type|
26
+ define_method("#{valid_type}?") { self.type == valid_type }
27
+ end
28
+
29
+ ##
30
+ # Ensures type is :get, :set, :result or :error
31
+ def type=(type)
32
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
33
+ super
34
+ end
35
+ end
36
+
37
+ end #Stanza
38
+ end
@@ -0,0 +1,58 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ ##
5
+ # Base Message stanza
6
+ class Message < Stanza
7
+ VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal]
8
+
9
+ register :message
10
+
11
+ def initialize(to = nil, body = nil, type = :chat)
12
+ super()
13
+ self.to = to
14
+ self.type = type
15
+ self.body = body
16
+ end
17
+
18
+ VALID_TYPES.each do |valid_type|
19
+ define_method("#{valid_type}?") { self.type == valid_type }
20
+ end
21
+
22
+ ##
23
+ # Ensures type is :chat, :error, :groupchat, :headline or :normal
24
+ def type=(type)
25
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
26
+ super
27
+ end
28
+
29
+ def body=(body)
30
+ remove_child :body
31
+ self << XMPPNode.new('body', body) if body
32
+ end
33
+
34
+ def body
35
+ content_from :body
36
+ end
37
+
38
+ def subject=(subject)
39
+ remove_child :subject
40
+ self << XMPPNode.new('subject', subject) if subject
41
+ end
42
+
43
+ def subject
44
+ content_from :subject
45
+ end
46
+
47
+ def thread=(thread)
48
+ remove_child :thread
49
+ self << XMPPNode.new('thread', thread) if thread
50
+ end
51
+
52
+ def thread
53
+ content_from :thread
54
+ end
55
+ end
56
+
57
+ end #Stanza
58
+ end
@@ -0,0 +1,78 @@
1
+ module Blather
2
+ class Stanza
3
+ class Presence
4
+
5
+ class Status < Presence
6
+ VALID_STATES = [:away, :chat, :dnd, :xa]
7
+
8
+ include Comparable
9
+
10
+ register :status, :status
11
+
12
+ def initialize(state = nil, message = nil)
13
+ super()
14
+ self.state = state
15
+ self.message = message
16
+ end
17
+
18
+ ##
19
+ # Ensures type is nil or :unavailable
20
+ def type=(type)
21
+ raise ArgumentError, "Invalid type (#{type}). Must be nil or unavailable" if type && type.to_sym != :unavailable
22
+ super
23
+ end
24
+
25
+ ##
26
+ # Ensure state is one of :away, :chat, :dnd, :xa or nil
27
+ def state=(state)
28
+ state = state.to_sym if state
29
+ state = nil if state == :available
30
+ raise ArgumentError, "Invalid Status (#{state}), use: #{VALID_STATES*' '}" if state && !VALID_STATES.include?(state)
31
+
32
+ remove_child :show
33
+ self << XMPPNode.new('show', state) if state
34
+ end
35
+
36
+ ##
37
+ # return:: :available if state is nil
38
+ def state
39
+ (type || content_from(:show) || :available).to_sym
40
+ end
41
+
42
+ ##
43
+ # Ensure priority is between -128 and 127
44
+ def priority=(new_priority)
45
+ raise ArgumentError, 'Priority must be between -128 and +127' if new_priority && !(-128..127).include?(new_priority.to_i)
46
+
47
+ remove_child :priority
48
+ self << XMPPNode.new('priority', new_priority) if new_priority
49
+ end
50
+
51
+ def priority
52
+ content_from(:priority).to_i
53
+ end
54
+
55
+ def message=(msg)
56
+ remove_child :status
57
+ self << XMPPNode.new('status', msg) if msg
58
+ end
59
+
60
+ def message
61
+ content_from :status
62
+ end
63
+
64
+ ##
65
+ # Compare status based on priority
66
+ # raises an error if the JIDs aren't the same
67
+ def <=>(o)
68
+ unless self.from && o.from && self.from.stripped == o.from.stripped
69
+ raise ArgumentError, "Cannot compare status from different JIDs: #{[self.from, o.from].inspect}"
70
+ end
71
+ self.priority <=> o.priority
72
+ end
73
+
74
+ end #Status
75
+
76
+ end #Presence
77
+ end #Stanza
78
+ end #Blather
@@ -0,0 +1,72 @@
1
+ module Blather
2
+ class Stanza
3
+ class Presence
4
+
5
+ class Subscription < Presence
6
+ register :subscription, :subscription
7
+
8
+ def initialize(to = nil, type = nil)
9
+ super()
10
+ self.to = to
11
+ self.type = type
12
+ end
13
+
14
+ def inherit(node)
15
+ inherit_attrs node.attributes
16
+ self
17
+ end
18
+
19
+ def to=(to)
20
+ super JID.new(to).stripped
21
+ end
22
+
23
+ ##
24
+ # Create an approve stanza
25
+ def approve!
26
+ self.type = :subscribed
27
+ morph_to_reply
28
+ end
29
+
30
+ ##
31
+ # Create a refuse stanza
32
+ def refuse!
33
+ self.type = :unsubscribed
34
+ morph_to_reply
35
+ end
36
+
37
+ ##
38
+ # Create an unsubscribe stanza
39
+ def unsubscribe!
40
+ self.type = :unsubscribe
41
+ morph_to_reply
42
+ end
43
+
44
+ ##
45
+ # Create a cancel stanza
46
+ def cancel!
47
+ self.type = :unsubscribed
48
+ morph_to_reply
49
+ end
50
+
51
+ ##
52
+ # Create a request stanza
53
+ def request!
54
+ self.type = :subscribe
55
+ morph_to_reply
56
+ end
57
+
58
+ def request?
59
+ self.type == :subscribe
60
+ end
61
+
62
+ private
63
+ def morph_to_reply
64
+ self.to = self.from if self.from
65
+ self.from = nil
66
+ self
67
+ end
68
+ end #Subscription
69
+
70
+ end #Presence
71
+ end #Stanza
72
+ end