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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4d840405e59e950232bf4a82a906c7395d3c581
4
- data.tar.gz: 26d630e954f5cc66b8bdd9dd5f01132ef6ca84af
3
+ metadata.gz: d46105489cd9f2748b15ea3a650893af40baad6d
4
+ data.tar.gz: 03d5bc731429c298f6e1d3367efab623c3d34549
5
5
  SHA512:
6
- metadata.gz: bd3aa274eb0b3da53e24f8cfb95b354025513a421fd094172a912791de0962e171f9817906bb811bdc125047f415173c78a612ca7f9179bc1fc34ed710cf9e51
7
- data.tar.gz: 61275fb3232b31c24b3e0d215107655ecbd65d4836b04d97b3d8c6a79c7896100428f59a509eb1e27c863597c08dc2455e265e75d53b7543b67ef48cecb39306
6
+ metadata.gz: 6bd7b7098a58b632bf67e48cd5d4b7ae4685bcbb53c8876a7a210a3450980221f247b931803f434198c111d943f42cdd9d1b5d5aca7ee2b1ac88048277e9b619
7
+ data.tar.gz: 5a26a6d99f8d66905a50de48062eddd6880b176c7ca3ac61fbc574964651980d847ac25f9457d4c4775b7e04ff20832d31904bd7ef86d789b9310080db580723
data/.gitignore CHANGED
@@ -165,3 +165,6 @@ GitHub.sublime-settings
165
165
  ### Custom rules ###
166
166
  # Ignore access token file
167
167
  access_token.txt
168
+
169
+ # Ignore API dumps
170
+ dumps/
@@ -5,4 +5,10 @@ language: ruby
5
5
  rvm:
6
6
  - 2.3.1
7
7
 
8
+ bundler_args: --without development doc
9
+
8
10
  before_install: gem install bundler -v 1.12.5
11
+
12
+ addons:
13
+ code_climate:
14
+ repo_token: 97786ceac8f7388a6ac89408a63f98454678567c780deef82377f8e66b9f7cd6
data/Gemfile CHANGED
@@ -2,3 +2,22 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in ratrix.gemspec
4
4
  gemspec
5
+
6
+ group :development do
7
+ gem 'pry', '~> 0.10'
8
+ end
9
+
10
+ group :test do
11
+ gem 'rake', '~> 11.0'
12
+ gem 'rspec', '~> 3.0'
13
+ gem 'codeclimate-test-reporter', '~> 0.5', require: false
14
+ end
15
+
16
+ group :development, :test do
17
+ gem 'rubocop', '~> 0.40.0'
18
+ end
19
+
20
+ group :doc do
21
+ gem 'yard', '~> 0.8'
22
+ gem 'redcarpet', '~> 3.3'
23
+ end
data/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  chatrix
2
2
  =======
3
3
 
4
+ [![Gem version][gem-badge-img]][gem-badge]
5
+ [![Dependency status][gemnasium-img]][gemnasium]
6
+ [![Build status][travis-img]][travis]
7
+ [![Code climate][cc-img]][cc]
8
+ [![Coverage][coverage-img]][coverage]
9
+ [![Inline docs][inch-img]][inch]
10
+
4
11
  A Ruby implementation of the [Matrix][matrix] API.
5
12
 
6
13
  ## License
@@ -27,9 +34,10 @@ Or install it yourself as:
27
34
  $ gem install chatrix
28
35
 
29
36
  ## Usage
30
-
37
+ ### Using the API class `Chatrix::Matrix`
31
38
  This implementation is currently very basic and exposes all the endpoints
32
- in the `Matrix` class. Example usage:
39
+ in the `Matrix` class and some sub-classes available as attributes on the
40
+ main `Matrix` class. Example usage:
33
41
 
34
42
  ```ruby
35
43
  # Uses the standard matrix.org homeserver
@@ -37,14 +45,65 @@ api = Chatrix::Matrix.new 'my secret token'
37
45
 
38
46
  # Join may raise ForbiddenError if client does not have permission
39
47
  # to join the room
40
- if id = api.join '#myroom:myserver.org'
41
- api.send_message id, 'Hello everyone!'
48
+ if id = api.rooms.actions.join '#myroom:myserver.org'
49
+ api.rooms.actions.send_message id, 'Hello everyone!'
42
50
  end
43
51
  ```
44
52
 
45
53
  Currently there is no asynchronous calls or built-in handling of
46
54
  rate-limiting.
47
55
 
56
+ All of the available endpoints *should* be available, if there are some
57
+ missing that are not deprecated in the official API docs, please open an
58
+ issue about it or add them yourself and submit a PR!
59
+
60
+ ### Using the client class `Chatrix::Client`
61
+ The client class works as a wrapper around the raw API calls to make working
62
+ with the API a little easier. It uses the [`wisper`][wisper] gem to broadcast
63
+ state changes.
64
+
65
+ ```ruby
66
+ # When setting up with an access token, there is no way to obtain your own
67
+ # user ID through the API, so it has to be supplied manually.
68
+ client = Chatrix::Client.new 'my token', 'my user id'
69
+
70
+ # This will spawn a new thread that continously syncs against the homeserver
71
+ # to check for new events. It can be stopped by calling Client#stop_syncing.
72
+ client.start_syncing
73
+
74
+ # Set up a listener for when a message arrives
75
+ client.on(:room_message) do |room, message|
76
+ puts "(#{room}) #{message.sender}: #{message.body}"
77
+ end
78
+
79
+ # We can also listen to messages in a specific room by subscribing to the
80
+ # timeline of that room.
81
+ myroom = client.get_room '#myroom:myserver.org'
82
+ myroom.timeline.on(:message) do |room, message|
83
+ # Reply with a "Pong!" if someone sends a message starting
84
+ # with the word "ping", but don't reply to ourselves.
85
+ if message.body.match(/^\bping\b/i) && message.sender != client.me
86
+ room.messaging.send_message 'Pong!'
87
+ end
88
+ end
89
+
90
+ # Permissions and room actions can be used with relative ease.
91
+ myroom.timeline.on(:message) do |room, message|
92
+ if message.body.match(/^!kickme$/i) && message.sender != client.me
93
+ if room.state.permissions.can? message.sender, :kick
94
+ room.admin.kick message.sender, 'They asked for it'
95
+ else
96
+ room.messaging.send_message "You do not have kick privileges, #{message.sender}"
97
+ end
98
+ end
99
+ end
100
+ ```
101
+
102
+ When subscribing to an event, make sure to
103
+ [not return inside the block][no-return-blocks].
104
+
105
+ *The client is currently a WIP.*
106
+
48
107
  ## Development
49
108
 
50
109
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -61,3 +120,19 @@ Bug reports and pull requests are welcome on [GitHub][issues].
61
120
  [issues]: https://github.com/Sharparam/chatrix/issues
62
121
  [matrix]: http://matrix.org
63
122
  [license-url]: http://opensource.org/licenses/MIT
123
+
124
+ [gem-badge]: https://badge.fury.io/rb/chatrix
125
+ [gem-badge-img]: https://badge.fury.io/rb/chatrix.svg
126
+ [gemnasium]: https://gemnasium.com/github.com/Sharparam/chatrix
127
+ [gemnasium-img]: https://gemnasium.com/badges/github.com/Sharparam/chatrix.svg
128
+ [travis]: https://travis-ci.org/Sharparam/chatrix
129
+ [travis-img]: https://travis-ci.org/Sharparam/chatrix.svg?branch=master
130
+ [cc]: https://codeclimate.com/github/Sharparam/chatrix
131
+ [cc-img]: https://codeclimate.com/github/Sharparam/chatrix/badges/gpa.svg
132
+ [coverage]: https://codeclimate.com/github/Sharparam/chatrix/coverage
133
+ [coverage-img]: https://codeclimate.com/github/Sharparam/chatrix/badges/coverage.svg
134
+ [inch]: http://inch-ci.org/github/Sharparam/chatrix
135
+ [inch-img]: http://inch-ci.org/github/Sharparam/chatrix.svg?branch=master
136
+
137
+ [wisper]: https://github.com/krisleech/wisper
138
+ [no-return-blocks]: http://product.reverb.com/2015/02/28/the-strange-case-of-wisper-and-ruby-blocks-behaving-like-procs/
@@ -23,12 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ['lib']
24
24
 
25
25
  spec.add_runtime_dependency 'httparty', '~> 0.13'
26
+ spec.add_runtime_dependency 'wisper', '~> 1.6'
26
27
 
27
28
  spec.add_development_dependency 'bundler', '~> 1.12'
28
- spec.add_development_dependency 'rake', '~> 10.0'
29
- spec.add_development_dependency 'rspec', '~> 3.0'
30
- spec.add_development_dependency 'pry', '~> 0.10'
31
- spec.add_development_dependency 'yard', '~> 0.8'
32
- spec.add_development_dependency 'redcarpet', '~> 3.3'
33
- spec.add_development_dependency 'rubocop', '~> 0.40.0'
34
29
  end
@@ -1,6 +1,7 @@
1
1
  require 'chatrix/version'
2
2
  require 'chatrix/errors'
3
3
  require 'chatrix/matrix'
4
+ require 'chatrix/client'
4
5
 
5
6
  # Encapsulates all gem functionality.
6
7
  module Chatrix
@@ -0,0 +1,21 @@
1
+ module Chatrix
2
+ module Api
3
+ # Wraps a matrix instance for use in calling API endpoints.
4
+ class ApiComponent
5
+ # Initializes a new ApiComponent instance.
6
+ def initialize(matrix)
7
+ @matrix = matrix
8
+ end
9
+
10
+ protected
11
+
12
+ # Makes an API request using the underlying Matrix instance.
13
+ # @param args Parameters to pass to Matrix#make_request.
14
+ # @yield (see Matrix#make_request)
15
+ # @return (see Matrix#make_request)
16
+ def make_request(*args, &block)
17
+ @matrix.make_request(*args, &block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ module Chatrix
2
+ module Api
3
+ # Contains methods for accessing the media endpoints of a server.
4
+ class Media < ApiComponent
5
+ # API path for media operations.
6
+ #
7
+ # Because for some reason this one is different.
8
+ MEDIA_PATH = '/_matrix/media/r0'.freeze
9
+
10
+ # Initializes a new Media instance.
11
+ # @param matrix [Matrix] The matrix API instance.
12
+ def initialize(matrix)
13
+ super
14
+ @media_uri = @matrix.homeserver + MEDIA_PATH
15
+ end
16
+
17
+ # Download media from the server.
18
+ # @param server [String] The server component of an `mxc://` URL.
19
+ # @param id [String] The media ID of the `mxc://` URL (the path).
20
+ # @return [HTTParty::Response] The HTTParty response object for the
21
+ # request. Use the response object to inspect the returned data.
22
+ def download(server, id)
23
+ make_request(
24
+ :get,
25
+ "/download/#{server}/#{id}",
26
+ headers: { 'Accept' => '*/*' },
27
+ base: @media_uri
28
+ )
29
+ end
30
+
31
+ # Download the thumbnail for a media resource.
32
+ # @param server [String] The server component of an `mxc://` URL.
33
+ # @param id [String] The media ID of the `mxc://` URL (the path).
34
+ # @param width [Fixnum] Desired width of the thumbnail.
35
+ # @param height [Fixnum] Desired height of the thumbnail.
36
+ # @param method ['scale', 'crop'] Desired resizing method.
37
+ # @return [HTTParty::Response] The HTTParty response object for the
38
+ # request. Use the response object to inspect the returned data.
39
+ def get_thumbnail(server, id, width, height, method = 'scale')
40
+ make_request(
41
+ :get,
42
+ "/thumbnail/#{server}/#{id}",
43
+ headers: { 'Accept' => 'image/jpeg, image/png' },
44
+ params: { width: width, height: height, method: method },
45
+ base: @media_uri
46
+ )
47
+ end
48
+
49
+ # Uploads a new media file to the server.
50
+ # @param type [String] The `Content-Type` of the media being uploaded.
51
+ # @param path [String] Path to a file to upload.
52
+ # @return [String] The MXC URL for the uploaded object (`mxc://...`).
53
+ def upload(type, path)
54
+ File.open(path, 'r') do |file|
55
+ make_request(
56
+ :post, '/upload',
57
+ headers: {
58
+ 'Content-Type' => type, 'Content-Length' => file.size.to_s,
59
+ 'Transfer-Encoding' => 'chunked'
60
+ },
61
+ content: file, base: @media_uri
62
+ )['content_uri']
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,107 @@
1
+ module Chatrix
2
+ module Api
3
+ # Contains methods for accessing the "push" endpoints on the server.
4
+ #
5
+ # Refer to the official documentation for more information about these.
6
+ class Push < ApiComponent
7
+ # Get all active pushers for the current user.
8
+ # @return [Hash] A list of pushers for the user.
9
+ def pushers
10
+ make_request(:get, '/pushers').parsed_response
11
+ end
12
+
13
+ # Add, remove, or modify pushers for the current user.
14
+ # @param pusher [Hash] Pusher information.
15
+ # @return [Boolean] `true` if the operation was carried out successfully,
16
+ # otherwise `false`.
17
+ def modify_pusher(pusher)
18
+ make_request(:post, '/pushers/set', content: pusher).code == 200
19
+ end
20
+
21
+ # Gets all the push rules defined for this user, optionally limited
22
+ # to a specific scope.
23
+ # @param scope [String,nil] If set, only rules within that scope will
24
+ # be returned.
25
+ # @return [Hash] A list of push rules for the user.
26
+ def get_rules(scope = nil)
27
+ path = scope ? "/pushrules/#{scope}" : '/pushrules'
28
+ make_request(:get, path).parsed_response
29
+ end
30
+
31
+ # Gets a specific rule.
32
+ #
33
+ # @param scope [String] The scope to access.
34
+ # @param kind [String] The kind of rule.
35
+ # @param id [String] The rule to get.
36
+ # @return [Hash] Details about the rule.
37
+ def get_rule(scope, kind, id)
38
+ make_request(:get, "/pushrules/#{scope}/#{kind}/#{id}").parsed_response
39
+ end
40
+
41
+ # Deletes a push rule.
42
+ #
43
+ # @param (see #get_rule)
44
+ # @param id [String] The rule ID to delete.
45
+ # @return [Boolean] `true` if the rule was deleted successfully,
46
+ # otherwise `false`.
47
+ def delete_rule(scope, kind, id)
48
+ make_request(:delete, "/pushrules/#{scope}/#{kind}/#{id}").code == 200
49
+ end
50
+
51
+ # Adds or change a push rule.
52
+ #
53
+ # @param (see #get_rule)
54
+ # @param id [String] The rule to add or change.
55
+ # @param opts [Hash{Symbol => String}] Additional options to pass in
56
+ # the query string. Note that only one of the options should be set.
57
+ #
58
+ # @option opts [String] :before Add this rule as the next-most
59
+ # important rule with respect to the rule specified here.
60
+ # @option opts [String] :after Add this rule as the next-less
61
+ # important rule with respect to the rule specified here.
62
+ #
63
+ # @return [Boolean] `true` if the operation was carried out successfully,
64
+ # otherwise `false`.
65
+ def add_rule(scope, kind, id, rule, opts = {})
66
+ path = "/pushrules/#{scope}/#{kind}/#{id}"
67
+ make_request(:put, path, params: opts, content: rule).code == 200
68
+ end
69
+
70
+ # Sets or modifies the actions for a rule.
71
+ #
72
+ # @param (see #get_rule)
73
+ # @param id [String] The rule ID to modify actions for.
74
+ # @param actions [Hash] Actions to perform when the conditions for
75
+ # this rule are met.
76
+ # @return [Boolean] `true` if the actions were modified successfully,
77
+ # otherwise `false`.
78
+ def set_actions(scope, kind, id, actions)
79
+ path = "/pushrules/#{scope}/#{kind}/#{id}/actions"
80
+ make_request(:put, path, content: actions).code == 200
81
+ end
82
+
83
+ # Sets the enabled state of a rule, the default is to enable it.
84
+ #
85
+ # @param (see #get_rule)
86
+ # @param id [String] The rule ID to modify the enable state for.
87
+ # @param enabled [Boolean] Whether to enable or disable the rule.
88
+ # @return [Boolean] `true` if the state was modified successfully,
89
+ # otherwise `false`.
90
+ def enable(scope, kind, id, enabled = true)
91
+ path = "/pushrules/#{scope}/#{kind}/#{id}/actions"
92
+ make_request(:put, path, content: { enabled: enabled }).code == 200
93
+ end
94
+
95
+ # Disable a push rule.
96
+ # This is essentially an alias for #enable with the last parameter
97
+ # as `false`.
98
+ #
99
+ # @param (see #get_rule)
100
+ # @param id [String] The rule ID to disable.
101
+ # @return [Boolean] `true` if the rule was disabled, otherwise `false`.
102
+ def disable(scope, kind, id)
103
+ enable(scope, kind, id, false)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,267 @@
1
+ module Chatrix
2
+ module Api
3
+ # Contains methods for performing actions on rooms.
4
+ class RoomActions < ApiComponent
5
+ # Initializes a new RoomActions instance.
6
+ # @param matrix [Matrix] The matrix API instance.
7
+ def initialize(matrix)
8
+ super
9
+ @transaction_id = 0
10
+ end
11
+
12
+ # Creates a new room on the server.
13
+ #
14
+ # @param data [Hash] Additional data to send when creating the room.
15
+ # None of these are required when creating a new room.
16
+ #
17
+ # @option data [Array<String>] :invite A list of user IDs to invite
18
+ # when the room has been created.
19
+ # @option data [String] :name A custom name to give the room.
20
+ # @option data ['public', 'private'] :visibility The visibility to
21
+ # create the room with.
22
+ # @option data [Array<Hash>] :invite_3pid A list of third-party
23
+ # ID objects to invite to the room.
24
+ # @option data [String] :topic A topic to set for the room.
25
+ # @option data ['public_chat', 'trusted_private_chat', 'private_chat']
26
+ # :preset Sets various state events based on a preset.
27
+ #
28
+ # * **`private_chat`**: `join_rules` is `invite`,
29
+ # `history_visibility` is `shared`.
30
+ # * **`trusted_private_chat`**: `join_rules` is `invite`,
31
+ # `history_visibility` is `shared`, all invited users get the
32
+ # same power level as the room creator.
33
+ # * **`public_chat`**: `join_rules` is `public`,
34
+ # `history_visibility` is `shared`.
35
+ # @option data [Hash{String => Object}] :creation_content Additional data
36
+ # to add to the `'m.room.create'` content.
37
+ # @option data [Array<Hash>] :initial_state A list of state events to
38
+ # set in the room.
39
+ # @option data [String] :room_alias_name The **localpart** of the alias
40
+ # to sets for this room. The localpart is the part of the alias
41
+ # between the "`#`" sign and the "`:host.tld`" ending part. In the
42
+ # alias `#hello:world.org`, the "`hello`" part is the localpart.
43
+ #
44
+ # @return [String] the ID of the created room.
45
+ #
46
+ # @example Create a room with an alias, name, and invited user
47
+ # id = create(
48
+ # room_alias_name: 'foobar',
49
+ # name: 'Foo Bar Baz!',
50
+ # invite: ['@silly:example.org']
51
+ # )
52
+ #
53
+ # puts "Room #{id} created!"
54
+ def create(data = {})
55
+ make_request(:post, '/createRoom', content: data)['room_id']
56
+ end
57
+
58
+ # Joins a room on the homeserver.
59
+ #
60
+ # @param room [String] The room to join.
61
+ # @param third_party_signed [Hash,nil] If provided, the homeserver must
62
+ # verify that it matches a pending `m.room.third_party_invite` event in
63
+ # the room, and perform key validity checking if required by the event.
64
+ # @return [String] The ID of the room that was joined is returned.
65
+ def join(room, third_party_signed = nil)
66
+ if third_party_signed
67
+ make_request(
68
+ :post,
69
+ "/join/#{room}",
70
+ content: { third_party_signed: third_party_signed }
71
+ )['room_id']
72
+ else
73
+ make_request(:post, "/join/#{room}")['room_id']
74
+ end
75
+ end
76
+
77
+ # @overload invite(room, user)
78
+ # Invites a user to a room by ID.
79
+ # @param room [String] The room to invite the user to.
80
+ # @param user [String] The user ID to send the invite to.
81
+ # @return [Boolean] `true` if the user was successfully invited,
82
+ # otherwise `false`.
83
+ # @overload invite(room, data)
84
+ # Invites a user to a room by their 3PID information.
85
+ # @param room [String] The room to invite the user to.
86
+ # @param data [Hash] 3PID info for the user.
87
+ # @return [Boolean] `true` if the user was successfully invited,
88
+ # otherwise `false`.
89
+ def invite(room, data)
90
+ data = { user_id: data } if data.is_a? String
91
+ make_request(:post, "/rooms/#{room}/invite", content: data).code == 200
92
+ end
93
+
94
+ # Leaves a room (but does not forget about it).
95
+ #
96
+ # @param room [String] The room to leave.
97
+ # @return [Boolean] `true` if the room was left successfully,
98
+ # otherwise `false`.
99
+ def leave(room)
100
+ make_request(:post, "/rooms/#{room}/leave").code == 200
101
+ end
102
+
103
+ # Forgets about a room.
104
+ #
105
+ # @param room [String] The room to forget about.
106
+ # @return [Boolean] `true` if the room was forgotten successfully,
107
+ # otherwise `false`.
108
+ def forget(room)
109
+ make_request(:post, "/rooms/#{room}/forget").code == 200
110
+ end
111
+
112
+ # Kicks a user from a room.
113
+ #
114
+ # This does not ban the user, they can rejoin unless the room is
115
+ # invite-only, in which case they need a new invite to join back.
116
+ #
117
+ # @param room [String] The room to kick the user from.
118
+ # @param user [String] The user to kick.
119
+ # @param reason [String] The reason for the kick.
120
+ # @return [Boolean] `true` if the user was successfully kicked,
121
+ # otherwise `false`.
122
+ #
123
+ # @example Kicking an annoying user
124
+ # kick('#fun:matrix.org', '@anon:4chan.org', 'Bad cropping')
125
+ def kick(room, user, reason)
126
+ make_request(
127
+ :post,
128
+ "/rooms/#{room}/kick",
129
+ content: { reason: reason, user_id: user }
130
+ ).code == 200
131
+ end
132
+
133
+ # Kicks and bans a user from a room.
134
+ #
135
+ # @param room [String] The room to ban the user from.
136
+ # @param user [String] The user to ban.
137
+ # @param reason [String] Reason why the ban was made.
138
+ # @return [Boolean] `true` if the ban was carried out successfully,
139
+ # otherwise `false`.
140
+ #
141
+ # @example Banning a spammer
142
+ # ban('#haven:matrix.org', '@spammer:spam.com', 'Spamming the room')
143
+ def ban(room, user, reason)
144
+ make_request(
145
+ :post,
146
+ "/rooms/#{room}/ban",
147
+ content: { reason: reason, user_id: user }
148
+ ).code == 200
149
+ end
150
+
151
+ # Unbans a user from a room.
152
+ #
153
+ # @param room [String] The room to unban the user from.
154
+ # @param user [String] The user to unban.
155
+ # @return [Boolean] `true` if the user was successfully unbanned,
156
+ # otherwise `false`.
157
+ def unban(room, user)
158
+ make_request(:post, "/rooms/#{room}/unban", content: { user_id: user })
159
+ .code == 200
160
+ end
161
+
162
+ # Sends a message object to a room.
163
+ #
164
+ # @param room [String] The room to send to.
165
+ # @param content [Hash] The message content to send.
166
+ # @param type [String] The type of message to send.
167
+ # @return [String] The event ID of the sent message is returned.
168
+ # @see #send_message_type
169
+ # @see #send_message
170
+ # @see #send_emote
171
+ # @see #send_notice
172
+ # @see #send_html
173
+ def send_message_raw(room, content, type = 'm.room.message')
174
+ make_request(
175
+ :put,
176
+ "/rooms/#{room}/send/#{type}/#{@transaction_id += 1}",
177
+ content: content
178
+ )['event_id']
179
+ end
180
+
181
+ # A helper method to send a simple message construct.
182
+ #
183
+ # @param room [String] The room to send the message to.
184
+ # @param content [String] The message to send.
185
+ # @param type [String] The type of message this is.
186
+ # For example: `'m.text'`, `'m.notice'`, `'m.emote'`.
187
+ # @return (see #send_message_raw)
188
+ def send_message(room, content, type = 'm.text')
189
+ send_message_raw room, msgtype: type, body: content
190
+ end
191
+
192
+ # Sends a message formatted using HTML markup.
193
+ #
194
+ # The `body` field in the content will have the HTML stripped out, and is
195
+ # usually presented in clients that don't support the formatting.
196
+ #
197
+ # The `formatted_body` field in the content will contain the actual HTML
198
+ # formatted message (as passed to the `html` parameter).
199
+ #
200
+ # @param room [String] The room to send to.
201
+ # @param html [String] The HTML formatted text to send.
202
+ # @return (see #send_message_raw)
203
+ #
204
+ # @example Sending an HTML message
205
+ # send_html('#html:matrix.org',
206
+ # '<strong>Hello</strong> <em>world</em>!')
207
+ def send_html(room, html)
208
+ send_message_raw(
209
+ room,
210
+ msgtype: 'm.text',
211
+ format: 'org.matrix.custom.html',
212
+ body: html.gsub(%r{</?[^>]*?>}, ''), # TODO: Make this better
213
+ formatted_body: html
214
+ )
215
+ end
216
+
217
+ # Sends a message to the server informing it about a user having started
218
+ # or stopped typing.
219
+ #
220
+ # @param room [String] The affected room.
221
+ # @param user [String] The user that started or stopped typing.
222
+ # @param typing [Boolean] Whether the user is typing.
223
+ # @param duration [Fixnum] How long the user will be typing for
224
+ # (in milliseconds).
225
+ # @return [Boolean] `true` if the message sent successfully, otherwise
226
+ # `false`.
227
+ def send_typing(room, user, typing = true, duration = 30_000)
228
+ make_request(
229
+ :put,
230
+ "/rooms/#{room}/typing/#{user}",
231
+ content: { typingState: { typing: typing, timeout: duration } }
232
+ ).code == 200
233
+ end
234
+
235
+ # Updates the marker for the given receipt type to point to the
236
+ # specified event.
237
+ #
238
+ # @param room [String] The room to update the receipt in.
239
+ # @param event [String] The new event to point the receipt to.
240
+ # @param type [String] The receipt type to update.
241
+ # @param data [Hash] Any additional data to attach to `content`.
242
+ # @return [Boolean] `true` if the receipt was successfully updated,
243
+ # otherwise `false`.
244
+ def set_receipt(room, event, type = 'm.read', data = {})
245
+ make_request(
246
+ :post,
247
+ "/rooms/#{room}/receipt/#{type}/#{event}",
248
+ content: data
249
+ ).code == 200
250
+ end
251
+
252
+ # Redacts a room event from the server.
253
+ #
254
+ # @param room [String] The room to redact the event from.
255
+ # @param event [String] The event to redact.
256
+ # @param reason [String] The reason for redacting the event.
257
+ # @return [String] The ID for the redaction event.
258
+ def redact(room, event, reason)
259
+ make_request(
260
+ :put,
261
+ "/rooms/#{room}/redact/#{event}/#{@transaction_id += 1}",
262
+ content: { reason: reason }
263
+ )['event_id']
264
+ end
265
+ end
266
+ end
267
+ end