matrix_sdk 2.1.2 → 2.4.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.
@@ -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_unstable, "/org.matrix.msc2946/rooms/#{id}/spaces", query: {
10
+ suggested_only: suggested_only,
11
+ max_rooms_per_space: max_rooms
12
+ }.compact
13
+ rescue
14
+ data = client.api.request :get, :client_r0, "/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
@@ -122,6 +122,14 @@ module MatrixSdk
122
122
  Time.now - (since / 1000)
123
123
  end
124
124
 
125
+ # Gets a direct message room with the user if one exists
126
+ #
127
+ # @return [Room,nil] A direct message room if one exists
128
+ # @see MatrixSdk::Client#direct_room
129
+ def direct_room
130
+ client.direct_room(id)
131
+ end
132
+
125
133
  # Returns all the current device keys for the user, retrieving them if necessary
126
134
  def device_keys
127
135
  @device_keys ||= client.api.keys_query(device_keys: { id => [] }).yield_self do |resp|
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MatrixSdk
4
+ class EventHandlerArray < Hash
5
+ include MatrixSdk::Logging
6
+ attr_accessor :reraise_exceptions
7
+
8
+ def initialize(*args)
9
+ @reraise_exceptions = false
10
+
11
+ super(*args)
12
+ end
13
+
14
+ def add_handler(filter = nil, id = nil, &block)
15
+ id ||= block.hash
16
+ self[id] = { filter: filter, id: id, block: block }
17
+ end
18
+
19
+ def remove_handler(id)
20
+ delete id
21
+ end
22
+
23
+ def fire(event, filter = nil)
24
+ reverse_each do |_k, h|
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}"
28
+
29
+ raise e if @reraise_exceptions
30
+ end
31
+ end
32
+ end
33
+
34
+ class Event
35
+ extend MatrixSdk::Extensions
36
+
37
+ attr_writer :handled
38
+
39
+ ignore_inspect :sender
40
+
41
+ def initialize(sender)
42
+ @sender = sender
43
+ @handled = false
44
+ end
45
+
46
+ def handled?
47
+ @handled
48
+ end
49
+
50
+ def matches?(_filter)
51
+ true
52
+ end
53
+ end
54
+
55
+ class ErrorEvent < Event
56
+ attr_accessor :error
57
+
58
+ def initialize(error, source)
59
+ @error = error
60
+ super source
61
+ end
62
+
63
+ def source
64
+ @sender
65
+ end
66
+ end
67
+
68
+ class MatrixEvent < Event
69
+ attr_accessor :event, :filter
70
+ alias data event
71
+
72
+ ignore_inspect :sender
73
+
74
+ def initialize(sender, event = nil, filter = nil)
75
+ @event = event
76
+ @filter = filter || @event[:type]
77
+ super sender
78
+ end
79
+
80
+ def matches?(filter, filter_override = nil)
81
+ return true if filter_override.nil? && (@filter.nil? || filter.nil?)
82
+
83
+ to_match = filter_override || @filter
84
+ if filter.is_a? Regexp
85
+ filter.match(to_match) { true } || false
86
+ else
87
+ to_match == filter
88
+ end
89
+ end
90
+
91
+ def [](key)
92
+ event[key]
93
+ end
94
+
95
+ def to_s
96
+ "#{event[:type]}: #{event.reject { |k, _v| k == :type }.to_json}"
97
+ end
98
+
99
+ def method_missing(method, *args)
100
+ return event[method] if event.key? method
101
+
102
+ super
103
+ end
104
+
105
+ def respond_to_missing?(method, *)
106
+ return true if event.key? method
107
+
108
+ super
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless Object.respond_to? :yield_self
4
+ class Object
5
+ def yield_self
6
+ yield(self)
7
+ end
8
+ end
9
+ end
10
+
11
+ module MatrixSdk
12
+ module Extensions
13
+ def events(*symbols)
14
+ module_name = "#{name}Events"
15
+
16
+ initializers = []
17
+ readers = []
18
+ methods = []
19
+
20
+ symbols.each do |sym|
21
+ name = sym.to_s
22
+
23
+ initializers << "
24
+ @on_#{name} = MatrixSdk::EventHandlerArray.new
25
+ "
26
+ readers << ":on_#{name}"
27
+ methods << "
28
+ def fire_#{name}(ev, filter = nil)
29
+ @on_#{name}.fire(ev, filter)
30
+ when_#{name}(ev) if !ev.handled?
31
+ end
32
+
33
+ def when_#{name}(ev); end
34
+ "
35
+ end
36
+
37
+ class_eval "
38
+ module #{module_name}
39
+ attr_reader #{readers.join ', '}
40
+
41
+ def event_initialize
42
+ #{initializers.join}
43
+ end
44
+
45
+ #{methods.join}
46
+ end
47
+
48
+ include #{module_name}
49
+ ", __FILE__, __LINE__ - 12
50
+ end
51
+
52
+ def ignore_inspect(*symbols)
53
+ class_eval %*
54
+ def inspect
55
+ reentrant = caller_locations.any? { |l| l.absolute_path == __FILE__ && l.label == 'inspect' }
56
+ "\\\#<\#{self.class} \#{instance_variables
57
+ .reject { |f| %i[#{symbols.map { |s| "@#{s}" }.join ' '}].include? f }
58
+ .map { |f| "\#{f}=\#{reentrant ? instance_variable_get(f) : instance_variable_get(f).inspect}" }.join " " }}>"
59
+ end
60
+ *, __FILE__, __LINE__ - 7
61
+ end
62
+ end
63
+
64
+ module Logging
65
+ def logger
66
+ return MatrixSdk.logger if MatrixSdk.global_logger?
67
+
68
+ @logger ||= ::Logging.logger[self]
69
+ end
70
+
71
+ def logger=(logger)
72
+ @logger = logger
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'matrix_sdk/util/tinycache_adapter'
4
+
5
+ module MatrixSdk::Util
6
+ module Tinycache
7
+ CACHE_LEVELS = {
8
+ none: 0,
9
+ some: 1,
10
+ all: 2
11
+ }.freeze
12
+
13
+ def self.adapter
14
+ @adapter ||= TinycacheAdapter
15
+ end
16
+
17
+ def self.adapter=(adapter)
18
+ @adapter = adapter
19
+ end
20
+
21
+ def self.extended(base)
22
+ helper_name = base.send(:cache_helper_module_name)
23
+ base.send :remove_const, helper_name if base.const_defined?(helper_name)
24
+ base.prepend base.const_set(helper_name, Module.new)
25
+
26
+ base.include InstanceMethods
27
+ end
28
+
29
+ def cached(*methods, **opts)
30
+ methods.each { |method| build_cache_methods(method, **opts) }
31
+ end
32
+
33
+ module InstanceMethods
34
+ def tinycache_adapter
35
+ @tinycache_adapter ||= Tinycache.adapter.new.tap do |adapter|
36
+ adapter.config = self.class.tinycache_adapter_config if adapter.respond_to? :config=
37
+ adapter.client = client if respond_to?(:client) && adapter.respond_to?(:client=)
38
+ end
39
+ end
40
+ end
41
+
42
+ def tinycache_adapter_config
43
+ @tinycache_adapter_config ||= {}
44
+ end
45
+
46
+ private
47
+
48
+ def default_cache_key
49
+ proc do |method_name, _method_args|
50
+ method_name.to_sym
51
+ end
52
+ end
53
+
54
+ def cache_helper_module_name
55
+ class_name = name&.gsub(/:/, '') || to_s.gsub(/[^a-zA-Z_0-9]/, '')
56
+ "#{class_name}Tinycache"
57
+ end
58
+
59
+ def build_cache_methods(method_name, cache_key: default_cache_key, cache_level: :none, expires_in: nil, **opts)
60
+ raise ArgumentError, 'Cache key must be a three-arg proc' unless cache_key.is_a? Proc
61
+
62
+ method_names = build_method_names(method_name)
63
+ tinycache_adapter_config[method_name] = {
64
+ level: cache_level,
65
+ expires: expires_in || 1 * 365 * 24 * 60 * 60 # 1 year
66
+ }
67
+
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
72
+ define_method(method_names[:cache_key]) do |*args|
73
+ cache_key.call(method_name, args)
74
+ end
75
+
76
+ define_method(method_names[:with_cache]) do |*args|
77
+ tinycache_adapter.fetch(__send__(method_names[:cache_key], *args), expires_in: expires_in) do
78
+ __send__(method_names[:without_cache], *args)
79
+ end
80
+ end
81
+
82
+ define_method(method_names[:without_cache]) do |*args|
83
+ orig = method(method_name).super_method
84
+ orig.call(*args)
85
+ end
86
+
87
+ define_method(method_names[:clear_cache]) do |*args|
88
+ tinycache_adapter.delete(__send__(method_names[:cache_key], *args))
89
+ end
90
+
91
+ define_method(method_names[:cached]) do
92
+ true
93
+ end
94
+
95
+ define_method(method_names[:has_value]) do |*args|
96
+ tinycache_adapter.valid?(__send__(method_names[:cache_key], *args))
97
+ end
98
+
99
+ define_method(method_name) do |*args|
100
+ unless_proc = opts[:unless].is_a?(Symbol) ? opts[:unless].to_proc : opts[:unless]
101
+
102
+ skip_cache = false
103
+ skip_cache ||= unless_proc&.call(self, method_name, args)
104
+ skip_cache ||= CACHE_LEVELS[client&.cache || :all] < CACHE_LEVELS[cache_level]
105
+
106
+ if skip_cache
107
+ __send__(method_names[:without_cache], *args)
108
+ else
109
+ __send__(method_names[:with_cache], *args)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def build_method_names(method)
116
+ # Clean up method name (split any suffix)
117
+ method_name = method.to_s.sub(/([?!=])$/, '')
118
+ punctuation = Regexp.last_match(-1)
119
+
120
+ {
121
+ cache_key: "#{method_name}_cache_key#{punctuation}",
122
+ with_cache: "#{method_name}_with_cache#{punctuation}",
123
+ without_cache: "#{method_name}_without_cache#{punctuation}",
124
+ clear_cache: "clear_#{method_name}_cache#{punctuation}",
125
+ cached: "#{method}_cached?",
126
+ has_value: "#{method}_has_value?"
127
+ }
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MatrixSdk::Util
4
+ class TinycacheAdapter
5
+ attr_accessor :config, :client
6
+
7
+ def initialize
8
+ @config = {}
9
+
10
+ clear
11
+ end
12
+
13
+ def read(key)
14
+ cache[key]&.value
15
+ end
16
+
17
+ def write(key, value, expires_in: nil, cache_level: nil)
18
+ expires_in ||= config.dig(key, :expires)
19
+ expires_in ||= 24 * 60 * 60
20
+ cache_level ||= client&.cache
21
+ cache_level ||= :all
22
+ cache_level = Tinycache::CACHE_LEVELS[cache_level] unless cache_level.is_a? Integer
23
+
24
+ return value if cache_level < Tinycache::CACHE_LEVELS[config.dig(key, :level) || :none]
25
+
26
+ cache[key] = Value.new(value, Time.now, Time.now + expires_in)
27
+ value
28
+ end
29
+
30
+ def exist?(key)
31
+ cache.key?(key)
32
+ end
33
+
34
+ def valid?(key)
35
+ exist?(key) && !cache[key].expired?
36
+ end
37
+
38
+ def fetch(key, expires_in: nil, cache_level: nil, **_opts)
39
+ expires_in ||= config.dig(key, :expires)
40
+ cache_level ||= client&.cache
41
+ cache_level ||= :all
42
+ cache_level = Tinycache::CACHE_LEVELS[cache_level]
43
+
44
+ return read(key) if exist?(key) && !cache[key].expired?
45
+
46
+ value = yield
47
+ write(key, value, expires_in: expires_in, cache_level: cache_level)
48
+ end
49
+
50
+ def delete(key)
51
+ return false unless exist?(key)
52
+
53
+ cache.delete key
54
+ true
55
+ end
56
+
57
+ def clear
58
+ @cache = {}
59
+ end
60
+
61
+ def cleanup
62
+ @cache.delete_if { |_, v| v.expired? }
63
+ end
64
+
65
+ private
66
+
67
+ Value = Struct.new(:value, :timestamp, :expires_at) do
68
+ def expired?
69
+ return false if expires_at.nil?
70
+
71
+ Time.now > expires_at
72
+ end
73
+ end
74
+
75
+ attr_reader :cache
76
+ end
77
+ end