discordrb 3.2.1 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of discordrb might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.gitignore +4 -0
- data/.rubocop.yml +3 -3
- data/.travis.yml +28 -3
- data/.yardopts +1 -1
- data/CHANGELOG.md +555 -144
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +0 -4
- data/README.md +86 -15
- data/Rakefile +2 -2
- data/bin/travis_build_docs.sh +17 -0
- data/discordrb-webhooks.gemspec +2 -1
- data/discordrb.gemspec +12 -5
- data/lib/discordrb.rb +2 -2
- data/lib/discordrb/api.rb +94 -25
- data/lib/discordrb/api/channel.rb +53 -17
- data/lib/discordrb/api/invite.rb +7 -4
- data/lib/discordrb/api/server.rb +173 -36
- data/lib/discordrb/api/user.rb +18 -4
- data/lib/discordrb/api/webhook.rb +83 -0
- data/lib/discordrb/await.rb +1 -1
- data/lib/discordrb/bot.rb +191 -102
- data/lib/discordrb/cache.rb +39 -9
- data/lib/discordrb/commands/command_bot.rb +79 -24
- data/lib/discordrb/commands/container.rb +16 -2
- data/lib/discordrb/commands/parser.rb +46 -7
- data/lib/discordrb/commands/rate_limiter.rb +8 -6
- data/lib/discordrb/container.rb +51 -7
- data/lib/discordrb/data.rb +1729 -286
- data/lib/discordrb/errors.rb +34 -1
- data/lib/discordrb/events/generic.rb +1 -1
- data/lib/discordrb/events/guilds.rb +1 -0
- data/lib/discordrb/events/message.rb +18 -12
- data/lib/discordrb/events/presence.rb +7 -2
- data/lib/discordrb/events/reactions.rb +13 -4
- data/lib/discordrb/events/roles.rb +7 -6
- data/lib/discordrb/events/typing.rb +1 -1
- data/lib/discordrb/events/webhooks.rb +61 -0
- data/lib/discordrb/gateway.rb +85 -32
- data/lib/discordrb/light.rb +1 -1
- data/lib/discordrb/logger.rb +8 -7
- data/lib/discordrb/permissions.rb +41 -4
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +10 -8
- data/lib/discordrb/voice/voice_bot.rb +4 -4
- data/lib/discordrb/websocket.rb +2 -2
- metadata +59 -14
data/lib/discordrb/api/user.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# API calls for User object
|
2
4
|
module Discordrb::API::User
|
3
5
|
module_function
|
@@ -27,7 +29,7 @@ module Discordrb::API::User
|
|
27
29
|
end
|
28
30
|
|
29
31
|
# Change the current bot's nickname on a server
|
30
|
-
def change_own_nickname(token, server_id, nick)
|
32
|
+
def change_own_nickname(token, server_id, nick, reason = nil)
|
31
33
|
Discordrb::API.request(
|
32
34
|
:guilds_sid_members_me_nick,
|
33
35
|
server_id, # This is technically a guild endpoint
|
@@ -35,7 +37,8 @@ module Discordrb::API::User
|
|
35
37
|
"#{Discordrb::API.api_base}/guilds/#{server_id}/members/@me/nick",
|
36
38
|
{ nick: nick }.to_json,
|
37
39
|
Authorization: token,
|
38
|
-
content_type: :json
|
40
|
+
content_type: :json,
|
41
|
+
'X-Audit-Log-Reason': reason
|
39
42
|
)
|
40
43
|
end
|
41
44
|
|
@@ -128,8 +131,19 @@ module Discordrb::API::User
|
|
128
131
|
)
|
129
132
|
end
|
130
133
|
|
134
|
+
# Returns one of the "default" discord avatars from the CDN given a discriminator
|
135
|
+
def default_avatar(discrim = 0)
|
136
|
+
index = discrim.to_i % 5
|
137
|
+
"#{Discordrb::API.cdn_url}/embed/avatars/#{index}.png"
|
138
|
+
end
|
139
|
+
|
131
140
|
# Make an avatar URL from the user and avatar IDs
|
132
|
-
def avatar_url(user_id, avatar_id)
|
133
|
-
|
141
|
+
def avatar_url(user_id, avatar_id, format = nil)
|
142
|
+
format ||= if avatar_id.start_with?('a_')
|
143
|
+
'gif'
|
144
|
+
else
|
145
|
+
'webp'
|
146
|
+
end
|
147
|
+
"#{Discordrb::API.cdn_url}/avatars/#{user_id}/#{avatar_id}.#{format}"
|
134
148
|
end
|
135
149
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# API calls for Webhook object
|
4
|
+
module Discordrb::API::Webhook
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Get a webhook
|
8
|
+
# https://discordapp.com/developers/docs/resources/webhook#get-webhook
|
9
|
+
def webhook(token, webhook_id)
|
10
|
+
Discordrb::API.request(
|
11
|
+
:webhooks_wid,
|
12
|
+
nil,
|
13
|
+
:get,
|
14
|
+
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}",
|
15
|
+
Authorization: token
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get a webhook via webhook token
|
20
|
+
# https://discordapp.com/developers/docs/resources/webhook#get-webhook-with-token
|
21
|
+
def token_webhook(webhook_token, webhook_id)
|
22
|
+
Discordrb::API.request(
|
23
|
+
:webhooks_wid,
|
24
|
+
nil,
|
25
|
+
:get,
|
26
|
+
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}"
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Update a webhook
|
31
|
+
# https://discordapp.com/developers/docs/resources/webhook#modify-webhook
|
32
|
+
def update_webhook(token, webhook_id, data, reason = nil)
|
33
|
+
Discordrb::API.request(
|
34
|
+
:webhooks_wid,
|
35
|
+
webhook_id,
|
36
|
+
:patch,
|
37
|
+
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}",
|
38
|
+
data.to_json,
|
39
|
+
Authorization: token,
|
40
|
+
content_type: :json,
|
41
|
+
'X-Audit-Log-Reason': reason
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Update a webhook via webhook token
|
46
|
+
# https://discordapp.com/developers/docs/resources/webhook#modify-webhook-with-token
|
47
|
+
def token_update_webhook(webhook_token, webhook_id, data, reason = nil)
|
48
|
+
Discordrb::API.request(
|
49
|
+
:webhooks_wid,
|
50
|
+
webhook_id,
|
51
|
+
:patch,
|
52
|
+
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}",
|
53
|
+
data.to_json,
|
54
|
+
content_type: :json,
|
55
|
+
'X-Audit-Log-Reason': reason
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Deletes a webhook
|
60
|
+
# https://discordapp.com/developers/docs/resources/webhook#delete-webhook
|
61
|
+
def delete_webhook(token, webhook_id, reason = nil)
|
62
|
+
Discordrb::API.request(
|
63
|
+
:webhooks_wid,
|
64
|
+
webhook_id,
|
65
|
+
:delete,
|
66
|
+
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}",
|
67
|
+
Authorization: token,
|
68
|
+
'X-Audit-Log-Reason': reason
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Deletes a webhook via webhook token
|
73
|
+
# https://discordapp.com/developers/docs/resources/webhook#delete-webhook-with-token
|
74
|
+
def token_delete_webhook(webhook_token, webhook_id, reason = nil)
|
75
|
+
Discordrb::API.request(
|
76
|
+
:webhooks_wid,
|
77
|
+
webhook_id,
|
78
|
+
:delete,
|
79
|
+
"#{Discordrb::API.api_base}/webhooks/#{webhook_id}/#{webhook_token}",
|
80
|
+
'X-Audit-Log-Reason': reason
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
data/lib/discordrb/await.rb
CHANGED
@@ -41,7 +41,7 @@ module Discordrb
|
|
41
41
|
# @return [Array] This await's key and whether or not it should be deleted. If there was no match, both are nil.
|
42
42
|
def match(event)
|
43
43
|
dummy_handler = EventContainer.handler_class(@type).new(@attributes, @bot)
|
44
|
-
return [nil, nil] unless dummy_handler.matches?(event)
|
44
|
+
return [nil, nil] unless event.instance_of?(@type) && dummy_handler.matches?(event)
|
45
45
|
|
46
46
|
should_delete = nil
|
47
47
|
should_delete = true if (@block && @block.call(event) != false) || !@block
|
data/lib/discordrb/bot.rb
CHANGED
@@ -17,6 +17,7 @@ require 'discordrb/events/await'
|
|
17
17
|
require 'discordrb/events/bans'
|
18
18
|
require 'discordrb/events/raw'
|
19
19
|
require 'discordrb/events/reactions'
|
20
|
+
require 'discordrb/events/webhooks'
|
20
21
|
|
21
22
|
require 'discordrb/api'
|
22
23
|
require 'discordrb/api/channel'
|
@@ -42,11 +43,12 @@ module Discordrb
|
|
42
43
|
# @return [Array<Thread>] The threads.
|
43
44
|
attr_reader :event_threads
|
44
45
|
|
45
|
-
#
|
46
|
+
# @return [true, false] whether or not the bot should parse its own messages. Off by default.
|
46
47
|
attr_accessor :should_parse_self
|
47
48
|
|
48
49
|
# The bot's name which discordrb sends to Discord when making any request, so Discord can identify bots with the
|
49
50
|
# same codebase. Not required but I recommend setting it anyway.
|
51
|
+
# @return [String] The bot's name.
|
50
52
|
attr_accessor :name
|
51
53
|
|
52
54
|
# @return [Array(Integer, Integer)] the current shard key
|
@@ -76,7 +78,8 @@ module Discordrb
|
|
76
78
|
# @param token [String] The token that should be used to log in. If your bot is a bot account, you have to specify
|
77
79
|
# this. If you're logging in as a user, make sure to also set the account type to :user so discordrb doesn't think
|
78
80
|
# you're trying to log in as a bot.
|
79
|
-
# @param client_id [Integer] If you're logging in as a bot, the bot's client ID.
|
81
|
+
# @param client_id [Integer] If you're logging in as a bot, the bot's client ID. This is optional, and may be fetched
|
82
|
+
# from the API by calling {Bot#bot_application} (see {Application}).
|
80
83
|
# @param type [Symbol] This parameter lets you manually overwrite the account type. This needs to be set when
|
81
84
|
# logging in as a user, otherwise discordrb will treat you as a bot account. Valid values are `:user` and `:bot`.
|
82
85
|
# @param name [String] Your bot's name. This will be sent to Discord with any API requests, who will use this to
|
@@ -89,24 +92,23 @@ module Discordrb
|
|
89
92
|
# @param parse_self [true, false] Whether the bot should react on its own messages. It's best to turn this off
|
90
93
|
# unless you really need this so you don't inadvertently create infinite loops.
|
91
94
|
# @param shard_id [Integer] The number of the shard this bot should handle. See
|
92
|
-
# https://github.com/
|
95
|
+
# https://github.com/discordapp/discord-api-docs/issues/17 for how to do sharding.
|
93
96
|
# @param num_shards [Integer] The total number of shards that should be running. See
|
94
|
-
# https://github.com/
|
97
|
+
# https://github.com/discordapp/discord-api-docs/issues/17 for how to do sharding.
|
95
98
|
# @param redact_token [true, false] Whether the bot should redact the token in logs. Default is true.
|
96
99
|
# @param ignore_bots [true, false] Whether the bot should ignore bot accounts or not. Default is false.
|
100
|
+
# @param compress_mode [:none, :large, :stream] Sets which compression mode should be used when connecting
|
101
|
+
# to Discord's gateway. `:none` will request that no payloads are received compressed (not recommended for
|
102
|
+
# production bots). `:large` will request that large payloads are received compressed. `:stream` will request
|
103
|
+
# that all data be received in a continuous compressed stream.
|
97
104
|
def initialize(
|
98
105
|
log_mode: :normal,
|
99
106
|
token: nil, client_id: nil,
|
100
107
|
type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
|
101
|
-
shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false
|
108
|
+
shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false,
|
109
|
+
compress_mode: :stream
|
102
110
|
)
|
103
|
-
|
104
|
-
LOGGER.mode = if log_mode.is_a? TrueClass # Specifically check for `true` because people might not have updated yet
|
105
|
-
:debug
|
106
|
-
else
|
107
|
-
log_mode
|
108
|
-
end
|
109
|
-
|
111
|
+
LOGGER.mode = log_mode
|
110
112
|
LOGGER.token = token if redact_token
|
111
113
|
|
112
114
|
@should_parse_self = parse_self
|
@@ -121,8 +123,10 @@ module Discordrb
|
|
121
123
|
LOGGER.fancy = fancy_log
|
122
124
|
@prevent_ready = suppress_ready
|
123
125
|
|
126
|
+
@compress_mode = compress_mode
|
127
|
+
|
124
128
|
@token = process_token(@type, token)
|
125
|
-
@gateway = Gateway.new(self, @token, @shard_key)
|
129
|
+
@gateway = Gateway.new(self, @token, @shard_key, @compress_mode)
|
126
130
|
|
127
131
|
init_cache
|
128
132
|
|
@@ -142,6 +146,7 @@ module Discordrb
|
|
142
146
|
# @return [Hash<Integer => User>] The users by ID.
|
143
147
|
def users
|
144
148
|
gateway_check
|
149
|
+
unavailable_servers_check
|
145
150
|
@users
|
146
151
|
end
|
147
152
|
|
@@ -149,29 +154,27 @@ module Discordrb
|
|
149
154
|
# @return [Hash<Integer => Server>] The servers by ID.
|
150
155
|
def servers
|
151
156
|
gateway_check
|
157
|
+
unavailable_servers_check
|
152
158
|
@servers
|
153
159
|
end
|
154
160
|
|
155
161
|
# @overload emoji(id)
|
156
162
|
# Return an emoji by its ID
|
157
|
-
# @param id [Integer] The emoji's ID.
|
158
|
-
# @return
|
163
|
+
# @param id [Integer, #resolve_id] The emoji's ID.
|
164
|
+
# @return [Emoji, nil] the emoji object. `nil` if the emoji was not found.
|
159
165
|
# @overload emoji
|
160
166
|
# The list of emoji the bot can use.
|
161
|
-
# @return [Array<
|
167
|
+
# @return [Array<Emoji>] the emoji available.
|
162
168
|
def emoji(id = nil)
|
163
169
|
gateway_check
|
170
|
+
unavailable_servers_check
|
171
|
+
|
172
|
+
emoji_hash = @servers.values.map(&:emoji).reduce(&:merge)
|
164
173
|
if id
|
165
|
-
|
166
|
-
|
174
|
+
id = id.resolve_id
|
175
|
+
emoji_hash[id]
|
167
176
|
else
|
168
|
-
|
169
|
-
@servers.each do |_, server|
|
170
|
-
server.emoji.values.each do |element|
|
171
|
-
emoji[element.name] = GlobalEmoji.new(element, self)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
@emoji = emoji.values
|
177
|
+
emoji_hash.values
|
175
178
|
end
|
176
179
|
end
|
177
180
|
|
@@ -199,8 +202,7 @@ module Discordrb
|
|
199
202
|
# The bot's OAuth application.
|
200
203
|
# @return [Application, nil] The bot's application info. Returns `nil` if bot is not a bot account.
|
201
204
|
def bot_application
|
202
|
-
|
203
|
-
return nil unless @type == :bot
|
205
|
+
return unless @type == :bot
|
204
206
|
response = API.oauth_application(token)
|
205
207
|
Application.new(JSON.parse(response), self)
|
206
208
|
end
|
@@ -215,31 +217,40 @@ module Discordrb
|
|
215
217
|
@token
|
216
218
|
end
|
217
219
|
|
218
|
-
# @return the raw token, without any prefix
|
220
|
+
# @return [String] the raw token, without any prefix
|
219
221
|
# @see #token
|
220
222
|
def raw_token
|
221
223
|
@token.split(' ').last
|
222
224
|
end
|
223
225
|
|
224
|
-
# Runs the bot, which logs into Discord and connects the WebSocket. This
|
225
|
-
#
|
226
|
-
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
|
226
|
+
# Runs the bot, which logs into Discord and connects the WebSocket. This
|
227
|
+
# prevents all further execution unless it is executed with
|
228
|
+
# `backround` = `true`.
|
229
|
+
# @param background [true, false] If it is `true`, then the bot will run in
|
230
|
+
# another thread to allow further execution. If it is `false`, this method
|
231
|
+
# will block until {#stop} is called. If the bot is run with `true`, make
|
232
|
+
# sure to eventually call {#join} so the script doesn't stop prematurely.
|
233
|
+
# @note Running the bot in the background means that you can call some
|
234
|
+
# methods that require a gateway connection *before* that connection is
|
235
|
+
# established. In most cases an exception will be raised if you try to do
|
236
|
+
# this. If you need a way to safely run code after the bot is fully
|
237
|
+
# connected, use a {#ready} event handler instead.
|
238
|
+
def run(background = false)
|
231
239
|
@gateway.run_async
|
232
|
-
return if
|
240
|
+
return if background
|
233
241
|
|
234
242
|
debug('Oh wait! Not exiting yet as run was run synchronously.')
|
235
243
|
@gateway.sync
|
236
244
|
end
|
237
245
|
|
238
|
-
#
|
239
|
-
#
|
240
|
-
|
246
|
+
# Joins the bot's connection thread with the current thread.
|
247
|
+
# This blocks execution until the websocket stops, which should only happen
|
248
|
+
# manually triggered. or due to an error. This is necessary to have a
|
249
|
+
# continuously running bot.
|
250
|
+
def join
|
241
251
|
@gateway.sync
|
242
252
|
end
|
253
|
+
alias_method :sync, :join
|
243
254
|
|
244
255
|
# Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that
|
245
256
|
# Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
|
@@ -255,18 +266,17 @@ module Discordrb
|
|
255
266
|
|
256
267
|
# Makes the bot join an invite to a server.
|
257
268
|
# @param invite [String, Invite] The invite to join. For possible formats see {#resolve_invite_code}.
|
258
|
-
def
|
269
|
+
def accept_invite(invite)
|
259
270
|
resolved = invite(invite).code
|
260
271
|
API::Invite.accept(token, resolved)
|
261
272
|
end
|
262
273
|
|
263
274
|
# Creates an OAuth invite URL that can be used to invite this bot to a particular server.
|
264
|
-
# Requires the application ID to have been set during initialization.
|
265
275
|
# @param server [Server, nil] The server the bot should be invited to, or nil if a general invite should be created.
|
266
276
|
# @param permission_bits [Integer, String] Permission bits that should be appended to invite url.
|
267
277
|
# @return [String] the OAuth invite URL.
|
268
278
|
def invite_url(server: nil, permission_bits: nil)
|
269
|
-
|
279
|
+
@client_id ||= bot_application.id
|
270
280
|
|
271
281
|
server_id_str = server ? "&guild_id=#{server.id}" : ''
|
272
282
|
permission_bits_str = permission_bits ? "&permissions=#{permission_bits}" : ''
|
@@ -279,7 +289,7 @@ module Discordrb
|
|
279
289
|
# Gets the voice bot for a particular server or channel. You can connect to a new channel using the {#voice_connect}
|
280
290
|
# method.
|
281
291
|
# @param thing [Channel, Server, Integer] the server or channel you want to get the voice bot for, or its ID.
|
282
|
-
# @return [VoiceBot, nil] the VoiceBot for the thing you specified, or nil if there is no connection yet
|
292
|
+
# @return [Voice::VoiceBot, nil] the VoiceBot for the thing you specified, or nil if there is no connection yet
|
283
293
|
def voice(thing)
|
284
294
|
id = thing.resolve_id
|
285
295
|
return @voices[id] if @voices[id]
|
@@ -297,7 +307,7 @@ module Discordrb
|
|
297
307
|
# data can then be sent. After connecting, the bot can also be accessed using {#voice}. If the bot is already
|
298
308
|
# connected to voice, the existing connection will be terminated - you don't have to call
|
299
309
|
# {Discordrb::Voice::VoiceBot#destroy} before calling this method.
|
300
|
-
# @param chan [Channel] The voice channel to connect to.
|
310
|
+
# @param chan [Channel, Integer, #resolve_id] The voice channel to connect to.
|
301
311
|
# @param encrypted [true, false] Whether voice communication should be encrypted using RbNaCl's SecretBox
|
302
312
|
# (uses an XSalsa20 stream cipher for encryption and Poly1305 for authentication)
|
303
313
|
# @return [Voice::VoiceBot] the initialized bot over which audio data can then be sent.
|
@@ -326,13 +336,14 @@ module Discordrb
|
|
326
336
|
|
327
337
|
# Disconnects the client from a specific voice connection given the server ID. Usually it's more convenient to use
|
328
338
|
# {Discordrb::Voice::VoiceBot#destroy} rather than this.
|
329
|
-
# @param
|
339
|
+
# @param server [Server, Integer, #resolve_id] The server the voice connection is on.
|
330
340
|
# @param destroy_vws [true, false] Whether or not the VWS should also be destroyed. If you're calling this method
|
331
341
|
# directly, you should leave it as true.
|
332
|
-
def voice_destroy(
|
333
|
-
|
334
|
-
@
|
335
|
-
@voices.
|
342
|
+
def voice_destroy(server, destroy_vws = true)
|
343
|
+
server = server.resolve_id
|
344
|
+
@gateway.send_voice_state_update(server.to_s, nil, false, false)
|
345
|
+
@voices[server].destroy if @voices[server] && destroy_vws
|
346
|
+
@voices.delete(server)
|
336
347
|
end
|
337
348
|
|
338
349
|
# Revokes an invite to a server. Will fail unless you have the *Manage Server* permission.
|
@@ -344,32 +355,32 @@ module Discordrb
|
|
344
355
|
end
|
345
356
|
|
346
357
|
# Sends a text message to a channel given its ID and the message's content.
|
347
|
-
# @param
|
358
|
+
# @param channel [Channel, Integer, #resolve_id] The channel to send something to.
|
348
359
|
# @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
|
349
360
|
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
|
350
361
|
# @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
|
351
362
|
# @return [Message] The message that was sent.
|
352
|
-
def send_message(
|
353
|
-
|
354
|
-
debug("Sending message to #{
|
363
|
+
def send_message(channel, content, tts = false, embed = nil)
|
364
|
+
channel = channel.resolve_id
|
365
|
+
debug("Sending message to #{channel} with content '#{content}'")
|
355
366
|
|
356
|
-
response = API::Channel.create_message(token,
|
367
|
+
response = API::Channel.create_message(token, channel, content, tts, embed ? embed.to_hash : nil)
|
357
368
|
Message.new(JSON.parse(response), self)
|
358
369
|
end
|
359
370
|
|
360
371
|
# Sends a text message to a channel given its ID and the message's content,
|
361
372
|
# then deletes it after the specified timeout in seconds.
|
362
|
-
# @param
|
373
|
+
# @param channel [Channel, Integer, #resolve_id] The channel to send something to.
|
363
374
|
# @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
|
364
375
|
# @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
|
365
376
|
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
|
366
377
|
# @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
|
367
|
-
def send_temporary_message(
|
378
|
+
def send_temporary_message(channel, content, timeout, tts = false, embed = nil)
|
368
379
|
Thread.new do
|
369
|
-
|
380
|
+
Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"
|
370
381
|
|
382
|
+
message = send_message(channel, content, tts, embed)
|
371
383
|
sleep(timeout)
|
372
|
-
|
373
384
|
message.delete
|
374
385
|
end
|
375
386
|
|
@@ -378,12 +389,15 @@ module Discordrb
|
|
378
389
|
|
379
390
|
# Sends a file to a channel. If it is an image, it will automatically be embedded.
|
380
391
|
# @note This executes in a blocking way, so if you're sending long files, be wary of delays.
|
381
|
-
# @param
|
392
|
+
# @param channel [Channel, Integer, #resolve_id] The channel to send something to.
|
382
393
|
# @param file [File] The file that should be sent.
|
383
394
|
# @param caption [string] The caption for the file.
|
384
395
|
# @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
|
385
|
-
|
386
|
-
|
396
|
+
# @example Send a file from disk
|
397
|
+
# bot.send_file(83281822225530880, File.open('rubytaco.png', 'r'))
|
398
|
+
def send_file(channel, file, caption: nil, tts: false)
|
399
|
+
channel = channel.resolve_id
|
400
|
+
response = API::Channel.upload_file(token, channel, file, caption: caption, tts: tts)
|
387
401
|
Message.new(JSON.parse(response), self)
|
388
402
|
end
|
389
403
|
|
@@ -391,19 +405,9 @@ module Discordrb
|
|
391
405
|
# @note Discord's API doesn't directly return the server when creating it, so this method
|
392
406
|
# waits until the data has been received via the websocket. This may make the execution take a while.
|
393
407
|
# @param name [String] The name the new server should have. Doesn't have to be alphanumeric.
|
394
|
-
# @param region [Symbol] The region where the server should be created
|
395
|
-
#
|
396
|
-
# * `:london`
|
397
|
-
# * `:amsterdam`
|
398
|
-
# * `:frankfurt`
|
399
|
-
# * `:us-east`
|
400
|
-
# * `:us-west`
|
401
|
-
# * `:us-south`
|
402
|
-
# * `:us-central`
|
403
|
-
# * `:singapore`
|
404
|
-
# * `:sydney`
|
408
|
+
# @param region [Symbol] The region where the server should be created, for example 'eu-central' or 'hongkong'.
|
405
409
|
# @return [Server] The server that was created.
|
406
|
-
def create_server(name, region = :
|
410
|
+
def create_server(name, region = :'eu-central')
|
407
411
|
response = API::Server.create(token, name, region)
|
408
412
|
id = JSON.parse(response)['id'].to_i
|
409
413
|
sleep 0.1 until @servers[id]
|
@@ -432,45 +436,51 @@ module Discordrb
|
|
432
436
|
API.update_oauth_application(@token, name, redirect_uris, description, icon)
|
433
437
|
end
|
434
438
|
|
435
|
-
# Gets the user, role or emoji from a mention of the user, role or emoji.
|
436
|
-
# @param mention [String] The mention, which should look like `<@12314873129>`, `<@&123456789>` or `<:
|
439
|
+
# Gets the user, channel, role or emoji from a mention of the user, channel, role or emoji.
|
440
|
+
# @param mention [String] The mention, which should look like `<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`.
|
437
441
|
# @param server [Server, nil] The server of the associated mention. (recommended for role parsing, to speed things up)
|
438
|
-
# @return [User, Role, Emoji] The user, role or emoji identified by the mention, or `nil` if none exists.
|
442
|
+
# @return [User, Channel, Role, Emoji] The user, channel, role or emoji identified by the mention, or `nil` if none exists.
|
439
443
|
def parse_mention(mention, server = nil)
|
440
444
|
# Mention format: <@id>
|
441
|
-
if /<@!?(?<id>\d+)
|
442
|
-
user(id
|
443
|
-
elsif
|
444
|
-
|
445
|
+
if /<@!?(?<id>\d+)>/ =~ mention
|
446
|
+
user(id)
|
447
|
+
elsif /<#(?<id>\d+)>/ =~ mention
|
448
|
+
channel(id, server)
|
449
|
+
elsif /<@&(?<id>\d+)>/ =~ mention
|
450
|
+
return server.role(id) if server
|
445
451
|
@servers.values.each do |element|
|
446
|
-
role = element.role(id
|
452
|
+
role = element.role(id)
|
447
453
|
return role unless role.nil?
|
448
454
|
end
|
449
455
|
|
450
456
|
# Return nil if no role is found
|
451
457
|
nil
|
452
|
-
elsif
|
453
|
-
emoji.
|
458
|
+
elsif /<(?<animated>a)?:(?<name>\w+):(?<id>\d+)>/ =~ mention
|
459
|
+
emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil)
|
454
460
|
end
|
455
461
|
end
|
456
462
|
|
457
463
|
# Updates presence status.
|
458
|
-
# @param status [String] The status the bot should show up as.
|
459
|
-
# @param
|
464
|
+
# @param status [String] The status the bot should show up as. Can be `online`, `dnd`, `idle`, or `invisible`
|
465
|
+
# @param activity [String, nil] The name of the activity to be played/watched/listened to/stream name on the stream.
|
460
466
|
# @param url [String, nil] The Twitch URL to display as a stream. nil for no stream.
|
461
467
|
# @param since [Integer] When this status was set.
|
462
468
|
# @param afk [true, false] Whether the bot is AFK.
|
469
|
+
# @param activity_type [Integer] The type of activity status to display. Can be 0 (Playing), 1 (Streaming), 2 (Listening), 3 (Watching)
|
463
470
|
# @see Gateway#send_status_update
|
464
|
-
def update_status(status,
|
471
|
+
def update_status(status, activity, url, since = 0, afk = false, activity_type = 0)
|
465
472
|
gateway_check
|
466
473
|
|
467
|
-
@
|
474
|
+
@activity = activity
|
468
475
|
@status = status
|
469
476
|
@streamurl = url
|
470
|
-
type = url ? 1 :
|
477
|
+
type = url ? 1 : activity_type
|
478
|
+
|
479
|
+
activity_obj = activity || url ? { 'name' => activity, 'url' => url, 'type' => type } : nil
|
480
|
+
@gateway.send_status_update(status, since, activity_obj, afk)
|
471
481
|
|
472
|
-
|
473
|
-
|
482
|
+
# Update the status in the cache
|
483
|
+
profile.update_presence('status' => status.to_s, 'game' => activity_obj)
|
474
484
|
end
|
475
485
|
|
476
486
|
# Sets the currently playing game to the specified game.
|
@@ -482,6 +492,26 @@ module Discordrb
|
|
482
492
|
name
|
483
493
|
end
|
484
494
|
|
495
|
+
alias_method :playing=, :game=
|
496
|
+
|
497
|
+
# Sets the current listening status to the specified name.
|
498
|
+
# @param name [String] The thing to be listened to.
|
499
|
+
# @return [String] The thing that is now being listened to.
|
500
|
+
def listening=(name)
|
501
|
+
gateway_check
|
502
|
+
update_status(@status, name, nil, nil, nil, 2)
|
503
|
+
name
|
504
|
+
end
|
505
|
+
|
506
|
+
# Sets the current watching status to the specified name.
|
507
|
+
# @param name [String] The thing to be watched.
|
508
|
+
# @return [String] The thing that is now being watched.
|
509
|
+
def watching=(name)
|
510
|
+
gateway_check
|
511
|
+
update_status(@status, name, nil, nil, nil, 3)
|
512
|
+
name
|
513
|
+
end
|
514
|
+
|
485
515
|
# Sets the currently online stream to the specified name and Twitch URL.
|
486
516
|
# @param name [String] The name of the stream to display.
|
487
517
|
# @param url [String] The url of the current Twitch stream.
|
@@ -495,7 +525,7 @@ module Discordrb
|
|
495
525
|
# Sets status to online.
|
496
526
|
def online
|
497
527
|
gateway_check
|
498
|
-
update_status(:online, @
|
528
|
+
update_status(:online, @activity, @streamurl)
|
499
529
|
end
|
500
530
|
|
501
531
|
alias_method :on, :online
|
@@ -503,7 +533,7 @@ module Discordrb
|
|
503
533
|
# Sets status to idle.
|
504
534
|
def idle
|
505
535
|
gateway_check
|
506
|
-
update_status(:idle, @
|
536
|
+
update_status(:idle, @activity, nil)
|
507
537
|
end
|
508
538
|
|
509
539
|
alias_method :away, :idle
|
@@ -511,13 +541,13 @@ module Discordrb
|
|
511
541
|
# Sets the bot's status to DnD (red icon).
|
512
542
|
def dnd
|
513
543
|
gateway_check
|
514
|
-
update_status(:dnd, @
|
544
|
+
update_status(:dnd, @activity, nil)
|
515
545
|
end
|
516
546
|
|
517
547
|
# Sets the bot's status to invisible (appears offline).
|
518
548
|
def invisible
|
519
549
|
gateway_check
|
520
|
-
update_status(:invisible, @
|
550
|
+
update_status(:invisible, @activity, nil)
|
521
551
|
end
|
522
552
|
|
523
553
|
# Sets debug mode. If debug mode is on, many things will be outputted to STDOUT.
|
@@ -543,6 +573,7 @@ module Discordrb
|
|
543
573
|
# @yield Is executed when the await is triggered.
|
544
574
|
# @yieldparam event [Event] The event object that was triggered.
|
545
575
|
# @return [Await] The await that was created.
|
576
|
+
# @deprecated Will be changed to blocking behavior in v4.0. Use {#add_await!} instead.
|
546
577
|
def add_await(key, type, attributes = {}, &block)
|
547
578
|
raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
|
548
579
|
await = Await.new(self, key, type, attributes, block)
|
@@ -550,6 +581,43 @@ module Discordrb
|
|
550
581
|
@awaits[key] = await
|
551
582
|
end
|
552
583
|
|
584
|
+
# Awaits an event, blocking the current thread until a response is received.
|
585
|
+
# @param type [Class] The event class that should be listened for.
|
586
|
+
# @option attributes [Numeric] :timeout the amount of time to wait for a response before returning `nil`. Waits forever if omitted.
|
587
|
+
# @return [Event, nil] The event object that was triggered, or `nil` if a `timeout` was set and no event was raised in time.
|
588
|
+
# @raise [ArgumentError] if `timeout` is given and is not a positive numeric value
|
589
|
+
def add_await!(type, attributes = {})
|
590
|
+
raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
|
591
|
+
|
592
|
+
timeout = attributes[:timeout]
|
593
|
+
raise ArgumentError, 'Timeout must be a number > 0' if timeout && timeout.is_a?(Numeric) && timeout <= 0
|
594
|
+
|
595
|
+
mutex = Mutex.new
|
596
|
+
cv = ConditionVariable.new
|
597
|
+
response = nil
|
598
|
+
block = lambda do |event|
|
599
|
+
mutex.synchronize do
|
600
|
+
response = event
|
601
|
+
cv.signal
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
handler = register_event(type, attributes, block)
|
606
|
+
|
607
|
+
if timeout
|
608
|
+
Thread.new do
|
609
|
+
sleep timeout
|
610
|
+
mutex.synchronize { cv.signal }
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
mutex.synchronize { cv.wait(mutex) }
|
615
|
+
|
616
|
+
remove_handler(handler)
|
617
|
+
raise 'ConditionVariable was signaled without returning an event!' if response.nil? && timeout.nil?
|
618
|
+
response
|
619
|
+
end
|
620
|
+
|
553
621
|
# Add a user to the list of ignored users. Those users will be ignored in message events at event processing level.
|
554
622
|
# @note Ignoring a user only prevents any message events (including mentions, commands etc.) from them! Typing and
|
555
623
|
# presence and any other events will still be received.
|
@@ -591,6 +659,7 @@ module Discordrb
|
|
591
659
|
raise_event(HeartbeatEvent.new(self))
|
592
660
|
end
|
593
661
|
|
662
|
+
# Makes the bot leave any groups with no recipients remaining
|
594
663
|
def prune_empty_groups
|
595
664
|
@channels.each_value do |channel|
|
596
665
|
channel.leave_group if channel.group? && channel.recipients.empty?
|
@@ -599,11 +668,18 @@ module Discordrb
|
|
599
668
|
|
600
669
|
private
|
601
670
|
|
602
|
-
# Throws a useful exception if there's currently no gateway connection
|
671
|
+
# Throws a useful exception if there's currently no gateway connection.
|
603
672
|
def gateway_check
|
604
|
-
|
673
|
+
raise "A gateway connection is necessary to call this method! You'll have to do it inside any event (e.g. `ready`) or after `bot.run :async`." unless connected?
|
674
|
+
end
|
605
675
|
|
606
|
-
|
676
|
+
# Logs a warning if there are servers which are still unavailable.
|
677
|
+
# e.g. due to a Discord outage or because the servers are large and taking a while to load.
|
678
|
+
def unavailable_servers_check
|
679
|
+
# Return unless there are servers that are unavailable.
|
680
|
+
return unless @unavailable_servers && @unavailable_servers > 0
|
681
|
+
LOGGER.warn("#{@unavailable_servers} servers haven't been cached yet.")
|
682
|
+
LOGGER.warn('Servers may be unavailable due to an outage, or your bot is on very large servers that are taking a while to load.')
|
607
683
|
end
|
608
684
|
|
609
685
|
### ## ## ######## ######## ######## ## ## ### ## ######
|
@@ -813,7 +889,12 @@ module Discordrb
|
|
813
889
|
server_id = data['guild_id'].to_i
|
814
890
|
server = @servers[server_id]
|
815
891
|
new_role = Role.new(role_data, self, server)
|
816
|
-
server.
|
892
|
+
existing_role = server.role(new_role.id)
|
893
|
+
if existing_role
|
894
|
+
existing_role.update_from(new_role)
|
895
|
+
else
|
896
|
+
server.add_role(new_role)
|
897
|
+
end
|
817
898
|
end
|
818
899
|
|
819
900
|
# Internal handler for GUILD_ROLE_DELETE
|
@@ -878,8 +959,8 @@ module Discordrb
|
|
878
959
|
# Check whether there are still unavailable servers and there have been more than 10 seconds since READY
|
879
960
|
if @unavailable_servers && @unavailable_servers > 0 && (Time.now - @unavailable_timeout_time) > 10
|
880
961
|
# The server streaming timed out!
|
881
|
-
LOGGER.
|
882
|
-
LOGGER.
|
962
|
+
LOGGER.debug("Server streaming timed out with #{@unavailable_servers} servers remaining")
|
963
|
+
LOGGER.debug('Calling ready now because server loading is taking a long time. Servers may be unavailable due to an outage, or your bot is on very large servers.')
|
883
964
|
|
884
965
|
# Unset the unavailable server count so this doesn't get triggered again
|
885
966
|
@unavailable_servers = 0
|
@@ -1014,11 +1095,15 @@ module Discordrb
|
|
1014
1095
|
when :MESSAGE_REACTION_ADD
|
1015
1096
|
add_message_reaction(data)
|
1016
1097
|
|
1098
|
+
return if profile.id == data['user_id'].to_i && !should_parse_self
|
1099
|
+
|
1017
1100
|
event = ReactionAddEvent.new(data, self)
|
1018
1101
|
raise_event(event)
|
1019
1102
|
when :MESSAGE_REACTION_REMOVE
|
1020
1103
|
remove_message_reaction(data)
|
1021
1104
|
|
1105
|
+
return if profile.id == data['user_id'].to_i && !should_parse_self
|
1106
|
+
|
1022
1107
|
event = ReactionRemoveEvent.new(data, self)
|
1023
1108
|
raise_event(event)
|
1024
1109
|
when :MESSAGE_REACTION_REMOVE_ALL
|
@@ -1030,7 +1115,7 @@ module Discordrb
|
|
1030
1115
|
# Ignore friends list presences
|
1031
1116
|
return unless data['guild_id']
|
1032
1117
|
|
1033
|
-
now_playing = data['game']
|
1118
|
+
now_playing = data['game'].nil? ? nil : data['game']['name']
|
1034
1119
|
presence_user = @users[data['user']['id'].to_i]
|
1035
1120
|
played_before = presence_user.nil? ? nil : presence_user.game
|
1036
1121
|
update_presence(data)
|
@@ -1177,6 +1262,9 @@ module Discordrb
|
|
1177
1262
|
event = ServerEmojiUpdateEvent.new(server, old_emoji_data[e], new_emoji_data[e], self)
|
1178
1263
|
raise_event(event)
|
1179
1264
|
end
|
1265
|
+
when :WEBHOOKS_UPDATE
|
1266
|
+
event = WebhookUpdateEvent.new(data, self)
|
1267
|
+
raise_event(event)
|
1180
1268
|
else
|
1181
1269
|
# another event that we don't support yet
|
1182
1270
|
debug "Event #{type} has been received but is unsupported. Raising UnknownEvent"
|
@@ -1211,7 +1299,8 @@ module Discordrb
|
|
1211
1299
|
|
1212
1300
|
@event_handlers ||= {}
|
1213
1301
|
handlers = @event_handlers[event.class]
|
1214
|
-
|
1302
|
+
return unless handlers
|
1303
|
+
handlers.dup.each do |handler|
|
1215
1304
|
call_event(handler, event) if handler.matches?(event)
|
1216
1305
|
end
|
1217
1306
|
end
|