matrix_sdk 2.1.3 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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