sprsquish-blather 0.1

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.
@@ -0,0 +1,179 @@
1
+ module Blather
2
+
3
+ module Stream
4
+
5
+ # Connect to the server
6
+ def self.start(client, jid, pass, host = nil, port = 5222)
7
+ jid = JID.new jid
8
+ host ||= jid.domain
9
+
10
+ EM.connect host, port, self, client, jid, pass
11
+ end
12
+
13
+ def initialize(client, jid, pass)
14
+ super()
15
+
16
+ @client = client
17
+
18
+ self.jid = jid
19
+ @pass = pass
20
+
21
+ @to = @jid.domain
22
+ @id = nil
23
+ @lang = 'en'
24
+ @version = '1.0'
25
+ @namespace = 'jabber:client'
26
+
27
+ @parser = Parser.new self
28
+ end
29
+
30
+ def connection_completed
31
+ # @keepalive = EM::Timer.new(60) { send_data ' ' }
32
+ @state = :stopped
33
+ dispatch
34
+ end
35
+
36
+ def receive_data(data)
37
+ @parser.parse data
38
+
39
+ rescue => e
40
+ @client.respond_to?(:rescue) ? @client.rescue(e) : raise(e)
41
+ end
42
+
43
+ def unbind
44
+ # @keepalive.cancel
45
+ @state == :stopped
46
+ end
47
+
48
+ def receive(node)
49
+ LOG.debug "\n"+('-'*30)+"\n"
50
+ LOG.debug "RECEIVING (#{node.element_name}) #{node}"
51
+ @node = node
52
+
53
+ case @node.element_name
54
+ when 'stream:stream'
55
+ @state = :ready if @state == :stopped
56
+
57
+ when 'stream:end'
58
+ @state = :stopped
59
+
60
+ when 'stream:features'
61
+ @features = @node.children
62
+ @state = :features
63
+ dispatch
64
+
65
+ when 'stream:error'
66
+ raise StreamError.new(@node)
67
+
68
+ else
69
+ dispatch
70
+
71
+ end
72
+ end
73
+
74
+ def send(stanza)
75
+ #TODO Queue if not ready
76
+ LOG.debug "SENDING: (#{caller[1]}) #{stanza}"
77
+ send_data stanza.to_s
78
+ end
79
+
80
+ def stopped?
81
+ @state == :stopped
82
+ end
83
+
84
+ def ready?
85
+ @state == :ready
86
+ end
87
+
88
+ def jid=(new_jid)
89
+ LOG.debug "NEW JID: #{new_jid}"
90
+ new_jid = JID.new new_jid
91
+ @client.jid = new_jid
92
+ @jid = new_jid
93
+ end
94
+
95
+ private
96
+ def dispatch
97
+ __send__ @state
98
+ end
99
+
100
+ def start
101
+ send <<-STREAM
102
+ <stream:stream
103
+ to='#{@to}'
104
+ xmlns='#{@namespace}'
105
+ xmlns:stream='http://etherx.jabber.org/streams'
106
+ version='#{@version}'
107
+ xml:lang='#{@lang}'
108
+ >
109
+ STREAM
110
+ end
111
+
112
+ def stop
113
+ send '</stream:stream>'
114
+ end
115
+
116
+ def stopped
117
+ start
118
+ end
119
+
120
+ def ready
121
+ @client.call @node.to_stanza
122
+ end
123
+
124
+ def features
125
+ feature = @features.first
126
+ LOG.debug "FEATURE: #{feature}"
127
+ @state = case feature ? feature['xmlns'] : nil
128
+ when 'urn:ietf:params:xml:ns:xmpp-tls' then :establish_tls
129
+ when 'urn:ietf:params:xml:ns:xmpp-sasl' then :authenticate_sasl
130
+ when 'urn:ietf:params:xml:ns:xmpp-bind' then :bind_resource
131
+ when 'urn:ietf:params:xml:ns:xmpp-session' then :establish_session
132
+ else :ready
133
+ end
134
+
135
+ dispatch unless ready?
136
+ end
137
+
138
+ def establish_tls
139
+ unless @tls
140
+ @tls = TLS.new self
141
+ @tls.success { LOG.debug "TLS: SUCCESS"; @tls = nil; start }
142
+ @tls.failure { LOG.debug "TLS: FAILURE"; stop }
143
+ @node = @features.shift
144
+ end
145
+ @tls.receive @node
146
+ end
147
+
148
+ def authenticate_sasl
149
+ unless @sasl
150
+ @sasl = SASL.new(self, @jid, @pass)
151
+ @sasl.success { LOG.debug "SASL SUCCESS"; @sasl = nil; start }
152
+ @sasl.failure { LOG.debug "SASL FAIL"; stop }
153
+ @node = @features.shift
154
+ end
155
+ @sasl.receive @node
156
+ end
157
+
158
+ def bind_resource
159
+ unless @resource
160
+ @resource = Resource.new self, @jid
161
+ @resource.success { |jid| LOG.debug "RESOURCE: SUCCESS"; @resource = nil; self.jid = jid; @state = :features; dispatch }
162
+ @resource.failure { LOG.debug "RESOURCE: FAILURE"; stop }
163
+ @node = @features.shift
164
+ end
165
+ @resource.receive @node
166
+ end
167
+
168
+ def establish_session
169
+ unless @session
170
+ @session = Session.new self, @to
171
+ @session.success { LOG.debug "SESSION: SUCCESS"; @session = nil; @client.stream_started(self); @state = :features; dispatch }
172
+ @session.failure { LOG.debug "SESSION: FAILURE"; stop }
173
+ @node = @features.shift
174
+ end
175
+ @session.receive @node
176
+ end
177
+ end
178
+
179
+ end
@@ -0,0 +1,148 @@
1
+ module LibXML
2
+ module XML
3
+
4
+ class Attributes
5
+ def remove(name)
6
+ name = name.to_s
7
+ self.each { |a| a.remove! or break if a.name == name }
8
+ end
9
+ end #Attributes
10
+
11
+ end #XML
12
+ end #LibXML
13
+
14
+ class Class # :nodoc:
15
+ def class_inheritable_reader(*syms)
16
+ syms.each do |sym|
17
+ next if sym.is_a?(Hash)
18
+ class_eval <<-EOS
19
+ def self.#{sym}
20
+ read_inheritable_attribute(:#{sym})
21
+ end
22
+
23
+ def #{sym}
24
+ self.class.#{sym}
25
+ end
26
+ EOS
27
+ end
28
+ end
29
+
30
+ def class_inheritable_writer(*syms)
31
+ syms.each do |sym|
32
+ class_eval <<-EOS
33
+ def self.#{sym}=(obj)
34
+ write_inheritable_attribute(:#{sym}, obj)
35
+ end
36
+ EOS
37
+ end
38
+ end
39
+
40
+ def class_inheritable_array_writer(*syms)
41
+ syms.each do |sym|
42
+ class_eval <<-EOS
43
+ def self.#{sym}=(obj)
44
+ write_inheritable_array(:#{sym}, obj)
45
+ end
46
+ EOS
47
+ end
48
+ end
49
+
50
+ def class_inheritable_hash_writer(*syms)
51
+ syms.each do |sym|
52
+ class_eval <<-EOS
53
+ def self.#{sym}=(obj)
54
+ write_inheritable_hash(:#{sym}, obj)
55
+ end
56
+ EOS
57
+ end
58
+ end
59
+
60
+ def class_inheritable_accessor(*syms)
61
+ class_inheritable_reader(*syms)
62
+ class_inheritable_writer(*syms)
63
+ end
64
+
65
+ def class_inheritable_array(*syms)
66
+ class_inheritable_reader(*syms)
67
+ class_inheritable_array_writer(*syms)
68
+ end
69
+
70
+ def class_inheritable_hash(*syms)
71
+ class_inheritable_reader(*syms)
72
+ class_inheritable_hash_writer(*syms)
73
+ end
74
+
75
+ def inheritable_attributes
76
+ @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
77
+ end
78
+
79
+ def write_inheritable_attribute(key, value)
80
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
81
+ @inheritable_attributes = {}
82
+ end
83
+ inheritable_attributes[key] = value
84
+ end
85
+
86
+ def write_inheritable_array(key, elements)
87
+ write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
88
+ write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
89
+ end
90
+
91
+ def write_inheritable_hash(key, hash)
92
+ write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
93
+ write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
94
+ end
95
+
96
+ def read_inheritable_attribute(key)
97
+ inheritable_attributes[key]
98
+ end
99
+
100
+ def reset_inheritable_attributes
101
+ @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
102
+ end
103
+
104
+ private
105
+ # Prevent this constant from being created multiple times
106
+ EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
107
+
108
+ def inherited_with_inheritable_attributes(child)
109
+ inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
110
+
111
+ if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
112
+ new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
113
+ else
114
+ new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
115
+ memo.update(key => value.duplicable? ? value.dup : value)
116
+ end
117
+ end
118
+
119
+ child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
120
+ end
121
+
122
+ alias inherited_without_inheritable_attributes inherited
123
+ alias inherited inherited_with_inheritable_attributes
124
+ end #Class
125
+
126
+ class Object
127
+ def duplicable?; true; end
128
+ end
129
+
130
+ class NilClass #:nodoc:
131
+ def duplicable?; false; end
132
+ end
133
+
134
+ class FalseClass #:nodoc:
135
+ def duplicable?; false; end
136
+ end
137
+
138
+ class TrueClass #:nodoc:
139
+ def duplicable?; false; end
140
+ end
141
+
142
+ class Symbol #:nodoc:
143
+ def duplicable?; false; end
144
+ end
145
+
146
+ class Numeric #:nodoc:
147
+ def duplicable?; false; end
148
+ end
@@ -0,0 +1,95 @@
1
+ module Blather
2
+
3
+ class XMPPNode < XML::Node
4
+ @@registrations = {}
5
+
6
+ alias_method :element_name, :name
7
+
8
+ class_inheritable_accessor :xmlns,
9
+ :name
10
+
11
+ def self.new(name = nil, content = nil)
12
+ name ||= self.name
13
+
14
+ args = []
15
+ args << name.to_s if name
16
+ args << content if content
17
+
18
+ elem = super *args
19
+ elem.xmlns = xmlns
20
+ elem
21
+ end
22
+
23
+ def self.register(name, xmlns = nil)
24
+ self.name = name.to_s
25
+ self.xmlns = xmlns
26
+ @@registrations[[name, xmlns]] = self
27
+ end
28
+
29
+ def self.class_from_registration(name, xmlns)
30
+ name = name.to_s
31
+ @@registrations[[name, xmlns]] || @@registrations[[name, nil]]
32
+ end
33
+
34
+ def self.import(node)
35
+ klass = class_from_registration(node.element_name, node.xmlns)
36
+ if klass && klass != self
37
+ klass.import(node)
38
+ else
39
+ new(node.element_name).inherit(node)
40
+ end
41
+ end
42
+
43
+ def to_stanza
44
+ self.class.import self
45
+ end
46
+
47
+ def xmlns=(ns)
48
+ attributes.remove :xmlns
49
+ self['xmlns'] = ns if ns
50
+ end
51
+
52
+ def xmlns
53
+ self['xmlns']
54
+ end
55
+
56
+ def remove_child(name, ns = nil)
57
+ name = name.to_s
58
+ self.each { |n| n.remove! if n.element_name == name && (!ns || n.xmlns == ns) }
59
+ end
60
+
61
+ def remove_children(name)
62
+ name = name.to_s
63
+ self.find(name).each { |n| n.remove! }
64
+ end
65
+
66
+ def content_from(name)
67
+ name = name.to_s
68
+ (child = self.detect { |n| n.element_name == name }) ? child.content : nil
69
+ end
70
+
71
+ def copy(deep = true)
72
+ self.class.new(self.element_name).inherit(self)
73
+ end
74
+
75
+ def inherit(stanza)
76
+ inherit_attrs stanza.attributes
77
+ stanza.children.each { |c| self << c.copy(true) }
78
+ self
79
+ end
80
+
81
+ def inherit_attrs(attrs)
82
+ attrs.each { |a| self[a.name] = a.value }
83
+ self
84
+ end
85
+
86
+ def to_s
87
+ super.gsub(">\n<", '><')
88
+ end
89
+
90
+ def find(what, nslist = nil)
91
+ (self.doc ? super(what, nslist) : select { |i| i.element_name == what})
92
+ end
93
+ end #XMPPNode
94
+
95
+ end
@@ -0,0 +1,57 @@
1
+ module Blather
2
+ module Extensions
3
+
4
+ module LastActivity
5
+ def self.included(base)
6
+ base.class_eval do
7
+ @@last_activity = Time.now
8
+
9
+ alias_method :send_data_without_activity, :send_data
10
+ def send_data(data)
11
+ @@last_activity = Time.now
12
+ send_data_without_activity data
13
+ end
14
+ end
15
+ end
16
+
17
+ def last_activity
18
+ (Time.now - @@last_activity).to_i
19
+ end
20
+
21
+ def receive_last_activity(stanza)
22
+ send_data stanza.reply!(last_activity) if stanza.type == 'get'
23
+ end
24
+ end #LastActivity
25
+
26
+ class LastActivityStanza < Query
27
+ register :last_activity, nil, 'jabber:iq:last'
28
+
29
+ def self.new(type = :get, seconds = nil)
30
+ elem = super type
31
+ elem.seconds = seconds
32
+ elem
33
+ end
34
+
35
+ def seconds=(seconds)
36
+ query.attributes.remove :seconds
37
+ query['seconds'] = seconds.to_i.to_s if seconds
38
+ end
39
+
40
+ def seconds
41
+ (query['seconds'] || 0).to_i
42
+ end
43
+
44
+ def reply(seconds)
45
+ elem = super()
46
+ elem.last_activity = seconds
47
+ end
48
+
49
+ def reply!(seconds)
50
+ self.last_activity = seconds
51
+ super()
52
+ end
53
+ end #LastActivityStanza
54
+ end
55
+ end
56
+
57
+ Blather::Client.__send__ :include, Blather::Extensions::LastActivity
@@ -0,0 +1,85 @@
1
+ module Blather
2
+ module Extensions
3
+
4
+ module Version
5
+ def self.included(base)
6
+ base.class_eval do
7
+ @@version = {}
8
+
9
+ def self.version(name, ver, os = nil)
10
+ @@version = {:name => name, :version => ver, :os => os}
11
+ end
12
+
13
+ add_callback(:iq, 100) do |c, s|
14
+ if s.detect { |n| n['xmlns'] == 'jabber:iq:version' }
15
+ ver = VersionStanza.new('result', c.version)
16
+ ver.id = s.id
17
+ ver.from = c.jid
18
+ ver.to = s.from
19
+ c.send_data ver
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def version
26
+ @@version
27
+ end
28
+ end #Version
29
+
30
+ class VersionStanza < Iq
31
+ def self.new(type = 'result', ver = {})
32
+ elem = super(type)
33
+
34
+ query = XML::Node.new('query')
35
+ query.xmlns = 'jabber:iq:version'
36
+ elem << query
37
+
38
+ elem.name = ver[:name] if ver[:name]
39
+ elem.version = ver[:version] if ver[:version]
40
+ elem.os = ver[:os] if ver[:os]
41
+
42
+ elem
43
+ end
44
+
45
+ def query
46
+ @query ||= self.detect { |n| n.name == 'query' }
47
+ end
48
+
49
+ def name=(name)
50
+ query.each { |n| n.remove! or break if n.name == 'name' }
51
+ query << XML::Node.new('name', name)
52
+ end
53
+
54
+ def name
55
+ if name = query.detect { |n| n.name == 'name' }
56
+ name.content
57
+ end
58
+ end
59
+
60
+ def version=(version)
61
+ query.each { |n| n.remove! or break if n.name == 'version' }
62
+ query << XML::Node.new('version', version)
63
+ end
64
+
65
+ def version
66
+ if version = query.detect { |n| n.version == 'version' }
67
+ version.content
68
+ end
69
+ end
70
+
71
+ def os=(os)
72
+ query.each { |n| n.remove! or break if n.name == 'os' }
73
+ query << XML::Node.new('os', os)
74
+ end
75
+
76
+ def os
77
+ if os = query.detect { |n| n.os == 'os' }
78
+ os.content
79
+ end
80
+ end
81
+ end #VersionStanza
82
+ end
83
+ end
84
+
85
+ Blather::Client.__send__ :include, Blather::Extensions::Version
data/lib/blather.rb ADDED
@@ -0,0 +1,54 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ %w[
4
+ rubygems
5
+ xml/libxml
6
+ eventmachine
7
+ digest/md5
8
+ logger
9
+
10
+ blather/callback
11
+
12
+ blather/core/errors
13
+ blather/core/jid
14
+ blather/core/roster
15
+ blather/core/roster_item
16
+ blather/core/sugar
17
+ blather/core/xmpp_node
18
+
19
+ blather/core/stanza
20
+ blather/core/stanza/iq
21
+ blather/core/stanza/iq/query
22
+ blather/core/stanza/iq/roster
23
+ blather/core/stanza/message
24
+ blather/core/stanza/presence
25
+ blather/core/stanza/presence/status
26
+ blather/core/stanza/presence/subscription
27
+
28
+ blather/core/stream
29
+ blather/core/stream/parser
30
+ blather/core/stream/resource
31
+ blather/core/stream/sasl
32
+ blather/core/stream/session
33
+ blather/core/stream/tls
34
+ ].each { |r| require r }
35
+
36
+ XML::Parser.indent_tree_output = false
37
+
38
+ module Blather
39
+ LOG = Logger.new STDOUT
40
+
41
+ def run(jid, password, client, host = nil, port = 5222)
42
+ EM.run { Stream.start client, JID.new(jid), password, host, port }
43
+ end
44
+ module_function :run
45
+
46
+ MAJOR = 0
47
+ MINOR = 1
48
+ VERSION = [MAJOR, MINOR]*'.'
49
+
50
+ def version
51
+ VERSION
52
+ end
53
+ module_function :version
54
+ end
@@ -0,0 +1,78 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'Blather::JID' do
4
+ it 'does nothing if creaded from JID' do
5
+ jid = JID.new 'n@d/r'
6
+ JID.new(jid).object_id.must_equal jid.object_id
7
+ end
8
+
9
+ it 'creates a new JID from (n,d,r)' do
10
+ jid = JID.new('n', 'd', 'r')
11
+ jid.node.must_equal 'n'
12
+ jid.domain.must_equal 'd'
13
+ jid.resource.must_equal 'r'
14
+ end
15
+
16
+ it 'creates a new JID from (n,d)' do
17
+ jid = JID.new('n', 'd')
18
+ jid.node.must_equal 'n'
19
+ jid.domain.must_equal 'd'
20
+ end
21
+
22
+ it 'creates a new JID from (n@d)' do
23
+ jid = JID.new('n@d')
24
+ jid.node.must_equal 'n'
25
+ jid.domain.must_equal 'd'
26
+ end
27
+
28
+ it 'creates a new JID from (n@d/r)' do
29
+ jid = JID.new('n@d/r')
30
+ jid.node.must_equal 'n'
31
+ jid.domain.must_equal 'd'
32
+ jid.resource.must_equal 'r'
33
+ end
34
+
35
+ it 'requires at least a node' do
36
+ proc { JID.new }.must_raise ArgumentError
37
+ end
38
+
39
+ it 'ensures length of node is no more than 1023 characters' do
40
+ proc { JID.new('n'*1024) }.must_raise Blather::ArgumentError
41
+ end
42
+
43
+ it 'ensures length of domain is no more than 1023 characters' do
44
+ proc { JID.new('n', 'd'*1024) }.must_raise Blather::ArgumentError
45
+ end
46
+
47
+ it 'ensures length of resource is no more than 1023 characters' do
48
+ proc { JID.new('n', 'd', 'r'*1024) }.must_raise Blather::ArgumentError
49
+ end
50
+
51
+ it 'compares JIDs' do
52
+ (JID.new('a@b/c') <=> JID.new('d@e/f')).must_equal -1
53
+ (JID.new('a@b/c') <=> JID.new('a@b/c')).must_equal 0
54
+ (JID.new('d@e/f') <=> JID.new('a@b/c')).must_equal 1
55
+ end
56
+
57
+ it 'checks for equality' do
58
+ (JID.new('n@d/r') == JID.new('n@d/r')).must_equal true
59
+ end
60
+
61
+ it 'will strip' do
62
+ jid = JID.new('n@d/r')
63
+ jid.stripped.must_equal JID.new('n@d')
64
+ jid.must_equal JID.new('n@d/r')
65
+ end
66
+
67
+ it 'will strip itself' do
68
+ jid = JID.new('n@d/r')
69
+ jid.strip!
70
+ jid.must_equal JID.new('n@d')
71
+ end
72
+
73
+ it 'has a string representation' do
74
+ JID.new('n@d/r').to_s.must_equal 'n@d/r'
75
+ JID.new('n', 'd', 'r').to_s.must_equal 'n@d/r'
76
+ JID.new('n', 'd').to_s.must_equal 'n@d'
77
+ end
78
+ end