jabber4r-revive 0.9.0
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/.gitignore +4 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/CHANGELOG +45 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +45 -0
- data/LICENSE +12 -0
- data/README.md +29 -0
- data/Rakefile +71 -0
- data/jabber4r-revive.gemspec +25 -0
- data/lib/jabber4r/bosh_session.rb +224 -0
- data/lib/jabber4r/connection.rb +258 -0
- data/lib/jabber4r/debugger.rb +61 -0
- data/lib/jabber4r/jid.rb +125 -0
- data/lib/jabber4r/protocol/iq.rb +260 -0
- data/lib/jabber4r/protocol/message.rb +246 -0
- data/lib/jabber4r/protocol/parsed_xml_element.rb +208 -0
- data/lib/jabber4r/protocol/presence.rb +160 -0
- data/lib/jabber4r/protocol/xml_element.rb +144 -0
- data/lib/jabber4r/protocol.rb +257 -0
- data/lib/jabber4r/rexml_1.8_patch.rb +16 -0
- data/lib/jabber4r/roster.rb +322 -0
- data/lib/jabber4r/session.rb +615 -0
- data/lib/jabber4r/vcard.rb +42 -0
- data/lib/jabber4r/version.rb +3 -0
- data/lib/jabber4r.rb +33 -0
- data/spec/lib/jabber4r/bosh_session_spec.rb +150 -0
- data/spec/lib/jabber4r/connection_spec.rb +174 -0
- data/spec/lib/jabber4r/debugger_spec.rb +36 -0
- data/spec/lib/jabber4r/jid_spec.rb +198 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/mocks/tcp_socket_mock.rb +8 -0
- metadata +151 -0
@@ -0,0 +1,246 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# License: see LICENSE
|
4
|
+
# Jabber4R - Jabber Instant Messaging Library for Ruby
|
5
|
+
# Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
|
6
|
+
|
7
|
+
module Jabber::Protocol
|
8
|
+
class Message
|
9
|
+
attr_accessor :to, :from, :id, :type, :body, :xhtml, :subject, :thread, :x, :oobData, :errorcode, :error
|
10
|
+
NORMAL = "normal"
|
11
|
+
ERROR="error"
|
12
|
+
CHAT="chat"
|
13
|
+
GROUPCHAT="groupchat"
|
14
|
+
HEADLINE="headline"
|
15
|
+
|
16
|
+
##
|
17
|
+
# Factory to build a Message from an XMLElement
|
18
|
+
#
|
19
|
+
# session:: [Jabber::Session] The Jabber session instance
|
20
|
+
# element:: [Jabber::Protocol::ParsedXMLElement] The received XML object
|
21
|
+
# return:: [Jabber::Protocol::Message] The newly created Message object
|
22
|
+
#
|
23
|
+
def self.from_element(session, element)
|
24
|
+
message = Message.new(element.attr_to)
|
25
|
+
message.from = Jabber::JID.new(element.attr_from) if element.attr_from
|
26
|
+
message.type = element.attr_type
|
27
|
+
message.id = element.attr_id
|
28
|
+
message.thread = element.thread.element_data
|
29
|
+
message.body = element.body.element_data
|
30
|
+
message.xhtml = element.xhtml.element_data
|
31
|
+
message.subject = element.subject.element_data
|
32
|
+
message.oobData = element.x.element_data
|
33
|
+
message.session=session
|
34
|
+
return message
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Creates a Message
|
39
|
+
#
|
40
|
+
# to:: [String | Jabber::JID] The jabber id to send this message to (or from)
|
41
|
+
# type:: [Integer=NORMAL] The type of message...Message::(NORMAL, CHAT, GROUPCHAT, HEADLINE)
|
42
|
+
#
|
43
|
+
def initialize(to, type=NORMAL)
|
44
|
+
return unless to
|
45
|
+
to = Jabber::JID.new(to) if to.kind_of? String
|
46
|
+
@to = to if to.kind_of? Jabber::JID
|
47
|
+
@type = type
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Chaining method...sets the body of the message
|
52
|
+
#
|
53
|
+
# body:: [String] The message body
|
54
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
55
|
+
#
|
56
|
+
def set_body(body)
|
57
|
+
@body = body.gsub(/[&]/, '&').gsub(/[<]/, '<').gsub(/[']/, ''')
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Chaining method...sets the subject of the message
|
63
|
+
#
|
64
|
+
# subject:: [String] The message subject
|
65
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
66
|
+
#
|
67
|
+
def set_subject(subject)
|
68
|
+
@subject = subject.gsub(/[&]/, '&').gsub(/[<]/, '<').gsub(/[']/, ''')
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Chaining method...sets the XHTML body of the message
|
74
|
+
#
|
75
|
+
# body:: [String] The message body
|
76
|
+
# return:: [Jabber::Protocol::Message] The current message object
|
77
|
+
#
|
78
|
+
def set_xhtml(xhtml)
|
79
|
+
@xhtml=xhtml
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Chaining method...sets the thread of the message
|
85
|
+
#
|
86
|
+
# thread:: [String] The message thread id
|
87
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
88
|
+
#
|
89
|
+
def set_thread(thread)
|
90
|
+
@thread = thread
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Chaining method...sets the OOB data of the message
|
96
|
+
#
|
97
|
+
# data:: [String] The message OOB data
|
98
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
99
|
+
#
|
100
|
+
def set_outofband(data)
|
101
|
+
@oobData = data
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Chaining method...sets the extended data of the message
|
107
|
+
#
|
108
|
+
# x:: [String] The message x data
|
109
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
110
|
+
#
|
111
|
+
def set_x(x)
|
112
|
+
@x = x
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Sets an error code to be returned(chaining method)
|
118
|
+
#
|
119
|
+
# code:: [Integer] the jabber error code
|
120
|
+
# reason:: [String] Why the error was reported
|
121
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
122
|
+
#
|
123
|
+
|
124
|
+
def set_error(code,reason)
|
125
|
+
@errorcode=code
|
126
|
+
@error=reason
|
127
|
+
@type="error"
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Convenience method for send(true)
|
133
|
+
#
|
134
|
+
# ttl:: [Integer = nil] The time (in seconds) to wait for a reply before assuming nil
|
135
|
+
# &block:: [Block] A block to process the message replies
|
136
|
+
#
|
137
|
+
def request(ttl=nil, &block)
|
138
|
+
send(true, ttl, &block)
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Sends the message to the Jabber service for delivery
|
143
|
+
#
|
144
|
+
# wait:: [Boolean = false] Wait for reply before return?
|
145
|
+
# ttl:: [Integer = nil] The time (in seconds) to wait for a reply before assuming nil
|
146
|
+
# &block:: [Block] A block to process the message replies
|
147
|
+
#
|
148
|
+
def send(wait=false, ttl=nil, &block)
|
149
|
+
if wait
|
150
|
+
message = nil
|
151
|
+
blockedThread = Thread.current
|
152
|
+
timer_thread = nil
|
153
|
+
timeout = false
|
154
|
+
unless ttl.nil?
|
155
|
+
timer_thread = Thread.new {
|
156
|
+
sleep ttl
|
157
|
+
timeout = true
|
158
|
+
blockedThread.wakeup
|
159
|
+
}
|
160
|
+
end
|
161
|
+
@session.connection.send(self.to_s, block) do |je|
|
162
|
+
if je.element_tag == "message" and je.thread.element_data == @thread
|
163
|
+
je.consume_element
|
164
|
+
message = Message.from_element(@session, je)
|
165
|
+
blockedThread.wakeup unless timeout
|
166
|
+
unless timer_thread.nil?
|
167
|
+
timer_thread.kill
|
168
|
+
timer_thread = nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
Thread.stop
|
173
|
+
return message
|
174
|
+
else
|
175
|
+
@session.connection.send(self.to_s, block) if @session
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# Sets the session instance
|
181
|
+
#
|
182
|
+
# session:: [Jabber::Session] The session instance
|
183
|
+
# return:: [Jabber::Protocol::Message] The current Message object
|
184
|
+
#
|
185
|
+
def session=(session)
|
186
|
+
@session = session
|
187
|
+
self
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Builds a reply to an existing message by setting:
|
192
|
+
# 1. to = from
|
193
|
+
# 2. id = id
|
194
|
+
# 3. thread = thread
|
195
|
+
# 4. type = type
|
196
|
+
# 5. session = session
|
197
|
+
#
|
198
|
+
# return:: [Jabber::Protocol::Message] The reply message
|
199
|
+
#
|
200
|
+
def reply
|
201
|
+
message = Message.new(nil)
|
202
|
+
message.to = @from
|
203
|
+
message.id = @id
|
204
|
+
message.thread = @thread
|
205
|
+
message.type = @type
|
206
|
+
message.session = @session
|
207
|
+
@is_reply = true
|
208
|
+
return message
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Generates XML that complies with the Jabber protocol for
|
213
|
+
# sending the message through the Jabber service.
|
214
|
+
#
|
215
|
+
# return:: [String] The XML string.
|
216
|
+
#
|
217
|
+
def to_xml
|
218
|
+
@thread = Jabber.gen_random_thread if @thread.nil? and (not @is_reply)
|
219
|
+
elem = XMLElement.new("message", {"to"=>@to, "type"=>@type})
|
220
|
+
elem.add_attribute("id", @id) if @id
|
221
|
+
elem.add_child("thread").add_data(@thread) if @thread
|
222
|
+
elem.add_child("subject").add_data(@subject) if @subject
|
223
|
+
elem.add_child("body").add_data(@body) if @body
|
224
|
+
if @xhtml then
|
225
|
+
t=elem.add_child("xhtml").add_attribute("xmlns","http://www.w3.org/1999/xhtml")
|
226
|
+
t.add_child("body").add_data(@xhtml)
|
227
|
+
end
|
228
|
+
if @type=="error" then
|
229
|
+
e=elem.add_child("error");
|
230
|
+
e.add_attribute("code",@errorcode) if @errorcode
|
231
|
+
e.add_data(@error) if @error
|
232
|
+
end
|
233
|
+
elem.add_child("x").add_attribute("xmlns", "jabber:x:oob").add_data(@oobData) if @oobData
|
234
|
+
elem.add_xml(@x.to_s) if @x
|
235
|
+
return elem.to_s
|
236
|
+
end
|
237
|
+
|
238
|
+
##
|
239
|
+
# see to_xml
|
240
|
+
#
|
241
|
+
def to_s
|
242
|
+
to_xml
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# License: see LICENSE
|
4
|
+
# Jabber4R - Jabber Instant Messaging Library for Ruby
|
5
|
+
# Copyright (C) 2002 Rich Kilmer <rich@infoether.com>
|
6
|
+
|
7
|
+
module Jabber::Protocol
|
8
|
+
##
|
9
|
+
# This class is constructed from XML data elements that are received from
|
10
|
+
# the Jabber service.
|
11
|
+
#
|
12
|
+
class ParsedXMLElement
|
13
|
+
|
14
|
+
##
|
15
|
+
# This class is used to return nil element values to prevent errors (and
|
16
|
+
# reduce the number of checks.
|
17
|
+
#
|
18
|
+
class NilParsedXMLElement
|
19
|
+
|
20
|
+
##
|
21
|
+
# Override to return nil
|
22
|
+
#
|
23
|
+
# return:: [nil]
|
24
|
+
#
|
25
|
+
def method_missing(methId, *args)
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Evaluate as nil
|
31
|
+
#
|
32
|
+
# return:: [Boolean] true
|
33
|
+
#
|
34
|
+
def nil?
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Return a zero count
|
40
|
+
#
|
41
|
+
# return:: [Integer] 0
|
42
|
+
#
|
43
|
+
def count
|
44
|
+
0
|
45
|
+
end
|
46
|
+
|
47
|
+
include Singleton
|
48
|
+
end
|
49
|
+
|
50
|
+
# The <tag> as String
|
51
|
+
attr_reader :element_tag
|
52
|
+
|
53
|
+
# The parent ParsedXMLElement
|
54
|
+
attr_reader :element_parent
|
55
|
+
|
56
|
+
# A hash of ParsedXMLElement children
|
57
|
+
attr_reader :element_children
|
58
|
+
|
59
|
+
# The data <tag>data</tag> for a tag
|
60
|
+
attr_reader :element_data
|
61
|
+
|
62
|
+
##
|
63
|
+
# Construct an instance for the given tag
|
64
|
+
#
|
65
|
+
# tag:: [String] The tag
|
66
|
+
# parent:: [Jabber::Protocol::ParsedXMLElement = nil] The parent element
|
67
|
+
#
|
68
|
+
def initialize(tag, parent=nil)
|
69
|
+
@element_tag = tag
|
70
|
+
@element_parent = parent
|
71
|
+
@element_children = {}
|
72
|
+
@attributes = {}
|
73
|
+
@element_consumed = false
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Add the attribute to the element
|
78
|
+
# <tag name="value">data</tag>
|
79
|
+
#
|
80
|
+
# name:: [String] The attribute name
|
81
|
+
# value:: [String] The attribute value
|
82
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] self for chaining
|
83
|
+
#
|
84
|
+
def add_attribute(name, value)
|
85
|
+
@attributes[name]=value
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Factory to build a child element from this element with the given tag
|
91
|
+
#
|
92
|
+
# tag:: [String] The tag name
|
93
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] The newly created child element
|
94
|
+
#
|
95
|
+
def add_child(tag)
|
96
|
+
child = ParsedXMLElement.new(tag, self)
|
97
|
+
@element_children[tag] = Array.new if not @element_children.has_key? tag
|
98
|
+
@element_children[tag] << child
|
99
|
+
return child
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# When an xml is received from the Jabber service and a ParsedXMLElement is created,
|
104
|
+
# it is propogated to all filters and listeners. Any one of those can consume the element
|
105
|
+
# to prevent its propogation to other filters or listeners. This method marks the element
|
106
|
+
# as consumed.
|
107
|
+
#
|
108
|
+
def consume_element
|
109
|
+
@element_consumed = true
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Checks if the element is consumed
|
114
|
+
#
|
115
|
+
# return:: [Boolean] True if the element is consumed
|
116
|
+
#
|
117
|
+
def element_consumed?
|
118
|
+
@element_consumed
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Appends data to the element
|
123
|
+
#
|
124
|
+
# data:: [String] The data to append
|
125
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] self for chaining
|
126
|
+
#
|
127
|
+
def append_data(data)
|
128
|
+
@element_data = "" unless @element_data
|
129
|
+
@element_data += data
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Calls the parent's element_children (hash) index off of this elements
|
135
|
+
# tag and gets the supplied index. In this sense it gets its sibling based
|
136
|
+
# on offset.
|
137
|
+
#
|
138
|
+
# number:: [Integer] The number of the sibling to get
|
139
|
+
# return:: [Jabber::Protocol::ParsedXMLElement] The sibling element
|
140
|
+
#
|
141
|
+
def [](number)
|
142
|
+
return @element_parent.element_children[@element_tag][number] if @element_parent
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Returns the count of siblings with this element's tag
|
147
|
+
#
|
148
|
+
# return:: [Integer] The number of sibling elements
|
149
|
+
#
|
150
|
+
def count
|
151
|
+
return @element_parent.element_children[@element_tag].size if @element_parent
|
152
|
+
return 0
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# see _count
|
157
|
+
#
|
158
|
+
def size
|
159
|
+
count
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# Overrides to allow for directly accessing child elements
|
164
|
+
# and attributes. If prefaced by attr_ it looks for an attribute
|
165
|
+
# that matches or checks for a child with a tag that matches
|
166
|
+
# the method name. If no match occurs, it returns a
|
167
|
+
# NilParsedXMLElement (singleton) instance.
|
168
|
+
#
|
169
|
+
# Example:: <alpha number="1"><beta number="2">Beta Data</beta></alpha>
|
170
|
+
#
|
171
|
+
# element.element_tag #=> alpha
|
172
|
+
# element.attr_number #=> 1
|
173
|
+
# element.beta.element_data #=> Beta Data
|
174
|
+
#
|
175
|
+
def method_missing(methId, *args)
|
176
|
+
tag = methId.id2name
|
177
|
+
if tag[0..4]=="attr_"
|
178
|
+
return @attributes[tag[5..-1]]
|
179
|
+
end
|
180
|
+
list = @element_children[tag]
|
181
|
+
return list[0] if list
|
182
|
+
return NilParsedXMLElement.instance
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Returns the valid XML as a string
|
187
|
+
#
|
188
|
+
# return:: [String] XML string
|
189
|
+
def to_s
|
190
|
+
begin
|
191
|
+
result = "\n<#{@element_tag}"
|
192
|
+
@attributes.each {|key, value| result += (' '+key+'="'+value+'"') }
|
193
|
+
if @element_children.size>0 or @element_data
|
194
|
+
result += ">"
|
195
|
+
else
|
196
|
+
result += "/>"
|
197
|
+
end
|
198
|
+
result += @element_data if @element_data
|
199
|
+
@element_children.each_value {|array| array.each {|je| result += je.to_s} }
|
200
|
+
result += "\n" if @element_children.size>0
|
201
|
+
result += "</#{@element_tag}>" if @element_children.size>0 or @element_data
|
202
|
+
result
|
203
|
+
rescue => exception
|
204
|
+
puts exception.to_s
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|