onyxcord 2.0.13 → 2.1.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/CHANGELOG.md +55 -0
- data/lib/onyxcord/allowed_mentions.rb +8 -0
- data/lib/onyxcord/api/channel.rb +8 -10
- data/lib/onyxcord/api/interaction.rb +4 -9
- data/lib/onyxcord/api/webhook.rb +9 -11
- data/lib/onyxcord/api.rb +64 -9
- data/lib/onyxcord/async/runtime.rb +1 -1
- data/lib/onyxcord/data/interaction.rb +13 -5
- data/lib/onyxcord/data/message.rb +6 -2
- data/lib/onyxcord/data/webhook.rb +5 -3
- data/lib/onyxcord/errors.rb +18 -3
- data/lib/onyxcord/gateway.rb +5 -0
- data/lib/onyxcord/http.rb +75 -4
- data/lib/onyxcord/message_payload.rb +78 -0
- data/lib/onyxcord/rate_limiter/async_rest.rb +3 -2
- data/lib/onyxcord/upload.rb +39 -0
- data/lib/onyxcord/version.rb +1 -1
- data/lib/onyxcord/webhooks/version.rb +1 -1
- data/lib/onyxcord.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df2a03d17ca81fd7a31574dbb8f94a53fc18b602a87ccd77d8390c472bb8a89b
|
|
4
|
+
data.tar.gz: 970063416e35a36c1f1f8c97d706a9e6a4895b5d7c1ec44eb19ab1e923b0fdcc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96fd63b818d4e7abcd493f747444692951ec8c2c45a17efdb7772ae5de0aee444f3a22dc410067b8585ad803baa300d52d54856dd3efeddd8938a207d0aa3037
|
|
7
|
+
data.tar.gz: 37d0a1e7c88edc3ced210a92a9bceefd9f980a9280f0802538efed8b4b0de17f92120924a1c2a0121a55d07a33aa9915a2cafca2b915f1630f47e4e7f3049828
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.0 - 2026-07-01
|
|
4
|
+
|
|
5
|
+
### Melhorias de REST e gateway
|
|
6
|
+
|
|
7
|
+
- Adiciona `OnyxCord::MessagePayload` e `OnyxCord::Upload` para centralizar payloads, anexos e multipart.
|
|
8
|
+
- Melhora rate limit global async para bloquear novas requests enquanto o limite global esta ativo.
|
|
9
|
+
- Adiciona retry REST para falhas temporarias `500`, `502`, `503`, `504` e erros de conexao.
|
|
10
|
+
- Adiciona erros HTTP tipados com `status`, `code`, `headers`, `route`, `body` e `response`.
|
|
11
|
+
- Torna o reconnect do gateway mais defensivo: quedas antes do `HELLO` voltam para `IDENTIFY`, quedas depois do `HELLO` tentam `RESUME`.
|
|
12
|
+
- Adiciona `AllowedMentions.none` e `AllowedMentions.all`.
|
|
13
|
+
- Ajusta edicao de mensagens para limpar `embeds` ao editar `content`, limpar `content` ao editar `embeds`, e preservar campos com `:keep`.
|
|
14
|
+
- Valida payloads antes da API para limites de embeds/anexos e combinacoes invalidas de Components V2.
|
|
15
|
+
|
|
16
|
+
## 2.0.20 - 2026-06-30
|
|
17
|
+
|
|
18
|
+
### Correcoes de REST
|
|
19
|
+
|
|
20
|
+
- Porta o formato multipart do disnake: lista de partes `files[n]` e `payload_json`, com reset do arquivo antes do envio.
|
|
21
|
+
|
|
22
|
+
## 2.0.19 - 2026-06-30
|
|
23
|
+
|
|
24
|
+
### Correcoes de REST
|
|
25
|
+
|
|
26
|
+
- Adiciona diagnostico de metodo, URL sanitizada, headers e formato do body em erros HTTP sem JSON.
|
|
27
|
+
|
|
28
|
+
## 2.0.18 - 2026-06-30
|
|
29
|
+
|
|
30
|
+
### Correcoes de REST
|
|
31
|
+
|
|
32
|
+
- Normaliza multipart de followup webhook com `payload_json` primeiro e sem campos vazios.
|
|
33
|
+
|
|
34
|
+
## 2.0.17 - 2026-06-30
|
|
35
|
+
|
|
36
|
+
### Correcoes de REST
|
|
37
|
+
|
|
38
|
+
- Envia `payload_json` com `Content-Type: application/json` e `.txt` como `text/plain` em multipart.
|
|
39
|
+
|
|
40
|
+
## 2.0.16 - 2026-06-30
|
|
41
|
+
|
|
42
|
+
### Correcoes de REST
|
|
43
|
+
|
|
44
|
+
- Forca HTTP/1.1 na sessao REST do HTTPX para evitar `protocol_error` em interacoes e followups.
|
|
45
|
+
|
|
46
|
+
## 2.0.15 - 2026-06-30
|
|
47
|
+
|
|
48
|
+
### Correcoes de REST
|
|
49
|
+
|
|
50
|
+
- Envia uploads `multipart/form-data` por `Net::HTTP` no adaptador REST para evitar rejeicao `HTTP 400` do Cloudflare em anexos.
|
|
51
|
+
|
|
52
|
+
## 2.0.14 - 2026-06-30
|
|
53
|
+
|
|
54
|
+
### Correcoes de REST
|
|
55
|
+
|
|
56
|
+
- Corrige uploads `multipart/form-data` no adaptador HTTPX usando `form:` nativo, evitando `HTTP 400` HTML do Cloudflare em anexos.
|
|
57
|
+
|
|
3
58
|
## 2.0.13 - 2026-06-28
|
|
4
59
|
|
|
5
60
|
### Correcoes de gateway
|
|
@@ -30,6 +30,14 @@ module OnyxCord
|
|
|
30
30
|
@replied_user = replied_user
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def self.none
|
|
34
|
+
new(parse: [], users: [], roles: [], replied_user: false)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.all
|
|
38
|
+
new(parse: %w[users roles everyone], replied_user: true)
|
|
39
|
+
end
|
|
40
|
+
|
|
33
41
|
# @!visibility private
|
|
34
42
|
def to_hash
|
|
35
43
|
{
|
data/lib/onyxcord/api/channel.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'onyxcord/
|
|
3
|
+
require 'onyxcord/message_payload'
|
|
4
4
|
|
|
5
5
|
# API calls for Channel
|
|
6
6
|
module OnyxCord::API::Channel
|
|
@@ -9,18 +9,12 @@ module OnyxCord::API::Channel
|
|
|
9
9
|
# Build attachment metadata payload for multipart uploads.
|
|
10
10
|
# Returns an array of { id:, filename: } hashes.
|
|
11
11
|
def attachment_payload(attachments)
|
|
12
|
-
|
|
13
|
-
{ id: index, filename: File.basename(attachment.path) }
|
|
14
|
-
end
|
|
12
|
+
OnyxCord::MessagePayload.attachment_payload(attachments)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
# Build multipart body with named file fields and JSON payload.
|
|
18
16
|
def multipart_body(body, attachments)
|
|
19
|
-
|
|
20
|
-
["files[#{index}]", attachment]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
{ **files, payload_json: body.to_json }
|
|
17
|
+
OnyxCord::MessagePayload.multipart_body(body, attachments)
|
|
24
18
|
end
|
|
25
19
|
|
|
26
20
|
# Get a channel's data
|
|
@@ -113,6 +107,7 @@ module OnyxCord::API::Channel
|
|
|
113
107
|
tts = false unless [true, false].include?(tts)
|
|
114
108
|
components = OnyxCord::MessageComponents.payload(components) unless components.nil?
|
|
115
109
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
|
|
110
|
+
OnyxCord::MessagePayload.validate!(content: message, embeds: embeds, components: components, flags: flags, attachments: attachments, poll: poll)
|
|
116
111
|
body = { content: message, tts: tts == true, embeds: embeds, nonce: nonce, allowed_mentions: allowed_mentions, message_reference: message_reference, components: components, attachments: attachments ? attachment_payload(attachments) : nil, flags: flags, enforce_nonce: enforce_nonce, poll: poll }.compact
|
|
117
112
|
body = if attachments
|
|
118
113
|
multipart_body(body, attachments)
|
|
@@ -151,12 +146,14 @@ module OnyxCord::API::Channel
|
|
|
151
146
|
def edit_message(token, channel_id, message_id, message, mentions = nil, embeds = nil, components = nil, flags = nil)
|
|
152
147
|
components = OnyxCord::MessageComponents.payload(components) unless components.nil? || components == :undef
|
|
153
148
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
|
|
149
|
+
body = OnyxCord::MessagePayload.edit_body(message, embeds)
|
|
150
|
+
body.merge!(allowed_mentions: mentions, components: components, flags: flags)
|
|
154
151
|
OnyxCord::API.request(
|
|
155
152
|
:channels_cid_messages_mid,
|
|
156
153
|
channel_id,
|
|
157
154
|
:patch,
|
|
158
155
|
"#{OnyxCord::API.api_base}/channels/#{channel_id}/messages/#{message_id}",
|
|
159
|
-
|
|
156
|
+
body.reject { |_, v| v == :undef }.to_json,
|
|
160
157
|
Authorization: token,
|
|
161
158
|
content_type: :json
|
|
162
159
|
)
|
|
@@ -655,6 +652,7 @@ module OnyxCord::API::Channel
|
|
|
655
652
|
# Start a thread in a forum or media channel.
|
|
656
653
|
# https://discord.com/developers/docs/resources/channel#start-thread-in-forum-or-media-channel
|
|
657
654
|
def start_thread_in_forum_or_media_channel(token, channel_id, name, message, attachments = nil, rate_limit_per_user = nil, auto_archive_duration = nil, applied_tags = nil, reason = nil)
|
|
655
|
+
OnyxCord::MessagePayload.validate!(attachments: attachments)
|
|
658
656
|
body = { name: name, message: message, rate_limit_per_user: rate_limit_per_user, auto_archive_duration: auto_archive_duration, applied_tags: applied_tags }.compact
|
|
659
657
|
|
|
660
658
|
body = if attachments
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'onyxcord/
|
|
3
|
+
require 'onyxcord/message_payload'
|
|
4
4
|
|
|
5
5
|
# API calls for interactions.
|
|
6
6
|
module OnyxCord::API::Interaction
|
|
@@ -9,18 +9,12 @@ module OnyxCord::API::Interaction
|
|
|
9
9
|
# Build attachment metadata payload for multipart uploads.
|
|
10
10
|
# Returns an array of { id:, filename: } hashes.
|
|
11
11
|
def attachment_payload(attachments)
|
|
12
|
-
|
|
13
|
-
{ id: index, filename: File.basename(attachment.path) }
|
|
14
|
-
end
|
|
12
|
+
OnyxCord::MessagePayload.attachment_payload(attachments)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
# Build multipart body with named file fields and JSON payload.
|
|
18
16
|
def multipart_body(body, attachments)
|
|
19
|
-
|
|
20
|
-
["files[#{index}]", attachment]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
{ **files, payload_json: body.to_json }
|
|
17
|
+
OnyxCord::MessagePayload.multipart_body(body, attachments)
|
|
24
18
|
end
|
|
25
19
|
|
|
26
20
|
# Respond to an interaction.
|
|
@@ -28,6 +22,7 @@ module OnyxCord::API::Interaction
|
|
|
28
22
|
def create_interaction_response(interaction_token, interaction_id, type, content = nil, tts = nil, embeds = nil, allowed_mentions = nil, flags = nil, components = nil, attachments = nil, choices = nil, with_response = nil, poll = nil)
|
|
29
23
|
components = OnyxCord::MessageComponents.payload(components) unless components.nil?
|
|
30
24
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
|
|
25
|
+
OnyxCord::MessagePayload.validate!(content: content, embeds: embeds, components: components, flags: flags, attachments: attachments, poll: poll)
|
|
31
26
|
data = { tts: tts, content: content, embeds: embeds, allowed_mentions: allowed_mentions, flags: flags, components: components, attachments: attachments ? attachment_payload(attachments) : nil, choices: choices, poll: poll }.compact
|
|
32
27
|
|
|
33
28
|
body = if attachments
|
data/lib/onyxcord/api/webhook.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'onyxcord/
|
|
3
|
+
require 'onyxcord/message_payload'
|
|
4
4
|
|
|
5
5
|
# API calls for Webhook object
|
|
6
6
|
module OnyxCord::API::Webhook
|
|
@@ -9,18 +9,12 @@ module OnyxCord::API::Webhook
|
|
|
9
9
|
# Build attachment metadata payload for multipart uploads.
|
|
10
10
|
# Returns an array of { id:, filename: } hashes.
|
|
11
11
|
def attachment_payload(attachments)
|
|
12
|
-
|
|
13
|
-
{ id: index, filename: File.basename(attachment.path) }
|
|
14
|
-
end
|
|
12
|
+
OnyxCord::MessagePayload.attachment_payload(attachments)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
# Build multipart body with named file fields and JSON payload.
|
|
18
16
|
def multipart_body(body, attachments)
|
|
19
|
-
|
|
20
|
-
["files[#{index}]", attachment]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
{ **files, payload_json: body.to_json }
|
|
17
|
+
OnyxCord::MessagePayload.multipart_body(body, attachments)
|
|
24
18
|
end
|
|
25
19
|
|
|
26
20
|
# Get a webhook
|
|
@@ -49,9 +43,12 @@ module OnyxCord::API::Webhook
|
|
|
49
43
|
# Execute a webhook via token.
|
|
50
44
|
# https://discord.com/developers/docs/resources/webhook#execute-webhook
|
|
51
45
|
def token_execute_webhook(webhook_token, webhook_id, wait = false, content = nil, username = nil, avatar_url = nil, tts = nil, file = nil, embeds = nil, allowed_mentions = nil, flags = nil, components = nil, attachments = nil, poll = nil)
|
|
46
|
+
raise ArgumentError, 'cannot mix file and attachments' if file && attachments
|
|
47
|
+
|
|
52
48
|
components = OnyxCord::MessageComponents.payload(components) unless components.nil?
|
|
53
49
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
|
|
54
|
-
|
|
50
|
+
OnyxCord::MessagePayload.validate!(content: content, embeds: embeds, components: components, flags: flags, attachments: attachments, poll: poll)
|
|
51
|
+
body = { content: content, username: username, avatar_url: avatar_url, tts: tts == true ? true : nil, embeds: embeds&.map(&:to_hash), allowed_mentions: allowed_mentions, flags: flags, components: components&.any? ? components : nil, attachments: attachments ? attachment_payload(attachments) : nil, poll: poll }.compact
|
|
55
52
|
|
|
56
53
|
body = if file
|
|
57
54
|
{ file: file, payload_json: body.to_json }
|
|
@@ -145,7 +142,8 @@ module OnyxCord::API::Webhook
|
|
|
145
142
|
def token_edit_message(webhook_token, webhook_id, message_id, content = nil, embeds = nil, allowed_mentions = nil, components = nil, attachments = nil, flags = nil, poll = nil)
|
|
146
143
|
components = OnyxCord::MessageComponents.payload(components) unless components.nil?
|
|
147
144
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
|
|
148
|
-
|
|
145
|
+
OnyxCord::MessagePayload.validate!(content: content, embeds: embeds, components: components, flags: flags, attachments: attachments, poll: poll)
|
|
146
|
+
body = OnyxCord::MessagePayload.edit_body(content, embeds).merge({ allowed_mentions: allowed_mentions, components: components, attachments: attachments ? attachment_payload(attachments) : nil, flags: flags, poll: poll }.compact)
|
|
149
147
|
|
|
150
148
|
body = if attachments
|
|
151
149
|
multipart_body(body, attachments)
|
data/lib/onyxcord/api.rb
CHANGED
|
@@ -105,9 +105,17 @@ module OnyxCord::API
|
|
|
105
105
|
response = OnyxCord::HTTP.request(type, url, body, **headers)
|
|
106
106
|
|
|
107
107
|
if response.code == 403
|
|
108
|
-
|
|
108
|
+
route = request_diagnostic(type, url, body, headers)
|
|
109
|
+
noprm = OnyxCord::Errors::NoPermission.new(
|
|
110
|
+
"The bot doesn't have the required permission to do this!",
|
|
111
|
+
status: response.code,
|
|
112
|
+
headers: response.headers,
|
|
113
|
+
route: route,
|
|
114
|
+
body: response.body,
|
|
115
|
+
response: response
|
|
116
|
+
)
|
|
109
117
|
noprm.define_singleton_method(:_response) { response }
|
|
110
|
-
raise noprm
|
|
118
|
+
raise noprm
|
|
111
119
|
end
|
|
112
120
|
|
|
113
121
|
# Retry on 502 Bad Gateway
|
|
@@ -157,20 +165,38 @@ module OnyxCord::API
|
|
|
157
165
|
|
|
158
166
|
response = nil
|
|
159
167
|
loop do
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
begin
|
|
169
|
+
response = OnyxCord::HTTP.request(type, url, body, **headers)
|
|
170
|
+
rescue StandardError => e
|
|
171
|
+
retries += 1
|
|
172
|
+
raise unless retries < max_retries
|
|
173
|
+
|
|
174
|
+
OnyxCord::LOGGER.warn("Temporary HTTP failure while sending request (#{e.class}), retrying")
|
|
175
|
+
OnyxCord::AsyncRuntime.sleep(retries * 0.5)
|
|
176
|
+
next
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
break unless [500, 502, 503, 504].include?(response.code)
|
|
162
180
|
|
|
163
181
|
retries += 1
|
|
164
182
|
break unless retries < max_retries
|
|
165
183
|
|
|
166
|
-
OnyxCord::LOGGER.warn(
|
|
184
|
+
OnyxCord::LOGGER.warn("Got HTTP #{response.code} while sending a request, retrying")
|
|
167
185
|
OnyxCord::AsyncRuntime.sleep(retries * 0.5)
|
|
168
186
|
end
|
|
169
187
|
|
|
170
188
|
if response.code == 403
|
|
171
|
-
|
|
189
|
+
route = request_diagnostic(type, url, body, headers)
|
|
190
|
+
noprm = OnyxCord::Errors::NoPermission.new(
|
|
191
|
+
"The bot doesn't have the required permission to do this!",
|
|
192
|
+
status: response.code,
|
|
193
|
+
headers: response.headers,
|
|
194
|
+
route: route,
|
|
195
|
+
body: response.body,
|
|
196
|
+
response: response
|
|
197
|
+
)
|
|
172
198
|
noprm.define_singleton_method(:_response) { response }
|
|
173
|
-
raise noprm
|
|
199
|
+
raise noprm
|
|
174
200
|
end
|
|
175
201
|
|
|
176
202
|
if response.code >= 400 && response.code != 429
|
|
@@ -180,10 +206,20 @@ module OnyxCord::API
|
|
|
180
206
|
nil
|
|
181
207
|
end
|
|
182
208
|
|
|
183
|
-
|
|
209
|
+
route = request_diagnostic(type, url, body, headers)
|
|
210
|
+
unless data
|
|
211
|
+
raise OnyxCord::Errors::HTTPError.new(
|
|
212
|
+
"HTTP #{response.code} #{route}: #{response.body}",
|
|
213
|
+
status: response.code,
|
|
214
|
+
headers: response.headers,
|
|
215
|
+
route: route,
|
|
216
|
+
body: response.body,
|
|
217
|
+
response: response
|
|
218
|
+
)
|
|
219
|
+
end
|
|
184
220
|
|
|
185
221
|
err_klass = OnyxCord::Errors.error_class_for(data['code'] || 0)
|
|
186
|
-
e = err_klass.new(data['message'], data['errors'])
|
|
222
|
+
e = err_klass.new(data['message'], data['errors'], status: response.code, headers: response.headers, route: route, body: response.body, response: response)
|
|
187
223
|
OnyxCord::LOGGER.error(e.full_message)
|
|
188
224
|
raise e
|
|
189
225
|
end
|
|
@@ -243,6 +279,25 @@ module OnyxCord::API
|
|
|
243
279
|
end
|
|
244
280
|
end
|
|
245
281
|
|
|
282
|
+
def request_diagnostic(type, url, body, headers)
|
|
283
|
+
clean_url = url.to_s.sub(%r{/webhooks/(\d+)/[^?]+}, '/webhooks/\1/[token]')
|
|
284
|
+
.sub(%r{/interactions/(\d+)/[^/]+}, '/interactions/\1/[token]')
|
|
285
|
+
header_keys = headers.keys.map(&:to_s).sort.join(',')
|
|
286
|
+
body_info = if body.is_a?(Hash)
|
|
287
|
+
body.map do |key, value|
|
|
288
|
+
path = value.path if value.respond_to?(:path)
|
|
289
|
+
detail = path ? "file:#{File.basename(path)}" : value.to_s.bytesize
|
|
290
|
+
"#{key}=#{detail}"
|
|
291
|
+
end.join(',')
|
|
292
|
+
else
|
|
293
|
+
body.nil? ? 'nil' : "#{body.class}:#{body.to_s.bytesize}"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
"(#{type.to_s.upcase} #{clean_url} headers=#{header_keys} body=#{body_info})"
|
|
297
|
+
rescue StandardError => e
|
|
298
|
+
"(diagnostic_failed=#{e.class}: #{e.message})"
|
|
299
|
+
end
|
|
300
|
+
|
|
246
301
|
# Make an icon URL from server and icon IDs
|
|
247
302
|
def icon_url(server_id, icon_id, format = 'webp')
|
|
248
303
|
"#{cdn_url}/icons/#{server_id}/#{icon_id}.#{format}"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'onyxcord/webhooks'
|
|
4
|
-
require 'onyxcord/
|
|
4
|
+
require 'onyxcord/message_payload'
|
|
5
5
|
|
|
6
6
|
module OnyxCord
|
|
7
7
|
# Base class for interaction objects.
|
|
@@ -260,7 +260,7 @@ module OnyxCord
|
|
|
260
260
|
components ||= view
|
|
261
261
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components, force: has_components || components_v2)
|
|
262
262
|
data = builder.to_json_hash
|
|
263
|
-
resp = OnyxCord::API::Interaction.edit_original_interaction_response(@token, @application_id,
|
|
263
|
+
resp = OnyxCord::API::Interaction.edit_original_interaction_response(@token, @application_id, edit_content(content, data), edit_embeds(embeds, data), data[:allowed_mentions], components.to_a, attachments, flags, data[:poll])
|
|
264
264
|
|
|
265
265
|
Interactions::Message.new(JSON.parse(resp), @bot, self)
|
|
266
266
|
end
|
|
@@ -324,7 +324,7 @@ module OnyxCord
|
|
|
324
324
|
data = builder.to_json_hash
|
|
325
325
|
|
|
326
326
|
resp = OnyxCord::API::Webhook.token_edit_message(
|
|
327
|
-
@token, @application_id, message.resolve_id,
|
|
327
|
+
@token, @application_id, message.resolve_id, edit_content(content, data), edit_embeds(embeds, data), data[:allowed_mentions], components.to_a, attachments, flags, data[:poll]
|
|
328
328
|
)
|
|
329
329
|
Interactions::Message.new(JSON.parse(resp), @bot, self)
|
|
330
330
|
end
|
|
@@ -392,9 +392,17 @@ module OnyxCord
|
|
|
392
392
|
# @param poll [Poll, Poll::Builder, Hash, nil]
|
|
393
393
|
def prepare_builder(builder, content, embeds, allowed_mentions, poll)
|
|
394
394
|
builder.poll = poll
|
|
395
|
-
builder.content = content
|
|
395
|
+
builder.content = content unless content == OnyxCord::MessagePayload::KEEP
|
|
396
396
|
builder.allowed_mentions = allowed_mentions
|
|
397
|
-
embeds&.each { |embed| builder << embed }
|
|
397
|
+
embeds&.each { |embed| builder << embed } unless embeds == OnyxCord::MessagePayload::KEEP
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def edit_content(content, data)
|
|
401
|
+
content == OnyxCord::MessagePayload::KEEP ? OnyxCord::MessagePayload::KEEP : data[:content]
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def edit_embeds(embeds, data)
|
|
405
|
+
embeds == OnyxCord::MessagePayload::KEEP ? OnyxCord::MessagePayload::KEEP : data[:embeds]
|
|
398
406
|
end
|
|
399
407
|
|
|
400
408
|
# @!visibility private
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'onyxcord/
|
|
3
|
+
require 'onyxcord/message_payload'
|
|
4
4
|
|
|
5
5
|
module OnyxCord
|
|
6
6
|
# A message on Discord that was sent to a text channel
|
|
@@ -304,7 +304,11 @@ module OnyxCord
|
|
|
304
304
|
# @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2) can be edited.
|
|
305
305
|
# @return [Message] the resulting message.
|
|
306
306
|
def edit(new_content, new_embeds = nil, new_components = nil, flags = 0)
|
|
307
|
-
new_embeds =
|
|
307
|
+
new_embeds = if new_embeds == OnyxCord::MessagePayload::KEEP
|
|
308
|
+
new_embeds
|
|
309
|
+
else
|
|
310
|
+
(new_embeds.instance_of?(Array) ? new_embeds.map { |embed| embed.respond_to?(:to_hash) ? embed.to_hash : embed } : [new_embeds&.to_hash]).compact
|
|
311
|
+
end
|
|
308
312
|
new_components = new_components.to_a
|
|
309
313
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, new_components)
|
|
310
314
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'onyxcord/webhooks/builder'
|
|
4
4
|
require 'onyxcord/webhooks/view'
|
|
5
|
-
require 'onyxcord/
|
|
5
|
+
require 'onyxcord/message_payload'
|
|
6
6
|
|
|
7
7
|
module OnyxCord
|
|
8
8
|
# A webhook on a server channel
|
|
@@ -178,7 +178,7 @@ module OnyxCord
|
|
|
178
178
|
def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, builder: nil, components: nil, flags: 0, has_components: false, components_v2: false)
|
|
179
179
|
raise OnyxCord::Errors::UnauthorizedWebhook unless @token
|
|
180
180
|
|
|
181
|
-
params = {
|
|
181
|
+
params = { allowed_mentions: allowed_mentions }.compact
|
|
182
182
|
|
|
183
183
|
builder ||= Webhooks::Builder.new
|
|
184
184
|
view ||= Webhooks::View.new
|
|
@@ -189,7 +189,9 @@ module OnyxCord
|
|
|
189
189
|
components ||= view
|
|
190
190
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components, force: has_components || components_v2)
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
edit_content = content == OnyxCord::MessagePayload::KEEP || !content.nil? ? content : data[:content]
|
|
193
|
+
edit_embeds = embeds == OnyxCord::MessagePayload::KEEP || !embeds.nil? ? embeds : data[:embeds]
|
|
194
|
+
resp = API::Webhook.token_edit_message(@token, @id, message.resolve_id, edit_content, edit_embeds, data[:allowed_mentions], components.to_a, nil, flags)
|
|
193
195
|
Message.new(JSON.parse(resp), @bot)
|
|
194
196
|
end
|
|
195
197
|
|
data/lib/onyxcord/errors.rb
CHANGED
|
@@ -14,8 +14,22 @@ module OnyxCord
|
|
|
14
14
|
# Raised when a message is over the character limit
|
|
15
15
|
class MessageTooLong < RuntimeError; end
|
|
16
16
|
|
|
17
|
+
class HTTPError < RuntimeError
|
|
18
|
+
attr_reader :status, :code, :headers, :route, :body, :response
|
|
19
|
+
|
|
20
|
+
def initialize(message = nil, status: nil, code: 0, headers: {}, route: nil, body: nil, response: nil)
|
|
21
|
+
super(message)
|
|
22
|
+
@status = status
|
|
23
|
+
@code = code || 0
|
|
24
|
+
@headers = headers
|
|
25
|
+
@route = route
|
|
26
|
+
@body = body
|
|
27
|
+
@response = response
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
17
31
|
# Raised when the bot can't do something because its permissions on the server are insufficient
|
|
18
|
-
class NoPermission <
|
|
32
|
+
class NoPermission < HTTPError; end
|
|
19
33
|
|
|
20
34
|
# Raised when the bot gets a HTTP 502 error, which is usually caused by Cloudflare.
|
|
21
35
|
class CloudflareError < RuntimeError; end
|
|
@@ -24,7 +38,7 @@ module OnyxCord
|
|
|
24
38
|
class UnauthorizedWebhook < RuntimeError; end
|
|
25
39
|
|
|
26
40
|
# Generic class for errors denoted by API error codes
|
|
27
|
-
class CodeError <
|
|
41
|
+
class CodeError < HTTPError
|
|
28
42
|
class << self
|
|
29
43
|
# @return [Integer] The error code represented by this error class.
|
|
30
44
|
attr_reader :code
|
|
@@ -33,10 +47,11 @@ module OnyxCord
|
|
|
33
47
|
# Create a new error with a particular message (the code should be defined by the class instance variable)
|
|
34
48
|
# @param message [String] the message to use
|
|
35
49
|
# @param errors [Hash] API errors
|
|
36
|
-
def initialize(message, errors = nil)
|
|
50
|
+
def initialize(message, errors = nil, status: nil, headers: {}, route: nil, body: nil, response: nil)
|
|
37
51
|
@message = message
|
|
38
52
|
|
|
39
53
|
@errors = errors ? flatten_errors(errors) : []
|
|
54
|
+
super(full_message, status: status, code: code, headers: headers, route: route, body: body, response: response)
|
|
40
55
|
end
|
|
41
56
|
|
|
42
57
|
# @return [Integer] The error code represented by this error.
|
data/lib/onyxcord/gateway.rb
CHANGED
|
@@ -202,6 +202,7 @@ module OnyxCord
|
|
|
202
202
|
|
|
203
203
|
def reconnect(attempt_resume = true)
|
|
204
204
|
@session.suspend if @session && attempt_resume
|
|
205
|
+
@session.invalidate if attempt_resume && @session && !@received_hello
|
|
205
206
|
@instant_reconnect = true
|
|
206
207
|
@should_reconnect = true
|
|
207
208
|
close(4000)
|
|
@@ -301,6 +302,7 @@ module OnyxCord
|
|
|
301
302
|
|
|
302
303
|
@zlib_reader = Zlib::Inflate.new
|
|
303
304
|
@pipe_broken = false
|
|
305
|
+
@received_hello = false
|
|
304
306
|
@closed = false
|
|
305
307
|
|
|
306
308
|
endpoint = websocket_endpoint(url)
|
|
@@ -316,9 +318,11 @@ module OnyxCord
|
|
|
316
318
|
end
|
|
317
319
|
end
|
|
318
320
|
rescue StandardError => e
|
|
321
|
+
@session&.invalidate unless @received_hello
|
|
319
322
|
LOGGER.error('Error connecting to gateway!')
|
|
320
323
|
LOGGER.log_exception(e)
|
|
321
324
|
ensure
|
|
325
|
+
@session&.invalidate unless @received_hello
|
|
322
326
|
@closed = true
|
|
323
327
|
@connection = nil
|
|
324
328
|
end
|
|
@@ -424,6 +428,7 @@ module OnyxCord
|
|
|
424
428
|
|
|
425
429
|
# Op 10
|
|
426
430
|
def handle_hello(packet)
|
|
431
|
+
@received_hello = true
|
|
427
432
|
LOGGER.debug('Hello!')
|
|
428
433
|
interval = packet['d']['heartbeat_interval'].to_f / 1000.0
|
|
429
434
|
setup_heartbeats(interval)
|
data/lib/onyxcord/http.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'httpx'
|
|
4
|
+
require 'net/http'
|
|
4
5
|
require 'onyxcord/json'
|
|
6
|
+
require 'onyxcord/upload'
|
|
7
|
+
require 'securerandom'
|
|
8
|
+
require 'uri'
|
|
5
9
|
|
|
6
10
|
module OnyxCord
|
|
7
11
|
# Modern HTTP adapter wrapping HTTPX with persistent connections, automatic
|
|
@@ -22,8 +26,9 @@ module OnyxCord
|
|
|
22
26
|
def initialize(httpx_response)
|
|
23
27
|
@raw = httpx_response
|
|
24
28
|
@body = httpx_response.body.to_s
|
|
25
|
-
@code = httpx_response.status
|
|
26
|
-
|
|
29
|
+
@code = httpx_response.respond_to?(:status) ? httpx_response.status : httpx_response.code.to_i
|
|
30
|
+
response_headers = httpx_response.respond_to?(:headers) ? httpx_response.headers : httpx_response.to_hash
|
|
31
|
+
@headers = normalize_headers(response_headers)
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
def to_s
|
|
@@ -39,6 +44,8 @@ module OnyxCord
|
|
|
39
44
|
def normalize_headers(headers)
|
|
40
45
|
headers.to_h.transform_keys do |key|
|
|
41
46
|
key.to_s.tr('-', '_').downcase.to_sym
|
|
47
|
+
end.transform_values do |value|
|
|
48
|
+
value.is_a?(Array) && value.size == 1 ? value.first : value
|
|
42
49
|
end
|
|
43
50
|
end
|
|
44
51
|
end
|
|
@@ -49,6 +56,10 @@ module OnyxCord
|
|
|
49
56
|
def session
|
|
50
57
|
Thread.current[:onyxcord_http_session] ||= HTTPX.plugin(:persistent)
|
|
51
58
|
.plugin(:follow_redirects)
|
|
59
|
+
.with(
|
|
60
|
+
fallback_protocol: 'http/1.1',
|
|
61
|
+
ssl: { alpn_protocols: ['http/1.1'] }
|
|
62
|
+
)
|
|
52
63
|
end
|
|
53
64
|
|
|
54
65
|
# Reset the HTTP session (useful for tests).
|
|
@@ -69,9 +80,9 @@ module OnyxCord
|
|
|
69
80
|
when :get
|
|
70
81
|
http.get(url)
|
|
71
82
|
when :post
|
|
72
|
-
if
|
|
83
|
+
if multipart?(body)
|
|
73
84
|
# Multipart upload
|
|
74
|
-
|
|
85
|
+
post_multipart(url, body, headers)
|
|
75
86
|
else
|
|
76
87
|
http.post(url, body: body)
|
|
77
88
|
end
|
|
@@ -91,6 +102,66 @@ module OnyxCord
|
|
|
91
102
|
Response.new(raw)
|
|
92
103
|
end
|
|
93
104
|
|
|
105
|
+
def post_multipart(url, body, headers)
|
|
106
|
+
uri = URI(url)
|
|
107
|
+
boundary = "----OnyxCord#{SecureRandom.hex(12)}"
|
|
108
|
+
request = Net::HTTP::Post.new(uri)
|
|
109
|
+
headers.each { |key, value| request[key.to_s] = value }
|
|
110
|
+
request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
|
|
111
|
+
request.body = multipart_body(body, boundary)
|
|
112
|
+
|
|
113
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
114
|
+
http.request(request)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def multipart_body(body, boundary)
|
|
119
|
+
output = String.new.b
|
|
120
|
+
|
|
121
|
+
multipart_parts(body).each do |part|
|
|
122
|
+
key = part[:name]
|
|
123
|
+
value = part[:value]
|
|
124
|
+
output << "--#{boundary}\r\n"
|
|
125
|
+
if part[:filename]
|
|
126
|
+
value.rewind if value.respond_to?(:rewind)
|
|
127
|
+
filename = part[:filename]
|
|
128
|
+
output << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{filename}\"\r\n"
|
|
129
|
+
output << "Content-Type: #{part[:content_type] || multipart_content_type(filename)}\r\n\r\n"
|
|
130
|
+
output << value.read.to_s
|
|
131
|
+
else
|
|
132
|
+
output << "Content-Disposition: form-data; name=\"#{key}\"\r\n"
|
|
133
|
+
output << "\r\n"
|
|
134
|
+
output << value.to_s
|
|
135
|
+
end
|
|
136
|
+
output << "\r\n"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
output << "--#{boundary}--\r\n"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def multipart_content_type(filename)
|
|
143
|
+
File.extname(filename).casecmp('.txt').zero? ? 'text/plain' : 'application/octet-stream'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def multipart?(body)
|
|
147
|
+
return true if body.is_a?(Array) && body.all? { |part| part.is_a?(Hash) && part[:name] && part.key?(:value) }
|
|
148
|
+
|
|
149
|
+
body.is_a?(Hash) && body.any? { |_, value| value.respond_to?(:read) || value.respond_to?(:path) }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def multipart_parts(body)
|
|
153
|
+
return body if body.is_a?(Array)
|
|
154
|
+
|
|
155
|
+
body.map do |key, value|
|
|
156
|
+
if value.respond_to?(:read) || value.respond_to?(:path)
|
|
157
|
+
upload = OnyxCord::Upload.wrap(value)
|
|
158
|
+
{ name: key, value: upload, filename: upload.filename, content_type: upload.content_type }
|
|
159
|
+
else
|
|
160
|
+
{ name: key, value: value }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
94
165
|
# Convenience wrappers
|
|
95
166
|
def get(url, **headers)
|
|
96
167
|
request(:get, url, nil, **headers)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'onyxcord/message_components'
|
|
4
|
+
require 'onyxcord/upload'
|
|
5
|
+
|
|
6
|
+
module OnyxCord
|
|
7
|
+
module MessagePayload
|
|
8
|
+
MAX_ATTACHMENTS = 10
|
|
9
|
+
MAX_EMBEDS = 10
|
|
10
|
+
KEEP = :keep
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def attachment_payload(attachments)
|
|
15
|
+
uploads(attachments).map.with_index { |upload, index| { id: index, filename: upload.filename } }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def multipart_body(body, attachments)
|
|
19
|
+
parts = uploads(attachments).map.with_index do |upload, index|
|
|
20
|
+
{
|
|
21
|
+
name: "files[#{index}]",
|
|
22
|
+
value: upload,
|
|
23
|
+
filename: upload.filename,
|
|
24
|
+
content_type: upload.content_type || 'application/octet-stream'
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
parts << { name: 'payload_json', value: body.to_json }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate!(content: nil, embeds: nil, components: nil, flags: nil, attachments: nil, poll: nil)
|
|
32
|
+
validate_limit!('attachments', attachments, MAX_ATTACHMENTS)
|
|
33
|
+
validate_limit!('embeds', embeds, MAX_EMBEDS)
|
|
34
|
+
validate_components_v2!(content, embeds, components, flags, poll)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def edit_body(content, embeds)
|
|
38
|
+
content_keep = content == KEEP
|
|
39
|
+
embeds_keep = embeds == KEEP
|
|
40
|
+
content_given = !content_keep && !content.nil?
|
|
41
|
+
embeds_given = !embeds_keep && !embeds.nil?
|
|
42
|
+
body = {}
|
|
43
|
+
body[:content] = content if !content_keep && (content_given || embeds_given)
|
|
44
|
+
body[:embeds] = embeds_given ? embeds : [] if !embeds_keep && (embeds_given || content_given)
|
|
45
|
+
body
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def uploads(attachments)
|
|
49
|
+
Array(attachments).map { |attachment| OnyxCord::Upload.wrap(attachment) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def validate_limit!(name, values, max)
|
|
53
|
+
return if values.nil? || Array(values).length <= max
|
|
54
|
+
|
|
55
|
+
raise ArgumentError, "#{name} cannot exceed #{max} elements"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate_components_v2!(content, embeds, components, flags, poll)
|
|
59
|
+
return unless components_v2?(components, flags)
|
|
60
|
+
return unless present?(content) || present?(embeds) || present?(poll)
|
|
61
|
+
|
|
62
|
+
raise ArgumentError, 'Components V2 messages cannot include content, embeds, or poll'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def components_v2?(components, flags)
|
|
66
|
+
OnyxCord::MessageComponents.components_v2?(components) ||
|
|
67
|
+
(OnyxCord::MessageComponents.flag_value(flags) & OnyxCord::MessageComponents::IS_COMPONENTS_V2).positive?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def present?(value)
|
|
71
|
+
return false if value.nil?
|
|
72
|
+
return false if value == KEEP
|
|
73
|
+
return !value.empty? if value.respond_to?(:empty?)
|
|
74
|
+
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -21,6 +21,7 @@ module OnyxCord
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def before_request(route, major_parameter)
|
|
24
|
+
wait_for(@global_lock)
|
|
24
25
|
wait_for(mutex_for(route, major_parameter))
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -142,8 +143,8 @@ module OnyxCord
|
|
|
142
143
|
end
|
|
143
144
|
|
|
144
145
|
def global_wait(time)
|
|
145
|
-
|
|
146
|
+
async_wait(time, @global_lock)
|
|
146
147
|
end
|
|
147
148
|
end
|
|
148
149
|
end
|
|
149
|
-
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
# Small wrapper for file uploads while keeping raw File/Tempfile support.
|
|
5
|
+
class Upload
|
|
6
|
+
attr_reader :io, :filename, :content_type
|
|
7
|
+
|
|
8
|
+
def self.wrap(file)
|
|
9
|
+
file.is_a?(self) ? file : new(file)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(io, filename: nil, content_type: nil)
|
|
13
|
+
@io = io
|
|
14
|
+
@filename = filename || upload_filename(io)
|
|
15
|
+
@content_type = content_type
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def read(...)
|
|
19
|
+
@io.read(...)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def rewind
|
|
23
|
+
@io.rewind if @io.respond_to?(:rewind)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def path
|
|
27
|
+
@io.path if @io.respond_to?(:path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def upload_filename(io)
|
|
33
|
+
path = io.path if io.respond_to?(:path)
|
|
34
|
+
return File.basename(path) if path && !path.empty?
|
|
35
|
+
|
|
36
|
+
'upload.dat'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/onyxcord/version.rb
CHANGED
data/lib/onyxcord.rb
CHANGED
|
@@ -8,6 +8,8 @@ require 'onyxcord/version'
|
|
|
8
8
|
require 'onyxcord/configuration'
|
|
9
9
|
require 'onyxcord/event_executor'
|
|
10
10
|
require 'onyxcord/message_components'
|
|
11
|
+
require 'onyxcord/message_payload'
|
|
12
|
+
require 'onyxcord/upload'
|
|
11
13
|
require 'onyxcord/webhooks'
|
|
12
14
|
require 'onyxcord/bot'
|
|
13
15
|
require 'onyxcord/commands/command_bot'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: onyxcord
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gustavo Silva
|
|
@@ -486,11 +486,13 @@ files:
|
|
|
486
486
|
- lib/onyxcord/light/light_bot.rb
|
|
487
487
|
- lib/onyxcord/logger.rb
|
|
488
488
|
- lib/onyxcord/message_components.rb
|
|
489
|
+
- lib/onyxcord/message_payload.rb
|
|
489
490
|
- lib/onyxcord/paginator.rb
|
|
490
491
|
- lib/onyxcord/permissions.rb
|
|
491
492
|
- lib/onyxcord/rate_limiter/async_rest.rb
|
|
492
493
|
- lib/onyxcord/rate_limiter/gateway.rb
|
|
493
494
|
- lib/onyxcord/rate_limiter/rest.rb
|
|
495
|
+
- lib/onyxcord/upload.rb
|
|
494
496
|
- lib/onyxcord/version.rb
|
|
495
497
|
- lib/onyxcord/voice/encoder.rb
|
|
496
498
|
- lib/onyxcord/voice/network.rb
|
|
@@ -530,7 +532,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
530
532
|
- !ruby/object:Gem::Version
|
|
531
533
|
version: '0'
|
|
532
534
|
requirements: []
|
|
533
|
-
rubygems_version:
|
|
535
|
+
rubygems_version: 3.6.9
|
|
534
536
|
specification_version: 4
|
|
535
537
|
summary: Discord API for Ruby with Components V2 support
|
|
536
538
|
test_files: []
|