matrix_sdk 0.0.4 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39fdb05386da317f2810f25e668ae14bdd261c807bafc61056fa09dd1d6a8175
4
- data.tar.gz: 88b32840ca73b6588f36e35edcccac15e721b071c69a60c6cfc383dceaf67e5f
3
+ metadata.gz: 4cf8d41f930fae4069a51d6fff87e211a790529c5f157ff6402c15b44770f64d
4
+ data.tar.gz: 7c06c98e08232dd24f9667dfbb850a2d12ed2021b2f30cb3ad0cbd2a38e8f6e0
5
5
  SHA512:
6
- metadata.gz: b04d1f07b5d9dd97f3a80a2adffe9a4a29983efc4ee7d1c116c0036233e017d5d9ead02abc9a3048fc9b0ba51c1ee3353fd7ae999a7b5bfc8a41294cd9c1b47c
7
- data.tar.gz: a27b7a3bf9b8c411e6026107d3d0a66cd0e86ac96f6bd6284ccc37ec35e3fd67a94a99b1ad2e67f948dd63003bd4f7495fc66baf3b0d1dc880ee300e1aa49b70
6
+ metadata.gz: dfc1717d3b368fffd50f3fb717a3742bf821f3863e4c493797117f15a724ee4bf15be3b45d2dc1d7971a0206e1f21febf128eef389cdd8fa42274e6da1208530
7
+ data.tar.gz: 1025e4fc9d9f6fe7718971ba3b4c74a0ac35297eb0bb592ad3e25505ec2ec39a36c5d518a835e357b93994db4197066566b17c0696c2298017cdc560e7e7a1ee
data/.rubocop.yml CHANGED
@@ -8,6 +8,9 @@ AllCops:
8
8
  Lint/Void:
9
9
  Enabled: false
10
10
 
11
+ Style/ClassAndModuleChildren:
12
+ Enabled: false
13
+
11
14
  # Don't enforce documentation
12
15
  Style/Documentation:
13
16
  Enabled: false
@@ -18,6 +21,10 @@ Metrics/ClassLength:
18
21
  Metrics/MethodLength:
19
22
  Max: 50
20
23
 
24
+ # Matrix has a lot of methods in the CS API
25
+ Metrics/ModuleLength:
26
+ Max: 500
27
+
21
28
  Metrics/LineLength:
22
29
  Max: 190
23
30
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## v0.1.0 - 2019-05-10
2
+
3
+ - Adds code for handling member lazy load in the client abstraction, and activates it by default
4
+ - Adds methods to read device keys from users
5
+ - Adds basic methods for device handling
6
+ - Restructures the API code to separate protocol implementations
7
+ - Improves the domain discovery code to support all currently specced methods
8
+ - Improves performance in sync calls
9
+ - Started work on an application service prototype, not ready for use yet
10
+ - Testing has been written for large parts of the code
11
+
1
12
  ## v0.0.4 - 2019-02-20
2
13
 
3
14
  - Adds a parameter to the client abstraction to allow retrying syncs on timeouts
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A Ruby gem for easing the development of software that communicates with servers implementing the Matrix protocol.
4
4
 
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
+
5
7
  Live YARD documentation can be found at; http://aleol57.gitlab-pages.liu.se/ruby-matrix-sdk
6
8
 
7
9
  ## Example usage
@@ -16,7 +16,8 @@ ROOM_DISCOVERY_FILTER = {
16
16
  'm.room.aliases',
17
17
  'm.room.canonical_alias',
18
18
  'm.room.member'
19
- ]
19
+ ],
20
+ lazy_load_members: true
20
21
  },
21
22
  timeline: { senders: [], types: [] },
22
23
  account_data: { senders: [], types: [] }
@@ -30,7 +31,8 @@ ROOM_STATE_FILTER = {
30
31
  room: {
31
32
  ephemeral: { senders: [], types: [] },
32
33
  state: {
33
- types: ['m.room.member']
34
+ types: ['m.room.member'],
35
+ lazy_load_members: true
34
36
  },
35
37
  timeline: {
36
38
  types: ['m.room.message']
@@ -93,6 +95,7 @@ if $PROGRAM_NAME == __FILE__
93
95
  raise "Usage: #{$PROGRAM_NAME} [-d] homeserver_url room_id_or_alias" unless ARGV.length >= 2
94
96
  begin
95
97
  if ARGV.first == '-d'
98
+ Thread.abort_on_exception = true
96
99
  MatrixSdk.debug!
97
100
  ARGV.shift
98
101
  end
@@ -111,7 +114,7 @@ if $PROGRAM_NAME == __FILE__
111
114
  # Only retrieve list of joined room in first sync
112
115
  sync_filter = client.sync_filter.merge(ROOM_DISCOVERY_FILTER)
113
116
  sync_filter[:room][:state][:senders] << client.mxid
114
- client.listen_for_events(5, filter: sync_filter.to_json)
117
+ client.listen_for_events(timeout: 5, filter: sync_filter.to_json)
115
118
 
116
119
  puts 'Finding room...'
117
120
  room = client.find_room(ARGV.last)
data/lib/matrix_sdk.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  require 'matrix_sdk/extensions'
2
2
  require 'matrix_sdk/version'
3
3
 
4
+ require 'json'
5
+
4
6
  autoload :Logging, 'logging'
5
7
 
6
8
  module MatrixSdk
7
9
  autoload :Api, 'matrix_sdk/api'
10
+ autoload :ApplicationService, 'matrix_sdk/application_service'
8
11
  autoload :Client, 'matrix_sdk/client'
9
12
  autoload :MXID, 'matrix_sdk/mxid'
10
13
  autoload :Response, 'matrix_sdk/response'
@@ -14,15 +17,23 @@ module MatrixSdk
14
17
  autoload :MatrixError, 'matrix_sdk/errors'
15
18
  autoload :MatrixRequestError, 'matrix_sdk/errors'
16
19
  autoload :MatrixConnectionError, 'matrix_sdk/errors'
20
+ autoload :MatrixTimeoutError, 'matrix_sdk/errors'
17
21
  autoload :MatrixUnexpectedResponseError, 'matrix_sdk/errors'
18
22
 
23
+ module Protocols
24
+ autoload :AS, 'matrix_sdk/protocols/as'
25
+ autoload :CS, 'matrix_sdk/protocols/cs'
26
+ autoload :IS, 'matrix_sdk/protocols/is'
27
+ autoload :SS, 'matrix_sdk/protocols/ss'
28
+ end
29
+
19
30
  def self.debug!
20
31
  logger.level = :debug
21
32
  end
22
33
 
23
34
  def self.logger
24
- @logger ||= Logging.logger[self].tap do |logger|
25
- logger.add_appenders Logging.appenders.stdout
35
+ @logger ||= ::Logging.logger[self].tap do |logger|
36
+ logger.add_appenders ::Logging.appenders.stdout
26
37
  logger.level = :info
27
38
  end
28
39
  end
@@ -1,13 +1,18 @@
1
1
  require 'matrix_sdk'
2
2
 
3
- require 'cgi'
4
- require 'json'
3
+ require 'erb'
5
4
  require 'net/http'
6
5
  require 'openssl'
7
6
  require 'uri'
8
7
 
9
8
  module MatrixSdk
10
9
  class Api
10
+ include MatrixSdk::Logging
11
+ include MatrixSdk::Protocols::AS
12
+ include MatrixSdk::Protocols::CS
13
+ include MatrixSdk::Protocols::IS
14
+ include MatrixSdk::Protocols::CS
15
+
11
16
  USER_AGENT = "Ruby Matrix SDK v#{MatrixSdk::VERSION}".freeze
12
17
  DEFAULT_HEADERS = {
13
18
  'accept' => 'application/json',
@@ -15,12 +20,13 @@ module MatrixSdk
15
20
  }.freeze
16
21
 
17
22
  attr_accessor :access_token, :connection_address, :connection_port, :device_id, :autoretry, :global_headers
18
- attr_reader :homeserver, :validate_certificate, :read_timeout
23
+ attr_reader :homeserver, :validate_certificate, :read_timeout, :protocols, :well_known
19
24
 
20
25
  ignore_inspect :access_token, :logger
21
26
 
22
27
  # @param homeserver [String,URI] The URL to the Matrix homeserver, without the /_matrix/ part
23
28
  # @param params [Hash] Additional parameters on creation
29
+ # @option params [Symbol[]] :protocols The protocols to include (:AS, :CS, :IS, :SS), defaults to :CS
24
30
  # @option params [String] :address The connection address to the homeserver, if different to the HS URL
25
31
  # @option params [Integer] :port The connection port to the homeserver, if different to the HS URL
26
32
  # @option params [String] :access_token The access token to use for the connection
@@ -32,12 +38,17 @@ module MatrixSdk
32
38
  # @option params [Numeric] :read_timeout (240) The timeout in seconds for reading responses
33
39
  # @option params [Hash] :global_headers Additional headers to set for all requests
34
40
  # @option params [Boolean] :skip_login Should the API skip logging in if the HS URL contains user information
41
+ # @option params [Hash] :well_known The .well-known object that the server was discovered through, should not be set manually
35
42
  def initialize(homeserver, params = {})
36
43
  @homeserver = homeserver
37
44
  @homeserver = URI.parse("#{'https://' unless @homeserver.start_with? 'http'}#{@homeserver}") unless @homeserver.is_a? URI
38
45
  @homeserver.path.gsub!(/\/?_matrix\/?/, '') if @homeserver.path =~ /_matrix\/?$/
39
46
  raise 'Please use the base URL for your HS (without /_matrix/)' if @homeserver.path.include? '/_matrix/'
40
47
 
48
+ @protocols = params.fetch(:protocols, %i[CS])
49
+ @protocols = [@protocols] unless @protocols.is_a? Array
50
+ @protocols << :CS if @protocols.include?(:AS) && !@protocols.include?(:CS)
51
+
41
52
  @connection_address = params.fetch(:address, nil)
42
53
  @connection_port = params.fetch(:port, nil)
43
54
  @access_token = params.fetch(:access_token, nil)
@@ -47,10 +58,11 @@ module MatrixSdk
47
58
  @transaction_id = params.fetch(:transaction_id, 0)
48
59
  @backoff_time = params.fetch(:backoff_time, 5000)
49
60
  @read_timeout = params.fetch(:read_timeout, 240)
61
+ @well_known = params.fetch(:well_known, {})
50
62
  @global_headers = DEFAULT_HEADERS.dup
51
63
  @global_headers.merge!(params.fetch(:global_headers)) if params.key? :global_headers
52
64
 
53
- login(user: @homeserver.user, password: @homeserver.password) if @homeserver.user && @homeserver.password && !@access_token && !params[:skip_login]
65
+ login(user: @homeserver.user, password: @homeserver.password) if @homeserver.user && @homeserver.password && !@access_token && !params[:skip_login] && protocol?(:CS)
54
66
  @homeserver.userinfo = '' unless params[:skip_login]
55
67
  end
56
68
 
@@ -66,25 +78,42 @@ module MatrixSdk
66
78
  # # => 443
67
79
  #
68
80
  # @param domain [String] The domain to set up the API connection for, can contain a ':' to denote a port
81
+ # @param target [:client,:identity,:server] The target for the domain lookup
82
+ # @param keep_wellknown [Boolean] Should the .well-known response be kept for further handling
69
83
  # @param params [Hash] Additional options to pass to .new
70
84
  # @return [API] The API connection
71
- def self.new_for_domain(domain, params = {})
72
- # Attempt SRV record discovery
73
- srv = if domain.include? ':'
74
- addr, port = domain.split ':'
75
- Resolv::DNS::Resource::IN::SRV.new 10, 1, port.to_i, addr
76
- else
77
- require 'resolv'
78
- resolver = Resolv::DNS.new
79
- begin
80
- resolver.getresource("_matrix._tcp.#{domain}")
81
- rescue Resolv::ResolvError
82
- nil
83
- end
84
- end
85
-
86
- # Attempt .well-known discovery
87
- if srv.nil?
85
+ def self.new_for_domain(domain, target: :client, keep_wellknown: false, ssl: true, **params)
86
+ domain, port = domain.split(':')
87
+ uri = URI("http#{ssl ? 's' : ''}://#{domain}")
88
+ well_known = nil
89
+ target_uri = nil
90
+
91
+ if !port.nil? && !port.empty?
92
+ target_uri = URI("https://#{domain}:#{port}")
93
+ elsif target == :server
94
+ # Attempt SRV record discovery
95
+ target_uri = begin
96
+ require 'resolv'
97
+ resolver = Resolv::DNS.new
98
+ resolver.getresource("_matrix._tcp.#{domain}")
99
+ rescue StandardError
100
+ nil
101
+ end
102
+
103
+ if target_uri.nil?
104
+ well_known = begin
105
+ data = Net::HTTP.get("https://#{domain}/.well-known/matrix/server")
106
+ JSON.parse(data)
107
+ rescue StandardError
108
+ nil
109
+ end
110
+
111
+ target_uri = well_known['m.server'] if well_known && well_known.key?('m.server')
112
+ else
113
+ target_uri = URI("https://#{target_uri.target}:#{target_uri.port}")
114
+ end
115
+ elsif %i[client identity].include? target
116
+ # Attempt .well-known discovery
88
117
  well_known = begin
89
118
  data = Net::HTTP.get("https://#{domain}/.well-known/matrix/client")
90
119
  JSON.parse(data)
@@ -92,26 +121,31 @@ module MatrixSdk
92
121
  nil
93
122
  end
94
123
 
95
- return new(well_known['m.homeserver']['base_url']) if well_known &&
96
- well_known.key?('m.homeserver') &&
97
- well_known['m.homerserver'].key?('base_url')
124
+ if well_known
125
+ key = 'm.homeserver'
126
+ key = 'm.identity_server' if target == :identity
127
+
128
+ if well_known.key?(key) && well_known[key].key?('base_url')
129
+ uri = URI(well_known[key]['base_url'])
130
+ target_uri = uri
131
+ end
132
+ end
98
133
  end
99
134
 
100
- # Fall back to A record on domain
101
- srv ||= Resolv::DNS::Resource::IN::SRV.new 10, 1, 8448, domain
135
+ # Fall back to direct domain connection
136
+ target_uri ||= URI("https://#{domain}:8448")
137
+
138
+ params[:well_known] = well_known if keep_wellknown
102
139
 
103
- domain = domain.split(':').first if domain.include? ':'
104
- new("https://#{domain}",
140
+ new(uri,
105
141
  params.merge(
106
- address: srv.target.to_s,
107
- port: srv.port
142
+ address: target_uri.host,
143
+ port: target_uri.port
108
144
  ))
109
145
  end
110
146
 
111
- # Gets the logger for the API
112
- # @return [Logging::Logger] The API-scope logger
113
- def logger
114
- @logger ||= Logging.logger[self]
147
+ def protocol?(protocol)
148
+ protocols.include? protocol
115
149
  end
116
150
 
117
151
  # @param seconds [Numeric]
@@ -139,690 +173,6 @@ module MatrixSdk
139
173
  @homeserver = hs_info
140
174
  end
141
175
 
142
- # Gets the available client API versions
143
- # @return [Array]
144
- def client_api_versions
145
- @client_api_versions ||= request(:get, :client, '/versions').versions.tap do |vers|
146
- vers.instance_eval <<-'CODE', __FILE__, __LINE__ + 1
147
- def latest
148
- latest
149
- end
150
- CODE
151
- end
152
- end
153
-
154
- # Gets the list of available unstable client API features
155
- # @return [Array]
156
- def client_api_unstable_features
157
- @client_api_unstable_features ||= request(:get, :client, '/versions').unstable_features.tap do |vers|
158
- vers.instance_eval <<-'CODE', __FILE__, __LINE__ + 1
159
- def has?(feature)
160
- fetch(feature, nil)
161
- end
162
- CODE
163
- end
164
- end
165
-
166
- # Gets the server version
167
- # @note This uses the unstable federation/v1 API
168
- def server_version
169
- Response.new(self, request(:get, :federation_v1, '/version').server).tap do |resp|
170
- resp.instance_eval <<-'CODE', __FILE__, __LINE__ + 1
171
- def to_s
172
- "#{name} #{version}"
173
- end
174
- CODE
175
- end
176
- end
177
-
178
- # Runs the client API /sync method
179
- # @param params [Hash] The sync options to use
180
- # @option params [Numeric] :timeout (30.0) The timeout in seconds for the sync
181
- # @option params :since The value of the batch token to base the sync from
182
- # @option params [String,Hash] :filter The filter to use on the sync
183
- # @option params [Boolean] :full_state Should the sync include the full state
184
- # @option params [Boolean] :set_presence Should the sync set the user status to online
185
- # @return [Response]
186
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#get-matrix-client-r0-sync
187
- # For more information on the parameters and what they mean
188
- def sync(params = {})
189
- query = {
190
- timeout: 30.0
191
- }.merge(params).select do |k, _v|
192
- %i[since timeout filter full_state set_presence].include? k
193
- end
194
-
195
- query[:timeout] = ((query[:timeout] || 30) * 1000).to_i
196
- query[:timeout] = params.delete(:timeout_ms).to_i if params.key? :timeout_ms
197
-
198
- request(:get, :client_r0, '/sync', query: query)
199
- end
200
-
201
- # Registers a user using the client API /register endpoint
202
- #
203
- # @example Regular user registration and login
204
- # api.register(username: 'example', password: 'NotARealPass')
205
- # # => { user_id: '@example:matrix.org', access_token: '...', home_server: 'matrix.org', device_id: 'ABCD123' }
206
- # api.whoami?
207
- # # => { user_id: '@example:matrix.org' }
208
- #
209
- # @param params [Hash] The registration information, all not handled by Ruby will be passed as JSON in the body
210
- # @option params [String,Symbol] :kind ('user') The kind of registration to use
211
- # @option params [Boolean] :store_token (true) Should the resulting access token be stored for the API
212
- # @option params [Boolean] :store_device_id (store_token value) Should the resulting device ID be stored for the API
213
- # @return [Response]
214
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-register
215
- # For options that are permitted in this call
216
- def register(params = {})
217
- kind = params.delete(:kind) { 'user' }
218
- store_token = params.delete(:store_token) { true }
219
- store_device_id = params.delete(:store_device_id) { store_token }
220
-
221
- request(:post, :client_r0, '/register', body: params, query: { kind: kind }).tap do |resp|
222
- @access_token = resp.token if resp.key?(:token) && store_token
223
- @device_id = resp.device_id if resp.key?(:device_id) && store_device_id
224
- end
225
- end
226
-
227
- # Logs in using the client API /login endpoint, and optionally stores the resulting access for API usage
228
- #
229
- # @example Logging in with username and password
230
- # api.login(user: 'example', password: 'NotARealPass')
231
- # # => { user_id: '@example:matrix.org', access_token: '...', home_server: 'matrix.org', device_id: 'ABCD123' }
232
- # api.whoami?
233
- # # => { user_id: '@example:matrix.org' }
234
- #
235
- # @example Advanced login, without storing details
236
- # api.whoami?
237
- # # => { user_id: '@example:matrix.org' }
238
- # api.login(medium: 'email', address: 'someone@somewhere.net', password: '...', store_token: false)
239
- # # => { user_id: '@someone:matrix.org', access_token: ...
240
- # api.whoami?.user_id
241
- # # => '@example:matrix.org'
242
- #
243
- # @param params [Hash] The login information to use, along with options for said log in
244
- # @option params [Boolean] :store_token (true) Should the resulting access token be stored for the API
245
- # @option params [Boolean] :store_device_id (store_token value) Should the resulting device ID be stored for the API
246
- # @option params [String] :login_type ('m.login.password') The type of login to attempt
247
- # @option params [String] :initial_device_display_name (USER_AGENT) The device display name to specify for this login attempt
248
- # @option params [String] :device_id The device ID to set on the login
249
- # @return [Response] A response hash with the parameters :user_id, :access_token, :home_server, and :device_id.
250
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-login
251
- # The Matrix Spec, for more information about the call and response
252
- def login(params = {})
253
- options = {}
254
- options[:store_token] = params.delete(:store_token) { true }
255
- options[:store_device_id] = params.delete(:store_device_id) { options[:store_token] }
256
-
257
- data = {
258
- type: params.delete(:login_type) { 'm.login.password' },
259
- initial_device_display_name: params.delete(:initial_device_display_name) { USER_AGENT }
260
- }.merge params
261
- data[:device_id] = device_id if device_id
262
-
263
- request(:post, :client_r0, '/login', body: data).tap do |resp|
264
- @access_token = resp.token if resp.key?(:token) && options[:store_token]
265
- @device_id = resp.device_id if resp.key?(:device_id) && options[:store_device_id]
266
- end
267
- end
268
-
269
- # Logs out the currently logged in user
270
- # @return [Response] An empty response if the logout was successful
271
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-logout
272
- # The Matrix Spec, for more information about the call and response
273
- def logout
274
- request(:post, :client_r0, '/logout')
275
- end
276
-
277
- # Creates a new room
278
- # @param params [Hash] The room creation details
279
- # @option params [Symbol] :visibility (:public) The room visibility
280
- # @option params [String] :room_alias A room alias to apply on creation
281
- # @option params [Boolean] :invite Should the room be created invite-only
282
- # @return [Response] A response hash with ...
283
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-createroom
284
- # The Matrix Spec, for more information about the call and response
285
- def create_room(params = {})
286
- content = {
287
- visibility: params.fetch(:visibility, :public)
288
- }
289
- content[:room_alias_name] = params[:room_alias] if params[:room_alias]
290
- content[:invite] = [params[:invite]].flatten if params[:invite]
291
-
292
- request(:post, :client_r0, '/createRoom', content)
293
- end
294
-
295
- # Joins a room
296
- # @param id_or_alias [MXID,String] The room ID or Alias to join
297
- # @return [Response] A response hash with the parameter :room_id
298
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#post-matrix-client-r0-join-roomidoralias
299
- # The Matrix Spec, for more information about the call and response
300
- def join_room(id_or_alias)
301
- # id_or_alias = MXID.new id_or_alias.to_s unless id_or_alias.is_a? MXID
302
- # raise ArgumentError, 'Not a room ID or alias' unless id_or_alias.room?
303
-
304
- id_or_alias = CGI.escape id_or_alias.to_s
305
-
306
- request(:post, :client_r0, "/join/#{id_or_alias}")
307
- end
308
-
309
- # Sends a state event to a room
310
- # @param room_id [MXID,String] The room ID to send the state event to
311
- # @param event_type [String] The event type to send
312
- # @param content [Hash] The contents of the state event
313
- # @param params [Hash] Options for the request
314
- # @option params [Integer] :timestamp The timestamp when the event was created, only used for AS events
315
- # @option params [String] :state_key The state key of the event, if there is one
316
- # @return [Response] A response hash with the parameter :event_id
317
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
318
- # https://matrix.org/docs/spec/client_server/r0.3.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype
319
- # The Matrix Spec, for more information about the call and response
320
- def send_state_event(room_id, event_type, content, params = {})
321
- query = {}
322
- query[:ts] = params[:timestamp].to_i if params.key? :timestamp
323
-
324
- room_id = CGI.escape room_id.to_s
325
- event_type = CGI.escape event_type.to_s
326
- state_key = CGI.escape params[:state_key].to_s if params.key? :state_key
327
-
328
- request(:put, :client_r0, "/rooms/#{room_id}/state/#{event_type}#{"/#{state_key}" unless state_key.nil?}", body: content, query: query)
329
- end
330
-
331
- # Sends a message event to a room
332
- # @param room_id [MXID,String] The room ID to send the message event to
333
- # @param event_type [String] The event type of the message
334
- # @param content [Hash] The contents of the message
335
- # @param params [Hash] Options for the request
336
- # @option params [Integer] :timestamp The timestamp when the event was created, only used for AS events
337
- # @option params [Integer] :txn_id The ID of the transaction, or automatically generated
338
- # @return [Response] A response hash with the parameter :event_id
339
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
340
- # The Matrix Spec, for more information about the call and response
341
- def send_message_event(room_id, event_type, content, params = {})
342
- query = {}
343
- query[:ts] = params[:timestamp].to_i if params.key? :timestamp
344
-
345
- txn_id = transaction_id
346
- txn_id = params.fetch(:txn_id, "#{txn_id}#{Time.now.to_i}")
347
-
348
- room_id = CGI.escape room_id.to_s
349
- event_type = CGI.escape event_type.to_s
350
- txn_id = CGI.escape txn_id.to_s
351
-
352
- request(:put, :client_r0, "/rooms/#{room_id}/send/#{event_type}/#{txn_id}", body: content, query: query)
353
- end
354
-
355
- # Redact an event in a room
356
- # @param room_id [MXID,String] The room ID to send the message event to
357
- # @param event_id [String] The event ID of the event to redact
358
- # @param params [Hash] Options for the request
359
- # @option params [Integer] :timestamp The timestamp when the event was created, only used for AS events
360
- # @option params [String] :reason The reason for the redaction
361
- # @option params [Integer] :txn_id The ID of the transaction, or automatically generated
362
- # @return [Response] A response hash with the parameter :event_id
363
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
364
- # The Matrix Spec, for more information about the call and response
365
- def redact_event(room_id, event_id, params = {})
366
- query = {}
367
- query[:ts] = params[:timestamp].to_i if params.key? :timestamp
368
-
369
- content = {}
370
- content[:reason] = params[:reason] if params[:reason]
371
-
372
- txn_id = transaction_id
373
- txn_id = params.fetch(:txn_id, "#{txn_id}#{Time.now.to_i}")
374
-
375
- room_id = CGI.escape room_id.to_s
376
- event_id = CGI.escape event_id.to_s
377
- txn_id = CGI.escape txn_id.to_s
378
-
379
- request(:put, :client_r0, "/rooms/#{room_id}/redact/#{event_id}/#{txn_id}", body: content, query: query)
380
- end
381
-
382
- # Send a content message to a room
383
- #
384
- # @example Sending an image to a room
385
- # send_content('!abcd123:localhost',
386
- # 'mxc://localhost/1234567',
387
- # 'An image of a cat',
388
- # 'm.image',
389
- # extra_information: {
390
- # h: 128,
391
- # w: 128,
392
- # mimetype: 'image/png',
393
- # size: 1024
394
- # })
395
- #
396
- # @example Sending a file to a room
397
- # send_content('!example:localhost',
398
- # 'mxc://localhost/fileurl',
399
- # 'Contract.pdf',
400
- # 'm.file',
401
- # extra_content: {
402
- # filename: 'contract.pdf'
403
- # },
404
- # extra_information: {
405
- # mimetype: 'application/pdf',
406
- # size: 96674
407
- # })
408
- #
409
- # @param room_id [MXID,String] The room ID to send the content to
410
- # @param url [URI,String] The URL to the content
411
- # @param name [String] The name of the content
412
- # @param msg_type [String] The message type of the content
413
- # @param params [Hash] Options for the request
414
- # @option params [Hash] :extra_information ({}) Extra information for the content
415
- # @option params [Hash] :extra_content Extra data to insert into the content hash
416
- # @return [Response] A response hash with the parameter :event_id
417
- # @see send_message_event For more information on the underlying call
418
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#m-image
419
- # https://matrix.org/docs/spec/client_server/r0.3.0.html#m-file
420
- # https://matrix.org/docs/spec/client_server/r0.3.0.html#m-video
421
- # https://matrix.org/docs/spec/client_server/r0.3.0.html#m-audio
422
- # The Matrix Spec, for more information about the call and response
423
- def send_content(room_id, url, name, msg_type, params = {})
424
- content = {
425
- url: url,
426
- msgtype: msg_type,
427
- body: name,
428
- info: params.delete(:extra_information) { {} }
429
- }
430
- content.merge!(params.fetch(:extra_content)) if params.key? :extra_content
431
-
432
- send_message_event(room_id, 'm.room.message', content, params)
433
- end
434
-
435
- # Send a geographic location to a room
436
- #
437
- # @param room_id [MXID,String] The room ID to send the location to
438
- # @param geo_uri [URI,String] The geographical URI to send
439
- # @param name [String] The name of the location
440
- # @param params [Hash] Options for the request
441
- # @option params [Hash] :extra_information ({}) Extra information for the location
442
- # @option params [URI,String] :thumbnail_url The URL to a thumbnail of the location
443
- # @option params [Hash] :thumbnail_info Image information about the location thumbnail
444
- # @return [Response] A response hash with the parameter :event_id
445
- # @see send_message_event For more information on the underlying call
446
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#m-location
447
- # The Matrix Spec, for more information about the call and response
448
- def send_location(room_id, geo_uri, name, params = {})
449
- content = {
450
- geo_uri: geo_uri,
451
- msgtype: 'm.location',
452
- body: name,
453
- info: params.delete(:extra_information) { {} }
454
- }
455
- content[:info][:thumbnail_url] = params.delete(:thumbnail_url) if params.key? :thumbnail_url
456
- content[:info][:thumbnail_info] = params.delete(:thumbnail_info) if params.key? :thumbnail_info
457
-
458
- send_message_event(room_id, 'm.room.message', content, params)
459
- end
460
-
461
- # Send a plaintext message to a room
462
- #
463
- # @param room_id [MXID,String] The room ID to send the message to
464
- # @param message [String] The message to send
465
- # @param params [Hash] Options for the request
466
- # @option params [String] :msg_type ('m.text') The message type to send
467
- # @return [Response] A response hash with the parameter :event_id
468
- # @see send_message_event For more information on the underlying call
469
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#m-text
470
- # The Matrix Spec, for more information about the call and response
471
- def send_message(room_id, message, params = {})
472
- content = {
473
- msgtype: params.delete(:msg_type) { 'm.text' },
474
- body: message
475
- }
476
- send_message_event(room_id, 'm.room.message', content, params)
477
- end
478
-
479
- # Send a plaintext emote to a room
480
- #
481
- # @param room_id [MXID,String] The room ID to send the message to
482
- # @param emote [String] The emote to send
483
- # @param params [Hash] Options for the request
484
- # @option params [String] :msg_type ('m.emote') The message type to send
485
- # @return [Response] A response hash with the parameter :event_id
486
- # @see send_message_event For more information on the underlying call
487
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#m-emote
488
- # The Matrix Spec, for more information about the call and response
489
- def send_emote(room_id, emote, params = {})
490
- content = {
491
- msgtype: params.delete(:msg_type) { 'm.emote' },
492
- body: emote
493
- }
494
- send_message_event(room_id, 'm.room.message', content, params)
495
- end
496
-
497
- # Send a plaintext notice to a room
498
- #
499
- # @param room_id [MXID,String] The room ID to send the message to
500
- # @param notice [String] The notice to send
501
- # @param params [Hash] Options for the request
502
- # @option params [String] :msg_type ('m.notice') The message type to send
503
- # @return [Response] A response hash with the parameter :event_id
504
- # @see send_message_event For more information on the underlying call
505
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#m-notice
506
- # The Matrix Spec, for more information about the call and response
507
- def send_notice(room_id, notice, params = {})
508
- content = {
509
- msgtype: params.delete(:msg_type) { 'm.notice' },
510
- body: notice
511
- }
512
- send_message_event(room_id, 'm.room.message', content, params)
513
- end
514
-
515
- # Retrieve additional messages in a room
516
- #
517
- # @param room_id [MXID,String] The room ID to retrieve messages for
518
- # @param token [String] The token to start retrieving from, can be from a sync or from an earlier get_room_messages call
519
- # @param direction [:b,:f] The direction to retrieve messages
520
- # @param params [Hash] Additional options for the request
521
- # @option params [Integer] :limit (10) The limit of messages to retrieve
522
- # @option params [String] :to A token to limit retrieval to
523
- # @option params [String] :filter A filter to limit the retrieval to
524
- # @return [Response] A response hash with the message information containing :start, :end, and :chunk fields
525
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#get-matrix-client-r0-rooms-roomid-messages
526
- # The Matrix Spec, for more information about the call and response
527
- def get_room_messages(room_id, token, direction, params = {})
528
- query = {
529
- roomId: room_id,
530
- from: token,
531
- dir: direction,
532
- limit: params.fetch(:limit, 10)
533
- }
534
- query[:to] = params[:to] if params.key? :to
535
- query[:filter] = params.fetch(:filter) if params.key? :filter
536
-
537
- room_id = CGI.escape room_id.to_s
538
-
539
- request(:get, :client_r0, "/rooms/#{room_id}/messages", query: query)
540
- end
541
-
542
- # Reads the latest instance of a room state event
543
- #
544
- # @param room_id [MXID,String] The room ID to read from
545
- # @param state_type [String] The state type to read
546
- # @return [Response] A response hash with the contents of the state event
547
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype
548
- # The Matrix Spec, for more information about the call and response
549
- def get_room_state(room_id, state_type)
550
- room_id = CGI.escape room_id.to_s
551
- state_type = CGI.escape state_type.to_s
552
-
553
- request(:get, :client_r0, "/rooms/#{room_id}/state/#{state_type}")
554
- end
555
-
556
- # Gets the display name of a room
557
- #
558
- # @param room_id [MXID,String] The room ID to look up
559
- # @return [Response] A response hash with the parameter :name
560
- # @see get_room_state
561
- # @see https://matrix.org/docs/spec/client_server/r0.3.0.html#m-room-name
562
- # The Matrix Spec, for more information about the event and data
563
- def get_room_name(room_id)
564
- get_room_state(room_id, 'm.room.name')
565
- end
566
-
567
- def set_room_name(room_id, name, params = {})
568
- content = {
569
- name: name
570
- }
571
- send_state_event(room_id, 'm.room.name', content, params)
572
- end
573
-
574
- def get_room_topic(room_id)
575
- get_room_state(room_id, 'm.room.topic')
576
- end
577
-
578
- def set_room_topic(room_id, topic, params = {})
579
- content = {
580
- topic: topic
581
- }
582
- send_state_event(room_id, 'm.room.topic', content, params)
583
- end
584
-
585
- def get_power_levels(room_id)
586
- get_room_state(room_id, 'm.room.power_levels')
587
- end
588
-
589
- def set_power_levels(room_id, content)
590
- content[:events] = {} unless content.key? :events
591
- send_state_event(room_id, 'm.room.power_levels', content)
592
- end
593
-
594
- def leave_room(room_id)
595
- room_id = CGI.escape room_id.to_s
596
-
597
- request(:post, :client_r0, "/rooms/#{room_id}/leave")
598
- end
599
-
600
- def forget_room(room_id)
601
- room_id = CGI.escape room_id.to_s
602
-
603
- request(:post, :client_r0, "/rooms/#{room_id}/forget")
604
- end
605
-
606
- def invite_user(room_id, user_id)
607
- content = {
608
- user_id: user_id
609
- }
610
-
611
- room_id = CGI.escape room_id.to_s
612
-
613
- request(:post, :client_r0, "/rooms/#{room_id}/invite", body: content)
614
- end
615
-
616
- def kick_user(room_id, user_id, params = {})
617
- set_membership(room_id, user_id, 'leave', params)
618
- end
619
-
620
- def get_membership(room_id, user_id)
621
- room_id = CGI.escape room_id.to_s
622
- user_id = CGI.escape user_id.to_s
623
-
624
- request(:get, :client_r0, "/rooms/#{room_id}/state/m.room.member/#{user_id}")
625
- end
626
-
627
- def set_membership(room_id, user_id, membership, params = {})
628
- content = {
629
- membership: membership,
630
- reason: params.delete(:reason) { '' }
631
- }
632
- content[:displayname] = params.delete(:displayname) if params.key? :displayname
633
- content[:avatar_url] = params.delete(:avatar_url) if params.key? :avatar_url
634
-
635
- send_state_event(room_id, 'm.room.member', content, params.merge(state_key: user_id))
636
- end
637
-
638
- def ban_user(room_id, user_id, params = {})
639
- content = {
640
- user_id: user_id,
641
- reason: params[:reason] || ''
642
- }
643
-
644
- room_id = CGI.escape room_id.to_s
645
-
646
- request(:post, :client_r0, "/rooms/#{room_id}/ban", body: content)
647
- end
648
-
649
- def unban_user(room_id, user_id)
650
- content = {
651
- user_id: user_id
652
- }
653
-
654
- room_id = CGI.escape room_id.to_s
655
-
656
- request(:post, :client_r0, "/rooms/#{room_id}/unban", body: content)
657
- end
658
-
659
- def get_user_tags(user_id, room_id)
660
- room_id = CGI.escape room_id.to_s
661
- user_id = CGI.escape user_id.to_s
662
-
663
- request(:get, :client_r0, "/user/#{user_id}/rooms/#{room_id}/tags")
664
- end
665
-
666
- def remove_user_tag(user_id, room_id, tag)
667
- room_id = CGI.escape room_id.to_s
668
- user_id = CGI.escape user_id.to_s
669
- tag = CGI.escape tag.to_s
670
-
671
- request(:delete, :client_r0, "/user/#{user_id}/rooms/#{room_id}/tags/#{tag}")
672
- end
673
-
674
- def add_user_tag(user_id, room_id, tag, params = {})
675
- if params[:body]
676
- content = params[:body]
677
- else
678
- content = {}
679
- content[:order] = params[:order] if params.key? :order
680
- end
681
-
682
- room_id = CGI.escape room_id.to_s
683
- user_id = CGI.escape user_id.to_s
684
- tag = CGI.escape tag.to_s
685
-
686
- request(:put, :client_r0, "/user/#{user_id}/rooms/#{room_id}/tags/#{tag}", body: content)
687
- end
688
-
689
- def get_account_data(user_id, type_key)
690
- user_id = CGI.escape user_id.to_s
691
- type_key = CGI.escape type_key.to_s
692
-
693
- request(:get, :client_r0, "/user/#{user_id}/account_data/#{type_key}")
694
- end
695
-
696
- def set_account_data(user_id, type_key, account_data)
697
- user_id = CGI.escape user_id.to_s
698
- type_key = CGI.escape type_key.to_s
699
-
700
- request(:put, :client_r0, "/user/#{user_id}/account_data/#{type_key}", body: account_data)
701
- end
702
-
703
- def get_room_account_data(user_id, room_id, type_key)
704
- user_id = CGI.escape user_id.to_s
705
- room_id = CGI.escape room_id.to_s
706
- type_key = CGI.escape type_key.to_s
707
-
708
- request(:get, :client_r0, "/user/#{user_id}/rooms/#{room_id}/account_data/#{type_key}")
709
- end
710
-
711
- def set_room_account_data(user_id, room_id, type_key, account_data)
712
- user_id = CGI.escape user_id.to_s
713
- room_id = CGI.escape room_id.to_s
714
- type_key = CGI.escape type_key.to_s
715
-
716
- request(:put, :client_r0, "/user/#{user_id}/rooms/#{room_id}/account_data/#{type_key}", body: account_data)
717
- end
718
-
719
- def get_filter(user_id, filter_id)
720
- user_id = CGI.escape user_id.to_s
721
- filter_id = CGI.escape filter_id.to_s
722
-
723
- request(:get, :client_r0, "/user/#{user_id}/filter/#{filter_id}")
724
- end
725
-
726
- def create_filter(user_id, filter_params)
727
- user_id = CGI.escape user_id.to_s
728
-
729
- request(:post, :client_r0, "/user/#{user_id}/filter", body: filter_params)
730
- end
731
-
732
- def media_upload(content, content_type)
733
- request(:post, :media_r0, '/upload', body: content, headers: { 'content-type' => content_type })
734
- end
735
-
736
- def get_display_name(user_id)
737
- user_id = CGI.escape user_id.to_s
738
-
739
- request(:get, :client_r0, "/profile/#{user_id}/displayname")
740
- end
741
-
742
- def set_display_name(user_id, display_name)
743
- content = {
744
- displayname: display_name
745
- }
746
-
747
- user_id = CGI.escape user_id.to_s
748
-
749
- request(:put, :client_r0, "/profile/#{user_id}/displayname", body: content)
750
- end
751
-
752
- def get_avatar_url(user_id)
753
- user_id = CGI.escape user_id.to_s
754
-
755
- request(:get, :client_r0, "/profile/#{user_id}/avatar_url")
756
- end
757
-
758
- def set_avatar_url(user_id, url)
759
- content = {
760
- avatar_url: url
761
- }
762
-
763
- user_id = CGI.escape user_id.to_s
764
-
765
- request(:put, :client_r0, "/profile/#{user_id}/avatar_url", body: content)
766
- end
767
-
768
- def get_download_url(mxcurl)
769
- mxcurl = URI.parse(mxcurl.to_s) unless mxcurl.is_a? URI
770
- raise 'Not a mxc:// URL' unless mxcurl.is_a? URI::MATRIX
771
-
772
- homeserver.dup.tap do |u|
773
- full_path = CGI.escape mxcurl.full_path.to_s
774
- u.path = "/_matrix/media/r0/download/#{full_path}"
775
- end
776
- end
777
-
778
- def get_room_id(room_alias)
779
- room_alias = CGI.escape room_alias.to_s
780
-
781
- request(:get, :client_r0, "/directory/room/#{room_alias}")
782
- end
783
-
784
- def set_room_alias(room_id, room_alias)
785
- content = {
786
- room_id: room_id
787
- }
788
-
789
- room_alias = CGI.escape room_alias.to_s
790
-
791
- request(:put, :client_r0, "/directory/room/#{room_alias}", body: content)
792
- end
793
-
794
- def remove_room_alias(room_alias)
795
- room_alias = CGI.escape room_alias.to_s
796
-
797
- request(:delete, :client_r0, "/directory/room/#{room_alias}")
798
- end
799
-
800
- def get_room_members(room_id)
801
- room_id = CGI.escape room_id.to_s
802
-
803
- request(:get, :client_r0, "/rooms/#{room_id}/members")
804
- end
805
-
806
- def set_join_rule(room_id, join_rule)
807
- content = {
808
- join_rule: join_rule
809
- }
810
-
811
- send_state_event(room_id, 'm.room.join_rules', content)
812
- end
813
-
814
- def set_guest_access(room_id, guest_access)
815
- # raise ArgumentError, '`guest_access` must be one of [:can_join, :forbidden]' unless %i[can_join forbidden].include? guest_access
816
- content = {
817
- guest_access: guest_access
818
- }
819
- send_state_event(room_id, 'm.room.guest_access', content)
820
- end
821
-
822
- def whoami?
823
- request(:get, :client_r0, '/account/whoami')
824
- end
825
-
826
176
  def request(method, api, path, options = {})
827
177
  url = homeserver.dup.tap do |u|
828
178
  u.path = api_to_path(api) + path
@@ -891,8 +241,8 @@ module MatrixSdk
891
241
  clean_body = JSON.parse(http.body) rescue nil if http.body
892
242
  clean_body.keys.each { |k| clean_body[k] = '[ REDACTED ]' if %w[password access_token].include?(k) }.to_json if clean_body
893
243
  logger.debug "#{dir} #{clean_body.length < 200 ? clean_body : clean_body.slice(0..200) + "... [truncated, #{clean_body.length} Bytes]"}" if clean_body
894
- rescue StandardError => ex
895
- logger.warn "#{ex.class} occured while printing request debug; #{ex.message}\n#{ex.backtrace.join "\n"}"
244
+ rescue StandardError => e
245
+ logger.warn "#{e.class} occured while printing request debug; #{e.message}\n#{e.backtrace.join "\n"}"
896
246
  end
897
247
 
898
248
  def transaction_id