matrix_sdk 2.3.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -60,7 +60,9 @@ module MatrixSdk::Protocols::MSC
60
60
  # rubocop:disable Metrics/BlockLength
61
61
  thread = Thread.new(cancellation_token) do |ctx|
62
62
  print_http(req)
63
+ @http_lock&.lock
63
64
  http.request req do |response|
65
+ @http_lock&.unlock
64
66
  break unless ctx[:run]
65
67
 
66
68
  print_http(response, body: false)
@@ -134,8 +136,11 @@ module MatrixSdk::Protocols::MSC
134
136
  break
135
137
  end
136
138
  end
139
+
137
140
  break unless ctx[:run]
138
141
  end
142
+ ensure
143
+ @http_lock.unlock if @http_lock&.owned?
139
144
  end
140
145
  # rubocop:enable Metrics/BlockLength
141
146
 
@@ -28,12 +28,14 @@ module MatrixSdk
28
28
  # An inspect method that skips a handful of instance variables to avoid
29
29
  # flooding the terminal with debug data.
30
30
  # @return [String] a regular inspect string without the data for some variables
31
- ignore_inspect :client, :events, :prev_batch, :logger
31
+ ignore_inspect :client, :events, :prev_batch, :logger, :tinycache_adapter
32
32
 
33
33
  # Requires heavy lookups, so they're cached for an hour
34
- cached :joined_members, :aliases, cache_level: :all, expires_in: 60 * 60
35
- # Only cache unfiltered requests for all members
36
- cached :all_members, unless: proc { |args| args.any? }, cache_level: :all, expires_in: 3600
34
+ cached :joined_members, cache_level: :all, expires_in: 60 * 60
35
+
36
+ # Only cache unfiltered requests for aliases and members
37
+ cached :aliases, unless: proc { |args| args.any? }, cache_level: :all, expires_in: 60 * 60
38
+ cached :all_members, unless: proc { |args| args.any? }, cache_level: :all, expires_in: 60 * 60
37
39
 
38
40
  # Much simpler to look up, and lighter data-wise, so the cache is wider
39
41
  cached :canonical_alias, :name, :avatar_url, :topic, :guest_access, :join_rule, :power_levels, cache_level: :some, expires_in: 15 * 60
@@ -53,7 +55,7 @@ module MatrixSdk
53
55
  # @option data [String] :topic The current topic of the room
54
56
  # @option data [String,MXID] :canonical_alias The canonical alias of the room
55
57
  # @option data [Array(String,MXID)] :aliases All non-canonical aliases of the room
56
- # @option data [:invite,:public] :join_rule The join rule for the room
58
+ # @option data [:invite,:public,:knock] :join_rule The join rule for the room
57
59
  # @option data [:can_join,:forbidden] :guest_access The guest access setting for the room
58
60
  # @option data [Boolean] :world_readable If the room is readable by the entire world
59
61
  # @option data [Array(User)] :members The list of joined members
@@ -63,6 +65,13 @@ module MatrixSdk
63
65
  # @option data [String,URI] :avatar_url The avatar URL for the room
64
66
  # @option data [String] :prev_batch The previous batch token for backfill
65
67
  def initialize(client, room_id, data = {})
68
+ if client.is_a? Room
69
+ copy = client
70
+ client = copy.client
71
+ room_id = copy.id
72
+ # data = copy.attributes
73
+ end
74
+
66
75
  raise ArgumentError, 'Must be given a Client instance' unless client.is_a? Client
67
76
 
68
77
  @client = client
@@ -71,6 +80,7 @@ module MatrixSdk
71
80
 
72
81
  @events = []
73
82
  @event_history_limit = 10
83
+ @room_type = nil
74
84
 
75
85
  @prev_batch = nil
76
86
 
@@ -89,6 +99,24 @@ module MatrixSdk
89
99
  logger.debug "Created room #{room_id}"
90
100
  end
91
101
 
102
+ #
103
+ # Casting operators
104
+ #
105
+
106
+ def to_space
107
+ return nil unless space?
108
+
109
+ Rooms::Space.new self, nil
110
+ end
111
+
112
+ def to_s
113
+ prefix = canonical_alias if canonical_alias_has_value?
114
+ prefix ||= id
115
+ return "#{prefix} | #{name}" if name_has_value?
116
+
117
+ prefix
118
+ end
119
+
92
120
  #
93
121
  # Event handlers
94
122
  #
@@ -204,14 +232,14 @@ module MatrixSdk
204
232
  #
205
233
  # @return [:can_join,:forbidden] The current guest access right
206
234
  def guest_access
207
- client.api.get_room_guest_access(id)[:guest_access].to_sym
235
+ client.api.get_room_guest_access(id)[:guest_access]&.to_sym
208
236
  end
209
237
 
210
238
  # Gets the join rule for the room
211
239
  #
212
240
  # @return [:public,:knock,:invite,:private] The current join rule
213
241
  def join_rule
214
- client.api.get_room_join_rules(id)[:join_rule].to_sym
242
+ client.api.get_room_join_rules(id)[:join_rule]&.to_sym
215
243
  end
216
244
 
217
245
  # Checks if +guest_access+ is set to +:can_join+
@@ -224,11 +252,16 @@ module MatrixSdk
224
252
  join_rule == :invite
225
253
  end
226
254
 
255
+ # Checks if +join_rule+ is set to +:knock+
256
+ def knock_only?
257
+ join_rule == :knock
258
+ end
259
+
227
260
  # Gets the history visibility of the room
228
261
  #
229
262
  # @return [:invited,:joined,:shared,:world_readable] The current history visibility for the room
230
263
  def history_visibility
231
- client.api.get_room_state(id, 'm.room.history_visibility')[:history_visibility].to_sym
264
+ client.api.get_room_state(id, 'm.room.history_visibility')[:history_visibility]&.to_sym
232
265
  end
233
266
 
234
267
  # Checks if the room history is world readable
@@ -241,15 +274,14 @@ module MatrixSdk
241
274
 
242
275
  # Gets the room aliases
243
276
  #
277
+ # @param canonical_only [Boolean] Should the list of aliases only contain the canonical ones
244
278
  # @return [Array[String]] The assigned room aliases
245
- def aliases
246
- client.api.get_room_aliases(id).aliases
247
- rescue MatrixNotFoundError
248
- data = client.api.get_room_state_all(id)
249
- data.select { |chunk| chunk[:type] == 'm.room.aliases' && chunk.key?(:content) && chunk[:content].key?(:aliases) }
250
- .map { |chunk| chunk[:content][:aliases] }
251
- .flatten
252
- .compact
279
+ def aliases(canonical_only: true)
280
+ canonical = client.api.get_room_state(id, 'm.room.canonical_alias') rescue {}
281
+ aliases = ([canonical[:alias]].compact + (canonical[:alt_aliases] || [])).uniq.sort
282
+ return aliases if canonical_only
283
+
284
+ (aliases + client.api.get_room_aliases(id).aliases).uniq.sort
253
285
  end
254
286
 
255
287
  #
@@ -499,6 +531,41 @@ module MatrixSdk
499
531
  true
500
532
  end
501
533
 
534
+ # Gets the room creation information
535
+ #
536
+ # @return [Response] The content of the m.room.create event
537
+ def creation_info
538
+ # Not caching here, easier to cache the important values separately instead
539
+ client.api.get_room_creation_info(id)
540
+ end
541
+
542
+ # Retrieves the type of the room
543
+ #
544
+ # @return ['m.space',String,nil] The type of the room
545
+ def room_type
546
+ # Can't change, so a permanent cache is ok
547
+ return @room_type if @room_type_retrieved || @room_type
548
+
549
+ @room_type_retrieved = true
550
+ @room_type ||= creation_info[:type]
551
+ end
552
+
553
+ # Retrieves the room version
554
+ #
555
+ # @return [String] The version of the room
556
+ def room_version
557
+ @room_version ||= creation_info[:room_version]
558
+ end
559
+
560
+ # Checks if the room is a Matrix Space
561
+ #
562
+ # @return [Boolean,nil] True if the room is a space
563
+ def space?
564
+ room_type == 'm.space'
565
+ rescue MatrixSdk::MatrixForbiddenError, MatrixSdk::MatrixNotFoundError
566
+ nil
567
+ end
568
+
502
569
  # Returns a list of the room tags
503
570
  #
504
571
  # @return [Response] A list of the tags and their data, with add and remove methods implemented
@@ -646,10 +713,10 @@ module MatrixSdk
646
713
 
647
714
  # Sets a new avatar URL for the room
648
715
  #
649
- # @param avatar_url [URI::MATRIX] The mxc:// URL for the new room avatar
716
+ # @param avatar_url [URI::MXC] The mxc:// URL for the new room avatar
650
717
  def avatar_url=(avatar_url)
651
718
  avatar_url = URI(avatar_url) unless avatar_url.is_a? URI
652
- raise ArgumentError, 'Must be a valid MXC URL' unless avatar_url.is_a? URI::MATRIX
719
+ raise ArgumentError, 'Must be a valid MXC URL' unless avatar_url.is_a? URI::MXC
653
720
 
654
721
  client.api.set_room_avatar(id, avatar_url)
655
722
  tinycache_adapter.write(:avatar_url, avatar_url)
@@ -676,7 +743,7 @@ module MatrixSdk
676
743
  user = MXID.new(user.to_s) unless user.is_a? MXID
677
744
  raise ArgumentError, 'Must provide a valid user or MXID' unless user.user?
678
745
 
679
- level = power_levels[:users][user.to_s.to_sym]
746
+ level = power_levels.dig(:users, user.to_s.to_sym)
680
747
  level = power_levels[:users_default] || 0 if level.nil? && use_default
681
748
  level
682
749
  end
@@ -751,8 +818,17 @@ module MatrixSdk
751
818
 
752
819
  if users
753
820
  data[:users] = {} unless data.key? :users
754
- data[:users].merge!(users)
755
- data[:users].delete_if { |_k, v| v.nil? }
821
+ users.each do |user, level|
822
+ user = user.id if user.is_a? User
823
+ user = MXID.new(user.to_s) unless user.is_a? MXID
824
+ raise ArgumentError, 'Must provide a valid user or MXID' unless user.user?
825
+
826
+ if level.nil?
827
+ data[:users].delete(user.to_s.to_sym)
828
+ else
829
+ data[:users][user.to_s.to_sym] = level
830
+ end
831
+ end
756
832
  end
757
833
 
758
834
  client.api.set_power_levels(id, data)
@@ -796,27 +872,27 @@ module MatrixSdk
796
872
  end
797
873
 
798
874
  def handle_room_name(event)
799
- tinycache_adapter.write(:name, event[:content][:name])
875
+ tinycache_adapter.write(:name, event.dig(*%i[content name]))
800
876
  end
801
877
 
802
878
  def handle_room_topic(event)
803
- tinycache_adapter.write(:topic, event[:content][:topic])
879
+ tinycache_adapter.write(:topic, event.dig(*%i[content topic]))
804
880
  end
805
881
 
806
882
  def handle_room_guest_access(event)
807
- tinycache_adapter.write(:guest_access, event[:content][:guest_access].to_sym)
883
+ tinycache_adapter.write(:guest_access, event.dig(*%i[content guest_access])&.to_sym)
808
884
  end
809
885
 
810
886
  def handle_room_join_rules(event)
811
- tinycache_adapter.write(:join_rule, event[:content][:join_rule].to_sym)
887
+ tinycache_adapter.write(:join_rule, event.dig(*%i[content join_rule])&.to_sym)
812
888
  end
813
889
 
814
890
  def handle_room_member(event)
815
891
  return unless client.cache == :all
816
892
 
817
- if event[:content][:membership] == 'join'
893
+ if event.dig(*%i[content membership]) == 'join'
818
894
  ensure_member(client.get_user(event[:state_key]).dup.tap do |u|
819
- u.instance_variable_set :@display_name, event[:content][:displayname]
895
+ u.instance_variable_set(:@display_name, event.dig(*%i[content displayname]))
820
896
  end)
821
897
  elsif tinycache_adapter.exist? :joined_members
822
898
  members = tinycache_adapter.read(:joined_members)
@@ -825,20 +901,12 @@ module MatrixSdk
825
901
  end
826
902
 
827
903
  def handle_room_canonical_alias(event)
828
- canonical_alias = tinycache_adapter.write :canonical_alias, event[:content][:alias]
904
+ canonical_alias = tinycache_adapter.write(:canonical_alias, event.dig(*%i[content alias]))
829
905
 
830
906
  data = tinycache_adapter.read(:aliases) || []
831
907
  data << canonical_alias
832
- tinycache_adapter.write(:aliases, data)
833
- end
834
-
835
- def handle_room_aliases(event)
836
- tinycache_adapter.write(:aliases, []) unless tinycache_adapter.exist? :aliases
837
-
838
- aliases = tinycache_adapter.read(:aliases) || []
839
- aliases.concat event[:content][:aliases]
840
-
841
- tinycache_adapter.write(:aliases, aliases)
908
+ data += event.dig(*%i[content alt_aliases]) || []
909
+ tinycache_adapter.write(:aliases, data.uniq.sort)
842
910
  end
843
911
 
844
912
  def room_handlers?
@@ -854,7 +922,6 @@ module MatrixSdk
854
922
  end
855
923
 
856
924
  INTERNAL_HANDLERS = {
857
- 'm.room.aliases' => :handle_room_aliases,
858
925
  'm.room.canonical_alias' => :handle_room_canonical_alias,
859
926
  'm.room.guest_access' => :handle_room_guest_access,
860
927
  'm.room.join_rules' => :handle_room_join_rules,
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MatrixSdk::Rooms
4
+ class Space < MatrixSdk::Room
5
+ TYPE = 'm.space'
6
+
7
+ def tree(suggested_only: nil, max_rooms: nil)
8
+ begin
9
+ data = client.api.request :get, :client_r0, "/rooms/#{id}/spaces", query: {
10
+ suggested_only: suggested_only,
11
+ max_rooms_per_space: max_rooms
12
+ }.compact
13
+ rescue MatrixRequestError
14
+ data = client.api.request :get, :client_unstable, "/org.matrix.msc2946/rooms/#{id}/spaces", query: {
15
+ suggested_only: suggested_only,
16
+ max_rooms_per_space: max_rooms
17
+ }.compact
18
+ end
19
+
20
+ rooms = data.rooms.map do |r|
21
+ next if r[:room_id] == id
22
+
23
+ room = client.ensure_room(r[:room_id])
24
+ room.instance_variable_set :@room_type, r[:room_type] if r.key? :room_type
25
+ room = room.to_space if room.space?
26
+
27
+ # Inject available room information
28
+ r.each do |k, v|
29
+ if room.respond_to?("#{k}_cached?".to_sym) && send("#{k}_cached?".to_sym)
30
+ room.send(:tinycache_adapter).write(k, v)
31
+ elsif room.instance_variable_defined? "@#{k}"
32
+ room.instance_variable_set("@#{k}", v)
33
+ end
34
+ end
35
+ room
36
+ end
37
+ rooms.compact!
38
+
39
+ grouping = {}
40
+ data.events.each do |ev|
41
+ next unless ev[:type] == 'm.space.child'
42
+ next unless ev[:content].key? :via
43
+
44
+ d = (grouping[ev[:room_id]] ||= [])
45
+ d << ev[:state_key]
46
+ end
47
+
48
+ build_tree = proc do |entry|
49
+ next if entry.nil?
50
+
51
+ room = self if entry == id
52
+ room ||= rooms.find { |r| r.id == entry }
53
+ puts "Unable to find room for entry #{entry}" unless room
54
+ # next if room.nil?
55
+
56
+ ret = {
57
+ room => []
58
+ }
59
+
60
+ grouping[entry]&.each do |child|
61
+ if grouping.key?(child)
62
+ ret[room] << build_tree.call(child)
63
+ else
64
+ child_r = self if child == id
65
+ child_r ||= rooms.find { |r| r.id == child }
66
+
67
+ ret[room] << child_r
68
+ end
69
+ end
70
+
71
+ ret[room].compact!
72
+
73
+ ret
74
+ end
75
+
76
+ build_tree.call(id)
77
+ end
78
+ end
79
+ end
@@ -59,7 +59,7 @@ module MatrixSdk
59
59
  # Only works for the current user object, as requested by
60
60
  # client.get_user(:self)
61
61
  #
62
- # @param url [String,URI::MATRIX] the new avatar URL
62
+ # @param url [String,URI::MXC] the new avatar URL
63
63
  # @note Requires a mxc:// URL, check example on
64
64
  # {MatrixSdk::Protocols::CS#set_avatar_url} for how this can be done
65
65
  # @see MatrixSdk::Protocols::CS#set_avatar_url
@@ -74,7 +74,7 @@ module MatrixSdk
74
74
  # @see MatrixSdk::Protocols::CS#get_presence_status
75
75
  # @note This information is not cached in the abstraction layer
76
76
  def presence
77
- raw_presence[:presence].to_sym
77
+ raw_presence[:presence]&.to_sym
78
78
  end
79
79
 
80
80
  # Sets the user's current presence status
@@ -132,8 +132,8 @@ module MatrixSdk
132
132
 
133
133
  # Returns all the current device keys for the user, retrieving them if necessary
134
134
  def device_keys
135
- @device_keys ||= client.api.keys_query(device_keys: { id => [] }).yield_self do |resp|
136
- resp[:device_keys][id.to_sym]
135
+ @device_keys ||= client.api.keys_query(device_keys: { id => [] }).yield_self do |resp| # rubocop:disable Style/ObjectThen # Keep Ruby 2.5 support a little longer
136
+ resp.dig(:device_keys, id.to_sym)
137
137
  end
138
138
  end
139
139
 
@@ -22,13 +22,11 @@ module MatrixSdk
22
22
 
23
23
  def fire(event, filter = nil)
24
24
  reverse_each do |_k, h|
25
- begin
26
- h[:block].call(event) if !h[:filter] || event.matches?(h[:filter], filter)
27
- rescue StandardError => e
28
- logger.error "#{e.class.name} occurred when firing event (#{event})\n#{e}"
25
+ h[:block].call(event) if !h[:filter] || event.matches?(h[:filter], filter)
26
+ rescue StandardError => e
27
+ logger.error "#{e.class.name} occurred when firing event (#{event})\n#{e}"
29
28
 
30
- raise e if @reraise_exceptions
31
- end
29
+ raise e if @reraise_exceptions
32
30
  end
33
31
  end
34
32
  end
@@ -1,19 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'uri'
4
-
5
- module URI
6
- # A mxc:// Matrix content URL
7
- class MATRIX < Generic
8
- def full_path
9
- select(:host, :port, :path, :query, :fragment)
10
- .reject(&:nil?)
11
- .join
12
- end
13
- end
14
-
15
- @@schemes['MXC'] = MATRIX
16
- end
3
+ require 'pp'
17
4
 
18
5
  unless Object.respond_to? :yield_self
19
6
  class Object
@@ -66,13 +53,20 @@ module MatrixSdk
66
53
 
67
54
  def ignore_inspect(*symbols)
68
55
  class_eval %*
69
- def inspect
70
- reentrant = caller_locations.any? { |l| l.absolute_path == __FILE__ && l.label == 'inspect' }
71
- "\#{to_s[0..-2]} \#{instance_variables
56
+ include PP::ObjectMixin
57
+
58
+ def pretty_print_instance_variables
59
+ instance_variables
72
60
  .reject { |f| %i[#{symbols.map { |s| "@#{s}" }.join ' '}].include? f }
73
- .map { |f| "\#{f}=\#{reentrant ? instance_variable_get(f) : instance_variable_get(f).inspect}" }.join " " }}>"
61
+ .sort
62
+ end
63
+
64
+ def pretty_print(pp)
65
+ pp.pp_object(self)
74
66
  end
75
- *, __FILE__, __LINE__ - 7
67
+
68
+ alias inspect pretty_print_inspect
69
+ *, __FILE__, __LINE__ - 14
76
70
  end
77
71
  end
78
72
 
@@ -20,7 +20,7 @@ module MatrixSdk::Util
20
20
 
21
21
  def self.extended(base)
22
22
  helper_name = base.send(:cache_helper_module_name)
23
- base.remove_const(helper_name) if base.const_defined?(helper_name)
23
+ base.send :remove_const, helper_name if base.const_defined?(helper_name)
24
24
  base.prepend base.const_set(helper_name, Module.new)
25
25
 
26
26
  base.include InstanceMethods
@@ -62,23 +62,38 @@ module MatrixSdk::Util
62
62
  method_names = build_method_names(method_name)
63
63
  tinycache_adapter_config[method_name] = {
64
64
  level: cache_level,
65
- expires: expires_in || 1 * 365 * 24 * 60 * 60 # 1 year
65
+ expires: expires_in || (1 * 365 * 24 * 60 * 60) # 1 year
66
66
  }
67
67
 
68
- const_get(cache_helper_module_name).class_eval do
68
+ helper = const_get(cache_helper_module_name)
69
+ return if method_names.any? { |k, _| helper.respond_to? k }
70
+
71
+ helper.class_eval do
69
72
  define_method(method_names[:cache_key]) do |*args|
70
73
  cache_key.call(method_name, args)
71
74
  end
72
75
 
73
76
  define_method(method_names[:with_cache]) do |*args|
74
77
  tinycache_adapter.fetch(__send__(method_names[:cache_key], *args), expires_in: expires_in) do
75
- __send__(method_names[:without_cache], *args)
78
+ named = args.delete_at(-1) if args.last.is_a? Hash
79
+
80
+ if named
81
+ __send__(method_names[:without_cache], *args, **named)
82
+ else
83
+ __send__(method_names[:without_cache], *args)
84
+ end
76
85
  end
77
86
  end
78
87
 
79
88
  define_method(method_names[:without_cache]) do |*args|
80
89
  orig = method(method_name).super_method
81
- orig.call(*args)
90
+ named = args.delete_at(-1) if args.last.is_a? Hash
91
+
92
+ if named
93
+ orig.call(*args, **named)
94
+ else
95
+ orig.call(*args)
96
+ end
82
97
  end
83
98
 
84
99
  define_method(method_names[:clear_cache]) do |*args|
@@ -89,11 +104,19 @@ module MatrixSdk::Util
89
104
  true
90
105
  end
91
106
 
107
+ define_method(method_names[:has_value]) do |*args|
108
+ tinycache_adapter.valid?(__send__(method_names[:cache_key], *args))
109
+ end
110
+
92
111
  define_method(method_name) do |*args|
93
112
  unless_proc = opts[:unless].is_a?(Symbol) ? opts[:unless].to_proc : opts[:unless]
94
113
 
114
+ raise ArgumentError, 'Invalid proc provided (must have arity between 1..3)' if unless_proc && !(1..3).include?(unless_proc.arity)
115
+
95
116
  skip_cache = false
96
- skip_cache ||= unless_proc&.call(self, method_name, args)
117
+ skip_cache ||= unless_proc.call(self, method_name, args) if unless_proc&.arity == 3
118
+ skip_cache ||= unless_proc.call(method_name, args) if unless_proc&.arity == 2
119
+ skip_cache ||= unless_proc.call(args) if unless_proc&.arity == 1
97
120
  skip_cache ||= CACHE_LEVELS[client&.cache || :all] < CACHE_LEVELS[cache_level]
98
121
 
99
122
  if skip_cache
@@ -115,7 +138,8 @@ module MatrixSdk::Util
115
138
  with_cache: "#{method_name}_with_cache#{punctuation}",
116
139
  without_cache: "#{method_name}_without_cache#{punctuation}",
117
140
  clear_cache: "clear_#{method_name}_cache#{punctuation}",
118
- cached: "#{method}_cached?"
141
+ cached: "#{method}_cached?",
142
+ has_value: "#{method}_has_value?"
119
143
  }
120
144
  end
121
145
  end
@@ -16,6 +16,7 @@ module MatrixSdk::Util
16
16
 
17
17
  def write(key, value, expires_in: nil, cache_level: nil)
18
18
  expires_in ||= config.dig(key, :expires)
19
+ expires_in ||= 24 * 60 * 60
19
20
  cache_level ||= client&.cache
20
21
  cache_level ||= :all
21
22
  cache_level = Tinycache::CACHE_LEVELS[cache_level] unless cache_level.is_a? Integer
@@ -30,6 +31,10 @@ module MatrixSdk::Util
30
31
  cache.key?(key)
31
32
  end
32
33
 
34
+ def valid?(key)
35
+ exist?(key) && !cache[key].expired?
36
+ end
37
+
33
38
  def fetch(key, expires_in: nil, cache_level: nil, **_opts)
34
39
  expires_in ||= config.dig(key, :expires)
35
40
  cache_level ||= client&.cache
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ module URI
6
+ # A mxc:// Matrix content URL
7
+ class MXC < Generic
8
+ def full_path
9
+ select(:host, :port, :path, :query, :fragment)
10
+ .compact
11
+ .join
12
+ end
13
+ end
14
+
15
+ # TODO: Use +register_scheme+ on Ruby >=3.1 and fall back to old behavior
16
+ # for older Rubies. May be removed at EOL of Ruby 3.0.
17
+ if respond_to? :register_scheme
18
+ register_scheme 'MXC', MXC
19
+ else
20
+ @@schemes['MXC'] = MXC
21
+ end
22
+
23
+ unless scheme_list.key? 'MATRIX'
24
+ # A matrix: URI according to MSC2312
25
+ class MATRIX < Generic
26
+ attr_reader :authority, :action, :mxid, :mxid2, :via
27
+
28
+ def initialize(*args)
29
+ super(*args)
30
+
31
+ @action = nil
32
+ @authority = nil
33
+ @mxid = nil
34
+ @mxid2 = nil
35
+ @via = nil
36
+
37
+ raise InvalidComponentError, 'missing opaque part for matrix URL' if !@opaque && !@path
38
+
39
+ if @path
40
+ @authority = @host
41
+ @authority += ":#{@port}" if @port
42
+ else
43
+ @path, @query = @opaque.split('?')
44
+ @query, @fragment = @query.split('#') if @query&.include? '#'
45
+ @path, @fragment = @path.split('#') if @path&.include? '#'
46
+ @path = "/#{path}"
47
+ @opaque = nil
48
+ end
49
+
50
+ components = @path.delete_prefix('/').split('/', -1)
51
+ raise InvalidComponentError, 'component count must be 2 or 4' if components.size != 2 && components.size != 4
52
+
53
+ sigil = case components.shift
54
+ when 'u', 'user'
55
+ '@'
56
+ when 'r', 'room'
57
+ '#'
58
+ when 'roomid'
59
+ '!'
60
+ else
61
+ raise InvalidComponentError, 'invalid component in path'
62
+ end
63
+
64
+ component = components.shift
65
+ raise InvalidComponentError, "component can't be empty" if component.nil? || component.empty?
66
+
67
+ @mxid = MatrixSdk::MXID.new("#{sigil}#{component}")
68
+
69
+ if components.size == 2
70
+ sigil2 = case components.shift
71
+ when 'e', 'event'
72
+ '$'
73
+ else
74
+ raise InvalidComponentError, 'invalid component in path'
75
+ end
76
+ component = components.shift
77
+ raise InvalidComponentError, "component can't be empty" if component.nil? || component.empty?
78
+
79
+ @mxid2 = MatrixSdk::MXID.new("#{sigil2}#{component}")
80
+ end
81
+
82
+ return unless @query
83
+
84
+ @action = @query.match(/action=([^&]+)/)&.captures&.first&.to_sym
85
+ @via = @query.scan(/via=([^&]+)/)&.flatten&.compact
86
+ end
87
+
88
+ def mxid2?
89
+ !@mxid2.nil?
90
+ end
91
+ end
92
+
93
+ # TODO: Use +register_scheme+ on Ruby >=3.1 and fall back to old behavior
94
+ # for older Rubies. May be removed at EOL of Ruby 3.0.
95
+ if respond_to? :register_scheme
96
+ register_scheme 'MATRIX', MATRIX
97
+ else
98
+ @@schemes['MATRIX'] = MATRIX
99
+ end
100
+ end
101
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MatrixSdk
4
- VERSION = '2.3.0'
4
+ VERSION = '2.6.0'
5
5
  end