matrix_sdk 2.1.3 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 550080056a38ee354ff8e633069d71f207e1927a567fcaed4f7ed9792bcbb79a
4
- data.tar.gz: ae8909f5a1092873453c4b7a876315efd29882cee353252d9bd92f069ce3c4ac
3
+ metadata.gz: 24a67aeb6af66f75480104c5c0dc44987e2fe54163ed96047da58bf091476752
4
+ data.tar.gz: e5935a94b430a1b195b55d748415cc0ad95ea649e32f5e97045cc82726dd2bd3
5
5
  SHA512:
6
- metadata.gz: 5838b47c8ea7df7becdb7dadcb07e57c31526c8c3cdd025a18cc2add7db9e9da9f0aff62a16ed4a3a5306566785f941e7cdba90e664f2b872959c7774b4ec4bc
7
- data.tar.gz: 3fcad5ea834b2dd0e6d68e55a8e88e5a88b444d07491f381420c4749ca1ff5375030f3a7c4b1b535ff947422caf7c4b86f81633d425614a05e5577aa9a4963ff
6
+ metadata.gz: 1ea88ea4c76b573dad7b03aeba49e91b8f7c01040118c38032b49ca00cfbe687bd931c4e17387545e27df529f6ae06fc835ee964b5e79489fb0c5cf84186b35b
7
+ data.tar.gz: e2a6a5cd78e7629ffb2cc4af5ec9c9154f2f63db570479ebb2c6fa9aff13b7b231bea877d56694ae799019698af8d4a87394f88fa162dcb172dbbd1ede09fc8f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ## 2.5.0 - 2022-01-14
2
+
3
+ - Adds preliminary support for the Matrix v1.1 `client/v3` API
4
+ - Adds some support for knocking rooms
5
+ - Adds mutex synchronization on API requests to avoid some threading issues
6
+ - Fixes error on attempting to skip cache for certain requests (#19)
7
+ - Fixes inconsistency in MXID typing for the Client abstraction (#18 #20)
8
+ - Fixes missed autoloader entries for errors (#22)
9
+ - Fixes some potential issues arising from broken user-provided state data
10
+
11
+ ## 2.4.0 - 2021-07-19
12
+
13
+ - Adds support for matrix: URI's according to MSC2312
14
+ - Adds some basic support for detecting Spaces (MSC1772)
15
+ - Fixes sync against Synapse 1.38.0 missing empty fields
16
+
17
+ ## 2.3.0 - 2021-03-26
18
+
19
+ - Adds support for Ruby 3.0 (#15)
20
+ - Adds support for requests against the Synapse admin API
21
+ - Adds helper methods for checking and changing user power levels
22
+ - Adds a proper caching system for room data
23
+ - Fixes argument error in `#get_room_messages`
24
+ - Removes unfinished and broken AS abstraction
25
+
26
+ ## 2.2.0 - 2020-11-20
27
+
28
+ - Adds direct message (1:1) room mapping to client abstraction
29
+ - Adds Api#get_room_event_context (#13)
30
+ - Improves support for JRuby
31
+
1
32
  ## 2.1.3 - 2020-09-18
2
33
 
3
34
  - Adds separate state event handler as Client#on_state_event
data/README.md CHANGED
@@ -4,7 +4,7 @@ A Ruby gem for easing the development of software that communicates with servers
4
4
 
5
5
  There is a Matrix room for the discussion about usage and development at [#ruby-matrix-sdk:kittenface.studio](https://matrix.to/#/#ruby-matrix-sdk:kittenface.studio).
6
6
 
7
- Live YARD documentation can be found at; http://aleol57.gitlab-pages.liu.se/ruby-matrix-sdk
7
+ Live YARD documentation can be found at; https://ruby-sdk.ananace.dev
8
8
 
9
9
  ## Example usage
10
10
 
@@ -38,6 +38,7 @@ module MatrixSdk
38
38
  # @option params [Numeric] :read_timeout (240) The timeout in seconds for reading responses
39
39
  # @option params [Hash] :global_headers Additional headers to set for all requests
40
40
  # @option params [Boolean] :skip_login Should the API skip logging in if the HS URL contains user information
41
+ # @option params [Boolean] :synapse (true) Is the API connecting to a Synapse instance
41
42
  # @option params [Hash] :well_known The .well-known object that the server was discovered through, should not be set manually
42
43
  def initialize(homeserver, **params)
43
44
  @homeserver = homeserver
@@ -56,12 +57,14 @@ module MatrixSdk
56
57
  @validate_certificate = params.fetch(:validate_certificate, false)
57
58
  @transaction_id = params.fetch(:transaction_id, 0)
58
59
  @backoff_time = params.fetch(:backoff_time, 5000)
59
- @open_timeout = params.fetch(:open_timeout, 60)
60
- @read_timeout = params.fetch(:read_timeout, 240)
60
+ @open_timeout = params.fetch(:open_timeout, nil)
61
+ @read_timeout = params.fetch(:read_timeout, nil)
61
62
  @well_known = params.fetch(:well_known, {})
62
63
  @global_headers = DEFAULT_HEADERS.dup
63
64
  @global_headers.merge!(params.fetch(:global_headers)) if params.key? :global_headers
65
+ @synapse = params.fetch(:synapse, true)
64
66
  @http = nil
67
+ @http_lock = Mutex.new
65
68
 
66
69
  ([params.fetch(:protocols, [:CS])].flatten - protocols).each do |proto|
67
70
  self.class.include MatrixSdk::Protocols.const_get(proto)
@@ -161,11 +164,13 @@ module MatrixSdk
161
164
 
162
165
  params[:well_known] = well_known if keep_wellknown
163
166
 
164
- new(uri,
165
- params.merge(
166
- address: target_uri.host,
167
- port: target_uri.port
168
- ))
167
+ new(
168
+ uri,
169
+ **params.merge(
170
+ address: target_uri.host,
171
+ port: target_uri.port
172
+ )
173
+ )
169
174
  end
170
175
 
171
176
  # Get a list of enabled protocols on the API client
@@ -268,23 +273,6 @@ module MatrixSdk
268
273
  u.query = [u.query, URI.encode_www_form(options.fetch(:query))].flatten.compact.join('&') if options[:query]
269
274
  u.query = nil if u.query.nil? || u.query.empty?
270
275
  end
271
- request = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new url.request_uri
272
- request.body = options[:body] if options.key? :body
273
- request.body = request.body.to_json if options.key?(:body) && !request.body.is_a?(String)
274
- request.body_stream = options[:body_stream] if options.key? :body_stream
275
-
276
- global_headers.each { |h, v| request[h] = v }
277
- if request.body || request.body_stream
278
- request.content_type = 'application/json'
279
- request.content_length = (request.body || request.body_stream).size
280
- end
281
-
282
- request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
283
- if options.key? :headers
284
- options[:headers].each do |h, v|
285
- request[h.to_s.downcase] = v
286
- end
287
- end
288
276
 
289
277
  failures = 0
290
278
  loop do
@@ -292,10 +280,13 @@ module MatrixSdk
292
280
 
293
281
  req_id = ('A'..'Z').to_a.sample(4).join
294
282
 
295
- print_http(request, id: req_id)
296
- begin
283
+ req_obj = construct_request(url: url, method: method, **options)
284
+ print_http(req_obj, id: req_id)
285
+ response = duration = nil
286
+
287
+ @http_lock.synchronize do
297
288
  dur_start = Time.now
298
- response = http.request request
289
+ response = http.request req_obj
299
290
  dur_end = Time.now
300
291
  duration = dur_end - dur_start
301
292
  rescue EOFError
@@ -344,14 +335,38 @@ module MatrixSdk
344
335
 
345
336
  private
346
337
 
338
+ def construct_request(method:, url:, **options)
339
+ request = Net::HTTP.const_get(method.to_s.capitalize.to_sym).new url.request_uri
340
+
341
+ # FIXME: Handle bodies better, avoid duplicating work
342
+ request.body = options[:body] if options.key? :body
343
+ request.body = request.body.to_json if options.key?(:body) && !request.body.is_a?(String)
344
+ request.body_stream = options[:body_stream] if options.key? :body_stream
345
+
346
+ global_headers.each { |h, v| request[h] = v }
347
+ if request.body || request.body_stream
348
+ request.content_type = 'application/json'
349
+ request.content_length = (request.body || request.body_stream).size
350
+ end
351
+
352
+ request['authorization'] = "Bearer #{access_token}" if access_token && !options.fetch(:skip_auth, false)
353
+ if options.key? :headers
354
+ options[:headers].each do |h, v|
355
+ request[h.to_s.downcase] = v
356
+ end
357
+ end
358
+
359
+ request
360
+ end
361
+
347
362
  def print_http(http, body: true, duration: nil, id: nil)
348
363
  return unless logger.debug?
349
364
 
350
365
  if http.is_a? Net::HTTPRequest
351
- dir = "#{id ? id + ' : ' : nil}>"
366
+ dir = "#{id ? "#{id} : " : nil}>"
352
367
  logger.debug "#{dir} Sending a #{http.method} request to `#{http.path}`:"
353
368
  else
354
- dir = "#{id ? id + ' : ' : nil}<"
369
+ dir = "#{id ? "#{id} : " : nil}<"
355
370
  logger.debug "#{dir} Received a #{http.code} #{http.message} response:#{duration ? " [#{(duration * 1000).to_i}ms]" : nil}"
356
371
  end
357
372
  http.to_hash.map { |k, v| "#{k}: #{k == 'authorization' ? '[ REDACTED ]' : v.join(', ')}" }.each do |h|
@@ -360,7 +375,7 @@ module MatrixSdk
360
375
  logger.debug dir
361
376
  if body
362
377
  clean_body = JSON.parse(http.body) rescue nil if http.body
363
- clean_body.keys.each { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body.is_a? Hash
378
+ clean_body.each_key { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body.is_a? Hash
364
379
  clean_body = clean_body.to_s if clean_body
365
380
  logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
366
381
  end
@@ -369,6 +384,8 @@ module MatrixSdk
369
384
  end
370
385
 
371
386
  def api_to_path(api)
387
+ return "/_synapse/#{api.to_s.split('_').join('/')}" if @synapse && api.to_s.start_with?('admin_')
388
+
372
389
  # TODO: <api>_current / <api>_latest
373
390
  "/_matrix/#{api.to_s.split('_').join('/')}"
374
391
  end
@@ -384,8 +401,8 @@ module MatrixSdk
384
401
  Net::HTTP.new(host, port)
385
402
  end
386
403
 
387
- @http.open_timeout = open_timeout
388
- @http.read_timeout = read_timeout
404
+ @http.open_timeout = open_timeout if open_timeout
405
+ @http.read_timeout = read_timeout if read_timeout
389
406
  @http.use_ssl = homeserver.scheme == 'https'
390
407
  @http.verify_mode = validate_certificate ? ::OpenSSL::SSL::VERIFY_PEER : ::OpenSSL::SSL::VERIFY_NONE
391
408
  @http.start
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'matrix_sdk'
4
+ require 'matrix_sdk/util/events'
4
5
 
5
6
  require 'English'
6
7
  require 'forwardable'
@@ -67,11 +68,12 @@ module MatrixSdk
67
68
  api.instance_variable_set("@#{k}", v) if api.instance_variable_defined? "@#{k}"
68
69
  end
69
70
  else
70
- @api = Api.new hs_url, params
71
+ @api = Api.new hs_url, **params
71
72
  end
72
73
 
73
74
  @cache = client_cache
74
75
  @identity_server = nil
76
+ @mxid = nil
75
77
 
76
78
  @sync_token = nil
77
79
  @sync_thread = nil
@@ -101,9 +103,8 @@ module MatrixSdk
101
103
  #
102
104
  # @return [MXID] The MXID of the current user
103
105
  def mxid
104
- @mxid ||= begin
105
- MXID.new api.whoami?[:user_id] if api&.access_token
106
- end
106
+ @mxid ||= MXID.new api.whoami?[:user_id] if api&.access_token
107
+ @mxid
107
108
  end
108
109
 
109
110
  alias user_id mxid
@@ -156,6 +157,25 @@ module MatrixSdk
156
157
  rooms
157
158
  end
158
159
 
160
+ # Gets a list of all direct chat rooms (1:1 chats / direct message chats) for the currenct user
161
+ #
162
+ # @return [Hash[String,Array[String]]] A mapping of MXIDs to a list of direct rooms with that user
163
+ def direct_rooms
164
+ api.get_account_data(mxid, 'm.direct').transform_keys(&:to_s)
165
+ end
166
+
167
+ # Gets a direct message room for the given user if one exists
168
+ #
169
+ # @note Will return the oldest room if multiple exist
170
+ # @return [Room,nil] A direct message room if one exists
171
+ def direct_room(mxid)
172
+ mxid = MatrixSdk::MXID.new mxid.to_s unless mxid.is_a? MatrixSdk::MXID
173
+ raise ArgumentError, 'Must be a valid user ID' unless mxid.user?
174
+
175
+ room_id = direct_rooms[mxid.to_s]&.first
176
+ ensure_room room_id if room_id
177
+ end
178
+
159
179
  # Gets a list of all relevant rooms, either the ones currently handled by
160
180
  # the client, or the list of currently joined ones if no rooms are handled
161
181
  #
@@ -172,6 +192,19 @@ module MatrixSdk
172
192
  @rooms.values
173
193
  end
174
194
 
195
+ # Get a list of all joined Matrix Spaces
196
+ #
197
+ # @return [Array[Room]] All the currently joined Spaces
198
+ def spaces
199
+ rooms = if cache == :none
200
+ api.get_joined_rooms.joined_rooms.map { |id| Room.new(self, id) }
201
+ else
202
+ self.rooms
203
+ end
204
+
205
+ rooms.select(&:space?)
206
+ end
207
+
175
208
  # Refresh the list of currently handled rooms, replacing it with the user's
176
209
  # currently joined rooms.
177
210
  #
@@ -189,6 +222,7 @@ module MatrixSdk
189
222
  true
190
223
  end
191
224
  alias refresh_rooms! reload_rooms!
225
+ alias reload_spaces! reload_rooms!
192
226
 
193
227
  # Register - and log in - on the connected HS as a guest
194
228
  #
@@ -339,7 +373,7 @@ module MatrixSdk
339
373
  # @return [Room] The resulting room
340
374
  # @see Protocols::CS#create_room
341
375
  def create_room(room_alias = nil, **params)
342
- data = api.create_room(params.merge(room_alias: room_alias))
376
+ data = api.create_room(**params.merge(room_alias: room_alias))
343
377
  ensure_room(data.room_id)
344
378
  end
345
379
 
@@ -407,13 +441,13 @@ module MatrixSdk
407
441
 
408
442
  # Upload a piece of data to the media repo
409
443
  #
410
- # @return [URI::MATRIX] A Matrix content (mxc://) URL pointing to the uploaded data
444
+ # @return [URI::MXC] A Matrix content (mxc://) URL pointing to the uploaded data
411
445
  # @param content [String] The data to upload
412
446
  # @param content_type [String] The MIME type of the data
413
447
  # @see Protocols::CS#media_upload
414
448
  def upload(content, content_type)
415
449
  data = api.media_upload(content, content_type)
416
- return data[:content_uri] if data.key? :content_uri
450
+ return URI(data[:content_uri]) if data.key? :content_uri
417
451
 
418
452
  raise MatrixUnexpectedResponseError, 'Upload succeeded, but no media URI returned'
419
453
  end
@@ -433,10 +467,11 @@ module MatrixSdk
433
467
  errors = 0
434
468
  thread, cancel_token = api.msc2108_sync_sse(params) do |data, event:, id:|
435
469
  @next_batch = id if id
436
- if event.to_sym == :sync
470
+ case event.to_sym
471
+ when :sync
437
472
  handle_sync_response(data)
438
473
  errors = 0
439
- elsif event.to_sym == :sync_error
474
+ when :sync_error
440
475
  logger.error "SSE Sync error received; #{data.type}: #{data.message}"
441
476
  errors += 1
442
477
 
@@ -447,7 +482,7 @@ module MatrixSdk
447
482
 
448
483
  @should_listen = cancel_token
449
484
  else
450
- thread = Thread.new { listen_forever(params) }
485
+ thread = Thread.new { listen_forever(**params) }
451
486
  end
452
487
  @sync_thread = thread
453
488
  thread.run
@@ -495,11 +530,9 @@ module MatrixSdk
495
530
 
496
531
  attempts = 0
497
532
  data = loop do
498
- begin
499
- break api.sync extra_params
500
- rescue MatrixSdk::MatrixTimeoutError => e
501
- raise e if (attempts += 1) >= params.fetch(:allow_sync_retry, 0)
502
- end
533
+ break api.sync(**extra_params)
534
+ rescue MatrixSdk::MatrixTimeoutError => e
535
+ raise e if (attempts += 1) >= params.fetch(:allow_sync_retry, 0)
503
536
  end
504
537
 
505
538
  @next_batch = data[:next_batch] unless skip_store_batch
@@ -519,18 +552,21 @@ module MatrixSdk
519
552
  raise ArgumentError, 'Must be a room ID' unless room_id.room_id?
520
553
 
521
554
  room_id = room_id.to_s
522
- @rooms.fetch(room_id) do
555
+ ret = @rooms.fetch(room_id) do
523
556
  room = Room.new(self, room_id)
524
557
  @rooms[room_id] = room unless cache == :none
525
558
  room
526
559
  end
560
+ # Need to figure out a way to handle multiple types
561
+ ret = @rooms[room_id] = ret.to_space if ret.instance_variable_get :@room_type
562
+ ret
527
563
  end
528
564
 
529
565
  def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 0, **params)
530
566
  orig_bad_sync_timeout = bad_sync_timeout + 0
531
567
  while @should_listen
532
568
  begin
533
- sync(params.merge(timeout: timeout))
569
+ sync(**params.merge(timeout: timeout))
534
570
 
535
571
  bad_sync_timeout = orig_bad_sync_timeout
536
572
  sleep(sync_interval) if sync_interval.positive?
@@ -552,7 +588,7 @@ module MatrixSdk
552
588
  private
553
589
 
554
590
  def post_authentication(data)
555
- @mxid = data[:user_id]
591
+ @mxid = MXID.new data[:user_id]
556
592
  @api.access_token = data[:access_token]
557
593
  @api.device_id = data[:device_id]
558
594
  @api.homeserver = data[:home_server]
@@ -566,65 +602,38 @@ module MatrixSdk
566
602
 
567
603
  room = ensure_room(room_id)
568
604
  room.send :put_state_event, state_event
569
- content = state_event[:content]
570
- case state_event[:type]
571
- when 'm.room.name'
572
- room.instance_variable_set '@name', content[:name]
573
- when 'm.room.canonical_alias'
574
- room.instance_variable_set '@canonical_alias', content[:alias]
575
- # Also add as a regular alias
576
- room.instance_variable_get('@aliases').concat [content[:alias]]
577
- when 'm.room.topic'
578
- room.instance_variable_set '@topic', content[:topic]
579
- when 'm.room.aliases'
580
- room.instance_variable_get('@aliases').concat content[:aliases]
581
- when 'm.room.join_rules'
582
- room.instance_variable_set '@join_rule', content[:join_rule].nil? ? nil : content[:join_rule].to_sym
583
- when 'm.room.guest_access'
584
- room.instance_variable_set '@guest_access', content[:guest_access].nil? ? nil : content[:guest_access].to_sym
585
- when 'm.room.member'
586
- return unless cache == :all
587
-
588
- if content[:membership] == 'join'
589
- room.send(:ensure_member, get_user(state_event[:state_key]).dup.tap do |u|
590
- u.instance_variable_set :@display_name, content[:displayname]
591
- end)
592
- elsif %w[leave kick invite].include? content[:membership]
593
- room.members.delete_if { |m| m.id == state_event[:state_key] }
594
- end
595
- end
596
605
  end
597
606
 
598
607
  def handle_sync_response(data)
599
- data[:presence][:events].each do |presence_update|
608
+ data.dig(:presence, :events)&.each do |presence_update|
600
609
  fire_presence_event(MatrixEvent.new(self, presence_update))
601
610
  end
602
611
 
603
- data[:rooms][:invite].each do |room_id, invite|
612
+ data.dig(:rooms, :invite)&.each do |room_id, invite|
604
613
  invite[:room_id] = room_id.to_s
605
614
  fire_invite_event(MatrixEvent.new(self, invite), room_id.to_s)
606
615
  end
607
616
 
608
- data[:rooms][:leave].each do |room_id, left|
617
+ data.dig(:rooms, :leave)&.each do |room_id, left|
609
618
  left[:room_id] = room_id.to_s
610
619
  fire_leave_event(MatrixEvent.new(self, left), room_id.to_s)
611
620
  end
612
621
 
613
- data[:rooms][:join].each do |room_id, join|
622
+ data.dig(:rooms, :join)&.each do |room_id, join|
614
623
  room = ensure_room(room_id)
615
- room.instance_variable_set '@prev_batch', join[:timeline][:prev_batch]
624
+ room.instance_variable_set '@prev_batch', join.dig(:timeline, :prev_batch)
616
625
  room.instance_variable_set :@members_loaded, true unless sync_filter.fetch(:room, {}).fetch(:state, {}).fetch(:lazy_load_members, false)
617
626
 
618
- join[:state][:events].each do |event|
627
+ join.dig(:state, :events)&.each do |event|
619
628
  event[:room_id] = room_id.to_s
620
629
  handle_state(room_id, event)
621
630
  end
622
631
 
623
- join[:timeline][:events].each do |event|
632
+ join.dig(:timeline, :events)&.each do |event|
624
633
  event[:room_id] = room_id.to_s
625
634
  # Avoid sending two identical state events if it's both in state and timeline
626
635
  if event.key?(:state_key)
627
- state_event = join.dig(:state, :events).find { |ev| ev[:event_id] == event[:event_id] }
636
+ state_event = join.dig(:state, :events)&.find { |ev| ev[:event_id] == event[:event_id] }
628
637
 
629
638
  handle_state(room_id, event) unless event == state_event
630
639
  end
@@ -633,7 +642,7 @@ module MatrixSdk
633
642
  fire_event(MatrixEvent.new(self, event), event[:type])
634
643
  end
635
644
 
636
- join[:ephemeral][:events].each do |event|
645
+ join.dig(:ephemeral, :events)&.each do |event|
637
646
  event[:room_id] = room_id.to_s
638
647
  room.send :put_ephemeral_event, event
639
648
 
@@ -641,6 +650,14 @@ module MatrixSdk
641
650
  end
642
651
  end
643
652
 
653
+ unless cache == :none
654
+ @rooms.each do |_id, room|
655
+ # Clean up old cache data after every sync
656
+ # TODO Run this in a thread?
657
+ room.tinycache_adapter.cleanup
658
+ end
659
+ end
660
+
644
661
  nil
645
662
  end
646
663
  end
@@ -41,9 +41,13 @@ module MatrixSdk
41
41
  end
42
42
 
43
43
  class MatrixNotAuthorizedError < MatrixRequestError; end
44
+
44
45
  class MatrixForbiddenError < MatrixRequestError; end
46
+
45
47
  class MatrixNotFoundError < MatrixRequestError; end
48
+
46
49
  class MatrixConflictError < MatrixRequestError; end
50
+
47
51
  class MatrixTooManyRequestsError < MatrixRequestError; end
48
52
 
49
53
  # An error raised when errors occur in the connection layer
@@ -12,7 +12,7 @@ module MatrixSdk
12
12
 
13
13
  # TODO: Community-as-a-Room / Profile-as-a-Room, in case they're going for room aliases
14
14
  @sigil = identifier[0]
15
- @localpart, @domain, @port = identifier[1..-1].split(':')
15
+ @localpart, @domain, @port = identifier[1..].split(':')
16
16
  @port = @port.to_i if @port
17
17
 
18
18
  raise ArgumentError, 'Identifier is not a valid MXID' unless valid?
@@ -32,13 +32,13 @@ module MatrixSdk
32
32
  #
33
33
  # @return [String]
34
34
  def homeserver
35
- port_s = port ? ':' + port.to_s : ''
35
+ port_s = port ? ":#{port}" : ''
36
36
  domain ? domain + port_s : ''
37
37
  end
38
38
 
39
39
  # Gets the homserver part of the ID as a suffix (':homeserver')
40
40
  def homeserver_suffix
41
- ':' + homeserver if domain
41
+ ":#{homeserver}" if domain
42
42
  end
43
43
 
44
44
  def to_s
@@ -49,18 +49,13 @@ module MatrixSdk
49
49
  #
50
50
  # @return [Symbol] The MXID type, one of (:user_id, :room_id, :event_id, :group_id, or :room_alias)
51
51
  def type
52
- case sigil
53
- when '@'
54
- :user_id
55
- when '!'
56
- :room_id
57
- when '$'
58
- :event_id
59
- when '+'
60
- :group_id
61
- when '#'
62
- :room_alias
63
- end
52
+ {
53
+ '@' => :user_id,
54
+ '!' => :room_id,
55
+ '$' => :event_id,
56
+ '+' => :group_id,
57
+ '#' => :room_alias
58
+ }[sigil]
64
59
  end
65
60
 
66
61
  # Checks if the ID is valid
@@ -105,5 +100,45 @@ module MatrixSdk
105
100
  def room_alias?
106
101
  type == :room_alias
107
102
  end
103
+
104
+ # Converts the MXID to a matrix: URI according to MSC2312
105
+ # @param event_id [String,MXID] An event ID to append to the URI (only valid for rooms)
106
+ # @param action [String,Symbol] The action that should be requested
107
+ # @param via [Array[String]] The list of servers to use for a join
108
+ # @see https://github.com/matrix-org/matrix-doc/blob/master/proposals/2312-matrix-uri.md
109
+ def to_uri(event_id: nil, action: nil, via: nil)
110
+ uri = ''
111
+
112
+ case sigil
113
+ when '@'
114
+ raise ArgumentError, "can't provide via for user URIs" if via
115
+ raise ArgumentError, "can't provide event_id for user URIs" if event_id
116
+
117
+ uri += 'u'
118
+ when '#'
119
+ uri += 'r'
120
+ when '!'
121
+ uri += 'roomid'
122
+ else
123
+ raise ArgumentError, "this MXID can't be converted to a URI"
124
+ end
125
+
126
+ uri = "matrix:#{uri}/#{localpart}#{homeserver_suffix}"
127
+
128
+ uri += "/e/#{event_id.to_s.delete_prefix('$')}" if event_id
129
+ query = []
130
+ query << "action=#{action}" if action
131
+ [via].flatten.compact.each { |v| query << "via=#{v}" }
132
+
133
+ uri += "?#{query.join('&')}" unless query.empty?
134
+
135
+ URI(uri)
136
+ end
137
+
138
+ # Check if two MXIDs are equal
139
+ # @return [Boolean]
140
+ def ==(other)
141
+ to_s == other.to_s
142
+ end
108
143
  end
109
144
  end