Class: MatrixSdk::Client
Instance Attribute Summary collapse
-
#api ⇒ Api
readonly
The underlying API connection.
-
#cache ⇒ :all, ...
The cache level.
-
#next_batch ⇒ Object
readonly
Returns the value of attribute next_batch.
-
#sync_filter ⇒ Hash, String
The global sync filter.
Class Method Summary collapse
-
.new_for_domain(domain, **params) ⇒ Client
Create a new client instance from only a Matrix HS domain.
Instance Method Summary collapse
-
#create_room(room_alias = nil, **params) ⇒ Room
Creates a new room.
-
#direct_room(mxid) ⇒ Room?
Gets a direct message room for the given user if one exists.
-
#direct_rooms ⇒ Hash[String,Array[String]]
Gets a list of all direct chat rooms (1:1 chats / direct message chats) for the currenct user.
-
#ensure_room(room_id) ⇒ Room
Ensures that a room exists in the cache.
-
#find_room(room_id_or_alias, only_canonical: false) ⇒ Room?
Find a room in the locally cached list of rooms that the current user is part of.
-
#get_user(user_id) ⇒ User
Get a User instance from a MXID.
-
#initialize(hs_url, client_cache: :all, **params) ⇒ Client
constructor
A new instance of Client.
-
#join_room(room_id_or_alias, server_name: []) ⇒ Room
Joins an already created room.
- #listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 0, **params) ⇒ Object
-
#listening? ⇒ Boolean
Check if there’s a thread listening for events.
-
#logged_in? ⇒ Boolean
Check if there’s a currently logged in session.
-
#login(username, password, sync_timeout: 15, full_state: false, **params) ⇒ Object
Logs in as a user on the connected HS.
-
#login_with_token(username, token, sync_timeout: 15, full_state: false, **params) ⇒ Object
Logs in as a user on the connected HS.
-
#logout ⇒ Object
Logs out of the current session.
-
#mxid ⇒ MXID
(also: #user_id)
Gets the currently logged in user’s MXID.
-
#presence ⇒ Response
Gets the current user presence status object.
-
#public_rooms ⇒ Array[Room]
Gets a list of all the public rooms on the connected HS.
-
#register_as_guest ⇒ Object
Register - and log in - on the connected HS as a guest.
-
#register_with_password(username, password, **params) ⇒ Object
Register a new user account on the connected HS.
-
#registered_3pids ⇒ Response
Retrieve a list of all registered third-party IDs for the current user.
-
#reload_rooms! ⇒ Boolean
(also: #refresh_rooms!, #reload_spaces!)
Refresh the list of currently handled rooms, replacing it with the user’s currently joined rooms.
-
#remove_room_alias(room_alias) ⇒ Object
Remove a room alias.
-
#rooms ⇒ Array[Room]
Gets a list of all relevant rooms, either the ones currently handled by the client, or the list of currently joined ones if no rooms are handled.
-
#set_presence(status, message: nil) ⇒ Object
Sets the current user’s presence status.
-
#spaces ⇒ Array[Room]
Get a list of all joined Matrix Spaces.
-
#start_listener_thread(**params) ⇒ Object
Starts a background thread that will listen to new events.
-
#stop_listener_thread ⇒ Object
Stops the running background thread if one is active.
-
#sync(skip_store_batch: false, **params) ⇒ Object
(also: #listen_for_events)
Run a message sync round, triggering events as necessary.
-
#upload(content, content_type) ⇒ URI::MXC
Upload a piece of data to the media repo.
Methods included from Extensions
Methods included from Logging
Constructor Details
#initialize(hs_url, client_cache: :all, **params) ⇒ Client
Returns a new instance of Client.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/matrix_sdk/client.rb', line 60 def initialize(hs_url, client_cache: :all, **params) event_initialize params[:user_id] ||= params[:mxid] if params[:mxid] if hs_url.is_a? Api @api = hs_url params.each do |k, v| api.instance_variable_set("@#{k}", v) if api.instance_variable_defined? "@#{k}" end else @api = Api.new hs_url, **params end @cache = client_cache @identity_server = nil @mxid = nil @sync_token = nil @sync_thread = nil @sync_filter = { room: { timeline: { limit: params.fetch(:sync_filter_limit, 20) }, state: { lazy_load_members: true } } } @next_batch = nil @bad_sync_timeout_limit = 60 * 60 params.each do |k, v| instance_variable_set("@#{k}", v) if instance_variable_defined? "@#{k}" end @rooms = {} @room_handlers = {} @users = {} @should_listen = false raise ArgumentError, 'Cache value must be one of of [:all, :some, :none]' unless %i[all some none].include? @cache return unless params[:user_id] @mxid = params[:user_id] end |
Instance Attribute Details
#api ⇒ Api (readonly)
The underlying API connection
24 25 26 |
# File 'lib/matrix_sdk/client.rb', line 24 def api @api end |
#cache ⇒ :all, ...
The cache level
24 |
# File 'lib/matrix_sdk/client.rb', line 24 attr_reader :api, :next_batch |
#next_batch ⇒ Object (readonly)
Returns the value of attribute next_batch.
24 |
# File 'lib/matrix_sdk/client.rb', line 24 attr_reader :api, :next_batch |
#sync_filter ⇒ Hash, String
The global sync filter
24 |
# File 'lib/matrix_sdk/client.rb', line 24 attr_reader :api, :next_batch |
Class Method Details
.new_for_domain(domain, **params) ⇒ Client
This method will not verify that the created client has a valid connection, it will only perform the necessary lookups to build a connection URL.
Create a new client instance from only a Matrix HS domain
This will use the well-known delegation lookup to find the correct client URL
46 47 48 49 50 51 52 |
# File 'lib/matrix_sdk/client.rb', line 46 def self.new_for_domain(domain, **params) api = MatrixSdk::Api.new_for_domain(domain, keep_wellknown: true) return new(api, params) unless api.well_known&.key?('m.identity_server') identity_server = MatrixSdk::Api.new(api.well_known['m.identity_server']['base_url'], protocols: %i[IS]) new(api, params.merge(identity_server: identity_server)) end |
Instance Method Details
#create_room(room_alias = nil, **params) ⇒ Room
Creates a new room
375 376 377 378 |
# File 'lib/matrix_sdk/client.rb', line 375 def create_room(room_alias = nil, **params) data = api.create_room(**params.merge(room_alias: room_alias)) ensure_room(data.room_id) end |
#direct_room(mxid) ⇒ Room?
Will return the oldest room if multiple exist
Gets a direct message room for the given user if one exists
171 172 173 174 175 176 177 |
# File 'lib/matrix_sdk/client.rb', line 171 def direct_room(mxid) mxid = MatrixSdk::MXID.new mxid.to_s unless mxid.is_a? MatrixSdk::MXID raise ArgumentError, 'Must be a valid user ID' unless mxid.user? room_id = direct_rooms[mxid.to_s]&.first ensure_room room_id if room_id end |
#direct_rooms ⇒ Hash[String,Array[String]]
Gets a list of all direct chat rooms (1:1 chats / direct message chats) for the currenct user
163 164 165 |
# File 'lib/matrix_sdk/client.rb', line 163 def direct_rooms api.get_account_data(mxid, 'm.direct').transform_keys(&:to_s) end |
#ensure_room(room_id) ⇒ Room
Ensures that a room exists in the cache
550 551 552 553 554 555 556 557 558 559 560 561 562 563 |
# File 'lib/matrix_sdk/client.rb', line 550 def ensure_room(room_id) room_id = MXID.new room_id.to_s unless room_id.is_a? MXID raise ArgumentError, 'Must be a room ID' unless room_id.room_id? room_id = room_id.to_s ret = @rooms.fetch(room_id) do room = Room.new(self, room_id) @rooms[room_id] = room unless cache == :none room end # Need to figure out a way to handle multiple types ret = @rooms[room_id] = ret.to_space if ret.instance_variable_get :@room_type ret end |
#find_room(room_id_or_alias, only_canonical: false) ⇒ Room?
Find a room in the locally cached list of rooms that the current user is part of
398 399 400 401 402 403 404 405 406 407 |
# File 'lib/matrix_sdk/client.rb', line 398 def find_room(room_id_or_alias, only_canonical: false) room_id_or_alias = MXID.new(room_id_or_alias.to_s) unless room_id_or_alias.is_a? MXID raise ArgumentError, 'Must be a room id or alias' unless room_id_or_alias.room? return @rooms.fetch(room_id_or_alias.to_s, nil) if room_id_or_alias.room_id? return @rooms.values.find { |r| r.canonical_alias == room_id_or_alias.to_s } if only_canonical @rooms.values.find { |r| r.aliases.include? room_id_or_alias.to_s } end |
#get_user(user_id) ⇒ User
The method doesn’t perform any existence checking, so the returned User object may point to a non-existent user
Get a User instance from a MXID
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/matrix_sdk/client.rb', line 415 def get_user(user_id) user_id = mxid if user_id == :self user_id = MXID.new user_id.to_s unless user_id.is_a? MXID raise ArgumentError, 'Must be a User ID' unless user_id.user? # To still use regular string storage in the hash itself user_id = user_id.to_s if cache == :all @users[user_id] ||= User.new(self, user_id) else User.new(self, user_id) end end |
#join_room(room_id_or_alias, server_name: []) ⇒ Room
Joins an already created room
386 387 388 389 390 |
# File 'lib/matrix_sdk/client.rb', line 386 def join_room(room_id_or_alias, server_name: []) server_name = [server_name] unless server_name.is_a? Array data = api.join_room(room_id_or_alias, server_name: server_name) ensure_room(data.fetch(:room_id, room_id_or_alias)) end |
#listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 0, **params) ⇒ Object
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 |
# File 'lib/matrix_sdk/client.rb', line 565 def listen_forever(timeout: 30, bad_sync_timeout: 5, sync_interval: 0, **params) orig_bad_sync_timeout = bad_sync_timeout + 0 while @should_listen begin sync(**params.merge(timeout: timeout)) bad_sync_timeout = orig_bad_sync_timeout sleep(sync_interval) if sync_interval.positive? rescue MatrixRequestError => e logger.warn("A #{e.class} occurred during sync") if e.httpstatus >= 500 logger.warn("Serverside error, retrying in #{bad_sync_timeout} seconds...") sleep(bad_sync_timeout) if bad_sync_timeout.positive? # rubocop:disable Metrics/BlockNesting bad_sync_timeout = [bad_sync_timeout * 2, @bad_sync_timeout_limit].min end end end rescue StandardError => e logger.error "Unhandled #{e.class} raised in background listener" logger.error [e., *e.backtrace].join($RS) fire_error(ErrorEvent.new(e, :listener_thread)) end |
#listening? ⇒ Boolean
Check if there’s a thread listening for events
508 509 510 |
# File 'lib/matrix_sdk/client.rb', line 508 def listening? @sync_thread&.alive? == true end |
#logged_in? ⇒ Boolean
This will not check if the session is valid, only if it exists
Check if there’s a currently logged in session
328 329 330 |
# File 'lib/matrix_sdk/client.rb', line 328 def logged_in? !@api.access_token.nil? end |
#login(username, password, sync_timeout: 15, full_state: false, **params) ⇒ Object
Logs in as a user on the connected HS
This will also trigger an initial sync unless no_sync is set
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/matrix_sdk/client.rb', line 272 def login(username, password, sync_timeout: 15, full_state: false, **params) username = username.to_s unless username.is_a?(String) password = password.to_s unless password.is_a?(String) raise ArgumentError, "Username can't be nil or empty" if username.nil? || username.empty? raise ArgumentError, "Password can't be nil or empty" if password.nil? || password.empty? data = api.login(user: username, password: password) post_authentication(data) return if params[:no_sync] sync timeout: sync_timeout, full_state: full_state, allow_sync_retry: params.fetch(:allow_sync_retry, nil) end |
#login_with_token(username, token, sync_timeout: 15, full_state: false, **params) ⇒ Object
Logs in as a user on the connected HS
This will also trigger an initial sync unless no_sync is set
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/matrix_sdk/client.rb', line 300 def login_with_token(username, token, sync_timeout: 15, full_state: false, **params) username = username.to_s unless username.is_a?(String) token = token.to_s unless token.is_a?(String) raise ArgumentError, "Username can't be nil or empty" if username.nil? || username.empty? raise ArgumentError, "Token can't be nil or empty" if token.nil? || token.empty? data = api.login(user: username, token: token, type: 'm.login.token') post_authentication(data) return if params[:no_sync] sync timeout: sync_timeout, full_state: full_state, allow_sync_retry: params.fetch(:allow_sync_retry, nil) end |
#logout ⇒ Object
Logs out of the current session
318 319 320 321 322 |
# File 'lib/matrix_sdk/client.rb', line 318 def logout api.logout @api.access_token = nil @mxid = nil end |
#mxid ⇒ MXID Also known as: user_id
Gets the currently logged in user’s MXID
105 106 107 108 |
# File 'lib/matrix_sdk/client.rb', line 105 def mxid @mxid ||= MXID.new api.whoami?[:user_id] if api&.access_token @mxid end |
#presence ⇒ Response
Gets the current user presence status object
117 118 119 |
# File 'lib/matrix_sdk/client.rb', line 117 def presence api.get_presence_status(mxid).tap { |h| h.delete :user_id } end |
#public_rooms ⇒ Array[Room]
This will try to list all public rooms on the HS, and may take a while on larger instances
Gets a list of all the public rooms on the connected HS
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/matrix_sdk/client.rb', line 137 def public_rooms rooms = [] since = nil loop do data = api.get_public_rooms since: since data[:chunk].each do |chunk| rooms << Room.new(self, chunk[:room_id], name: chunk[:name], topic: chunk[:topic], aliases: chunk[:aliases], canonical_alias: chunk[:canonical_alias], avatar_url: chunk[:avatar_url], join_rule: :public, world_readable: chunk[:world_readable]).tap do |r| r.instance_variable_set :@guest_access, chunk[:guest_can_join] ? :can_join : :forbidden end end break if data[:next_batch].nil? since = data.next_batch end rooms end |
#register_as_guest ⇒ Object
This feature is not commonly supported by many HSes
Register - and log in - on the connected HS as a guest
230 231 232 233 |
# File 'lib/matrix_sdk/client.rb', line 230 def register_as_guest data = api.register(kind: :guest) post_authentication(data) end |
#register_with_password(username, password, **params) ⇒ Object
This method will currently always use auth type ‘m.login.dummy’
Register a new user account on the connected HS
This will also trigger an initial sync unless no_sync is set
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/matrix_sdk/client.rb', line 245 def register_with_password(username, password, **params) username = username.to_s unless username.is_a?(String) password = password.to_s unless password.is_a?(String) raise ArgumentError, "Username can't be nil or empty" if username.nil? || username.empty? raise ArgumentError, "Password can't be nil or empty" if password.nil? || username.empty? data = api.register(auth: { type: 'm.login.dummy' }, username: username, password: password) post_authentication(data) return if params[:no_sync] sync full_state: true, allow_sync_retry: params.fetch(:allow_sync_retry, nil) end |
#registered_3pids ⇒ Response
Retrieve a list of all registered third-party IDs for the current user
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/matrix_sdk/client.rb', line 336 def registered_3pids data = api.get_3pids data.threepids.each do |obj| obj.instance_eval do def added_at Time.at(self[:added_at] / 1000) end def validated_at return unless validated? Time.at(self[:validated_at] / 1000) end def validated? key? :validated_at end def to_s "#{self[:medium]}:#{self[:address]}" end def inspect "#<MatrixSdk::Response 3pid=#{to_s.inspect} added_at=\"#{added_at}\"#{validated? ? " validated_at=\"#{validated_at}\"" : ''}>" end end end data end |
#reload_rooms! ⇒ Boolean Also known as: refresh_rooms!, reload_spaces!
This will be a no-op if the cache level is set to :none
Refresh the list of currently handled rooms, replacing it with the user’s currently joined rooms.
213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/matrix_sdk/client.rb', line 213 def reload_rooms! return true if cache == :none @rooms.clear api.get_joined_rooms.joined_rooms.each do |id| r = ensure_room(id) r.reload! end true end |
#remove_room_alias(room_alias) ⇒ Object
Remove a room alias
435 436 437 438 439 440 |
# File 'lib/matrix_sdk/client.rb', line 435 def remove_room_alias(room_alias) room_alias = MXID.new room_alias.to_s unless room_alias.is_a? MXID raise ArgumentError, 'Must be a room alias' unless room_alias.room_alias? api.remove_room_alias(room_alias) end |
#rooms ⇒ Array[Room]
This will always return the empty array if the cache level is set to :none
Gets a list of all relevant rooms, either the ones currently handled by the client, or the list of currently joined ones if no rooms are handled
185 186 187 188 189 190 191 192 193 |
# File 'lib/matrix_sdk/client.rb', line 185 def rooms if @rooms.empty? && cache != :none api.get_joined_rooms.joined_rooms.each do |id| ensure_room(id) end end @rooms.values end |
#set_presence(status, message: nil) ⇒ Object
Sets the current user’s presence status
127 128 129 130 131 |
# File 'lib/matrix_sdk/client.rb', line 127 def set_presence(status, message: nil) raise ArgumentError, 'Presence must be one of :online, :offline, :unavailable' unless %i[online offline unavailable].include?(status) api.set_presence_status(mxid, status, message: ) end |
#spaces ⇒ Array[Room]
Get a list of all joined Matrix Spaces
198 199 200 201 202 203 204 205 206 |
# File 'lib/matrix_sdk/client.rb', line 198 def spaces rooms = if cache == :none api.get_joined_rooms.joined_rooms.map { |id| Room.new(self, id) } else self.rooms end rooms.select(&:space?) end |
#start_listener_thread(**params) ⇒ Object
Starts a background thread that will listen to new events
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 |
# File 'lib/matrix_sdk/client.rb', line 458 def start_listener_thread(**params) return if listening? @should_listen = true if api.protocol?(:MSC) && api.msc2108? params[:filter] = sync_filter unless params.key? :filter params[:filter] = params[:filter].to_json unless params[:filter].nil? || params[:filter].is_a?(String) params[:since] = @next_batch if @next_batch errors = 0 thread, cancel_token = api.msc2108_sync_sse(params) do |data, event:, id:| @next_batch = id if id case event.to_sym when :sync handle_sync_response(data) errors = 0 when :sync_error logger.error "SSE Sync error received; #{data.type}: #{data.}" errors += 1 # TODO: Allow configuring raise 'Aborting due to excessive errors' if errors >= 5 end end @should_listen = cancel_token else thread = Thread.new { listen_forever(**params) } end @sync_thread = thread thread.run end |
#stop_listener_thread ⇒ Object
Stops the running background thread if one is active
492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
# File 'lib/matrix_sdk/client.rb', line 492 def stop_listener_thread return unless @sync_thread if @should_listen.is_a? Hash @should_listen[:run] = false else @should_listen = false end if @sync_thread.alive? ret = @sync_thread.join(2) @sync_thread.kill unless ret end @sync_thread = nil end |
#sync(skip_store_batch: false, **params) ⇒ Object Also known as: listen_for_events
Run a message sync round, triggering events as necessary
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 |
# File 'lib/matrix_sdk/client.rb', line 522 def sync(skip_store_batch: false, **params) extra_params = { filter: sync_filter, timeout: 30 } extra_params[:since] = @next_batch unless @next_batch.nil? extra_params.merge!(params) extra_params[:filter] = extra_params[:filter].to_json unless extra_params[:filter].is_a? String attempts = 0 data = loop do break api.sync(**extra_params) rescue MatrixSdk::MatrixTimeoutError => e raise e if (attempts += 1) >= params.fetch(:allow_sync_retry, 0) end @next_batch = data[:next_batch] unless skip_store_batch handle_sync_response(data) true end |
#upload(content, content_type) ⇒ URI::MXC
Upload a piece of data to the media repo
448 449 450 451 452 453 |
# File 'lib/matrix_sdk/client.rb', line 448 def upload(content, content_type) data = api.media_upload(content, content_type) return URI(data[:content_uri]) if data.key? :content_uri raise MatrixUnexpectedResponseError, 'Upload succeeded, but no media URI returned' end |