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.
@@ -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 MatrixRequestError
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
@@ -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
@@ -122,10 +122,18 @@ 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|
128
- resp[:device_keys][id.to_sym]
136
+ resp.dig(:device_keys, id.to_sym)
129
137
  end
130
138
  end
131
139
 
@@ -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,134 @@
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
+ raise ArgumentError, 'Invalid proc provided (must have arity between 1..3)' if unless_proc && !(1..3).include?(unless_proc.arity)
103
+
104
+ skip_cache = false
105
+ skip_cache ||= unless_proc.call(self, method_name, args) if unless_proc&.arity == 3
106
+ skip_cache ||= unless_proc.call(method_name, args) if unless_proc&.arity == 2
107
+ skip_cache ||= unless_proc.call(args) if unless_proc&.arity == 1
108
+ skip_cache ||= CACHE_LEVELS[client&.cache || :all] < CACHE_LEVELS[cache_level]
109
+
110
+ if skip_cache
111
+ __send__(method_names[:without_cache], *args)
112
+ else
113
+ __send__(method_names[:with_cache], *args)
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ def build_method_names(method)
120
+ # Clean up method name (split any suffix)
121
+ method_name = method.to_s.sub(/([?!=])$/, '')
122
+ punctuation = Regexp.last_match(-1)
123
+
124
+ {
125
+ cache_key: "#{method_name}_cache_key#{punctuation}",
126
+ with_cache: "#{method_name}_with_cache#{punctuation}",
127
+ without_cache: "#{method_name}_without_cache#{punctuation}",
128
+ clear_cache: "clear_#{method_name}_cache#{punctuation}",
129
+ cached: "#{method}_cached?",
130
+ has_value: "#{method}_has_value?"
131
+ }
132
+ end
133
+ end
134
+ 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
@@ -0,0 +1,89 @@
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
+ .reject(&:nil?)
11
+ .join
12
+ end
13
+ end
14
+
15
+ @@schemes['MXC'] = MXC
16
+
17
+ unless @@schemes.key? 'MATRIX'
18
+ # A matrix: URI according to MSC2312
19
+ class MATRIX < Generic
20
+ attr_reader :authority, :action, :mxid, :mxid2, :via
21
+
22
+ def initialize(*args)
23
+ super(*args)
24
+
25
+ @action = nil
26
+ @authority = nil
27
+ @mxid = nil
28
+ @mxid2 = nil
29
+ @via = nil
30
+
31
+ raise InvalidComponentError, 'missing opaque part for matrix URL' if !@opaque && !@path
32
+
33
+ if @path
34
+ @authority = @host
35
+ @authority += ":#{@port}" if @port
36
+ else
37
+ @path, @query = @opaque.split('?')
38
+ @query, @fragment = @query.split('#') if @query&.include? '#'
39
+ @path, @fragment = @path.split('#') if @path&.include? '#'
40
+ @path = "/#{path}"
41
+ @opaque = nil
42
+ end
43
+
44
+ components = @path.delete_prefix('/').split('/', -1)
45
+ raise InvalidComponentError, 'component count must be 2 or 4' if components.size != 2 && components.size != 4
46
+
47
+ sigil = case components.shift
48
+ when 'u', 'user'
49
+ '@'
50
+ when 'r', 'room'
51
+ '#'
52
+ when 'roomid'
53
+ '!'
54
+ else
55
+ raise InvalidComponentError, 'invalid component in path'
56
+ end
57
+
58
+ component = components.shift
59
+ raise InvalidComponentError, "component can't be empty" if component.nil? || component.empty?
60
+
61
+ @mxid = MatrixSdk::MXID.new("#{sigil}#{component}")
62
+
63
+ if components.size == 2
64
+ sigil2 = case components.shift
65
+ when 'e', 'event'
66
+ '$'
67
+ else
68
+ raise InvalidComponentError, 'invalid component in path'
69
+ end
70
+ component = components.shift
71
+ raise InvalidComponentError, "component can't be empty" if component.nil? || component.empty?
72
+
73
+ @mxid2 = MatrixSdk::MXID.new("#{sigil2}#{component}")
74
+ end
75
+
76
+ return unless @query
77
+
78
+ @action = @query.match(/action=([^&]+)/)&.captures&.first&.to_sym
79
+ @via = @query.scan(/via=([^&]+)/)&.flatten&.compact
80
+ end
81
+
82
+ def mxid2?
83
+ !@mxid2.nil?
84
+ end
85
+ end
86
+
87
+ @@schemes['MATRIX'] = MATRIX
88
+ end
89
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MatrixSdk
4
- VERSION = '2.1.3'
4
+ VERSION = '2.5.0'
5
5
  end
data/lib/matrix_sdk.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'matrix_sdk/extensions'
3
+ require 'matrix_sdk/util/extensions'
4
+ require 'matrix_sdk/util/uri'
4
5
  require 'matrix_sdk/version'
5
6
 
6
7
  require 'json'
@@ -18,10 +19,24 @@ module MatrixSdk
18
19
 
19
20
  autoload :MatrixError, 'matrix_sdk/errors'
20
21
  autoload :MatrixRequestError, 'matrix_sdk/errors'
22
+ autoload :MatrixNotAuthorizedError, 'matrix_sdk/errors'
23
+ autoload :MatrixForbiddenError, 'matrix_sdk/errors'
24
+ autoload :MatrixNotFoundError, 'matrix_sdk/errors'
25
+ autoload :MatrixConflictError, 'matrix_sdk/errors'
26
+ autoload :MatrixTooManyRequestsError, 'matrix_sdk/errors'
21
27
  autoload :MatrixConnectionError, 'matrix_sdk/errors'
22
28
  autoload :MatrixTimeoutError, 'matrix_sdk/errors'
23
29
  autoload :MatrixUnexpectedResponseError, 'matrix_sdk/errors'
24
30
 
31
+ module Rooms
32
+ autoload :Space, 'matrix_sdk/rooms/space'
33
+ end
34
+
35
+ module Util
36
+ autoload :Tinycache, 'matrix_sdk/util/tinycache'
37
+ autoload :TinycacheAdapter, 'matrix_sdk/util/tinycache_adapter'
38
+ end
39
+
25
40
  module Protocols
26
41
  autoload :AS, 'matrix_sdk/protocols/as'
27
42
  autoload :CS, 'matrix_sdk/protocols/cs'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matrix_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.3
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Olofsson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-18 00:00:00.000000000 Z
11
+ date: 2022-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mocha
@@ -95,10 +95,8 @@ files:
95
95
  - README.md
96
96
  - lib/matrix_sdk.rb
97
97
  - lib/matrix_sdk/api.rb
98
- - lib/matrix_sdk/application_service.rb
99
98
  - lib/matrix_sdk/client.rb
100
99
  - lib/matrix_sdk/errors.rb
101
- - lib/matrix_sdk/extensions.rb
102
100
  - lib/matrix_sdk/mxid.rb
103
101
  - lib/matrix_sdk/protocols/as.rb
104
102
  - lib/matrix_sdk/protocols/cs.rb
@@ -107,13 +105,19 @@ files:
107
105
  - lib/matrix_sdk/protocols/ss.rb
108
106
  - lib/matrix_sdk/response.rb
109
107
  - lib/matrix_sdk/room.rb
108
+ - lib/matrix_sdk/rooms/space.rb
110
109
  - lib/matrix_sdk/user.rb
110
+ - lib/matrix_sdk/util/events.rb
111
+ - lib/matrix_sdk/util/extensions.rb
112
+ - lib/matrix_sdk/util/tinycache.rb
113
+ - lib/matrix_sdk/util/tinycache_adapter.rb
114
+ - lib/matrix_sdk/util/uri.rb
111
115
  - lib/matrix_sdk/version.rb
112
116
  homepage: https://github.com/ananace/ruby-matrix-sdk
113
117
  licenses:
114
118
  - MIT
115
119
  metadata: {}
116
- post_install_message:
120
+ post_install_message:
117
121
  rdoc_options: []
118
122
  require_paths:
119
123
  - lib
@@ -128,8 +132,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
132
  - !ruby/object:Gem::Version
129
133
  version: '0'
130
134
  requirements: []
131
- rubygems_version: 3.1.2
132
- signing_key:
135
+ rubygems_version: 3.2.22
136
+ signing_key:
133
137
  specification_version: 4
134
138
  summary: SDK for applications using the Matrix protocol
135
139
  test_files: []