xrc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1765652f98f9ded0ba8dc89c729babf653461914
4
+ data.tar.gz: d2192424e3c6737e616d2774d02fefa485ba029c
5
+ SHA512:
6
+ metadata.gz: 26d909993287c8bef933efc2b7f5a6121216240dc107e31c55410d7eefba4ee3c900a4bdf66476e62c952ef59ef47ac791d01d2730e7b597b06d507e8cc0cbd8
7
+ data.tar.gz: d1278831dabc08f37324389d1532bfd16c73599ded497b2118d3c1b3fc41d588dbdca9b511eaeda7042a79f90939c14ee8e9d4c3f168eed8a28a7958d9e78134
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xrc.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ryo Nakamura
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Xrc
2
+ XMPP Ruby Client.
3
+
4
+ ## Usage
5
+ ```ruby
6
+ # Loads all classes & modules defined in this library.
7
+ require "xrc"
8
+
9
+ # Constructs a new Client class to connect to a XMPP server.
10
+ client = Xrc::Client.new(
11
+ jid: "foo@example.com", # required
12
+ nickname: "bot" # optional
13
+ password: "xxx", # optional
14
+ port: 5222, # optional, default: 5222
15
+ room_jid: "bar@example.com", # optional
16
+ )
17
+
18
+ # Responds to "ping" and returns "pong".
19
+ client.on_private_message do |message|
20
+ if message.body == "ping"
21
+ client.reply(body: "pong", to: message)
22
+ end
23
+ end
24
+
25
+ # Responds to "Thank you" and returns "You're welcome".
26
+ client.on_room_message do |message|
27
+ if message.body == "Thank you"
28
+ client.reply(body: "You're welcome", to: message)
29
+ end
30
+ end
31
+
32
+ # Connects to a XMPP server and waits for new messages.
33
+ client.connect
34
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/lib/xrc.rb ADDED
@@ -0,0 +1,43 @@
1
+ require "active_support/core_ext/enumerable"
2
+ require "active_support/core_ext/object/try"
3
+ require "active_support/core_ext/string/indent"
4
+ require "base64"
5
+ require "openssl"
6
+ require "ostruct"
7
+ require "resolv"
8
+ require "rexml/document"
9
+ require "rexml/element"
10
+ require "rexml/parsers/sax2parser"
11
+ require "securerandom"
12
+ require "socket"
13
+
14
+ require "xrc/method_loggable"
15
+ require "xrc/client"
16
+ require "xrc/connection"
17
+ require "xrc/connection/connector"
18
+ require "xrc/connection/tsl_connector"
19
+ require "xrc/elements/auth"
20
+ require "xrc/elements/bind"
21
+ require "xrc/elements/message"
22
+ require "xrc/elements/presence"
23
+ require "xrc/elements/join"
24
+ require "xrc/elements/ping"
25
+ require "xrc/elements/roster"
26
+ require "xrc/elements/session"
27
+ require "xrc/elements/starttls"
28
+ require "xrc/elements/stream"
29
+ require "xrc/hosts_resolver"
30
+ require "xrc/message_builder"
31
+ require "xrc/messages/base"
32
+ require "xrc/messages/null"
33
+ require "xrc/messages/message"
34
+ require "xrc/messages/private_message"
35
+ require "xrc/messages/room_message"
36
+ require "xrc/messages/subject"
37
+ require "xrc/namespaces"
38
+ require "xrc/jid"
39
+ require "xrc/parser"
40
+ require "xrc/roster"
41
+ require "xrc/socket"
42
+ require "xrc/user"
43
+ require "xrc/version"
data/lib/xrc/client.rb ADDED
@@ -0,0 +1,318 @@
1
+ module Xrc
2
+ class Client
3
+ DEFAULT_PORT = 5222
4
+
5
+ PING_INTERVAL = 30
6
+
7
+ # @return [Xrc::Roster] Users information existing in the server
8
+ attr_reader :users
9
+
10
+ # @option options [String] :port Port number to connect server (default: 5222)
11
+ # @option options [String] :jid Jabber ID of your account (required)
12
+ # @option options [String] :nickname Pass nickname for the Room (optional)
13
+ # @option options [String] :password Password to connect server (optional)
14
+ # @option options [String] :room_jid Room Jabber ID to join in after authentication (optional)
15
+ # @example
16
+ # client = Xrc::Client.new(jid: "alice@example.com")
17
+ def initialize(options = {})
18
+ @options = options
19
+ end
20
+
21
+ # Connects to the JID's server and waits for message
22
+ # @return [nil] Returns nothing
23
+ def connect
24
+ connection.connect
25
+ nil
26
+ end
27
+
28
+ # Registers a callback called when client received a new private message (a.k.a. 1vs1 message)
29
+ # @yield Executes a given callback in the Client's context
30
+ # @yieldparam element [Xrc::Message] Represents a given message
31
+ # @return [Proc] Returns given block
32
+ # @example
33
+ # client.on_private_message do |message|
34
+ # puts "#{message.from}: #{message.body}"
35
+ # end
36
+ #
37
+ def on_private_message(&block)
38
+ @on_private_message_block = block
39
+ end
40
+
41
+ # Registers a callback called when client received a new room message
42
+ # @yield Executes a given callback in the Client's context
43
+ # @yieldparam element [Xrc::Message] Represents a given message
44
+ # @return [Proc] Returns given block
45
+ # @example
46
+ # client.on_room_message do |message|
47
+ # puts "#{message.from}: #{message.body}"
48
+ # end
49
+ #
50
+ def on_room_message(&block)
51
+ @on_room_message_block = block
52
+ end
53
+
54
+ # Registers a callback called when client received a new message with subject
55
+ # @yield Executes a given callback in the Client's context
56
+ # @yieldparam element [Xrc::Message] Represents a given message
57
+ # @return [Proc] Returns given block
58
+ # @example
59
+ # client.on_subject do |element|
60
+ # puts "Subject: #{element.subject}"
61
+ # end
62
+ #
63
+ def on_subject(&block)
64
+ @on_subject_block = block
65
+ end
66
+
67
+ # Registers a callback called when client received a new XML element from server
68
+ # @yield Executes a given callback in the Client's context
69
+ # @yieldparam element [REXML::Element] Represents a new XML element
70
+ # @return [Proc] Returns given block
71
+ # @example
72
+ # client.on_event do |element|
73
+ # puts "Received #{element}"
74
+ # end
75
+ #
76
+ def on_event(&block)
77
+ @on_event_block = block
78
+ end
79
+
80
+ # Replies to given message
81
+ # @option options [Xrc::Messages::Base] :to A message object given from server
82
+ # @option options [String] :body A text to be sent to server
83
+ # @return [REXML::Element] Returns an element sent to server
84
+ # @example
85
+ # client.reply(body: "Thanks", to: message)
86
+ def reply(options)
87
+ say(
88
+ body: options[:body],
89
+ from: options[:to].to,
90
+ to: options[:to].from,
91
+ type: options[:to].type,
92
+ )
93
+ end
94
+
95
+ # @return [String] Mention name of this account
96
+ # @example
97
+ # client.mention_name #=> "alice"
98
+ def mention_name
99
+ users[jid].try(:mention_name)
100
+ end
101
+
102
+ private
103
+
104
+ def on_event_block
105
+ @on_event_block ||= ->(element) {}
106
+ end
107
+
108
+ def on_private_message_block
109
+ @on_private_message_block ||= ->(element) {}
110
+ end
111
+
112
+ def on_room_message_block
113
+ @on_room_message_block ||= ->(element) {}
114
+ end
115
+
116
+ def on_subject_block
117
+ @on_subject_block ||= ->(element) {}
118
+ end
119
+
120
+ def on_received(element)
121
+ on_event_block.call(element)
122
+ case
123
+ when element.attribute("id") && has_reply_callbacks_to?(element.attribute("id").value)
124
+ on_replied(element)
125
+ when element.name == "message"
126
+ on_room_message_received(element)
127
+ when element.prefix == "stream" && element.name == "features"
128
+ on_features_received(element)
129
+ when element.name == "proceed" && element.namespace == Namespaces::TLS
130
+ on_tls_proceeded(element)
131
+ when element.name == "success" && element.namespace == Namespaces::SASL
132
+ on_authentication_succeeded(element)
133
+ when element.name == "failure" && element.namespace == Namespaces::SASL
134
+ on_authentication_failed(element)
135
+ end
136
+ end
137
+
138
+ def jid
139
+ @jid ||= Jid.new(@options[:jid])
140
+ end
141
+
142
+ def password
143
+ @options[:password]
144
+ end
145
+
146
+ def nickname
147
+ @options[:nickname]
148
+ end
149
+
150
+ def port
151
+ @options[:port] || DEFAULT_PORT
152
+ end
153
+
154
+ def room_jid
155
+ Jid.new("#{@options[:room_jid]}") if @options[:room_jid]
156
+ end
157
+
158
+ def connection
159
+ @connection ||= Connection.new(domain: jid.domain, port: port) do |element|
160
+ on_received(element)
161
+ end
162
+ end
163
+
164
+ def on_room_message_received(element)
165
+ case message = MessageBuilder.build(element)
166
+ when Messages::RoomMessage
167
+ on_room_message_block.call(message)
168
+ when Messages::PrivateMessage
169
+ on_private_message_block.call(message)
170
+ when Messages::Subject
171
+ on_subject_block.call(message)
172
+ end
173
+ end
174
+
175
+ def on_bound(element)
176
+ @jid = Jid.new(element.elements["/bind/jid/text()"].value)
177
+ establish_session
178
+ require_roster
179
+ end
180
+
181
+ def on_replied(element)
182
+ id = element.attribute("id").value
183
+ callback = reply_callbacks.delete(id)
184
+ callback.call(element)
185
+ end
186
+
187
+ def on_features_received(element)
188
+ element.each do |feature|
189
+ case
190
+ when feature.name == "bind" && feature.namespace == Namespaces::BIND
191
+ bind
192
+ when feature.name == "starttls" && feature.namespace == Namespaces::TLS
193
+ start_tls
194
+ when feature.name == "mechanisms" && feature.namespace == Namespaces::SASL
195
+ on_mechanisms_received(feature)
196
+ else
197
+ features[feature.name] = feature.namespace
198
+ end
199
+ end
200
+ end
201
+
202
+ def on_authentication_succeeded(element)
203
+ connection.open
204
+ end
205
+
206
+ def on_authentication_failed(element)
207
+ raise NotImplementedError
208
+ end
209
+
210
+ def on_tls_proceeded(element)
211
+ connection.encrypt
212
+ end
213
+
214
+ def on_mechanisms_received(element)
215
+ element.each_element("mechanism") do |mechanism|
216
+ mechanisms << mechanism.text
217
+ end
218
+ authenticate if password
219
+ end
220
+
221
+ def on_roster_received(element)
222
+ @users = Roster.new(element)
223
+ attend
224
+ on_connection_established
225
+ end
226
+
227
+ def on_connection_established
228
+ join if room_jid
229
+ start_ping_thread
230
+ end
231
+
232
+ def features
233
+ @features ||= {}
234
+ end
235
+
236
+ def mechanisms
237
+ @mechanisms ||= []
238
+ end
239
+
240
+ def reply_callbacks
241
+ @reply_callbacks ||= {}
242
+ end
243
+
244
+ def has_reply_callbacks_to?(id)
245
+ reply_callbacks.has_key?(id)
246
+ end
247
+
248
+ def post(element, &block)
249
+ if block
250
+ id = generate_id
251
+ element.add_attributes("id" => id)
252
+ reply_callbacks[id] = block
253
+ end
254
+ connection.write(element)
255
+ element
256
+ end
257
+
258
+ # See RFC1750 for Randomness Recommendations for Security
259
+ def generate_id
260
+ SecureRandom.hex(8)
261
+ end
262
+
263
+ def bind
264
+ post(Elements::Bind.new(resource: jid.resource), &method(:on_bound))
265
+ end
266
+
267
+ def establish_session
268
+ post(Elements::Session.new)
269
+ end
270
+
271
+ def require_roster
272
+ post(Elements::Roster.new, &method(:on_roster_received))
273
+ end
274
+
275
+ def attend
276
+ post(Elements::Presence.new)
277
+ end
278
+
279
+ def join
280
+ post(Elements::Join.new(from: jid.strip, to: "#{room_jid}/#{nickname}"))
281
+ end
282
+
283
+ def ping
284
+ post(Elements::Ping.new(from: jid.to_s, to: jid.domain))
285
+ end
286
+
287
+ def start_tls
288
+ post(Elements::Starttls.new)
289
+ end
290
+
291
+ def start
292
+ post(Elements::Stream.new(domain: jid.domain))
293
+ end
294
+
295
+ # Only supports PLAIN authentication
296
+ def authenticate
297
+ post(Elements::Auth.new(jid: jid, password: password))
298
+ end
299
+
300
+ def say(options)
301
+ post Elements::Message.new(
302
+ body: options[:body],
303
+ from: options[:from],
304
+ to: options[:to],
305
+ type: options[:type],
306
+ )
307
+ end
308
+
309
+ def start_ping_thread
310
+ Thread.new do
311
+ loop do
312
+ ping
313
+ sleep(PING_INTERVAL)
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end