Class: Chatrix::Matrix

Inherits:
Object
  • Object
show all
Includes:
HTTParty
Defined in:
lib/chatrix/matrix.rb

Overview

TODO:

Try to extract functionality to make this class smaller.

Note:

Any of the methods may raise the errors listed in #parse_response. Consider this when calling the methods.

Note:

Endpoints that require a room ID in the official API can be passed a room alias in this implementation, the room ID will be automatically looked up from the homeserver.

Provides an interface to the Matrix API on a homeserver.

Detailed information about the data structures is not included here and can be found on the Matrix API page.

rubocop:disable ClassLength

Constant Summary collapse

METHODS =

Maps HTTP methods to their respective HTTParty method.

{
  get: -> (path, options, &block) { get path, options, &block },
  put: -> (path, options, &block) { put path, options, &block },
  post: -> (path, options, &block) { post path, options, &block },
  delete: -> (path, options, &block) { delete path, options, &block }
}.freeze
DEFAULT_HOMESERVER =

Default homeserver used if none is specified.

'https://matrix.org'.freeze
API_PATH =

API path used.

'/_matrix/client/r0'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(token = nil, homeserver = DEFAULT_HOMESERVER) ⇒ Matrix

Initializes a new instance of Matrix.


55
56
57
58
59
60
# File 'lib/chatrix/matrix.rb', line 55

def initialize(token = nil, homeserver = DEFAULT_HOMESERVER)
  @homeserver = homeserver
  @base_uri = @homeserver + API_PATH
  @transaction_id = 0
  @access_token = token
end

Instance Attribute Details

#access_tokenString


45
46
47
# File 'lib/chatrix/matrix.rb', line 45

def access_token
  @access_token
end

#homeserverObject (readonly)

Returns the value of attribute homeserver


49
50
51
# File 'lib/chatrix/matrix.rb', line 49

def homeserver
  @homeserver
end

Instance Method Details

#ban(room, user, reason) ⇒ Boolean

Kicks and bans a user from a room.

Examples:

Banning a spammer

ban('#haven:matrix.org', '@spammer:spam.com', 'Spamming the room')

431
432
433
434
435
436
437
438
# File 'lib/chatrix/matrix.rb', line 431

def ban(room, user, reason)
  room = get_room_id room if room.start_with? '#'
  make_request(
    :post,
    "/rooms/#{room}/ban",
    content: { reason: reason, user_id: user }
  ).code == 200
end

#forget(room) ⇒ Boolean

Forgets about a room.


445
446
447
448
# File 'lib/chatrix/matrix.rb', line 445

def forget(room)
  room = get_room_id room if room.start_with? '#'
  make_request(:post, "/rooms/#{room}/forget").code == 200
end

#get_avatar(user) ⇒ String

Get the URL to a user's avatar (an mxp:// URL).

Raises:


88
89
90
91
92
# File 'lib/chatrix/matrix.rb', line 88

def get_avatar(user)
  make_request(:get, "/profile/#{user}/avatar_url")['avatar_url']
rescue NotFoundError
  raise AvatarNotFoundError.new(user), 'Avatar or user could not be found'
end

#get_displayname(user) ⇒ Object

Get a user's display name (not username).

Raises:


98
99
100
101
102
# File 'lib/chatrix/matrix.rb', line 98

def get_displayname(user)
  make_request(:get, "/profile/#{user}/displayname")['displayname']
rescue NotFoundError
  raise UserNotFoundError.new(user), 'The specified user could not be found'
end

#get_event_context(room, event, limit = 10) ⇒ Hash

Gets context for an event in a room.

The method will return events that happened before and after the specified event.


171
172
173
174
175
176
177
178
# File 'lib/chatrix/matrix.rb', line 171

def get_event_context(room, event, limit = 10)
  room = get_room_id room if room.start_with? '#'
  make_request(
    :get,
    "/rooms/#{room}/context/#{event}",
    params: { limit: limit }
  ).parsed_response
end

#get_presence_list(user) ⇒ Array

TODO:

The official documentation on this endpoint is weird, what does this really do?

Gets the presence list for a user.


574
575
576
# File 'lib/chatrix/matrix.rb', line 574

def get_presence_list(user)
  make_request(:get, "/presence/list/#{user}").parsed_response
end

#get_presence_status(user) ⇒ Hash

Gets the presence status of a user.


612
613
614
# File 'lib/chatrix/matrix.rb', line 612

def get_presence_status(user)
  make_request(:get, "/presence/#{user}/status").parsed_response
end

#get_room_alias_info(room_alias) ⇒ Hash

Get information about a room alias.

This can be used to get the room ID that an alias points to.


145
146
147
148
149
150
# File 'lib/chatrix/matrix.rb', line 145

def get_room_alias_info(room_alias)
  make_request(:get, "/directory/room/#{room_alias}").parsed_response
rescue NotFoundError
  raise RoomNotFoundError.new(room_alias),
        'The specified room alias could not be found'
end

#get_room_id(room_alias) ⇒ String

Get a room's ID from its alias.


156
157
158
# File 'lib/chatrix/matrix.rb', line 156

def get_room_id(room_alias)
  get_room_alias_info(room_alias)['room_id']
end

#get_room_members(room) ⇒ Array

Get the members of a room.


184
185
186
187
# File 'lib/chatrix/matrix.rb', line 184

def get_room_members(room)
  room = get_room_id room if room.start_with? '#'
  make_request(:get, "/rooms/#{room}/members")['chunk']
end

#get_room_messages(room, from, direction, limit = 10) ⇒ Hash

Get a list of messages from a room.


197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/chatrix/matrix.rb', line 197

def get_room_messages(room, from, direction, limit = 10)
  room = get_room_id room if room.start_with? '#'
  make_request(
    :get,
    "/rooms/#{room}/messages",
    params: {
      from: from,
      dir: direction,
      limit: limit
    }
  ).parsed_response
end

#get_room_state(room) ⇒ Array #get_room_state(room, type) ⇒ Hash #get_room_state(room, type, key) ⇒ Hash

rubocop:disable MethodLength

Overloads:

  • #get_room_state(room) ⇒ Array

    Get state events for the current state of a room.

  • #get_room_state(room, type) ⇒ Hash

    Get the contents of a specific kind of state in the room.

  • #get_room_state(room, type, key) ⇒ Hash

    Get the contents of a specific kind of state including only the specified key in the result.


326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/chatrix/matrix.rb', line 326

def get_room_state(room, type = nil, key = nil)
  room = get_room_id room if room.start_with? '#'

  if type && key
    make_request(
      :get,
      "/rooms/#{room}/state/#{type}/#{key}"
    ).parsed_response
  elsif type
    make_request(:get, "/rooms/#{room}/state/#{type}").parsed_response
  else
    make_request(:get, "/rooms/#{room}/state").parsed_response
  end
end

#get_user(user) ⇒ Hash

Gets information about a specific user.

Examples:

Print a user's display name

puts get_user('@foo:matrix.org')['displayname']

Raises:


77
78
79
80
81
# File 'lib/chatrix/matrix.rb', line 77

def get_user(user)
  make_request(:get, "/profile/#{user}").parsed_response
rescue NotFoundError
  raise UserNotFoundError.new(user), 'The specified user could not be found'
end

#get_user_room_tags(user, room) ⇒ Hash{String=>Hash}

Get tags that a specific user has set on a room.


130
131
132
133
# File 'lib/chatrix/matrix.rb', line 130

def get_user_room_tags(user, room)
  room = get_room_id room if room.start_with? '#'
  make_request(:get, "/user/#{user}/rooms/#{room}/tags")['tags']
end

#join(room, third_party_signed = nil) ⇒ String

Joins a room on the homeserver.


409
410
411
412
413
414
415
416
417
418
419
# File 'lib/chatrix/matrix.rb', line 409

def join(room, third_party_signed = nil)
  if third_party_signed
    make_request(
      :post,
      "/join/#{room}",
      content: { third_party_signed: third_party_signed }
    )['room_id']
  else
    make_request(:post, "/join/#{room}")['room_id']
  end
end

#kick(room, user, reason) ⇒ Boolean

Kicks a user from a room.

This does not ban the user, they can rejoin unless the room is invite-only, in which case they need a new invite to join back.

Examples:

Kicking an annoying user

kick('#fun:matrix.org', '@anon:4chan.org', 'Bad cropping')

463
464
465
466
467
468
469
470
# File 'lib/chatrix/matrix.rb', line 463

def kick(room, user, reason)
  room = get_room_id room if room.start_with? '#'
  make_request(
    :post,
    "/rooms/#{room}/kick",
    content: { reason: reason, user_id: user }
  ).code == 200
end

#leave(room) ⇒ Boolean

Leaves a room (but does not forget about it).


477
478
479
480
# File 'lib/chatrix/matrix.rb', line 477

def leave(room)
  room = get_room_id room if room.start_with? '#'
  make_request(:post, "/rooms/#{room}/leave").code == 200
end

#login(method, options = {}) ⇒ Hash

Note:

A successful login will update the access_token to the new one returned from the login response.

Performs a login attempt.

Examples:

Logging in with username and password

('m.login.password', user: '@snoo:reddit.com', password: 'hunter2')

512
513
514
515
516
517
518
519
520
521
522
523
# File 'lib/chatrix/matrix.rb', line 512

def (method, options = {})
  response = make_request(
    :post,
    '/login',
    content: { type: method }.merge!(options)
  )

  # Update the local access token
  @access_token = response['access_token']

  response.parsed_response
end

#logoutHash

Note:

This will invalidate the access token. It will no longer be valid for API calls.

Logs out.


531
532
533
534
535
536
537
538
# File 'lib/chatrix/matrix.rb', line 531

def logout
  response = make_request :post, '/logout'

  # A successful logout means the access token has been invalidated
  @access_token = nil

  response.parsed_response
end

#make_request(method, path, params: nil, content: nil) {|fragment| ... } ⇒ HTTParty::Response (private)

Helper method for performing requests to the homeserver.

Yields:

  • (fragment)

    HTTParty will call the block during the request.


698
699
700
701
702
703
# File 'lib/chatrix/matrix.rb', line 698

def make_request(method, path, params: nil, content: nil, &block)
  path = @base_uri + URI.encode(path)
  options = make_request_options params, content

  parse_response METHODS[method].call(path, options, &block)
end

#make_request_options(params, content) ⇒ Hash (private)

Create an options Hash to pass to a server request.

This method embeds the access_token into the query parameters.


674
675
676
677
678
679
680
681
682
683
# File 'lib/chatrix/matrix.rb', line 674

def make_request_options(params, content)
  options = {
    query: @access_token ? { access_token: @access_token } : {}
  }

  options[:query].merge!(params) if params.is_a? Hash
  options[:body] = content.to_json if content.is_a? Hash

  options
end

#parse_response(response) ⇒ HTTParty::Response (private)

Parses a HTTParty Response object and returns it if it was successful.

rubocop:disable MethodLength

Raises:

  • (ForbiddenError)

    If a 403 response code was returned from the request.

  • (NotFoundError)

    If a 404 response code was returned from the request.

  • (RequestError)

    If an error object was returned from the server.

  • (ApiError)

    If an unparsable error was returned from the server.


719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
# File 'lib/chatrix/matrix.rb', line 719

def parse_response(response)
  case response.code
  when 200 # OK
    response
  when 403 # Forbidden
    raise ForbiddenError, 'You do not have access to that resource'
  when 404 # Not found
    raise NotFoundError, 'The specified resource could not be found'
  else
    if %w{(errcode), (error)}.all? { |k| response.include? k }
      raise RequestError.new(response.parsed_response), 'Request failed'
    end

    raise ApiError, 'Unknown API error occurred when carrying out request'
  end
end

#refresh(token = nil) ⇒ Hash

Note:

On success, the internal access_token will be updated automatically for use in subsequent API calls.

Gets a new access token to use for API calls when the current one expires.


552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/chatrix/matrix.rb', line 552

def refresh(token = nil)
  refresh_token = token || @refresh_token || @access_token

  response = make_request(
    :post,
    '/tokenrefresh',
    content: { refresh_token: refresh_token }
  )

  @access_token = response['access_token']
  @refresh_token = response['refresh_token']

  response.parsed_response
end

#rooms(from: 'START', to: 'END', limit: 10, direction: 'b') ⇒ Hash

Get the list of public rooms on the server.

The start and end values returned in the result can be passed to from and to, for pagination purposes.


650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/chatrix/matrix.rb', line 650

def rooms(from: 'START', to: 'END', limit: 10, direction: 'b')
  make_request(
    :get,
    '/publicRooms',
    params: {
      from: from,
      to: to,
      limit: limit,
      dir: direction
    }
  ).parsed_response
end

#send_emote(room, content) ⇒ String

Sends an emote to a room.

/me <message here>

Examples:

Sending an emote

# Will show up as: "* <user> is having fun"
send_emote('#party:matrix.org', 'is having fun')

280
281
282
# File 'lib/chatrix/matrix.rb', line 280

def send_emote(room, content)
  send_message_type room, content, 'm.emote'
end

#send_html(room, html) ⇒ String

Sends a message formatted using HTML markup.

The body field in the content will have the HTML stripped out, and is usually presented in clients that don't support the formatting.

The formatted_body field in the content will contain the actual HTML formatted message (as passed to the html parameter).

Examples:

Sending an HTML message

send_html('#html:matrix.org', '<strong>Hello</strong> <em>world</em>!')

298
299
300
301
302
303
304
305
306
# File 'lib/chatrix/matrix.rb', line 298

def send_html(room, html)
  send_message_raw(
    room,
    msgtype: 'm.text',
    format: 'org.matrix.custom.html',
    body: html.gsub(%r{</?[^>]*?>}, ''), # TODO: Make this better
    formatted_body: html
  )
end

#send_message(room, content) ⇒ String

Sends a plaintext message to a room.

Examples:

Sending a simple message

send_message('#party:matrix.org', 'Hello everyone!')

253
254
255
# File 'lib/chatrix/matrix.rb', line 253

def send_message(room, content)
  send_message_type room, content
end

#send_message_raw(room, content, type = 'm.room.message') ⇒ String

Sends a message object to a room.


221
222
223
224
225
226
227
228
229
# File 'lib/chatrix/matrix.rb', line 221

def send_message_raw(room, content, type = 'm.room.message')
  room = get_room_id room if room.start_with? '#'
  @transaction_id += 1
  make_request(
    :put,
    "/rooms/#{room}/send/#{type}/#{@transaction_id}",
    content: content
  )['event_id']
end

#send_message_type(room, content, type = 'm.text') ⇒ String

A helper method to send a simple message construct.


241
242
243
# File 'lib/chatrix/matrix.rb', line 241

def send_message_type(room, content, type = 'm.text')
  send_message_raw room, msgtype: type, body: content
end

#send_notice(room, content) ⇒ String

Sends a notice message to a room.

Examples:

Sending a notice

send_notice('#stuff:matrix.org', 'This is a notice')

265
266
267
# File 'lib/chatrix/matrix.rb', line 265

def send_notice(room, content)
  send_message_type room, content, 'm.notice'
end

#send_typing(room, user, typing = true, duration = 30_000) ⇒ Boolean

Sends a message to the server informing it about a user having started or stopped typing.


352
353
354
355
356
357
358
359
360
361
362
# File 'lib/chatrix/matrix.rb', line 352

def send_typing(room, user, typing = true, duration = 30_000)
  room = get_room_id room if room.start_with? '#'

  content = { typingState: { typing: typing, timeout: duration } }

  make_request(
    :put,
    "/rooms/#{room}/typing/#{user}",
    content: content
  ).code == 200
end

#set_displayname(user, displayname) ⇒ Boolean

Note:

Can only be used on the user who possesses the access_token currently in use.

Sets a new display name for a user.


113
114
115
116
117
118
119
120
121
# File 'lib/chatrix/matrix.rb', line 113

def set_displayname(user, displayname)
  make_request(
    :put,
    "/profile/#{user}/displayname",
    content: {
      displayname: displayname
    }
  ).code == 200
end

#sync(filter: nil, since: nil, full_state: false, set_presence: true, timeout: 30_000) ⇒ Hash

Synchronize with the latest state on the server.

For initial sync, call this method with the since parameter set to nil.

rubocop:disable MethodLength


384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/chatrix/matrix.rb', line 384

def sync(filter: nil, since: nil, full_state: false,
         set_presence: true, timeout: 30_000)
  options = { full_state: full_state }

  options[:since] = since if since
  options[:set_presence] = 'offline' unless set_presence
  options[:timeout] = timeout if timeout

  if filter.is_a? Integer
    options[:filter] = filter
  elsif filter.is_a? Hash
    options[:filter] = URI.encode filter.to_json
  end

  make_request(:get, '/sync', params: options).parsed_response
end

#threepidsArray

Gets third-party IDs associated with the current account.


65
66
67
# File 'lib/chatrix/matrix.rb', line 65

def threepids
  make_request(:get, '/account/3pid')['threepids']
end

#unban(room, user) ⇒ Boolean

Unbans a user from a room.


488
489
490
491
492
493
494
495
# File 'lib/chatrix/matrix.rb', line 488

def unban(room, user)
  room = get_room_id room if room.start_with? '#'
  make_request(
    :post,
    "/rooms/#{room}/unban",
    content: { user_id: user }
  ).code == 200
end

#update_presence_list(user, data) ⇒ Boolean

Adds or removes users from a user's presence list.

Examples:

Add and remove two users

update_presence_list(
  '@me:home.org',
  {
    invite: ['@friend:home.org'],
    drop: ['@enemy:other.org']
  }
)

598
599
600
601
602
603
604
# File 'lib/chatrix/matrix.rb', line 598

def update_presence_list(user, data)
  make_request(
    :post,
    "/presence/list/#{user}",
    content: { presence_diff: data }
  ).code == 200
end

#update_presence_status(user, status, message = nil) ⇒ Boolean

Note:

Only the user for whom the access_token is valid for can have their presence updated.

Updates the presence status of a user.


627
628
629
630
631
632
633
634
635
636
637
# File 'lib/chatrix/matrix.rb', line 627

def update_presence_status(user, status, message = nil)
  content = { presenceState: { presence: status } }

  content[:presenceState][:status_msg] = message if message

  make_request(
    :put,
    "/presence/#{user}/status",
    content: content
  ).code == 200
end