chatrix 1.0.0.pre → 1.0.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 +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +19 -0
- data/README.md +79 -4
- data/chatrix.gemspec +1 -6
- data/lib/chatrix.rb +1 -0
- data/lib/chatrix/api/api_component.rb +21 -0
- data/lib/chatrix/api/media.rb +67 -0
- data/lib/chatrix/api/push.rb +107 -0
- data/lib/chatrix/api/room_actions.rb +267 -0
- data/lib/chatrix/api/rooms.rb +213 -0
- data/lib/chatrix/api/session.rb +154 -0
- data/lib/chatrix/api/users.rb +176 -0
- data/lib/chatrix/client.rb +98 -0
- data/lib/chatrix/components/admin.rb +58 -0
- data/lib/chatrix/components/messaging.rb +42 -0
- data/lib/chatrix/components/permissions.rb +55 -0
- data/lib/chatrix/components/state.rb +160 -0
- data/lib/chatrix/components/timeline.rb +50 -0
- data/lib/chatrix/errors.rb +75 -7
- data/lib/chatrix/events.rb +39 -0
- data/lib/chatrix/matrix.rb +138 -595
- data/lib/chatrix/message.rb +53 -0
- data/lib/chatrix/room.rb +93 -0
- data/lib/chatrix/rooms.rb +90 -0
- data/lib/chatrix/user.rb +109 -0
- data/lib/chatrix/users.rb +74 -0
- data/lib/chatrix/version.rb +1 -1
- metadata +30 -81
data/lib/chatrix/errors.rb
CHANGED
@@ -5,15 +5,41 @@ module Chatrix
|
|
5
5
|
|
6
6
|
# Errors that stem from an API call.
|
7
7
|
class ApiError < ChatrixError
|
8
|
+
# @return [Hash] the raw error response object.
|
9
|
+
attr_reader :error
|
10
|
+
|
11
|
+
# @return [String] the type of error. `'E_UNKNOWN'` if the server
|
12
|
+
# did not give an error code.
|
13
|
+
attr_reader :code
|
14
|
+
|
15
|
+
# @return [String] the error message returned from the server.
|
16
|
+
# `'Unknown error'` if the server did not give any message.
|
17
|
+
attr_reader :api_message
|
18
|
+
|
19
|
+
# Initializes a new RequestError instance.
|
20
|
+
# @param error [Hash{String=>String}] The error response object.
|
21
|
+
def initialize(error = {})
|
22
|
+
@error = error
|
23
|
+
@code = error['errcode'] || 'E_UNKNOWN'
|
24
|
+
@api_message = error['error'] || 'Unknown error'
|
25
|
+
end
|
8
26
|
end
|
9
27
|
|
10
28
|
# Error raised when a request is badly formatted.
|
11
29
|
class RequestError < ApiError
|
12
|
-
|
30
|
+
end
|
31
|
+
|
32
|
+
# Error raised when the API request limit is reached.
|
33
|
+
class RateLimitError < ApiError
|
34
|
+
# @return [Fixnum,nil] number of milliseconds to wait before attempting
|
35
|
+
# this request again. If no delay was provided this will be `nil`.
|
36
|
+
attr_reader :retry_delay
|
13
37
|
|
14
|
-
|
15
|
-
|
16
|
-
|
38
|
+
# Initializes a new RateLimitError instance.
|
39
|
+
# @param error [Hash] The error response object.
|
40
|
+
def initialize(error = {})
|
41
|
+
super
|
42
|
+
@retry_delay = error['retry_after_ms'] if error.key? 'retry_after_ms'
|
17
43
|
end
|
18
44
|
end
|
19
45
|
|
@@ -27,28 +53,70 @@ module Chatrix
|
|
27
53
|
|
28
54
|
# Raised when a user is not found.
|
29
55
|
class UserNotFoundError < NotFoundError
|
56
|
+
# @return [String] the name of the user that was not found.
|
30
57
|
attr_reader :username
|
31
58
|
|
32
|
-
|
59
|
+
# Initializes a new UserNotFoundError instance.
|
60
|
+
# @param username [String] The user that wasn't found.
|
61
|
+
# @param error [Hash] The error response from the server.
|
62
|
+
def initialize(username, error = {})
|
63
|
+
super error
|
33
64
|
@username = username
|
34
65
|
end
|
35
66
|
end
|
36
67
|
|
37
68
|
# Raised when a user's avatar is not found.
|
38
69
|
class AvatarNotFoundError < NotFoundError
|
70
|
+
# @return [String] the user whose avatar was not found.
|
39
71
|
attr_reader :username
|
40
72
|
|
41
|
-
|
73
|
+
# Initializes a new AvatarNotFoundError instance.
|
74
|
+
# @param username [String] Name of the user whose avatar was not found.
|
75
|
+
# @param error [Hash] The error response from the server.
|
76
|
+
def initialize(username, error = {})
|
77
|
+
super error
|
42
78
|
@username = username
|
43
79
|
end
|
44
80
|
end
|
45
81
|
|
46
82
|
# Raised when a room is not found.
|
47
83
|
class RoomNotFoundError < NotFoundError
|
84
|
+
# @return [String] the room that was not found.
|
48
85
|
attr_reader :room
|
49
86
|
|
50
|
-
|
87
|
+
# Initializes a new RoomNotFoundError instance.
|
88
|
+
# @param room [String] Name of the room that was not found.
|
89
|
+
# @param error [Hash] The error response from the server.
|
90
|
+
def initialize(room, error = {})
|
91
|
+
super error
|
51
92
|
@room = room
|
52
93
|
end
|
53
94
|
end
|
95
|
+
|
96
|
+
# Raised when there is an issue with authentication.
|
97
|
+
#
|
98
|
+
# This can either be because authentication failed outright or because
|
99
|
+
# more information is required by the server to successfully authenticate.
|
100
|
+
#
|
101
|
+
# If authentication failed then the `data` attribute will be an empty hash.
|
102
|
+
#
|
103
|
+
# If more information is required the `data` hash will contain information
|
104
|
+
# about what additional information is needed to authenticate.
|
105
|
+
class AuthenticationError < ApiError
|
106
|
+
# @return [Hash] a hash with information about the additional information
|
107
|
+
# required by the server for authentication, if any. If the
|
108
|
+
# authentication request failed, this will be an empty hash or `nil`.
|
109
|
+
attr_reader :data
|
110
|
+
|
111
|
+
# Initializes a new AuthenticationError instance.
|
112
|
+
# @param error [Hash] The error response from the server.
|
113
|
+
def initialize(error = {})
|
114
|
+
super
|
115
|
+
|
116
|
+
# Set data to be the error response hash WITHOUT the error code and
|
117
|
+
# error values. This will leave it with only the data relevant for
|
118
|
+
# handling authentication.
|
119
|
+
@data = error.select { |key| !%w{(errcode), (error)}.include? key }
|
120
|
+
end
|
121
|
+
end
|
54
122
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Chatrix
|
2
|
+
# Keeps track of what events have already been processed.
|
3
|
+
module Events
|
4
|
+
@processed = []
|
5
|
+
|
6
|
+
# Marks an event as having been processed.
|
7
|
+
# @param event [String,Hash] The affected event.
|
8
|
+
def self.processed(event)
|
9
|
+
@processed.push parse_event event
|
10
|
+
end
|
11
|
+
|
12
|
+
# Checks if an event has been processed.
|
13
|
+
#
|
14
|
+
# @param event [String,Hash] The event to check.
|
15
|
+
# @return [Boolean] `true` if the event has been processed,
|
16
|
+
# otherwise `false`.
|
17
|
+
def self.processed?(event)
|
18
|
+
@processed.member? parse_event event
|
19
|
+
end
|
20
|
+
|
21
|
+
# Extract the event ID from an event object.
|
22
|
+
# If this is a string, it's returned verbatim.
|
23
|
+
#
|
24
|
+
# @param event [String,Hash] The event object to extract information from.
|
25
|
+
# @return [String] The event ID.
|
26
|
+
#
|
27
|
+
# @raise ArgumentError if the event object is of an invalid type or does
|
28
|
+
# not contain an ID.
|
29
|
+
def self.parse_event(event)
|
30
|
+
if event.is_a? String
|
31
|
+
event
|
32
|
+
elsif event.is_a?(Hash) && event.key?('event_id')
|
33
|
+
event['event_id']
|
34
|
+
else
|
35
|
+
raise ArgumentError, 'Invalid event object'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/chatrix/matrix.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
require 'chatrix/errors'
|
2
2
|
|
3
|
+
require 'chatrix/api/session'
|
4
|
+
require 'chatrix/api/users'
|
5
|
+
require 'chatrix/api/rooms'
|
6
|
+
require 'chatrix/api/media'
|
7
|
+
require 'chatrix/api/push'
|
8
|
+
|
3
9
|
require 'httparty'
|
4
10
|
|
5
11
|
module Chatrix
|
@@ -14,10 +20,6 @@ module Chatrix
|
|
14
20
|
# @note Endpoints that require a room ID in the official API can be passed
|
15
21
|
# a room alias in this implementation, the room ID will be automatically
|
16
22
|
# looked up from the homeserver.
|
17
|
-
#
|
18
|
-
# @todo Try to extract functionality to make this class smaller.
|
19
|
-
#
|
20
|
-
# rubocop:disable ClassLength
|
21
23
|
class Matrix
|
22
24
|
include HTTParty
|
23
25
|
|
@@ -33,21 +35,50 @@ module Chatrix
|
|
33
35
|
delete: -> (path, options, &block) { delete path, options, &block }
|
34
36
|
}.freeze
|
35
37
|
|
38
|
+
# Registered request error handlers.
|
39
|
+
ERROR_HANDLERS = {
|
40
|
+
400 => [RequestError, 'Request failed'],
|
41
|
+
401 => [AuthenticationError, 'Server requests additional authentication'],
|
42
|
+
403 => [ForbiddenError, 'You do not have access to that resource'],
|
43
|
+
404 => [NotFoundError, 'The resource was not found'],
|
44
|
+
429 => [RateLimitError, 'The request was rate limited']
|
45
|
+
}.tap do |h|
|
46
|
+
h.default = [ApiError, 'An unknown API error occurred.']
|
47
|
+
end.freeze
|
48
|
+
|
36
49
|
# Default homeserver used if none is specified.
|
37
50
|
DEFAULT_HOMESERVER = 'https://matrix.org'.freeze
|
38
51
|
|
39
52
|
# API path used.
|
40
53
|
API_PATH = '/_matrix/client/r0'.freeze
|
41
54
|
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# to the homeserver.
|
55
|
+
# @return [String] the access token used when performing requests
|
56
|
+
# to the homeserver.
|
45
57
|
attr_accessor :access_token
|
46
58
|
|
47
|
-
#
|
48
|
-
# @return [String] The homeserver for this API object.
|
59
|
+
# @return [String] the homeserver for this API object.
|
49
60
|
attr_reader :homeserver
|
50
61
|
|
62
|
+
# @return [Api::Session] the instance of Api::Session to perform
|
63
|
+
# session-related API calls with.
|
64
|
+
attr_reader :session
|
65
|
+
|
66
|
+
# @return [Api::Users] the instance of Api::Users to perform user-related
|
67
|
+
# API calls with.
|
68
|
+
attr_reader :users
|
69
|
+
|
70
|
+
# @return [Api::Rooms] the instance of Api::Rooms to perform room-related
|
71
|
+
# API calls with.
|
72
|
+
attr_reader :rooms
|
73
|
+
|
74
|
+
# @return [Api::Media] the instance of Api::Media to perform media-related
|
75
|
+
# API calls with.
|
76
|
+
attr_reader :media
|
77
|
+
|
78
|
+
# @return [Api::Push] the instance of Api::Push to perform push-related
|
79
|
+
# API calls with.
|
80
|
+
attr_reader :push
|
81
|
+
|
51
82
|
# Initializes a new instance of Matrix.
|
52
83
|
#
|
53
84
|
# @param token [String] The access token to use.
|
@@ -55,307 +86,28 @@ module Chatrix
|
|
55
86
|
def initialize(token = nil, homeserver = DEFAULT_HOMESERVER)
|
56
87
|
@homeserver = homeserver
|
57
88
|
@base_uri = @homeserver + API_PATH
|
58
|
-
@transaction_id = 0
|
59
89
|
@access_token = token
|
60
|
-
end
|
61
|
-
|
62
|
-
# Gets third-party IDs associated with the current account.
|
63
|
-
#
|
64
|
-
# @return [Array] A list of 3rd party IDs.
|
65
|
-
def threepids
|
66
|
-
make_request(:get, '/account/3pid')['threepids']
|
67
|
-
end
|
68
|
-
|
69
|
-
# Gets information about a specific user.
|
70
|
-
#
|
71
|
-
# @param user [String] The user to query (`@user:host.tld`).
|
72
|
-
# @return [Hash] The user information.
|
73
|
-
# @raise [UserNotFoundError] If the user could not be found.
|
74
|
-
#
|
75
|
-
# @example Print a user's display name
|
76
|
-
# puts get_user('@foo:matrix.org')['displayname']
|
77
|
-
def get_user(user)
|
78
|
-
make_request(:get, "/profile/#{user}").parsed_response
|
79
|
-
rescue NotFoundError
|
80
|
-
raise UserNotFoundError.new(user), 'The specified user could not be found'
|
81
|
-
end
|
82
|
-
|
83
|
-
# Get the URL to a user's avatar (an `mxp://` URL).
|
84
|
-
#
|
85
|
-
# @param (see #get_user)
|
86
|
-
# @return [String] The avatar URL.
|
87
|
-
# @raise [AvatarNotFoundError] If the avatar or user could not be found.
|
88
|
-
def get_avatar(user)
|
89
|
-
make_request(:get, "/profile/#{user}/avatar_url")['avatar_url']
|
90
|
-
rescue NotFoundError
|
91
|
-
raise AvatarNotFoundError.new(user), 'Avatar or user could not be found'
|
92
|
-
end
|
93
|
-
|
94
|
-
# Get a user's display name (**not** username).
|
95
|
-
#
|
96
|
-
# @param (see #get_user)
|
97
|
-
# @raise (see #get_user)
|
98
|
-
def get_displayname(user)
|
99
|
-
make_request(:get, "/profile/#{user}/displayname")['displayname']
|
100
|
-
rescue NotFoundError
|
101
|
-
raise UserNotFoundError.new(user), 'The specified user could not be found'
|
102
|
-
end
|
103
|
-
|
104
|
-
# Sets a new display name for a user.
|
105
|
-
#
|
106
|
-
# @note Can only be used on the user who possesses the
|
107
|
-
# {#access_token access_token} currently in use.
|
108
|
-
#
|
109
|
-
# @param user [String] The user to modify (`@user:host.tld`).
|
110
|
-
# @param displayname [String] The new displayname to set.
|
111
|
-
# @return [Boolean] `true` if the new display name was successfully set,
|
112
|
-
# otherwise `false`.
|
113
|
-
def set_displayname(user, displayname)
|
114
|
-
make_request(
|
115
|
-
:put,
|
116
|
-
"/profile/#{user}/displayname",
|
117
|
-
content: {
|
118
|
-
displayname: displayname
|
119
|
-
}
|
120
|
-
).code == 200
|
121
|
-
end
|
122
|
-
|
123
|
-
# Get tags that a specific user has set on a room.
|
124
|
-
#
|
125
|
-
# @param user [String] The user whose settings to retrieve
|
126
|
-
# (`@user:host.tld`).
|
127
|
-
# @param room [String] The room to get tags from (ID or alias).
|
128
|
-
# @return [Hash{String=>Hash}] A hash with tag data. The tag name is
|
129
|
-
# the key and any additional metadata is contained in the Hash value.
|
130
|
-
def get_user_room_tags(user, room)
|
131
|
-
room = get_room_id room if room.start_with? '#'
|
132
|
-
make_request(:get, "/user/#{user}/rooms/#{room}/tags")['tags']
|
133
|
-
end
|
134
|
-
|
135
|
-
# Get information about a room alias.
|
136
|
-
#
|
137
|
-
# This can be used to get the room ID that an alias points to.
|
138
|
-
#
|
139
|
-
# @param room_alias [String] The room alias to query, this **must** be an
|
140
|
-
# alias and not an ID.
|
141
|
-
# @return [Hash] Returns information about the alias in a Hash.
|
142
|
-
#
|
143
|
-
# @see #get_room_id #get_room_id is an example of how this method could be
|
144
|
-
# used to get a room's ID.
|
145
|
-
def get_room_alias_info(room_alias)
|
146
|
-
make_request(:get, "/directory/room/#{room_alias}").parsed_response
|
147
|
-
rescue NotFoundError
|
148
|
-
raise RoomNotFoundError.new(room_alias),
|
149
|
-
'The specified room alias could not be found'
|
150
|
-
end
|
151
|
-
|
152
|
-
# Get a room's ID from its alias.
|
153
|
-
#
|
154
|
-
# @param room_alias [String] The room alias to query.
|
155
|
-
# @return [String] The actual room ID for the room.
|
156
|
-
def get_room_id(room_alias)
|
157
|
-
get_room_alias_info(room_alias)['room_id']
|
158
|
-
end
|
159
|
-
|
160
|
-
# Gets context for an event in a room.
|
161
|
-
#
|
162
|
-
# The method will return events that happened before and after the
|
163
|
-
# specified event.
|
164
|
-
#
|
165
|
-
# @param room [String] The room to query.
|
166
|
-
# @param event [String] The event to get context for.
|
167
|
-
# @param limit [Fixnum] Maximum number of events to retrieve.
|
168
|
-
# @return [Hash] The returned hash contains information about the events
|
169
|
-
# happening before and after the specified event, as well as start and
|
170
|
-
# end timestamps and state information for the event.
|
171
|
-
def get_event_context(room, event, limit = 10)
|
172
|
-
room = get_room_id room if room.start_with? '#'
|
173
|
-
make_request(
|
174
|
-
:get,
|
175
|
-
"/rooms/#{room}/context/#{event}",
|
176
|
-
params: { limit: limit }
|
177
|
-
).parsed_response
|
178
|
-
end
|
179
90
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
room = get_room_id room if room.start_with? '#'
|
186
|
-
make_request(:get, "/rooms/#{room}/members")['chunk']
|
91
|
+
@session = Api::Session.new self
|
92
|
+
@users = Api::Users.new self
|
93
|
+
@rooms = Api::Rooms.new self
|
94
|
+
@media = Api::Media.new self
|
95
|
+
@push = Api::Push.new self
|
187
96
|
end
|
188
97
|
|
189
|
-
#
|
190
|
-
#
|
191
|
-
|
192
|
-
# @param from [String] Token to return events from.
|
193
|
-
# @param direction ['b', 'f'] Direction to return events from.
|
194
|
-
# @param limit [Fixnum] Maximum number of events to return.
|
195
|
-
# @return [Hash] A hash containing the messages, as well as `start` and
|
196
|
-
# `end` tokens for pagination.
|
197
|
-
def get_room_messages(room, from, direction, limit = 10)
|
198
|
-
room = get_room_id room if room.start_with? '#'
|
98
|
+
# Gets supported API versions from the server.
|
99
|
+
# @return [Array<String>] an array with the supported versions.
|
100
|
+
def versions
|
199
101
|
make_request(
|
200
|
-
:get,
|
201
|
-
|
202
|
-
params: {
|
203
|
-
from: from,
|
204
|
-
dir: direction,
|
205
|
-
limit: limit
|
206
|
-
}
|
207
|
-
).parsed_response
|
102
|
+
:get, '/versions', base: "#{@homeserver}/_matrix/client"
|
103
|
+
)['versions']
|
208
104
|
end
|
209
105
|
|
210
|
-
#
|
211
|
-
#
|
212
|
-
# @
|
213
|
-
|
214
|
-
|
215
|
-
# @return [String] The event ID of the sent message is returned.
|
216
|
-
# @see #send_message_type
|
217
|
-
# @see #send_message
|
218
|
-
# @see #send_emote
|
219
|
-
# @see #send_notice
|
220
|
-
# @see #send_html
|
221
|
-
def send_message_raw(room, content, type = 'm.room.message')
|
222
|
-
room = get_room_id room if room.start_with? '#'
|
223
|
-
@transaction_id += 1
|
224
|
-
make_request(
|
225
|
-
:put,
|
226
|
-
"/rooms/#{room}/send/#{type}/#{@transaction_id}",
|
227
|
-
content: content
|
228
|
-
)['event_id']
|
229
|
-
end
|
230
|
-
|
231
|
-
# A helper method to send a simple message construct.
|
232
|
-
#
|
233
|
-
# @param room [String] The room to send the message to.
|
234
|
-
# @param content [String] The message to send.
|
235
|
-
# @param type [String] The type of message this is.
|
236
|
-
# @return (see #send_message_raw)
|
237
|
-
# @see #send_message
|
238
|
-
# @see #send_notice
|
239
|
-
# @see #send_emote
|
240
|
-
# @see #send_html
|
241
|
-
def send_message_type(room, content, type = 'm.text')
|
242
|
-
send_message_raw room, msgtype: type, body: content
|
243
|
-
end
|
244
|
-
|
245
|
-
# Sends a plaintext message to a room.
|
246
|
-
#
|
247
|
-
# @param room [String] The room to send to.
|
248
|
-
# @param content [String] The message to send.
|
249
|
-
# @return (see #send_message_raw)
|
250
|
-
#
|
251
|
-
# @example Sending a simple message
|
252
|
-
# send_message('#party:matrix.org', 'Hello everyone!')
|
253
|
-
def send_message(room, content)
|
254
|
-
send_message_type room, content
|
255
|
-
end
|
256
|
-
|
257
|
-
# Sends a notice message to a room.
|
258
|
-
#
|
259
|
-
# @param room [String] The room to send to.
|
260
|
-
# @param content [String] The message to send.
|
261
|
-
# @return (see #send_message_raw)
|
262
|
-
#
|
263
|
-
# @example Sending a notice
|
264
|
-
# send_notice('#stuff:matrix.org', 'This is a notice')
|
265
|
-
def send_notice(room, content)
|
266
|
-
send_message_type room, content, 'm.notice'
|
267
|
-
end
|
268
|
-
|
269
|
-
# Sends an emote to a room.
|
270
|
-
#
|
271
|
-
# `/me <message here>`
|
272
|
-
#
|
273
|
-
# @param room [String] The room to send to.
|
274
|
-
# @param content [String] The emote to send.
|
275
|
-
# @return (see #send_message_raw)
|
276
|
-
#
|
277
|
-
# @example Sending an emote
|
278
|
-
# # Will show up as: "* <user> is having fun"
|
279
|
-
# send_emote('#party:matrix.org', 'is having fun')
|
280
|
-
def send_emote(room, content)
|
281
|
-
send_message_type room, content, 'm.emote'
|
282
|
-
end
|
283
|
-
|
284
|
-
# Sends a message formatted using HTML markup.
|
285
|
-
#
|
286
|
-
# The `body` field in the content will have the HTML stripped out, and is
|
287
|
-
# usually presented in clients that don't support the formatting.
|
288
|
-
#
|
289
|
-
# The `formatted_body` field in the content will contain the actual HTML
|
290
|
-
# formatted message (as passed to the `html` parameter).
|
291
|
-
#
|
292
|
-
# @param room [String] The room to send to.
|
293
|
-
# @param html [String] The HTML formatted text to send.
|
294
|
-
# @return (see #send_message_raw)
|
295
|
-
#
|
296
|
-
# @example Sending an HTML message
|
297
|
-
# send_html('#html:matrix.org', '<strong>Hello</strong> <em>world</em>!')
|
298
|
-
def send_html(room, html)
|
299
|
-
send_message_raw(
|
300
|
-
room,
|
301
|
-
msgtype: 'm.text',
|
302
|
-
format: 'org.matrix.custom.html',
|
303
|
-
body: html.gsub(%r{</?[^>]*?>}, ''), # TODO: Make this better
|
304
|
-
formatted_body: html
|
305
|
-
)
|
306
|
-
end
|
307
|
-
|
308
|
-
# @overload get_room_state(room)
|
309
|
-
# Get state events for the current state of a room.
|
310
|
-
# @param room [String] The room to get events for.
|
311
|
-
# @return [Array] An array with state events for the room.
|
312
|
-
# @overload get_room_state(room, type)
|
313
|
-
# Get the contents of a specific kind of state in the room.
|
314
|
-
# @param room [String] The room to get the data from.
|
315
|
-
# @param type [String] The type of state to get.
|
316
|
-
# @return [Hash] Information about the state type.
|
317
|
-
# @overload get_room_state(room, type, key)
|
318
|
-
# Get the contents of a specific kind of state including only the
|
319
|
-
# specified key in the result.
|
320
|
-
# @param room [String] The room to get the data from.
|
321
|
-
# @param type [String] The type of state to get.
|
322
|
-
# @param key [String] The key of the state to look up.
|
323
|
-
# @return [Hash] Information about the requested state.
|
324
|
-
def get_room_state(room, type = nil, key = nil)
|
325
|
-
room = get_room_id room if room.start_with? '#'
|
326
|
-
|
327
|
-
if type && key
|
328
|
-
make_request(
|
329
|
-
:get,
|
330
|
-
"/rooms/#{room}/state/#{type}/#{key}"
|
331
|
-
).parsed_response
|
332
|
-
elsif type
|
333
|
-
make_request(:get, "/rooms/#{room}/state/#{type}").parsed_response
|
334
|
-
else
|
335
|
-
make_request(:get, "/rooms/#{room}/state").parsed_response
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
# Sends a message to the server informing it about a user having started
|
340
|
-
# or stopped typing.
|
341
|
-
#
|
342
|
-
# @param room [String] The affected room.
|
343
|
-
# @param user [String] The user that started or stopped typing.
|
344
|
-
# @param typing [Boolean] Whether the user is typing.
|
345
|
-
# @param duration [Fixnum] How long the user will be typing for
|
346
|
-
# (in milliseconds).
|
347
|
-
# @return [Boolean] `true` if the message sent successfully, otherwise
|
348
|
-
# `false`.
|
349
|
-
def send_typing(room, user, typing = true, duration = 30_000)
|
350
|
-
room = get_room_id room if room.start_with? '#'
|
351
|
-
|
352
|
-
content = { typingState: { typing: typing, timeout: duration } }
|
353
|
-
|
354
|
-
make_request(
|
355
|
-
:put,
|
356
|
-
"/rooms/#{room}/typing/#{user}",
|
357
|
-
content: content
|
358
|
-
).code == 200
|
106
|
+
# Performs a whois lookup on the specified user.
|
107
|
+
# @param user [String] The user to look up.
|
108
|
+
# @return [Hash] Information about the user.
|
109
|
+
def whois(user)
|
110
|
+
make_request(:get, "/admin/whois/#{user}").parsed_response
|
359
111
|
end
|
360
112
|
|
361
113
|
# Synchronize with the latest state on the server.
|
@@ -383,275 +135,71 @@ module Chatrix
|
|
383
135
|
options[:since] = since if since
|
384
136
|
options[:set_presence] = 'offline' unless set_presence
|
385
137
|
options[:timeout] = timeout if timeout
|
386
|
-
|
387
|
-
if filter.is_a? Integer
|
388
|
-
options[:filter] = filter
|
389
|
-
elsif filter.is_a? Hash
|
390
|
-
options[:filter] = URI.encode filter.to_json
|
391
|
-
end
|
138
|
+
options[:filter] = parse_filter filter
|
392
139
|
|
393
140
|
make_request(:get, '/sync', params: options).parsed_response
|
394
141
|
end
|
395
142
|
|
396
|
-
#
|
397
|
-
#
|
398
|
-
# @param
|
399
|
-
# @
|
400
|
-
|
401
|
-
|
402
|
-
# @return [String] The ID of the room that was joined is returned.
|
403
|
-
def join(room, third_party_signed = nil)
|
404
|
-
if third_party_signed
|
405
|
-
make_request(
|
406
|
-
:post,
|
407
|
-
"/join/#{room}",
|
408
|
-
content: { third_party_signed: third_party_signed }
|
409
|
-
)['room_id']
|
410
|
-
else
|
411
|
-
make_request(:post, "/join/#{room}")['room_id']
|
412
|
-
end
|
143
|
+
# Gets the definition for a filter.
|
144
|
+
# @param user [String] The user that has the filter.
|
145
|
+
# @param filter [String] The ID of the filter to get.
|
146
|
+
# @return [Hash] The filter definition.
|
147
|
+
def get_filter(user, filter)
|
148
|
+
make_request(:get, "/user/#{user}/filter/#{filter}").parsed_response
|
413
149
|
end
|
414
150
|
|
415
|
-
#
|
416
|
-
#
|
417
|
-
# @param
|
418
|
-
# @
|
419
|
-
|
420
|
-
|
421
|
-
# otherwise `false`.
|
422
|
-
#
|
423
|
-
# @example Banning a spammer
|
424
|
-
# ban('#haven:matrix.org', '@spammer:spam.com', 'Spamming the room')
|
425
|
-
def ban(room, user, reason)
|
426
|
-
room = get_room_id room if room.start_with? '#'
|
427
|
-
make_request(
|
428
|
-
:post,
|
429
|
-
"/rooms/#{room}/ban",
|
430
|
-
content: { reason: reason, user_id: user }
|
431
|
-
).code == 200
|
151
|
+
# Uploads a filter to the server.
|
152
|
+
# @param user [String] The user to upload the filter as.
|
153
|
+
# @param filter [Hash] The filter definition.
|
154
|
+
# @return [String] The ID of the created filter.
|
155
|
+
def create_filter(user, filter)
|
156
|
+
make_request(:post, "/user/#{user}/filter", content: filter)['filter_id']
|
432
157
|
end
|
433
158
|
|
434
|
-
#
|
435
|
-
#
|
436
|
-
#
|
437
|
-
# @
|
438
|
-
#
|
439
|
-
|
440
|
-
|
441
|
-
make_request(:post, "/rooms/#{room}/forget").code == 200
|
442
|
-
end
|
443
|
-
|
444
|
-
# Kicks a user from a room.
|
445
|
-
#
|
446
|
-
# This does not ban the user, they can rejoin unless the room is
|
447
|
-
# invite-only, in which case they need a new invite to join back.
|
448
|
-
#
|
449
|
-
# @param room [String] The room to kick the user from.
|
450
|
-
# @param user [String] The user to kick.
|
451
|
-
# @param reason [String] The reason for the kick.
|
452
|
-
# @return [Boolean] `true` if the user was successfully kicked,
|
453
|
-
# otherwise `false`.
|
454
|
-
#
|
455
|
-
# @example Kicking an annoying user
|
456
|
-
# kick('#fun:matrix.org', '@anon:4chan.org', 'Bad cropping')
|
457
|
-
def kick(room, user, reason)
|
458
|
-
room = get_room_id room if room.start_with? '#'
|
459
|
-
make_request(
|
460
|
-
:post,
|
461
|
-
"/rooms/#{room}/kick",
|
462
|
-
content: { reason: reason, user_id: user }
|
463
|
-
).code == 200
|
464
|
-
end
|
465
|
-
|
466
|
-
# Leaves a room (but does not forget about it).
|
467
|
-
#
|
468
|
-
# @param room [String] The room to leave.
|
469
|
-
# @return [Boolean] `true` if the room was left successfully,
|
470
|
-
# otherwise `false`.
|
471
|
-
def leave(room)
|
472
|
-
room = get_room_id room if room.start_with? '#'
|
473
|
-
make_request(:post, "/rooms/#{room}/leave").code == 200
|
474
|
-
end
|
475
|
-
|
476
|
-
# Unbans a user from a room.
|
477
|
-
#
|
478
|
-
# @param room [String] The room to unban the user from.
|
479
|
-
# @param user [String] The user to unban.
|
480
|
-
# @return [Boolean] `true` if the user was successfully unbanned,
|
481
|
-
# otherwise `false`.
|
482
|
-
def unban(room, user)
|
483
|
-
room = get_room_id room if room.start_with? '#'
|
159
|
+
# Performs a full text search on the server.
|
160
|
+
# @param from [String] Where to return events from, if given. This can be
|
161
|
+
# obtained from previous calls to {#search}.
|
162
|
+
# @param options [Hash] Search options, see the official documentation
|
163
|
+
# for details on how to structure this.
|
164
|
+
# @return [Hash] the search results.
|
165
|
+
def search(from: nil, options: {})
|
484
166
|
make_request(
|
485
|
-
:post,
|
486
|
-
|
487
|
-
content: { user_id: user }
|
488
|
-
).code == 200
|
489
|
-
end
|
490
|
-
|
491
|
-
# Performs a login attempt.
|
492
|
-
#
|
493
|
-
# @note A successful login will update the {#access_token access_token}
|
494
|
-
# to the new one returned from the login response.
|
495
|
-
#
|
496
|
-
# @param method [String] The method to use for logging in.
|
497
|
-
# For user/password combination, this should be `m.login.password`.
|
498
|
-
# @param options [Hash{String=>String}] Options to pass for logging in.
|
499
|
-
# For a password login, this should contain a key `:user` for the
|
500
|
-
# username, and a key `:password` for the password.
|
501
|
-
# @return [Hash] The response from the server. A successful login will
|
502
|
-
# return a hash containing the user id, access token, and homeserver.
|
503
|
-
#
|
504
|
-
# @example Logging in with username and password
|
505
|
-
# login('m.login.password', user: '@snoo:reddit.com', password: 'hunter2')
|
506
|
-
def login(method, options = {})
|
507
|
-
response = make_request(
|
508
|
-
:post,
|
509
|
-
'/login',
|
510
|
-
content: { type: method }.merge!(options)
|
511
|
-
)
|
512
|
-
|
513
|
-
# Update the local access token
|
514
|
-
@access_token = response['access_token']
|
515
|
-
|
516
|
-
response.parsed_response
|
517
|
-
end
|
518
|
-
|
519
|
-
# Logs out.
|
520
|
-
#
|
521
|
-
# @note This will **invalidate the access token**. It will no longer be
|
522
|
-
# valid for API calls.
|
523
|
-
#
|
524
|
-
# @return [Hash] The response from the server (an empty hash).
|
525
|
-
def logout
|
526
|
-
response = make_request :post, '/logout'
|
527
|
-
|
528
|
-
# A successful logout means the access token has been invalidated
|
529
|
-
@access_token = nil
|
530
|
-
|
531
|
-
response.parsed_response
|
532
|
-
end
|
533
|
-
|
534
|
-
# Gets a new access token to use for API calls when the current one
|
535
|
-
# expires.
|
536
|
-
#
|
537
|
-
# @note On success, the internal {#access_token access_token} will be
|
538
|
-
# updated automatically for use in subsequent API calls.
|
539
|
-
#
|
540
|
-
# @param token [String,nil] The `refresh_token` to provide for the server
|
541
|
-
# when requesting a new token. If not set, the internal refresh and
|
542
|
-
# access tokens will be used.
|
543
|
-
# @return [Hash] The response hash from the server will contain the new
|
544
|
-
# access token and a refresh token to use the next time a new access
|
545
|
-
# token is needed.
|
546
|
-
def refresh(token = nil)
|
547
|
-
refresh_token = token || @refresh_token || @access_token
|
548
|
-
|
549
|
-
response = make_request(
|
550
|
-
:post,
|
551
|
-
'/tokenrefresh',
|
552
|
-
content: { refresh_token: refresh_token }
|
553
|
-
)
|
554
|
-
|
555
|
-
@access_token = response['access_token']
|
556
|
-
@refresh_token = response['refresh_token']
|
557
|
-
|
558
|
-
response.parsed_response
|
167
|
+
:post, '/search', params: { next_batch: from }, content: options
|
168
|
+
).parsed_response
|
559
169
|
end
|
560
170
|
|
561
|
-
# Gets the
|
562
|
-
#
|
563
|
-
# @
|
564
|
-
|
565
|
-
|
566
|
-
# @todo The official documentation on this endpoint is weird, what does
|
567
|
-
# this really do?
|
568
|
-
def get_presence_list(user)
|
569
|
-
make_request(:get, "/presence/list/#{user}").parsed_response
|
171
|
+
# Gets information about the TURN server. Contains credentials and
|
172
|
+
# server URIs for connecting.
|
173
|
+
# @return [Hash] TURN server details.
|
174
|
+
def turn_server
|
175
|
+
make_request(:get, '/voip/turnServer').parsed_response
|
570
176
|
end
|
571
177
|
|
572
|
-
#
|
573
|
-
#
|
574
|
-
# @param user [String] The user whose list to modify.
|
575
|
-
# @param data [Hash{String=>Array<String>}] Contains two arrays,
|
576
|
-
# `invite` and `drop`. Users listed in the `invite` array will be
|
577
|
-
# invited to join the presence list. Users listed in the `drop` array
|
578
|
-
# will be removed from the presence list.
|
579
|
-
# Note that both arrays are not required but at least one must be
|
580
|
-
# present.
|
581
|
-
# @return [Boolean] `true` if the list was successfully updated,
|
582
|
-
# otherwise `false`.
|
178
|
+
# Helper method for performing requests to the homeserver.
|
583
179
|
#
|
584
|
-
# @
|
585
|
-
#
|
586
|
-
#
|
587
|
-
#
|
588
|
-
#
|
589
|
-
# drop: ['@enemy:other.org']
|
590
|
-
# }
|
591
|
-
# )
|
592
|
-
def update_presence_list(user, data)
|
593
|
-
make_request(
|
594
|
-
:post,
|
595
|
-
"/presence/list/#{user}",
|
596
|
-
content: { presence_diff: data }
|
597
|
-
).code == 200
|
598
|
-
end
|
599
|
-
|
600
|
-
# Gets the presence status of a user.
|
180
|
+
# @param method [Symbol] HTTP request method to use. Use only symbols
|
181
|
+
# available as keys in {METHODS}.
|
182
|
+
# @param path [String] The API path to query, relative to the base
|
183
|
+
# API path, eg. `/login`.
|
184
|
+
# @param opts [Hash] Additional request options.
|
601
185
|
#
|
602
|
-
# @
|
603
|
-
#
|
604
|
-
#
|
605
|
-
#
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
# Updates the presence status of a user.
|
186
|
+
# @option opts [Hash] :params Additional parameters to include in the
|
187
|
+
# query string (part of the URL, not put in the request body).
|
188
|
+
# @option opts [Hash,#read] :content Content to put in the request body.
|
189
|
+
# If set, must be a Hash or a stream object.
|
190
|
+
# @option opts [Hash{String => String}] :headers Additional headers to
|
191
|
+
# include in the request.
|
192
|
+
# @option opts [String,nil] :base If this is set, it will be used as the
|
193
|
+
# base URI for the request instead of the default (`@base_uri`).
|
611
194
|
#
|
612
|
-
# @
|
613
|
-
# valid for can have their presence updated.
|
195
|
+
# @yield [fragment] HTTParty will call the block during the request.
|
614
196
|
#
|
615
|
-
# @
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
# @return [Boolean] `true` if the presence was updated successfully,
|
620
|
-
# otherwise `false`.
|
621
|
-
def update_presence_status(user, status, message = nil)
|
622
|
-
content = { presenceState: { presence: status } }
|
623
|
-
|
624
|
-
content[:presenceState][:status_msg] = message if message
|
625
|
-
|
626
|
-
make_request(
|
627
|
-
:put,
|
628
|
-
"/presence/#{user}/status",
|
629
|
-
content: content
|
630
|
-
).code == 200
|
631
|
-
end
|
197
|
+
# @return [HTTParty::Response] The HTTParty response object.
|
198
|
+
def make_request(method, path, opts = {}, &block)
|
199
|
+
path = (opts[:base] || @base_uri) + URI.encode(path)
|
200
|
+
options = make_options opts[:params], opts[:content], opts[:headers]
|
632
201
|
|
633
|
-
|
634
|
-
#
|
635
|
-
# The `start` and `end` values returned in the result can be passed to
|
636
|
-
# `from` and `to`, for pagination purposes.
|
637
|
-
#
|
638
|
-
# @param from [String] The stream token to start looking from.
|
639
|
-
# @param to [String] The stream token to stop looking at.
|
640
|
-
# @param limit [Fixnum] Maximum number of results to return in one request.
|
641
|
-
# @param direction ['f', 'b'] Direction to look in.
|
642
|
-
# @return [Hash] Hash containing the list of rooms (in the `chunk` value),
|
643
|
-
# and pagination parameters `start` and `end`.
|
644
|
-
def rooms(from: 'START', to: 'END', limit: 10, direction: 'b')
|
645
|
-
make_request(
|
646
|
-
:get,
|
647
|
-
'/publicRooms',
|
648
|
-
params: {
|
649
|
-
from: start,
|
650
|
-
to: to,
|
651
|
-
limit: limit,
|
652
|
-
dir: direction
|
653
|
-
}
|
654
|
-
).parsed_response
|
202
|
+
parse_response METHODS[method].call(path, options, &block)
|
655
203
|
end
|
656
204
|
|
657
205
|
private
|
@@ -663,37 +211,27 @@ module Chatrix
|
|
663
211
|
#
|
664
212
|
# @param params [Hash{String=>String},nil] Query parameters to add to
|
665
213
|
# the options hash.
|
666
|
-
# @param content [Hash,nil] Request content
|
214
|
+
# @param content [Hash,#read,nil] Request content. Can be a hash,
|
215
|
+
# stream, or `nil`.
|
667
216
|
# @return [Hash] Options hash ready to be passed into a server request.
|
668
|
-
def
|
669
|
-
|
670
|
-
query
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
options[:body] = content.to_json if content.is_a? Hash
|
675
|
-
|
676
|
-
options
|
217
|
+
def make_options(params, content, headers = {})
|
218
|
+
{ headers: headers }.tap do |o|
|
219
|
+
o[:query] = @access_token ? { access_token: @access_token } : {}
|
220
|
+
o[:query].merge!(params) if params.is_a? Hash
|
221
|
+
o.merge! make_body content
|
222
|
+
end
|
677
223
|
end
|
678
224
|
|
679
|
-
#
|
680
|
-
#
|
681
|
-
#
|
682
|
-
#
|
683
|
-
# @
|
684
|
-
#
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
# @yield [fragment] HTTParty will call the block during the request.
|
690
|
-
#
|
691
|
-
# @return [HTTParty::Response] The HTTParty response object.
|
692
|
-
def make_request(method, path, params: nil, content: nil, &block)
|
693
|
-
path = @base_uri + URI.encode(path)
|
694
|
-
options = make_request_options params, content
|
695
|
-
|
696
|
-
parse_response METHODS[method].call(path, options, &block)
|
225
|
+
# Create a hash with body content based on the type of `content`.
|
226
|
+
# @param content [Hash,#read,Object] Some kind of content to put into
|
227
|
+
# the request body. Can be a Hash, stream object, or other kind of
|
228
|
+
# object.
|
229
|
+
# @return [Hash{Symbol => Object}] A hash with the relevant body key
|
230
|
+
# and value.
|
231
|
+
def make_body(content)
|
232
|
+
key = content.respond_to?(:read) ? :body_stream : :body
|
233
|
+
value = content.is_a? Hash ? content.to_json : content
|
234
|
+
{ key => value }
|
697
235
|
end
|
698
236
|
|
699
237
|
# Parses a HTTParty Response object and returns it if it was successful.
|
@@ -702,29 +240,34 @@ module Chatrix
|
|
702
240
|
# @return [HTTParty::Response] The same response object that was passed
|
703
241
|
# in, if the request was successful.
|
704
242
|
#
|
243
|
+
# @raise [RequestError] If a `400` response code was returned from the
|
244
|
+
# request.
|
245
|
+
# @raise [AuthenticationError] If a `401` response code was returned
|
246
|
+
# from the request.
|
705
247
|
# @raise [ForbiddenError] If a `403` response code was returned from the
|
706
248
|
# request.
|
707
249
|
# @raise [NotFoundError] If a `404` response code was returned from the
|
708
250
|
# request.
|
709
|
-
# @raise [
|
710
|
-
#
|
711
|
-
#
|
712
|
-
#
|
251
|
+
# @raise [RateLimitError] If a `429` response code was returned from the
|
252
|
+
# request.
|
253
|
+
# @raise [ApiError] If an unknown response code was returned from the
|
254
|
+
# request.
|
713
255
|
def parse_response(response)
|
714
256
|
case response.code
|
715
257
|
when 200 # OK
|
716
258
|
response
|
717
|
-
when 403 # Forbidden
|
718
|
-
raise ForbiddenError, 'You do not have access to that resource'
|
719
|
-
when 404 # Not found
|
720
|
-
raise NotFoundError, 'The specified resource could not be found'
|
721
259
|
else
|
722
|
-
|
723
|
-
|
724
|
-
end
|
725
|
-
|
726
|
-
raise ApiError, 'Unknown API error occurred when carrying out request'
|
260
|
+
handler = ERROR_HANDLERS[response.code]
|
261
|
+
raise handler.first.new(response.parsed_response), handler.last
|
727
262
|
end
|
728
263
|
end
|
264
|
+
|
265
|
+
# Parses a filter object for use in a query string.
|
266
|
+
# @param filter [String,Hash] The filter object to parse.
|
267
|
+
# @return [String] Query-friendly filter object. Or the `filter`
|
268
|
+
# parameter as-is if it failed to parse.
|
269
|
+
def parse_filter(filter)
|
270
|
+
filter.is_a?(Hash) ? URI.encode(filter.to_json) : filter
|
271
|
+
end
|
729
272
|
end
|
730
273
|
end
|