matrix_sdk 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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