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.
- data/LICENSE +2 -0
- data/README.rdoc +100 -0
- data/Rakefile +110 -0
- data/examples/drb_client.rb +5 -0
- data/examples/echo.rb +18 -0
- data/ext/extconf.rb +65 -0
- data/ext/push_parser.c +231 -0
- data/lib/blather/client.rb +219 -44
- data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
- data/lib/blather/core_ext/libxml.rb +28 -0
- data/lib/blather/errors/sasl_error.rb +87 -0
- data/lib/blather/errors/stanza_error.rb +262 -0
- data/lib/blather/errors/stream_error.rb +253 -0
- data/lib/blather/errors.rb +48 -0
- data/lib/blather/{core/jid.rb → jid.rb} +15 -26
- data/lib/blather/{core/roster.rb → roster.rb} +22 -0
- data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
- data/lib/blather/stanza/iq/disco.rb +11 -0
- data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
- data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
- data/lib/blather/stanza/iq/query.rb +51 -0
- data/lib/blather/stanza/iq/roster.rb +90 -0
- data/lib/blather/stanza/iq.rb +38 -0
- data/lib/blather/stanza/message.rb +58 -0
- data/lib/blather/stanza/presence/status.rb +78 -0
- data/lib/blather/stanza/presence/subscription.rb +72 -0
- data/lib/blather/stanza/presence.rb +45 -0
- data/lib/blather/stanza.rb +101 -0
- data/lib/blather/stream/client.rb +26 -0
- data/lib/blather/stream/component.rb +34 -0
- data/lib/blather/stream/parser.rb +70 -0
- data/lib/blather/stream/resource.rb +48 -0
- data/lib/blather/stream/sasl.rb +173 -0
- data/lib/blather/stream/session.rb +36 -0
- data/lib/blather/stream/stream_handler.rb +39 -0
- data/lib/blather/stream/tls.rb +33 -0
- data/lib/blather/stream.rb +249 -0
- data/lib/blather/xmpp_node.rb +199 -0
- data/lib/blather.rb +40 -41
- data/spec/blather/core_ext/libxml_spec.rb +58 -0
- data/spec/blather/errors/sasl_error_spec.rb +56 -0
- data/spec/blather/errors/stanza_error_spec.rb +148 -0
- data/spec/blather/errors/stream_error_spec.rb +114 -0
- data/spec/blather/errors_spec.rb +40 -0
- data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
- data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
- data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
- data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
- data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
- data/spec/blather/stanza/iq/query_spec.rb +34 -0
- data/spec/blather/stanza/iq/roster_spec.rb +123 -0
- data/spec/blather/stanza/iq_spec.rb +40 -0
- data/spec/blather/stanza/message_spec.rb +52 -0
- data/spec/blather/stanza/presence/status_spec.rb +102 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +85 -0
- data/spec/blather/stanza/presence_spec.rb +53 -0
- data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
- data/spec/blather/stream/client_spec.rb +787 -0
- data/spec/blather/stream/component_spec.rb +86 -0
- data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
- data/spec/build_safe.rb +20 -0
- data/spec/spec_helper.rb +7 -17
- metadata +79 -59
- data/CHANGELOG +0 -1
- data/blather.gemspec +0 -73
- data/lib/blather/callback.rb +0 -24
- data/lib/blather/core/errors.rb +0 -24
- data/lib/blather/core/stanza.rb +0 -90
- data/lib/blather/core/stream.rb +0 -179
- data/lib/blather/core/xmpp_node.rb +0 -95
- data/lib/blather/extensions/last_activity.rb +0 -57
- data/lib/blather/extensions/version.rb +0 -85
- 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
|
-
|
22
|
+
case item
|
23
|
+
when JID
|
15
24
|
self.jid = item.stripped
|
16
|
-
|
17
|
-
self.jid
|
18
|
-
|
19
|
-
self.
|
20
|
-
self.
|
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
|
-
|
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}),
|
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,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
|