matrix_sdk 1.5.0 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/lib/matrix_sdk.rb +3 -0
- data/lib/matrix_sdk/api.rb +108 -35
- data/lib/matrix_sdk/client.rb +261 -30
- data/lib/matrix_sdk/extensions.rb +3 -0
- data/lib/matrix_sdk/mxid.rb +25 -2
- data/lib/matrix_sdk/protocols/cs.rb +729 -53
- data/lib/matrix_sdk/protocols/msc.rb +147 -0
- data/lib/matrix_sdk/response.rb +11 -0
- data/lib/matrix_sdk/room.rb +127 -15
- data/lib/matrix_sdk/user.rb +75 -8
- data/lib/matrix_sdk/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5af1e7af0f473a5c44df1ede83655cdbdb120e215e42f125b6af576740b88aa6
|
4
|
+
data.tar.gz: cca80fe97ca22f0ddb0c5a2b39b24b70345baa25fb20bea305fa019f46ca7d3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e4c694d8489dae1b2602d01f4aab08b20119c6697e83b41a2a463adb7ebcaf7b1afd439db04a6e54a0979c5a93be865c8afa3d2d03b4ca34b98f82afae87a5f
|
7
|
+
data.tar.gz: 8f52c12d1eb8af55ac5bc011852c9d5f648e9de1cc3241ea1ed76530036b02cff373cb69b8a4c721956add3df1359377d79b2fa2562e8dab8efe5ce877596e2d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,47 @@
|
|
1
|
+
## 2.1.2 - 2020-09-10
|
2
|
+
|
3
|
+
- Adds method for reading complete member lists for rooms, improves the CS spec adherence
|
4
|
+
- Adds test for state events
|
5
|
+
- Fixes state event handler for rooms not actually passing events
|
6
|
+
- Fixes Api#new_for_domain using a faulty URI in certain cases
|
7
|
+
|
8
|
+
## 2.1.1 - 2020-08-21
|
9
|
+
|
10
|
+
- Fixes crash if state event content is null (#11)
|
11
|
+
- Fixes an uninitialized URI constant exception when requiring only the main library file
|
12
|
+
- Fixes the Api#get_pushrules method missing an ending slash in the request URI
|
13
|
+
- Fixes discovery code for client/server connections based on domain
|
14
|
+
|
15
|
+
## 2.1.0 - 2020-05-22
|
16
|
+
|
17
|
+
- Adds unique query IDs as well as duration in API debug output, to make it easier to track long requests
|
18
|
+
- Finishes up MSC support, get sync over SSE working flawlessly
|
19
|
+
- Exposes the #listen_forever method in the client abstraction
|
20
|
+
- Fixes room access methods
|
21
|
+
|
22
|
+
## 2.0.1 - 2020-03-13
|
23
|
+
|
24
|
+
- Adds code for handling non-final MSC's in protocols
|
25
|
+
- Currently implementing clients parts of MSC2018 for Sync over Server Sent Events
|
26
|
+
|
27
|
+
## 2.0.0 - 2020-02-14
|
28
|
+
|
29
|
+
**NB**, this release includes backwards-incompatible changes;
|
30
|
+
- Changes room state lookup to separate specific state lookups from full state retrieval.
|
31
|
+
This will require changes in client code where `#get_room_state` is called to retrieve
|
32
|
+
all state, as it now requires a state key. For retrieving full room state,
|
33
|
+
`#get_room_state_all` is now the method to use.
|
34
|
+
- Changes some advanced parameters to named parameters, ensure your code is updated if it makes use of them
|
35
|
+
- Fixes SSL verification to actually verify certs (#9)
|
36
|
+
|
37
|
+
- Adds multiple CS API endpoints
|
38
|
+
- Adds `:room_id` key to all room events
|
39
|
+
- Adds `:self` as a valid option to the client abstraction's `#get_user` method
|
40
|
+
- Separates homeserver part stringification for MXIDs
|
41
|
+
- Exposes some previously private client abstraction methods (`#ensure_room`, `#next_batch`) for easier bot usage
|
42
|
+
- Changes room abstraction member lookups to use `#get_room_joined_members`, reducing transferred data amounts
|
43
|
+
- Fixes debug print of methods that return arrays (e.g. CS `/room/{id}/state`)
|
44
|
+
|
1
45
|
## 1.5.0 - 2019-10-25
|
2
46
|
|
3
47
|
- Adds error event to the client abstraction, for handling errors in the background listener
|
data/lib/matrix_sdk.rb
CHANGED
data/lib/matrix_sdk/api.rb
CHANGED
@@ -11,10 +11,6 @@ module MatrixSdk
|
|
11
11
|
class Api
|
12
12
|
extend MatrixSdk::Extensions
|
13
13
|
include MatrixSdk::Logging
|
14
|
-
include MatrixSdk::Protocols::AS
|
15
|
-
include MatrixSdk::Protocols::CS
|
16
|
-
include MatrixSdk::Protocols::IS
|
17
|
-
include MatrixSdk::Protocols::SS
|
18
14
|
|
19
15
|
USER_AGENT = "Ruby Matrix SDK v#{MatrixSdk::VERSION}"
|
20
16
|
DEFAULT_HEADERS = {
|
@@ -23,7 +19,7 @@ module MatrixSdk
|
|
23
19
|
}.freeze
|
24
20
|
|
25
21
|
attr_accessor :access_token, :connection_address, :connection_port, :device_id, :autoretry, :global_headers
|
26
|
-
attr_reader :homeserver, :validate_certificate, :open_timeout, :read_timeout, :
|
22
|
+
attr_reader :homeserver, :validate_certificate, :open_timeout, :read_timeout, :well_known, :proxy_uri
|
27
23
|
|
28
24
|
ignore_inspect :access_token, :logger
|
29
25
|
|
@@ -51,10 +47,6 @@ module MatrixSdk
|
|
51
47
|
@homeserver.path.gsub!(/\/?_matrix\/?/, '') if @homeserver.path =~ /_matrix\/?$/
|
52
48
|
raise ArgumentError, 'Please use the base URL for your HS (without /_matrix/)' if @homeserver.path.include? '/_matrix/'
|
53
49
|
|
54
|
-
@protocols = params.fetch(:protocols, %i[CS])
|
55
|
-
@protocols = [@protocols] unless @protocols.is_a? Array
|
56
|
-
@protocols << :CS if @protocols.include?(:AS) && !@protocols.include?(:CS)
|
57
|
-
|
58
50
|
@proxy_uri = params.fetch(:proxy_uri, nil)
|
59
51
|
@connection_address = params.fetch(:address, nil)
|
60
52
|
@connection_port = params.fetch(:port, nil)
|
@@ -71,6 +63,10 @@ module MatrixSdk
|
|
71
63
|
@global_headers.merge!(params.fetch(:global_headers)) if params.key? :global_headers
|
72
64
|
@http = nil
|
73
65
|
|
66
|
+
([params.fetch(:protocols, [:CS])].flatten - protocols).each do |proto|
|
67
|
+
self.class.include MatrixSdk::Protocols.const_get(proto)
|
68
|
+
end
|
69
|
+
|
74
70
|
login(user: @homeserver.user, password: @homeserver.password) if @homeserver.user && @homeserver.password && !@access_token && !params[:skip_login] && protocol?(:CS)
|
75
71
|
@homeserver.userinfo = '' unless params[:skip_login]
|
76
72
|
end
|
@@ -96,24 +92,37 @@ module MatrixSdk
|
|
96
92
|
uri = URI("http#{ssl ? 's' : ''}://#{domain}")
|
97
93
|
well_known = nil
|
98
94
|
target_uri = nil
|
95
|
+
logger = ::Logging.logger[self]
|
96
|
+
logger.debug "Resolving #{domain}"
|
99
97
|
|
100
98
|
if !port.nil? && !port.empty?
|
99
|
+
# If the domain is fully qualified according to Matrix (FQDN and port) then skip discovery
|
101
100
|
target_uri = URI("https://#{domain}:#{port}")
|
102
101
|
elsif target == :server
|
103
102
|
# Attempt SRV record discovery
|
104
103
|
target_uri = begin
|
105
104
|
require 'resolv'
|
106
105
|
resolver = Resolv::DNS.new
|
107
|
-
|
108
|
-
|
106
|
+
srv = "_matrix._tcp.#{domain}"
|
107
|
+
logger.debug "Trying DNS #{srv}..."
|
108
|
+
d = resolver.getresource(srv, Resolv::DNS::Resource::IN::SRV)
|
109
|
+
d
|
110
|
+
rescue StandardError => e
|
111
|
+
logger.debug "DNS lookup failed with #{e.class}: #{e.message}"
|
109
112
|
nil
|
110
113
|
end
|
111
114
|
|
112
115
|
if target_uri.nil?
|
116
|
+
# Attempt .well-known discovery for server-to-server
|
113
117
|
well_known = begin
|
114
|
-
|
118
|
+
wk_uri = URI("https://#{domain}/.well-known/matrix/server")
|
119
|
+
logger.debug "Trying #{wk_uri}..."
|
120
|
+
data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http|
|
121
|
+
http.get(wk_uri.path).body
|
122
|
+
end
|
115
123
|
JSON.parse(data)
|
116
|
-
rescue StandardError
|
124
|
+
rescue StandardError => e
|
125
|
+
logger.debug "Well-known failed with #{e.class}: #{e.message}"
|
117
126
|
nil
|
118
127
|
end
|
119
128
|
|
@@ -124,9 +133,14 @@ module MatrixSdk
|
|
124
133
|
elsif %i[client identity].include? target
|
125
134
|
# Attempt .well-known discovery
|
126
135
|
well_known = begin
|
127
|
-
|
128
|
-
|
129
|
-
|
136
|
+
wk_uri = URI("https://#{domain}/.well-known/matrix/client")
|
137
|
+
logger.debug "Trying #{wk_uri}..."
|
138
|
+
data = Net::HTTP.start(wk_uri.host, wk_uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5, write_timeout: 5) do |http|
|
139
|
+
http.get(wk_uri.path).body
|
140
|
+
end
|
141
|
+
data = JSON.parse(data)
|
142
|
+
rescue StandardError => e
|
143
|
+
logger.debug "Well-known failed with #{e.class}: #{e.message}"
|
130
144
|
nil
|
131
145
|
end
|
132
146
|
|
@@ -140,6 +154,7 @@ module MatrixSdk
|
|
140
154
|
end
|
141
155
|
end
|
142
156
|
end
|
157
|
+
logger.debug "Using #{target_uri.inspect}"
|
143
158
|
|
144
159
|
# Fall back to direct domain connection
|
145
160
|
target_uri ||= URI("https://#{domain}:8448")
|
@@ -153,6 +168,29 @@ module MatrixSdk
|
|
153
168
|
))
|
154
169
|
end
|
155
170
|
|
171
|
+
# Get a list of enabled protocols on the API client
|
172
|
+
#
|
173
|
+
# @example
|
174
|
+
# MatrixSdk::Api.new_for_domain('matrix.org').protocols
|
175
|
+
# # => [:IS, :CS]
|
176
|
+
#
|
177
|
+
# @return [Symbol[]] An array of enabled APIs
|
178
|
+
def protocols
|
179
|
+
self
|
180
|
+
.class.included_modules
|
181
|
+
.reject { |m| m&.name.nil? }
|
182
|
+
.select { |m| m.name.start_with? 'MatrixSdk::Protocols::' }
|
183
|
+
.map { |m| m.name.split('::').last.to_sym }
|
184
|
+
end
|
185
|
+
|
186
|
+
# Check if a protocol is enabled on the API connection
|
187
|
+
#
|
188
|
+
# @example Checking for identity server API support
|
189
|
+
# api.protocol? :IS
|
190
|
+
# # => false
|
191
|
+
#
|
192
|
+
# @param protocol [Symbol] The protocol to check
|
193
|
+
# @return [Boolean] Is the protocol enabled
|
156
194
|
def protocol?(protocol)
|
157
195
|
protocols.include? protocol
|
158
196
|
end
|
@@ -201,6 +239,29 @@ module MatrixSdk
|
|
201
239
|
@proxy_uri = proxy_uri
|
202
240
|
end
|
203
241
|
|
242
|
+
# Perform a raw Matrix API request
|
243
|
+
#
|
244
|
+
# @example Simple API query
|
245
|
+
# api.request(:get, :client_r0, '/account/whoami')
|
246
|
+
# # => { :user_id => "@alice:matrix.org" }
|
247
|
+
#
|
248
|
+
# @example Advanced API request
|
249
|
+
# api.request(:post,
|
250
|
+
# :media_r0,
|
251
|
+
# '/upload',
|
252
|
+
# body_stream: open('./file'),
|
253
|
+
# headers: { 'content-type' => 'image/png' })
|
254
|
+
# # => { :content_uri => "mxc://example.com/AQwafuaFswefuhsfAFAgsw" }
|
255
|
+
#
|
256
|
+
# @param method [Symbol] The method to use, can be any of the ones under Net::HTTP
|
257
|
+
# @param api [Symbol] The API symbol to use, :client_r0 is the current CS one
|
258
|
+
# @param path [String] The API path to call, this is the part that comes after the API definition in the spec
|
259
|
+
# @param options [Hash] Additional options to pass along to the request
|
260
|
+
# @option options [Hash] :query Query parameters to set on the URL
|
261
|
+
# @option options [Hash,String] :body The body to attach to the request, will be JSON-encoded if sent as a hash
|
262
|
+
# @option options [IO] :body_stream A body stream to attach to the request
|
263
|
+
# @option options [Hash] :headers Additional headers to set on the request
|
264
|
+
# @option options [Boolean] :skip_auth (false) Skip authentication
|
204
265
|
def request(method, api, path, **options)
|
205
266
|
url = homeserver.dup.tap do |u|
|
206
267
|
u.path = api_to_path(api) + path
|
@@ -218,7 +279,7 @@ module MatrixSdk
|
|
218
279
|
request.content_length = (request.body || request.body_stream).size
|
219
280
|
end
|
220
281
|
|
221
|
-
request['authorization'] = "Bearer #{access_token}" if access_token
|
282
|
+
request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
|
222
283
|
if options.key? :headers
|
223
284
|
options[:headers].each do |h, v|
|
224
285
|
request[h.to_s.downcase] = v
|
@@ -229,14 +290,20 @@ module MatrixSdk
|
|
229
290
|
loop do
|
230
291
|
raise MatrixConnectionError, "Server still too busy to handle request after #{failures} attempts, try again later" if failures >= 10
|
231
292
|
|
232
|
-
|
293
|
+
req_id = ('A'..'Z').to_a.sample(4).join
|
294
|
+
|
295
|
+
print_http(request, id: req_id)
|
233
296
|
begin
|
297
|
+
dur_start = Time.now
|
234
298
|
response = http.request request
|
235
|
-
|
299
|
+
dur_end = Time.now
|
300
|
+
duration = dur_end - dur_start
|
301
|
+
rescue EOFError
|
236
302
|
logger.error 'Socket closed unexpectedly'
|
237
|
-
raise
|
303
|
+
raise
|
238
304
|
end
|
239
|
-
print_http(response)
|
305
|
+
print_http(response, duration: duration, id: req_id)
|
306
|
+
|
240
307
|
data = JSON.parse(response.body, symbolize_names: true) rescue nil
|
241
308
|
|
242
309
|
if response.is_a? Net::HTTPTooManyRequests
|
@@ -255,35 +322,41 @@ module MatrixSdk
|
|
255
322
|
end
|
256
323
|
end
|
257
324
|
|
325
|
+
# Generate a transaction ID
|
326
|
+
#
|
327
|
+
# @return [String] An arbitrary transaction ID
|
328
|
+
def transaction_id
|
329
|
+
ret = @transaction_id ||= 0
|
330
|
+
@transaction_id = @transaction_id.succ
|
331
|
+
ret
|
332
|
+
end
|
333
|
+
|
258
334
|
private
|
259
335
|
|
260
|
-
def print_http(http)
|
336
|
+
def print_http(http, body: true, duration: nil, id: nil)
|
261
337
|
return unless logger.debug?
|
262
338
|
|
263
339
|
if http.is_a? Net::HTTPRequest
|
264
|
-
dir = '>
|
340
|
+
dir = "#{id ? id + ' : ' : nil}>"
|
265
341
|
logger.debug "#{dir} Sending a #{http.method} request to `#{http.path}`:"
|
266
342
|
else
|
267
|
-
dir = '<
|
268
|
-
logger.debug "#{dir} Received a #{http.code} #{http.message} response:"
|
343
|
+
dir = "#{id ? id + ' : ' : nil}<"
|
344
|
+
logger.debug "#{dir} Received a #{http.code} #{http.message} response:#{duration ? " [#{(duration * 1000).to_i}ms]" : nil}"
|
269
345
|
end
|
270
346
|
http.to_hash.map { |k, v| "#{k}: #{k == 'authorization' ? '[ REDACTED ]' : v.join(', ')}" }.each do |h|
|
271
347
|
logger.debug "#{dir} #{h}"
|
272
348
|
end
|
273
349
|
logger.debug dir
|
274
|
-
|
275
|
-
|
276
|
-
|
350
|
+
if body
|
351
|
+
clean_body = JSON.parse(http.body) rescue nil if http.body
|
352
|
+
clean_body.keys.each { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body.is_a? Hash
|
353
|
+
clean_body = clean_body.to_s if clean_body
|
354
|
+
logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
|
355
|
+
end
|
277
356
|
rescue StandardError => e
|
278
357
|
logger.warn "#{e.class} occured while printing request debug; #{e.message}\n#{e.backtrace.join "\n"}"
|
279
358
|
end
|
280
359
|
|
281
|
-
def transaction_id
|
282
|
-
ret = @transaction_id ||= 0
|
283
|
-
@transaction_id = @transaction_id.succ
|
284
|
-
ret
|
285
|
-
end
|
286
|
-
|
287
360
|
def api_to_path(api)
|
288
361
|
# TODO: <api>_current / <api>_latest
|
289
362
|
"/_matrix/#{api.to_s.split('_').join('/')}"
|
@@ -303,7 +376,7 @@ module MatrixSdk
|
|
303
376
|
@http.open_timeout = open_timeout
|
304
377
|
@http.read_timeout = read_timeout
|
305
378
|
@http.use_ssl = homeserver.scheme == 'https'
|
306
|
-
@http.verify_mode = validate_certificate ? ::OpenSSL::SSL::
|
379
|
+
@http.verify_mode = validate_certificate ? ::OpenSSL::SSL::VERIFY_PEER : ::OpenSSL::SSL::VERIFY_NONE
|
307
380
|
@http.start
|
308
381
|
@http
|
309
382
|
end
|
data/lib/matrix_sdk/client.rb
CHANGED
@@ -11,7 +11,16 @@ module MatrixSdk
|
|
11
11
|
include MatrixSdk::Logging
|
12
12
|
extend Forwardable
|
13
13
|
|
14
|
-
|
14
|
+
# @!attribute api [r] The underlying API connection
|
15
|
+
# @return [Api] The underlying API connection
|
16
|
+
# @!attribute next_batch [r] The batch token for a running sync
|
17
|
+
# @return [String] The opaque batch token
|
18
|
+
# @!attribute cache [rw] The cache level
|
19
|
+
# @return [:all,:some,:none] The level of caching to do
|
20
|
+
# @!attribute sync_filter [rw] The global sync filter
|
21
|
+
# @return [Hash,String] A filter definition, either as defined by the
|
22
|
+
# Matrix spec, or as an identifier returned by a filter creation request
|
23
|
+
attr_reader :api, :next_batch
|
15
24
|
attr_accessor :cache, :sync_filter
|
16
25
|
|
17
26
|
events :error, :event, :presence_event, :invite_event, :leave_event, :ephemeral_event
|
@@ -22,9 +31,20 @@ module MatrixSdk
|
|
22
31
|
:access_token, :access_token=, :device_id, :device_id=, :homeserver, :homeserver=,
|
23
32
|
:validate_certificate, :validate_certificate=
|
24
33
|
|
34
|
+
# Create a new client instance from only a Matrix HS domain
|
35
|
+
#
|
36
|
+
# This will use the well-known delegation lookup to find the correct client URL
|
37
|
+
#
|
38
|
+
# @note This method will not verify that the created client has a valid connection,
|
39
|
+
# it will only perform the necessary lookups to build a connection URL.
|
40
|
+
# @return [Client] The new client instance
|
41
|
+
# @param domain [String] The domain name to look up
|
42
|
+
# @param params [Hash] Additional parameters to pass along to {Api.new_for_domain} as well as {initialize}
|
43
|
+
# @see Api.new_for_domain
|
44
|
+
# @see #initialize
|
25
45
|
def self.new_for_domain(domain, **params)
|
26
46
|
api = MatrixSdk::Api.new_for_domain(domain, keep_wellknown: true)
|
27
|
-
return new(api, params) unless api.well_known
|
47
|
+
return new(api, params) unless api.well_known&.key?('m.identity_server')
|
28
48
|
|
29
49
|
identity_server = MatrixSdk::Api.new(api.well_known['m.identity_server']['base_url'], protocols: %i[IS])
|
30
50
|
new(api, params.merge(identity_server: identity_server))
|
@@ -53,7 +73,7 @@ module MatrixSdk
|
|
53
73
|
@rooms = {}
|
54
74
|
@users = {}
|
55
75
|
@cache = client_cache
|
56
|
-
@identity_server =
|
76
|
+
@identity_server = nil
|
57
77
|
|
58
78
|
@sync_token = nil
|
59
79
|
@sync_thread = nil
|
@@ -75,22 +95,42 @@ module MatrixSdk
|
|
75
95
|
@mxid = params[:user_id]
|
76
96
|
end
|
77
97
|
|
98
|
+
# Gets the currently logged in user's MXID
|
99
|
+
#
|
100
|
+
# @return [MXID] The MXID of the current user
|
78
101
|
def mxid
|
79
102
|
@mxid ||= begin
|
80
|
-
api.whoami?[:user_id] if api&.access_token
|
103
|
+
MXID.new api.whoami?[:user_id] if api&.access_token
|
81
104
|
end
|
82
105
|
end
|
83
106
|
|
84
|
-
|
85
|
-
id = MXID.new id.to_s unless id.is_a? MXID
|
86
|
-
raise ArgumentError, 'Must be a User ID' unless id.user?
|
107
|
+
alias user_id mxid
|
87
108
|
|
88
|
-
|
109
|
+
# Gets the current user presence status object
|
110
|
+
#
|
111
|
+
# @return [Response] The user presence
|
112
|
+
# @see User#presence
|
113
|
+
# @see Protocols::CS#get_presence_status
|
114
|
+
def presence
|
115
|
+
api.get_presence_status(mxid).tap { |h| h.delete :user_id }
|
89
116
|
end
|
90
117
|
|
91
|
-
|
92
|
-
|
118
|
+
# Sets the current user's presence status
|
119
|
+
#
|
120
|
+
# @param status [:online,:offline,:unavailable] The new status to use
|
121
|
+
# @param message [String] A custom status message to set
|
122
|
+
# @see User#presence=
|
123
|
+
# @see Protocols::CS#set_presence_status
|
124
|
+
def set_presence(status, message: nil)
|
125
|
+
raise ArgumentError, 'Presence must be one of :online, :offline, :unavailable' unless %i[online offline unavailable].include?(status)
|
126
|
+
|
127
|
+
api.set_presence_status(mxid, status, message: message)
|
128
|
+
end
|
93
129
|
|
130
|
+
# Gets a list of all the public rooms on the connected HS
|
131
|
+
#
|
132
|
+
# @note This will try to list all public rooms on the HS, and may take a while on larger instances
|
133
|
+
# @return [Array[Room]] The public rooms
|
94
134
|
def public_rooms
|
95
135
|
rooms = []
|
96
136
|
since = nil
|
@@ -114,6 +154,12 @@ module MatrixSdk
|
|
114
154
|
rooms
|
115
155
|
end
|
116
156
|
|
157
|
+
# Gets a list of all relevant rooms, either the ones currently handled by
|
158
|
+
# the client, or the list of currently joined ones if no rooms are handled
|
159
|
+
#
|
160
|
+
# @return [Array[Room]] All the currently handled rooms
|
161
|
+
# @note This will always return the empty array if the cache level is set
|
162
|
+
# to :none
|
117
163
|
def rooms
|
118
164
|
if @rooms.empty? && cache != :none
|
119
165
|
api.get_joined_rooms.joined_rooms.each do |id|
|
@@ -124,6 +170,11 @@ module MatrixSdk
|
|
124
170
|
@rooms.values
|
125
171
|
end
|
126
172
|
|
173
|
+
# Refresh the list of currently handled rooms, replacing it with the user's
|
174
|
+
# currently joined rooms.
|
175
|
+
#
|
176
|
+
# @note This will be a no-op if the cache level is set to :none
|
177
|
+
# @return [Boolean] If the refresh succeeds
|
127
178
|
def reload_rooms!
|
128
179
|
return true if cache == :none
|
129
180
|
|
@@ -137,12 +188,25 @@ module MatrixSdk
|
|
137
188
|
end
|
138
189
|
alias refresh_rooms! reload_rooms!
|
139
190
|
|
191
|
+
# Register - and log in - on the connected HS as a guest
|
192
|
+
#
|
193
|
+
# @note This feature is not commonly supported by many HSes
|
140
194
|
def register_as_guest
|
141
195
|
data = api.register(kind: :guest)
|
142
196
|
post_authentication(data)
|
143
197
|
end
|
144
198
|
|
145
|
-
|
199
|
+
# Register a new user account on the connected HS
|
200
|
+
#
|
201
|
+
# This will also trigger an initial sync unless no_sync is set
|
202
|
+
#
|
203
|
+
# @note This method will currently always use auth type 'm.login.dummy'
|
204
|
+
# @param username [String] The new user's name
|
205
|
+
# @param password [String] The new user's password
|
206
|
+
# @param params [Hash] Additional options
|
207
|
+
# @option params [Boolean] :no_sync Skip the initial sync on registering
|
208
|
+
# @option params [Boolean] :allow_sync_retry Allow sync to retry on failure
|
209
|
+
def register_with_password(username, password, **params)
|
146
210
|
username = username.to_s unless username.is_a?(String)
|
147
211
|
password = password.to_s unless password.is_a?(String)
|
148
212
|
|
@@ -154,10 +218,21 @@ module MatrixSdk
|
|
154
218
|
|
155
219
|
return if params[:no_sync]
|
156
220
|
|
157
|
-
sync full_state:
|
221
|
+
sync full_state: true,
|
158
222
|
allow_sync_retry: params.fetch(:allow_sync_retry, nil)
|
159
223
|
end
|
160
224
|
|
225
|
+
# Logs in as a user on the connected HS
|
226
|
+
#
|
227
|
+
# This will also trigger an initial sync unless no_sync is set
|
228
|
+
#
|
229
|
+
# @param username [String] The username of the user
|
230
|
+
# @param password [String] The password of the user
|
231
|
+
# @param sync_timeout [Numeric] The timeout of the initial sync on login
|
232
|
+
# @param full_state [Boolean] Should the initial sync retrieve full state
|
233
|
+
# @param params [Hash] Additional options
|
234
|
+
# @option params [Boolean] :no_sync Skip the initial sync on registering
|
235
|
+
# @option params [Boolean] :allow_sync_retry Allow sync to retry on failure
|
161
236
|
def login(username, password, sync_timeout: 15, full_state: false, **params)
|
162
237
|
username = username.to_s unless username.is_a?(String)
|
163
238
|
password = password.to_s unless password.is_a?(String)
|
@@ -175,6 +250,17 @@ module MatrixSdk
|
|
175
250
|
allow_sync_retry: params.fetch(:allow_sync_retry, nil)
|
176
251
|
end
|
177
252
|
|
253
|
+
# Logs in as a user on the connected HS
|
254
|
+
#
|
255
|
+
# This will also trigger an initial sync unless no_sync is set
|
256
|
+
#
|
257
|
+
# @param username [String] The username of the user
|
258
|
+
# @param token [String] The token to log in with
|
259
|
+
# @param sync_timeout [Numeric] The timeout of the initial sync on login
|
260
|
+
# @param full_state [Boolean] Should the initial sync retrieve full state
|
261
|
+
# @param params [Hash] Additional options
|
262
|
+
# @option params [Boolean] :no_sync Skip the initial sync on registering
|
263
|
+
# @option params [Boolean] :allow_sync_retry Allow sync to retry on failure
|
178
264
|
def login_with_token(username, token, sync_timeout: 15, full_state: false, **params)
|
179
265
|
username = username.to_s unless username.is_a?(String)
|
180
266
|
token = token.to_s unless token.is_a?(String)
|
@@ -192,30 +278,90 @@ module MatrixSdk
|
|
192
278
|
allow_sync_retry: params.fetch(:allow_sync_retry, nil)
|
193
279
|
end
|
194
280
|
|
281
|
+
# Logs out of the current session
|
195
282
|
def logout
|
196
283
|
api.logout
|
197
284
|
@api.access_token = nil
|
198
285
|
@mxid = nil
|
199
286
|
end
|
200
287
|
|
288
|
+
# Check if there's a currently logged in session
|
289
|
+
#
|
290
|
+
# @note This will not check if the session is valid, only if it exists
|
291
|
+
# @return [Boolean] If there's a current session
|
201
292
|
def logged_in?
|
202
|
-
|
293
|
+
!@api.access_token.nil?
|
294
|
+
end
|
295
|
+
|
296
|
+
# Retrieve a list of all registered third-party IDs for the current user
|
297
|
+
#
|
298
|
+
# @return [Response] A response hash containing the key :threepids
|
299
|
+
# @see Protocols::CS#get_3pids
|
300
|
+
def registered_3pids
|
301
|
+
data = api.get_3pids
|
302
|
+
data.threepids.each do |obj|
|
303
|
+
obj.instance_eval do
|
304
|
+
def added_at
|
305
|
+
Time.at(self[:added_at] / 1000)
|
306
|
+
end
|
307
|
+
|
308
|
+
def validated_at
|
309
|
+
return unless validated?
|
310
|
+
|
311
|
+
Time.at(self[:validated_at] / 1000)
|
312
|
+
end
|
313
|
+
|
314
|
+
def validated?
|
315
|
+
key? :validated_at
|
316
|
+
end
|
317
|
+
|
318
|
+
def to_s
|
319
|
+
"#{self[:medium]}:#{self[:address]}"
|
320
|
+
end
|
321
|
+
|
322
|
+
def inspect
|
323
|
+
"#<MatrixSdk::Response 3pid=#{to_s.inspect} added_at=\"#{added_at}\"#{validated? ? " validated_at=\"#{validated_at}\"" : ''}>"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
data
|
203
328
|
end
|
204
329
|
|
330
|
+
# Creates a new room
|
331
|
+
#
|
332
|
+
# @example Creating a room with an alias
|
333
|
+
# client.create_room('myroom')
|
334
|
+
# #<MatrixSdk::Room ... >
|
335
|
+
#
|
336
|
+
# @param room_alias [String] A default alias to set on the room, should only be the localpart
|
337
|
+
# @return [Room] The resulting room
|
338
|
+
# @see Protocols::CS#create_room
|
205
339
|
def create_room(room_alias = nil, **params)
|
206
340
|
data = api.create_room(params.merge(room_alias: room_alias))
|
207
341
|
ensure_room(data.room_id)
|
208
342
|
end
|
209
343
|
|
344
|
+
# Joins an already created room
|
345
|
+
#
|
346
|
+
# @param room_id_or_alias [String,MXID] A room alias (#room:example.com) or a room ID (!id:example.com)
|
347
|
+
# @param server_name [Array[String]] A list of servers to attempt the join through, required for IDs
|
348
|
+
# @return [Room] The resulting room
|
349
|
+
# @see Protocols::CS#join_room
|
210
350
|
def join_room(room_id_or_alias, server_name: [])
|
211
351
|
server_name = [server_name] unless server_name.is_a? Array
|
212
352
|
data = api.join_room(room_id_or_alias, server_name: server_name)
|
213
353
|
ensure_room(data.fetch(:room_id, room_id_or_alias))
|
214
354
|
end
|
215
355
|
|
356
|
+
# Find a room in the locally cached list of rooms that the current user is part of
|
357
|
+
#
|
358
|
+
# @param room_id_or_alias [String,MXID] A room ID or alias
|
359
|
+
# @param only_canonical [Boolean] Only match alias against the canonical alias
|
360
|
+
# @return [Room] The found room
|
361
|
+
# @return [nil] If no room was found
|
216
362
|
def find_room(room_id_or_alias, only_canonical: false)
|
217
363
|
room_id_or_alias = MXID.new(room_id_or_alias.to_s) unless room_id_or_alias.is_a? MXID
|
218
|
-
raise ArgumentError, 'Must be a room id or alias' unless
|
364
|
+
raise ArgumentError, 'Must be a room id or alias' unless room_id_or_alias.room?
|
219
365
|
|
220
366
|
return @rooms.fetch(room_id_or_alias.to_s, nil) if room_id_or_alias.room_id?
|
221
367
|
|
@@ -224,7 +370,21 @@ module MatrixSdk
|
|
224
370
|
@rooms.values.find { |r| r.aliases.include? room_id_or_alias.to_s }
|
225
371
|
end
|
226
372
|
|
373
|
+
# Get a User instance from a MXID
|
374
|
+
#
|
375
|
+
# @param user_id [String,MXID,:self] The MXID to look up, will also accept :self in order to get the currently logged-in user
|
376
|
+
# @return [User] The User instance for the specified user
|
377
|
+
# @raise [ArgumentError] If the input isn't a valid user ID
|
378
|
+
# @note The method doesn't perform any existence checking, so the returned User object may point to a non-existent user
|
227
379
|
def get_user(user_id)
|
380
|
+
user_id = mxid if user_id == :self
|
381
|
+
|
382
|
+
user_id = MXID.new user_id.to_s unless user_id.is_a? MXID
|
383
|
+
raise ArgumentError, 'Must be a User ID' unless user_id.user?
|
384
|
+
|
385
|
+
# To still use regular string storage in the hash itself
|
386
|
+
user_id = user_id.to_s
|
387
|
+
|
228
388
|
if cache == :all
|
229
389
|
@users[user_id] ||= User.new(self, user_id)
|
230
390
|
else
|
@@ -232,10 +392,23 @@ module MatrixSdk
|
|
232
392
|
end
|
233
393
|
end
|
234
394
|
|
395
|
+
# Remove a room alias
|
396
|
+
#
|
397
|
+
# @param room_alias [String,MXID] The room alias to remove
|
398
|
+
# @see Protocols::CS#remove_room_alias
|
235
399
|
def remove_room_alias(room_alias)
|
400
|
+
room_alias = MXID.new room_alias.to_s unless room_alias.is_a? MXID
|
401
|
+
raise ArgumentError, 'Must be a room alias' unless room_alias.room_alias?
|
402
|
+
|
236
403
|
api.remove_room_alias(room_alias)
|
237
404
|
end
|
238
405
|
|
406
|
+
# Upload a piece of data to the media repo
|
407
|
+
#
|
408
|
+
# @return [URI::MATRIX] A Matrix content (mxc://) URL pointing to the uploaded data
|
409
|
+
# @param content [String] The data to upload
|
410
|
+
# @param content_type [String] The MIME type of the data
|
411
|
+
# @see Protocols::CS#media_upload
|
239
412
|
def upload(content, content_type)
|
240
413
|
data = api.media_upload(content, content_type)
|
241
414
|
return data[:content_uri] if data.key? :content_uri
|
@@ -243,25 +416,72 @@ module MatrixSdk
|
|
243
416
|
raise MatrixUnexpectedResponseError, 'Upload succeeded, but no media URI returned'
|
244
417
|
end
|
245
418
|
|
419
|
+
# Starts a background thread that will listen to new events
|
420
|
+
#
|
421
|
+
# @see sync For What parameters are accepted
|
246
422
|
def start_listener_thread(**params)
|
423
|
+
return if listening?
|
424
|
+
|
247
425
|
@should_listen = true
|
248
|
-
|
426
|
+
if api.protocol?(:MSC) && api.msc2108?
|
427
|
+
params[:filter] = sync_filter unless params.key? :filter
|
428
|
+
params[:filter] = params[:filter].to_json unless params[:filter].nil? || params[:filter].is_a?(String)
|
429
|
+
params[:since] = @next_batch if @next_batch
|
430
|
+
|
431
|
+
errors = 0
|
432
|
+
thread, cancel_token = api.msc2108_sync_sse(params) do |data, event:, id:|
|
433
|
+
@next_batch = id if id
|
434
|
+
if event.to_sym == :sync
|
435
|
+
handle_sync_response(data)
|
436
|
+
errors = 0
|
437
|
+
elsif event.to_sym == :sync_error
|
438
|
+
logger.error "SSE Sync error received; #{data.type}: #{data.message}"
|
439
|
+
errors += 1
|
440
|
+
|
441
|
+
# TODO: Allow configuring
|
442
|
+
raise 'Aborting due to excessive errors' if errors >= 5
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
@should_listen = cancel_token
|
447
|
+
else
|
448
|
+
thread = Thread.new { listen_forever(params) }
|
449
|
+
end
|
249
450
|
@sync_thread = thread
|
250
451
|
thread.run
|
251
452
|
end
|
252
453
|
|
454
|
+
# Stops the running background thread if one is active
|
253
455
|
def stop_listener_thread
|
254
456
|
return unless @sync_thread
|
255
457
|
|
256
|
-
@should_listen
|
257
|
-
|
458
|
+
if @should_listen.is_a? Hash
|
459
|
+
@should_listen[:run] = false
|
460
|
+
else
|
461
|
+
@should_listen = false
|
462
|
+
end
|
463
|
+
if @sync_thread.alive?
|
464
|
+
ret = @sync_thread.join(2)
|
465
|
+
@sync_thread.kill unless ret
|
466
|
+
end
|
258
467
|
@sync_thread = nil
|
259
468
|
end
|
260
469
|
|
470
|
+
# Check if there's a thread listening for events
|
261
471
|
def listening?
|
262
472
|
@sync_thread&.alive? == true
|
263
473
|
end
|
264
474
|
|
475
|
+
# Run a message sync round, triggering events as necessary
|
476
|
+
#
|
477
|
+
# @param skip_store_batch [Boolean] Should this sync skip storing the returned next_batch token,
|
478
|
+
# doing this would mean the next sync re-runs from the same point. Useful with use of filters.
|
479
|
+
# @param params [Hash] Additional options
|
480
|
+
# @option params [String,Hash] :filter (#sync_filter) A filter to use for this sync
|
481
|
+
# @option params [Numeric] :timeout (30) A timeout value in seconds for the sync request
|
482
|
+
# @option params [Numeric] :allow_sync_retry (0) The number of retries allowed for this sync request
|
483
|
+
# @option params [String] :since An override of the "since" token to provide to the sync request
|
484
|
+
# @see Protocols::CS#sync
|
265
485
|
def sync(skip_store_batch: false, **params)
|
266
486
|
extra_params = {
|
267
487
|
filter: sync_filter,
|
@@ -283,11 +503,26 @@ module MatrixSdk
|
|
283
503
|
@next_batch = data[:next_batch] unless skip_store_batch
|
284
504
|
|
285
505
|
handle_sync_response(data)
|
506
|
+
true
|
286
507
|
end
|
287
508
|
|
288
509
|
alias listen_for_events sync
|
289
510
|
|
290
|
-
|
511
|
+
# Ensures that a room exists in the cache
|
512
|
+
#
|
513
|
+
# @param room_id [String,MXID] The room ID to ensure
|
514
|
+
# @return [Room] The room object for the requested room
|
515
|
+
def ensure_room(room_id)
|
516
|
+
room_id = MXID.new room_id.to_s unless room_id.is_a? MXID
|
517
|
+
raise ArgumentError, 'Must be a room ID' unless room_id.room_id?
|
518
|
+
|
519
|
+
room_id = room_id.to_s
|
520
|
+
@rooms.fetch(room_id) do
|
521
|
+
room = Room.new(self, room_id)
|
522
|
+
@rooms[room_id] = room unless cache == :none
|
523
|
+
room
|
524
|
+
end
|
525
|
+
end
|
291
526
|
|
292
527
|
def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 30, **params)
|
293
528
|
orig_bad_sync_timeout = bad_sync_timeout + 0
|
@@ -312,6 +547,8 @@ module MatrixSdk
|
|
312
547
|
fire_error(ErrorEvent.new(e, :listener_thread))
|
313
548
|
end
|
314
549
|
|
550
|
+
private
|
551
|
+
|
315
552
|
def post_authentication(data)
|
316
553
|
@mxid = data[:user_id]
|
317
554
|
@api.access_token = data[:access_token]
|
@@ -320,19 +557,11 @@ module MatrixSdk
|
|
320
557
|
access_token
|
321
558
|
end
|
322
559
|
|
323
|
-
def ensure_room(room_id)
|
324
|
-
room_id = room_id.to_s unless room_id.is_a? String
|
325
|
-
@rooms.fetch(room_id) do
|
326
|
-
room = Room.new(self, room_id)
|
327
|
-
@rooms[room_id] = room unless cache == :none
|
328
|
-
room
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
560
|
def handle_state(room_id, state_event)
|
333
561
|
return unless state_event.key? :type
|
334
562
|
|
335
563
|
room = ensure_room(room_id)
|
564
|
+
room.send :put_state_event, state_event
|
336
565
|
content = state_event[:content]
|
337
566
|
case state_event[:type]
|
338
567
|
when 'm.room.name'
|
@@ -346,9 +575,9 @@ module MatrixSdk
|
|
346
575
|
when 'm.room.aliases'
|
347
576
|
room.instance_variable_get('@aliases').concat content[:aliases]
|
348
577
|
when 'm.room.join_rules'
|
349
|
-
room.instance_variable_set '@join_rule', content[:join_rule].to_sym
|
578
|
+
room.instance_variable_set '@join_rule', content[:join_rule].nil? ? nil : content[:join_rule].to_sym
|
350
579
|
when 'm.room.guest_access'
|
351
|
-
room.instance_variable_set '@guest_access', content[:guest_access].to_sym
|
580
|
+
room.instance_variable_set '@guest_access', content[:guest_access].nil? ? nil : content[:guest_access].to_sym
|
352
581
|
when 'm.room.member'
|
353
582
|
return unless cache == :all
|
354
583
|
|
@@ -368,10 +597,12 @@ module MatrixSdk
|
|
368
597
|
end
|
369
598
|
|
370
599
|
data[:rooms][:invite].each do |room_id, invite|
|
600
|
+
invite[:room_id] = room_id.to_s
|
371
601
|
fire_invite_event(MatrixEvent.new(self, invite), room_id.to_s)
|
372
602
|
end
|
373
603
|
|
374
604
|
data[:rooms][:leave].each do |room_id, left|
|
605
|
+
left[:room_id] = room_id.to_s
|
375
606
|
fire_leave_event(MatrixEvent.new(self, left), room_id.to_s)
|
376
607
|
end
|
377
608
|
|
@@ -387,7 +618,7 @@ module MatrixSdk
|
|
387
618
|
|
388
619
|
join[:timeline][:events].each do |event|
|
389
620
|
event[:room_id] = room_id.to_s
|
390
|
-
handle_state(room_id, event)
|
621
|
+
handle_state(room_id, event) if event.key? :state_key
|
391
622
|
room.send :put_event, event
|
392
623
|
|
393
624
|
fire_event(MatrixEvent.new(self, event), event[:type])
|