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.
- data/.autotest +13 -0
- data/.gemtest +0 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +249 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +413 -0
- data/Rakefile +20 -0
- data/TODO.md +2 -0
- data/blather.gemspec +51 -0
- data/examples/certs/README +20 -0
- data/examples/certs/ca-bundle.crt +3987 -0
- data/examples/echo.rb +19 -0
- data/examples/execute.rb +17 -0
- data/examples/ping_pong.rb +38 -0
- data/examples/print_hierarchy.rb +77 -0
- data/examples/rosterprint.rb +15 -0
- data/examples/stream_only.rb +28 -0
- data/examples/trusted_echo.rb +21 -0
- data/examples/xmpp4r/echo.rb +36 -0
- data/lib/blather.rb +112 -0
- data/lib/blather/cert_store.rb +53 -0
- data/lib/blather/client.rb +95 -0
- data/lib/blather/client/client.rb +345 -0
- data/lib/blather/client/dsl.rb +320 -0
- data/lib/blather/client/dsl/pubsub.rb +174 -0
- data/lib/blather/core_ext/eventmachine.rb +125 -0
- data/lib/blather/core_ext/ipaddr.rb +20 -0
- data/lib/blather/errors.rb +69 -0
- data/lib/blather/errors/sasl_error.rb +44 -0
- data/lib/blather/errors/stanza_error.rb +110 -0
- data/lib/blather/errors/stream_error.rb +84 -0
- data/lib/blather/file_transfer.rb +107 -0
- data/lib/blather/file_transfer/ibb.rb +68 -0
- data/lib/blather/file_transfer/s5b.rb +114 -0
- data/lib/blather/jid.rb +141 -0
- data/lib/blather/roster.rb +118 -0
- data/lib/blather/roster_item.rb +146 -0
- data/lib/blather/stanza.rb +167 -0
- data/lib/blather/stanza/disco.rb +32 -0
- data/lib/blather/stanza/disco/capabilities.rb +161 -0
- data/lib/blather/stanza/disco/disco_info.rb +205 -0
- data/lib/blather/stanza/disco/disco_items.rb +134 -0
- data/lib/blather/stanza/iq.rb +144 -0
- data/lib/blather/stanza/iq/command.rb +339 -0
- data/lib/blather/stanza/iq/ibb.rb +86 -0
- data/lib/blather/stanza/iq/ping.rb +50 -0
- data/lib/blather/stanza/iq/query.rb +53 -0
- data/lib/blather/stanza/iq/roster.rb +185 -0
- data/lib/blather/stanza/iq/s5b.rb +208 -0
- data/lib/blather/stanza/iq/si.rb +415 -0
- data/lib/blather/stanza/iq/vcard.rb +149 -0
- data/lib/blather/stanza/message.rb +428 -0
- data/lib/blather/stanza/message/muc_user.rb +119 -0
- data/lib/blather/stanza/muc/muc_user_base.rb +54 -0
- data/lib/blather/stanza/presence.rb +172 -0
- data/lib/blather/stanza/presence/c.rb +100 -0
- data/lib/blather/stanza/presence/muc.rb +35 -0
- data/lib/blather/stanza/presence/muc_user.rb +147 -0
- data/lib/blather/stanza/presence/status.rb +218 -0
- data/lib/blather/stanza/presence/subscription.rb +100 -0
- data/lib/blather/stanza/pubsub.rb +119 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
- data/lib/blather/stanza/pubsub/create.rb +65 -0
- data/lib/blather/stanza/pubsub/errors.rb +18 -0
- data/lib/blather/stanza/pubsub/event.rb +139 -0
- data/lib/blather/stanza/pubsub/items.rb +103 -0
- data/lib/blather/stanza/pubsub/publish.rb +103 -0
- data/lib/blather/stanza/pubsub/retract.rb +92 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
- data/lib/blather/stanza/pubsub/subscription.rb +135 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +83 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +84 -0
- data/lib/blather/stanza/pubsub_owner.rb +51 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
- data/lib/blather/stanza/x.rb +416 -0
- data/lib/blather/stream.rb +266 -0
- data/lib/blather/stream/client.rb +32 -0
- data/lib/blather/stream/component.rb +39 -0
- data/lib/blather/stream/features.rb +70 -0
- data/lib/blather/stream/features/register.rb +38 -0
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/features/sasl.rb +190 -0
- data/lib/blather/stream/features/session.rb +45 -0
- data/lib/blather/stream/features/tls.rb +29 -0
- data/lib/blather/stream/parser.rb +102 -0
- data/lib/blather/version.rb +3 -0
- data/lib/blather/xmpp_node.rb +94 -0
- data/spec/blather/client/client_spec.rb +687 -0
- data/spec/blather/client/dsl/pubsub_spec.rb +492 -0
- data/spec/blather/client/dsl_spec.rb +266 -0
- data/spec/blather/errors/sasl_error_spec.rb +33 -0
- data/spec/blather/errors/stanza_error_spec.rb +129 -0
- data/spec/blather/errors/stream_error_spec.rb +108 -0
- data/spec/blather/errors_spec.rb +33 -0
- data/spec/blather/file_transfer_spec.rb +135 -0
- data/spec/blather/jid_spec.rb +87 -0
- data/spec/blather/roster_item_spec.rb +134 -0
- data/spec/blather/roster_spec.rb +107 -0
- data/spec/blather/stanza/discos/disco_info_spec.rb +247 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +154 -0
- data/spec/blather/stanza/iq/command_spec.rb +206 -0
- data/spec/blather/stanza/iq/ibb_spec.rb +124 -0
- data/spec/blather/stanza/iq/ping_spec.rb +45 -0
- data/spec/blather/stanza/iq/query_spec.rb +64 -0
- data/spec/blather/stanza/iq/roster_spec.rb +139 -0
- data/spec/blather/stanza/iq/s5b_spec.rb +57 -0
- data/spec/blather/stanza/iq/si_spec.rb +98 -0
- data/spec/blather/stanza/iq/vcard_spec.rb +93 -0
- data/spec/blather/stanza/iq_spec.rb +61 -0
- data/spec/blather/stanza/message/muc_user_spec.rb +152 -0
- data/spec/blather/stanza/message_spec.rb +282 -0
- data/spec/blather/stanza/presence/c_spec.rb +56 -0
- data/spec/blather/stanza/presence/muc_spec.rb +37 -0
- data/spec/blather/stanza/presence/muc_user_spec.rb +83 -0
- data/spec/blather/stanza/presence/status_spec.rb +144 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +102 -0
- data/spec/blather/stanza/presence_spec.rb +125 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +98 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +74 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +68 -0
- data/spec/blather/stanza/x_spec.rb +231 -0
- data/spec/blather/stanza_spec.rb +134 -0
- data/spec/blather/stream/client_spec.rb +1090 -0
- data/spec/blather/stream/component_spec.rb +108 -0
- data/spec/blather/stream/parser_spec.rb +152 -0
- data/spec/blather/stream/ssl_spec.rb +32 -0
- data/spec/blather/xmpp_node_spec.rb +47 -0
- data/spec/blather_spec.rb +34 -0
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +17 -0
- data/yard/templates/default/class/html/handlers.erb +18 -0
- data/yard/templates/default/class/setup.rb +10 -0
- data/yard/templates/default/class/text/handlers.erb +1 -0
- metadata +459 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
module Blather
|
|
2
|
+
class Stanza
|
|
3
|
+
|
|
4
|
+
# # Message Stanza
|
|
5
|
+
#
|
|
6
|
+
# [RFC 3921 Section 2.1 - Message Syntax](http://xmpp.org/rfcs/rfc3921.html#rfc.section.2.1)
|
|
7
|
+
#
|
|
8
|
+
# Exchanging messages is a basic use of XMPP and occurs when a user
|
|
9
|
+
# generates a message stanza that is addressed to another entity. The
|
|
10
|
+
# sender's server is responsible for delivering the message to the intended
|
|
11
|
+
# recipient (if the recipient is on the same local server) or for routing
|
|
12
|
+
# the message to the recipient's server (if the recipient is on a remote
|
|
13
|
+
# server). Thus a message stanza is used to "push" information to another
|
|
14
|
+
# entity.
|
|
15
|
+
#
|
|
16
|
+
# ## "To" Attribute
|
|
17
|
+
#
|
|
18
|
+
# An instant messaging client specifies an intended recipient for a message
|
|
19
|
+
# by providing the JID of an entity other than the sender in the `to`
|
|
20
|
+
# attribute of the Message stanza. If the message is being sent outside the
|
|
21
|
+
# context of any existing chat session or received message, the value of the
|
|
22
|
+
# `to` address SHOULD be of the form "user@domain" rather than of the form
|
|
23
|
+
# "user@domain/resource".
|
|
24
|
+
#
|
|
25
|
+
# msg = Message.new 'user@domain.tld/resource'
|
|
26
|
+
# msg.to == 'user@domain.tld/resource'
|
|
27
|
+
#
|
|
28
|
+
# msg.to = 'another-user@some-domain.tld/resource'
|
|
29
|
+
# msg.to == 'another-user@some-domain.tld/resource'
|
|
30
|
+
#
|
|
31
|
+
# The `to` attribute on a Message stanza works like any regular ruby object
|
|
32
|
+
# attribute
|
|
33
|
+
#
|
|
34
|
+
# ## "Type" Attribute
|
|
35
|
+
#
|
|
36
|
+
# Common uses of the message stanza in instant messaging applications
|
|
37
|
+
# include: single messages; messages sent in the context of a one-to-one
|
|
38
|
+
# chat session; messages sent in the context of a multi-user chat room;
|
|
39
|
+
# alerts, notifications, or other information to which no reply is expected;
|
|
40
|
+
# and errors. These uses are differentiated via the `type` attribute. If
|
|
41
|
+
# included, the `type` attribute MUST have one of the following values:
|
|
42
|
+
#
|
|
43
|
+
# * `:chat` -- The message is sent in the context of a one-to-one chat
|
|
44
|
+
# session. Typically a receiving client will present message of type
|
|
45
|
+
# `chat` in an interface that enables one-to-one chat between the two
|
|
46
|
+
# parties, including an appropriate conversation history.
|
|
47
|
+
#
|
|
48
|
+
# * `:error` -- The message is generated by an entity that experiences an
|
|
49
|
+
# error in processing a message received from another entity. A client
|
|
50
|
+
# that receives a message of type `error` SHOULD present an appropriate
|
|
51
|
+
# interface informing the sender of the nature of the error.
|
|
52
|
+
#
|
|
53
|
+
# * `:groupchat` -- The message is sent in the context of a multi-user chat
|
|
54
|
+
# environment (similar to that of [IRC]). Typically a receiving client
|
|
55
|
+
# will present a message of type `groupchat` in an interface that enables
|
|
56
|
+
# many-to-many chat between the parties, including a roster of parties in
|
|
57
|
+
# the chatroom and an appropriate conversation history.
|
|
58
|
+
#
|
|
59
|
+
# * `:headline` -- The message provides an alert, a notification, or other
|
|
60
|
+
# information to which no reply is expected (e.g., news headlines, sports
|
|
61
|
+
# updates, near-real-time market data, and syndicated content). Because no
|
|
62
|
+
# reply to the message is expected, typically a receiving client will
|
|
63
|
+
# present a message of type "headline" in an interface that appropriately
|
|
64
|
+
# differentiates the message from standalone messages, chat messages, or
|
|
65
|
+
# groupchat messages (e.g., by not providing the recipient with the
|
|
66
|
+
# ability to reply).
|
|
67
|
+
#
|
|
68
|
+
# * `:normal` -- The message is a standalone message that is sent outside
|
|
69
|
+
# the context of a one-to-one conversation or groupchat, and to which it
|
|
70
|
+
# is expected that the recipient will reply. Typically a receiving client
|
|
71
|
+
# will present a message of type `normal` in an interface that enables the
|
|
72
|
+
# recipient to reply, but without a conversation history. The default
|
|
73
|
+
# value of the `type` attribute is `normal`.
|
|
74
|
+
#
|
|
75
|
+
# Blather provides a helper for each possible type:
|
|
76
|
+
#
|
|
77
|
+
# Message#chat?
|
|
78
|
+
# Message#error?
|
|
79
|
+
# Message#groupchat?
|
|
80
|
+
# Message#headline?
|
|
81
|
+
# Message#normal?
|
|
82
|
+
#
|
|
83
|
+
# Blather treats the `type` attribute like a normal ruby object attribute
|
|
84
|
+
# providing a getter and setter. The default `type` is `chat`.
|
|
85
|
+
#
|
|
86
|
+
# msg = Message.new
|
|
87
|
+
# msg.type # => :chat
|
|
88
|
+
# msg.chat? # => true
|
|
89
|
+
# msg.type = :normal
|
|
90
|
+
# msg.normal? # => true
|
|
91
|
+
# msg.chat? # => false
|
|
92
|
+
#
|
|
93
|
+
# msg.type = :invalid # => RuntimeError
|
|
94
|
+
#
|
|
95
|
+
#
|
|
96
|
+
# ## "Body" Element
|
|
97
|
+
#
|
|
98
|
+
# The `body` element contains human-readable XML character data that
|
|
99
|
+
# specifies the textual contents of the message; this child element is
|
|
100
|
+
# normally included but is optional.
|
|
101
|
+
#
|
|
102
|
+
# Blather provides an attribute-like syntax for Message `body` elements.
|
|
103
|
+
#
|
|
104
|
+
# msg = Message.new 'user@domain.tld', 'message body'
|
|
105
|
+
# msg.body # => 'message body'
|
|
106
|
+
#
|
|
107
|
+
# msg.body = 'other message'
|
|
108
|
+
# msg.body # => 'other message'
|
|
109
|
+
#
|
|
110
|
+
# ## "Subject" Element
|
|
111
|
+
#
|
|
112
|
+
# The `subject` element contains human-readable XML character data that
|
|
113
|
+
# specifies the topic of the message.
|
|
114
|
+
#
|
|
115
|
+
# Blather provides an attribute-like syntax for Message `subject` elements.
|
|
116
|
+
#
|
|
117
|
+
# msg = Message.new 'user@domain.tld', 'message body'
|
|
118
|
+
# msg.subject = 'message subject'
|
|
119
|
+
# msg.subject # => 'message subject'
|
|
120
|
+
#
|
|
121
|
+
# ## "Thread" Element
|
|
122
|
+
#
|
|
123
|
+
# The primary use of the XMPP `thread` element is to uniquely identify a
|
|
124
|
+
# conversation thread or "chat session" between two entities instantiated by
|
|
125
|
+
# Message stanzas of type `chat`. However, the XMPP thread element can also
|
|
126
|
+
# be used to uniquely identify an analogous thread between two entities
|
|
127
|
+
# instantiated by Message stanzas of type `headline` or `normal`, or among
|
|
128
|
+
# multiple entities in the context of a multi-user chat room instantiated by
|
|
129
|
+
# Message stanzas of type `groupchat`. It MAY also be used for Message
|
|
130
|
+
# stanzas not related to a human conversation, such as a game session or an
|
|
131
|
+
# interaction between plugins. The `thread` element is not used to identify
|
|
132
|
+
# individual messages, only conversations or messagingg sessions. The
|
|
133
|
+
# inclusion of the `thread` element is optional.
|
|
134
|
+
#
|
|
135
|
+
# The value of the `thread` element is not human-readable and MUST be
|
|
136
|
+
# treated as opaque by entities; no semantic meaning can be derived from it,
|
|
137
|
+
# and only exact comparisons can be made against it. The value of the
|
|
138
|
+
# `thread` element MUST be a universally unique identifier (UUID) as
|
|
139
|
+
# described in [UUID].
|
|
140
|
+
#
|
|
141
|
+
# The `thread` element MAY possess a 'parent' attribute that identifies
|
|
142
|
+
# another thread of which the current thread is an offshoot or child; the
|
|
143
|
+
# value of the 'parent' must conform to the syntax of the `thread` element
|
|
144
|
+
# itself.
|
|
145
|
+
#
|
|
146
|
+
# Blather provides an attribute-like syntax for Message `thread` elements.
|
|
147
|
+
#
|
|
148
|
+
# msg = Message.new
|
|
149
|
+
# msg.thread = '12345'
|
|
150
|
+
# msg.thread # => '12345'
|
|
151
|
+
#
|
|
152
|
+
# Parent threads can be set using a hash:
|
|
153
|
+
#
|
|
154
|
+
# msg.thread = {'parent-id' => 'thread-id'}
|
|
155
|
+
# msg.thread # => 'thread-id'
|
|
156
|
+
# msg.parent_thread # => 'parent-id'
|
|
157
|
+
#
|
|
158
|
+
# @handler :message
|
|
159
|
+
class Message < Stanza
|
|
160
|
+
# @private
|
|
161
|
+
VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal].freeze
|
|
162
|
+
|
|
163
|
+
# @private
|
|
164
|
+
VALID_CHAT_STATES = [:active, :composing, :gone, :inactive, :paused].freeze
|
|
165
|
+
# @private
|
|
166
|
+
CHAT_STATE_NS = 'http://jabber.org/protocol/chatstates'.freeze
|
|
167
|
+
|
|
168
|
+
# @private
|
|
169
|
+
HTML_NS = 'http://jabber.org/protocol/xhtml-im'.freeze
|
|
170
|
+
# @private
|
|
171
|
+
HTML_BODY_NS = 'http://www.w3.org/1999/xhtml'.freeze
|
|
172
|
+
|
|
173
|
+
register :message
|
|
174
|
+
|
|
175
|
+
# @private
|
|
176
|
+
def self.import(node)
|
|
177
|
+
klass = nil
|
|
178
|
+
node.children.detect do |e|
|
|
179
|
+
ns = e.namespace ? e.namespace.href : nil
|
|
180
|
+
klass = class_from_registration(e.element_name, ns)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
if klass == Blather::Stanza::Presence::MUCUser
|
|
184
|
+
klass = Blather::Stanza::Message::MUCUser
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if klass && klass != self && ![Blather::Stanza::X, Blather::Stanza::Iq].include?(klass)
|
|
188
|
+
klass.import(node)
|
|
189
|
+
else
|
|
190
|
+
new(node[:type]).inherit(node)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Create a new Message stanza
|
|
195
|
+
#
|
|
196
|
+
# @param [#to_s] to the JID to send the message to
|
|
197
|
+
# @param [#to_s] body the body of the message
|
|
198
|
+
# @param [Symbol] type the message type. Must be one of VALID_TYPES
|
|
199
|
+
def self.new(to = nil, body = nil, type = :chat)
|
|
200
|
+
node = super :message
|
|
201
|
+
node.to = to
|
|
202
|
+
node.type = type
|
|
203
|
+
node.body = body
|
|
204
|
+
node.chat_state = :active if [:chat, :groupchat].include?(type)
|
|
205
|
+
node
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Overrides the parent method to ensure the current chat state is removed
|
|
209
|
+
#
|
|
210
|
+
# @see Blather::Stanza::Iq#inherit
|
|
211
|
+
def inherit(node)
|
|
212
|
+
xpath('ns:*', :ns => CHAT_STATE_NS).remove
|
|
213
|
+
super
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Check if the Message is of type :chat
|
|
217
|
+
#
|
|
218
|
+
# @return [true, false]
|
|
219
|
+
def chat?
|
|
220
|
+
self.type == :chat
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Check if the Message is of type :error
|
|
224
|
+
#
|
|
225
|
+
# @return [true, false]
|
|
226
|
+
def error?
|
|
227
|
+
self.type == :error
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Check if the Message is of type :groupchat
|
|
231
|
+
#
|
|
232
|
+
# @return [true, false]
|
|
233
|
+
def groupchat?
|
|
234
|
+
self.type == :groupchat
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Check if the Message is of type :headline
|
|
238
|
+
#
|
|
239
|
+
# @return [true, false]
|
|
240
|
+
def headline?
|
|
241
|
+
self.type == :headline
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Check if the Message is of type :normal
|
|
245
|
+
#
|
|
246
|
+
# @return [true, false]
|
|
247
|
+
def normal?
|
|
248
|
+
self.type == :normal
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Ensures type is :get, :set, :result or :error
|
|
252
|
+
#
|
|
253
|
+
# @param [#to_sym] type the Message type. Must be one of VALID_TYPES
|
|
254
|
+
def type=(type)
|
|
255
|
+
if type && !VALID_TYPES.include?(type.to_sym)
|
|
256
|
+
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}"
|
|
257
|
+
end
|
|
258
|
+
super
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Get the message body
|
|
262
|
+
#
|
|
263
|
+
# @return [String]
|
|
264
|
+
def body
|
|
265
|
+
read_content :body
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Set the message body
|
|
269
|
+
#
|
|
270
|
+
# @param [#to_s] body the message body
|
|
271
|
+
def body=(body)
|
|
272
|
+
set_content_for :body, body
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Get the message xhtml node
|
|
276
|
+
# This will create the node if it doesn't exist
|
|
277
|
+
#
|
|
278
|
+
# @return [XML::Node]
|
|
279
|
+
def xhtml_node
|
|
280
|
+
unless h = find_first('ns:html', :ns => HTML_NS) || find_first('ns:html', :ns => HTML_BODY_NS)
|
|
281
|
+
self << (h = XMPPNode.new('html', self.document))
|
|
282
|
+
h.namespace = HTML_NS
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
unless b = h.find_first('ns:body', :ns => HTML_BODY_NS)
|
|
286
|
+
b = XMPPNode.new('body', self.document)
|
|
287
|
+
b.namespace = HTML_BODY_NS
|
|
288
|
+
h << b
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
b
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Get the message xhtml
|
|
295
|
+
#
|
|
296
|
+
# @return [String]
|
|
297
|
+
def xhtml
|
|
298
|
+
self.xhtml_node.inner_html.strip
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Set the message xhtml
|
|
302
|
+
# This will use Nokogiri to ensure the xhtml is valid
|
|
303
|
+
#
|
|
304
|
+
# @param [#to_s] valid xhtml
|
|
305
|
+
def xhtml=(xhtml_body)
|
|
306
|
+
self.xhtml_node.inner_html = Nokogiri::XML::DocumentFragment.parse(xhtml_body)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Get the message subject
|
|
310
|
+
#
|
|
311
|
+
# @return [String]
|
|
312
|
+
def subject
|
|
313
|
+
read_content :subject
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Set the message subject
|
|
317
|
+
#
|
|
318
|
+
# @param [#to_s] body the message subject
|
|
319
|
+
def subject=(subject)
|
|
320
|
+
set_content_for :subject, subject
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Get the message thread
|
|
324
|
+
#
|
|
325
|
+
# @return [String]
|
|
326
|
+
def thread
|
|
327
|
+
read_content :thread
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Get the parent thread
|
|
331
|
+
#
|
|
332
|
+
# @return [String, nil]
|
|
333
|
+
def parent_thread
|
|
334
|
+
n = find_first('thread')
|
|
335
|
+
n[:parent] if n
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Set the thread
|
|
339
|
+
#
|
|
340
|
+
# @overload thread=(hash)
|
|
341
|
+
# Set a thread with a parent
|
|
342
|
+
# @param [Hash<parent-id => thread-id>] thread
|
|
343
|
+
# @overload thread=(thread)
|
|
344
|
+
# Set a thread id
|
|
345
|
+
# @param [#to_s] thread the new thread id
|
|
346
|
+
def thread=(thread)
|
|
347
|
+
parent, thread = thread.to_a.flatten if thread.is_a?(Hash)
|
|
348
|
+
set_content_for :thread, thread
|
|
349
|
+
find_first('thread')[:parent] = parent
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Returns the message's x:data form child
|
|
353
|
+
def form
|
|
354
|
+
X.find_or_create self
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Get the message chat state
|
|
358
|
+
#
|
|
359
|
+
# @return [Symbol]
|
|
360
|
+
def chat_state
|
|
361
|
+
if (elem = find_first('ns:*', :ns => CHAT_STATE_NS)) && VALID_CHAT_STATES.include?(name = elem.name.to_sym)
|
|
362
|
+
name
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Set the message chat state
|
|
367
|
+
#
|
|
368
|
+
# @param [#to_s] chat_state the message chat state. Must be one of VALID_CHAT_STATES
|
|
369
|
+
def chat_state=(chat_state)
|
|
370
|
+
if chat_state && !VALID_CHAT_STATES.include?(chat_state.to_sym)
|
|
371
|
+
raise ArgumentError, "Invalid Chat State (#{chat_state}), use: #{VALID_CHAT_STATES*' '}"
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
xpath('ns:*', :ns => CHAT_STATE_NS).remove
|
|
375
|
+
|
|
376
|
+
if chat_state
|
|
377
|
+
state = XMPPNode.new(chat_state, self.document)
|
|
378
|
+
state.namespace = CHAT_STATE_NS
|
|
379
|
+
self << state
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def delay
|
|
384
|
+
if d = find_first('ns:delay', :ns => "urn:xmpp:delay")
|
|
385
|
+
Delay.new d
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def delayed?
|
|
390
|
+
!!delay
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
class Delay < XMPPNode
|
|
394
|
+
def self.new(stamp = nil, from = nil, description = nil)
|
|
395
|
+
new_node = super :delay
|
|
396
|
+
|
|
397
|
+
case stamp
|
|
398
|
+
when Nokogiri::XML::Node
|
|
399
|
+
new_node.inherit stamp
|
|
400
|
+
when Hash
|
|
401
|
+
new_node.stamp = stamp[:stamp]
|
|
402
|
+
new_node.from = stamp[:from]
|
|
403
|
+
new_node.description = stamp[:description]
|
|
404
|
+
else
|
|
405
|
+
new_node.stamp = stamp
|
|
406
|
+
new_node.from = from
|
|
407
|
+
new_node.description = description
|
|
408
|
+
end
|
|
409
|
+
new_node
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def from
|
|
413
|
+
read_attr :from
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def stamp
|
|
417
|
+
s = read_attr :stamp
|
|
418
|
+
s && Time.parse(s)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def description
|
|
422
|
+
content.strip
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
end
|
|
428
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'blather/stanza/muc/muc_user_base'
|
|
2
|
+
|
|
3
|
+
module Blather
|
|
4
|
+
class Stanza
|
|
5
|
+
class Message
|
|
6
|
+
|
|
7
|
+
class MUCUser < Message
|
|
8
|
+
include Blather::Stanza::MUC::MUCUserBase
|
|
9
|
+
|
|
10
|
+
register :muc_user_message, :x, "http://jabber.org/protocol/muc#user"
|
|
11
|
+
|
|
12
|
+
def self.new(to = nil, body = nil, type = :normal)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def invite?
|
|
17
|
+
!!find_invite_node
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def invite_decline?
|
|
21
|
+
!!find_decline_node
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def invite
|
|
25
|
+
if invite = find_invite_node
|
|
26
|
+
Invite.new invite
|
|
27
|
+
else
|
|
28
|
+
muc_user << (invite = Invite.new nil, nil, nil, self.document)
|
|
29
|
+
invite
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def find_invite_node
|
|
34
|
+
muc_user.find_first 'ns:invite', :ns => self.class.registered_ns
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def decline
|
|
38
|
+
if decline = find_decline_node
|
|
39
|
+
Decline.new decline
|
|
40
|
+
else
|
|
41
|
+
muc_user << (decline = Decline.new nil, nil, nil, self.document)
|
|
42
|
+
decline
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def find_decline_node
|
|
47
|
+
muc_user.find_first 'ns:decline', :ns => self.class.registered_ns
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class InviteBase < XMPPNode
|
|
51
|
+
def self.new(element_name, to = nil, from = nil, reason = nil, document = nil)
|
|
52
|
+
new_node = super element_name, document
|
|
53
|
+
|
|
54
|
+
case to
|
|
55
|
+
when self
|
|
56
|
+
to.document ||= document
|
|
57
|
+
return to
|
|
58
|
+
when Nokogiri::XML::Node
|
|
59
|
+
new_node.inherit to
|
|
60
|
+
when Hash
|
|
61
|
+
new_node.to = to[:to]
|
|
62
|
+
new_node.from = to[:from]
|
|
63
|
+
new_node.reason = to[:reason]
|
|
64
|
+
else
|
|
65
|
+
new_node.to = to
|
|
66
|
+
new_node.from = from
|
|
67
|
+
new_node.reason = reason
|
|
68
|
+
end
|
|
69
|
+
new_node
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to
|
|
73
|
+
read_attr :to
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def to=(val)
|
|
77
|
+
write_attr :to, val
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def from
|
|
81
|
+
read_attr :from
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def from=(val)
|
|
85
|
+
write_attr :from, val
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def reason
|
|
89
|
+
reason_node.content.strip
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def reason=(val)
|
|
93
|
+
reason_node.content = val
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def reason_node
|
|
97
|
+
unless reason = find_first('ns:reason', :ns => MUCUser.registered_ns)
|
|
98
|
+
self << (reason = XMPPNode.new('reason', self.document))
|
|
99
|
+
end
|
|
100
|
+
reason
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class Invite < InviteBase
|
|
105
|
+
def self.new(*args)
|
|
106
|
+
new_node = super :invite, *args
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class Decline < InviteBase
|
|
111
|
+
def self.new(*args)
|
|
112
|
+
new_node = super :decline, *args
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end # MUC
|
|
116
|
+
|
|
117
|
+
end # Presence
|
|
118
|
+
end # Stanza
|
|
119
|
+
end # Blather
|