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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +1 -1
- data/lib/matrix_sdk.rb +11 -1
- data/lib/matrix_sdk/api.rb +84 -59
- data/lib/matrix_sdk/client.rb +80 -57
- data/lib/matrix_sdk/errors.rb +4 -0
- data/lib/matrix_sdk/mxid.rb +50 -15
- data/lib/matrix_sdk/protocols/cs.rb +61 -37
- data/lib/matrix_sdk/protocols/msc.rb +1 -1
- data/lib/matrix_sdk/response.rb +3 -1
- data/lib/matrix_sdk/room.rb +361 -112
- data/lib/matrix_sdk/rooms/space.rb +79 -0
- data/lib/matrix_sdk/user.rb +9 -1
- data/lib/matrix_sdk/util/events.rb +111 -0
- data/lib/matrix_sdk/util/extensions.rb +75 -0
- data/lib/matrix_sdk/util/tinycache.rb +130 -0
- data/lib/matrix_sdk/util/tinycache_adapter.rb +77 -0
- data/lib/matrix_sdk/util/uri.rb +89 -0
- data/lib/matrix_sdk/version.rb +1 -1
- metadata +12 -8
- data/lib/matrix_sdk/application_service.rb +0 -212
- data/lib/matrix_sdk/extensions.rb +0 -197
@@ -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
|
data/lib/matrix_sdk/user.rb
CHANGED
@@ -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::
|
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
|