onyxcord 1.1.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile +0 -1
- data/lib/onyxcord/api/channel.rb +1 -13
- data/lib/onyxcord/api/webhook.rb +1 -1
- data/lib/onyxcord/api.rb +90 -37
- data/lib/onyxcord/bot.rb +2 -5
- data/lib/onyxcord/cache.rb +10 -11
- data/lib/onyxcord/commands/command_bot.rb +11 -17
- data/lib/onyxcord/configuration.rb +44 -1
- data/lib/onyxcord/data/interaction.rb +11 -13
- data/lib/onyxcord/event_executor.rb +29 -10
- data/lib/onyxcord/gateway.rb +88 -499
- data/lib/onyxcord/rate_limiter/rest.rb +1 -1
- data/lib/onyxcord/version.rb +1 -1
- data/lib/onyxcord/voice/network.rb +2 -2
- data/lib/onyxcord/webhooks/builder.rb +150 -0
- data/lib/onyxcord/webhooks/client.rb +184 -0
- data/lib/onyxcord/webhooks/embeds.rb +250 -0
- data/lib/onyxcord/webhooks/modal.rb +284 -0
- data/lib/onyxcord/webhooks/version.rb +9 -0
- data/lib/onyxcord/webhooks/view.rb +578 -0
- data/lib/onyxcord/websocket.rb +59 -40
- data/lib/onyxcord.rb +5 -0
- data/onyxcord-webhooks.gemspec +14 -5
- data/onyxcord.gemspec +20 -7
- metadata +114 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '00639da6b7f1bb86015ad2789db3c8a31a8a2865d26d594bb0bdf15914dbb534'
|
|
4
|
+
data.tar.gz: 0ac2f4e68b91633c3d1006faf2def1d6df6a99a9898fc2c38de819d5048f41f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae1eab8467d518c3bd98148274b3bc152e1c9d8b72696c27fe621e069a7d2ff75ff33dddfda22b00702f25a85b38702a31accaf1d00d0366d831cc96b38cea3b
|
|
7
|
+
data.tar.gz: dc2bb8086656c80b8d97c7992f99a75aed3efd7481a9de4c52713edbbc02536ab6074d27931b88430b92a72a3b5412e58fdff8ee5ccb4e936edd19f08c7edbc0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.0 - 2026-06-28
|
|
4
|
+
|
|
5
|
+
### Arquitetura & Performance (Major Refactoring)
|
|
6
|
+
|
|
7
|
+
- **Runtime 100% Async**: A infraestrutura de threads (`Thread.new`) foi substituída pelo modelo de fibers do Ruby usando a gem `async`. Gateway, heartbeats, fila REST, worker de eventos e comandos agora executam de forma não-bloqueante no reator Async.
|
|
8
|
+
- **REST Moderno via HTTPX**: Substituída a gem legada `rest-client` pela `httpx`, trazendo suporte nativo a conexões persistentes (Keep-Alive), HTTP/2, multipart uploads nativos e retries automáticos em erros 502.
|
|
9
|
+
- **Gateway via Async-WebSocket**: Substituída a implementação de raw TCP sockets + `websocket-client-simple` por `async-websocket`, proporcionando um event loop de gateway extremamente rápido e escalável.
|
|
10
|
+
- **Parse JSON via Oj**: A gem `oj` foi integrada em modo de compatibilidade (`mode: :compat`), acelerando transparentemente todas as serializações e deserializações de pacotes do Discord na lib inteira.
|
|
11
|
+
- **Cache Inteligente LRU**: Os caches em memória (usuários, canais, servidores, membros) agora utilizam `LruRedux::ThreadSafeCache`. Os tamanhos padrão foram aumentados (`users: 50_000`, `channels: 10_000`, `servers: 1_000`, `members: 100_000`) e podem ser customizados via `OnyxCord.configure { |c| c.cache_sizes.users = 100_000 }`.
|
|
12
|
+
- **Fusão do Webhooks**: A funcionalidade da gem separada `onyxcord-webhooks` foi integrada diretamente no núcleo da gem `onyxcord`. A gem `onyxcord-webhooks` agora atua apenas como um shim de transição deprecado.
|
|
13
|
+
- **Alvo Ruby ≥ 3.4**: Atualizada a versão mínima requerida do Ruby para aproveitar as otimizações modernas do interpretador e fibras.
|
|
14
|
+
|
|
3
15
|
## 1.1.8 - 2026-06-28
|
|
4
16
|
|
|
5
17
|
### Correcoes
|
|
@@ -10,6 +22,7 @@
|
|
|
10
22
|
### Validacao
|
|
11
23
|
|
|
12
24
|
- `bundle exec rspec spec/components_v2_spec.rb`: sucesso.
|
|
25
|
+
- `bundle exec rspec`: 460 exemplos, 0 falhas, 3 pendentes.
|
|
13
26
|
- `ruby -c lib/onyxcord/data/component.rb`: sucesso.
|
|
14
27
|
- `ruby -c spec/components_v2_spec.rb`: sucesso.
|
|
15
28
|
- `gem build onyxcord.gemspec`: sucesso.
|
data/Gemfile
CHANGED
data/lib/onyxcord/api/channel.rb
CHANGED
|
@@ -23,7 +23,6 @@ module OnyxCord::API::Channel
|
|
|
23
23
|
{ **files, payload_json: body.to_json }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
# Get a channel's data
|
|
28
27
|
# https://discord.com/developers/docs/resources/channel#get-channel
|
|
29
28
|
def resolve(token, channel_id)
|
|
@@ -111,7 +110,7 @@ module OnyxCord::API::Channel
|
|
|
111
110
|
# @param attachments [Array<File>, nil] Attachments to use with `attachment://` in embeds. See
|
|
112
111
|
# https://discord.com/developers/docs/resources/channel#create-message-using-attachments-within-embeds
|
|
113
112
|
def create_message(token, channel_id, message, tts = false, embeds = nil, nonce = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = nil, enforce_nonce = false, poll = nil)
|
|
114
|
-
tts = false unless
|
|
113
|
+
tts = false unless [true, false].include?(tts)
|
|
115
114
|
components = OnyxCord::MessageComponents.payload(components) unless components.nil?
|
|
116
115
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
|
|
117
116
|
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
|
|
@@ -132,11 +131,6 @@ module OnyxCord::API::Channel
|
|
|
132
131
|
body,
|
|
133
132
|
**headers
|
|
134
133
|
)
|
|
135
|
-
rescue RestClient::BadRequest => e
|
|
136
|
-
parsed = JSON.parse(e.response.body)
|
|
137
|
-
raise OnyxCord::Errors::MessageTooLong, "Message over the character limit (#{message.length} > 2000)" if parsed['content'].is_a?(Array) && parsed['content'].first == 'Must be 2000 or fewer in length.'
|
|
138
|
-
|
|
139
|
-
raise
|
|
140
134
|
end
|
|
141
135
|
|
|
142
136
|
# Send a file as a message to a channel
|
|
@@ -425,12 +419,6 @@ module OnyxCord::API::Channel
|
|
|
425
419
|
Authorization: token,
|
|
426
420
|
content_type: :json
|
|
427
421
|
)
|
|
428
|
-
rescue RestClient::InternalServerError
|
|
429
|
-
raise 'Attempted to add self as a new group channel recipient!'
|
|
430
|
-
rescue RestClient::NoContent
|
|
431
|
-
raise 'Attempted to create a group channel with the PM channel recipient!'
|
|
432
|
-
rescue RestClient::Forbidden
|
|
433
|
-
raise 'Attempted to add a user to group channel without permission!'
|
|
434
422
|
end
|
|
435
423
|
|
|
436
424
|
# Add a user to a group channel.
|
data/lib/onyxcord/api/webhook.rb
CHANGED
|
@@ -62,7 +62,7 @@ module OnyxCord::API::Webhook
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
headers = { content_type: :json } unless file || attachments
|
|
65
|
-
with_components = components&.any?
|
|
65
|
+
with_components = components&.any? || nil
|
|
66
66
|
query = URI.encode_www_form({ wait: wait, with_components: with_components }.compact)
|
|
67
67
|
|
|
68
68
|
OnyxCord::API.request(
|
data/lib/onyxcord/api.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
require 'json'
|
|
3
|
+
require 'onyxcord/http'
|
|
4
|
+
require 'onyxcord/json'
|
|
5
5
|
require 'time'
|
|
6
6
|
|
|
7
7
|
require 'onyxcord/errors'
|
|
@@ -55,7 +55,7 @@ module OnyxCord::API
|
|
|
55
55
|
required = "DiscordBot (https://github.com/kruldevb/OnyxCord, v#{OnyxCord::VERSION})"
|
|
56
56
|
@bot_name ||= ''
|
|
57
57
|
|
|
58
|
-
"#{required}
|
|
58
|
+
"#{required} httpx/#{HTTPX::VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} onyxcord/#{OnyxCord::VERSION} #{@bot_name}"
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Resets all rate limit mutexes
|
|
@@ -82,49 +82,96 @@ module OnyxCord::API
|
|
|
82
82
|
mutex.unlock
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
# Performs a
|
|
85
|
+
# Performs a raw HTTP request using HTTPX.
|
|
86
86
|
# @param type [Symbol] The type of HTTP request to use.
|
|
87
|
-
# @param
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
# @param url [String] The URL to request.
|
|
88
|
+
# @param body [String, Hash, nil] The request body.
|
|
89
|
+
# @param headers [Hash] Additional headers.
|
|
90
|
+
# @return [OnyxCord::HTTP::Response]
|
|
91
|
+
def raw_request(type, url, body = nil, **headers)
|
|
92
|
+
headers[:user_agent] = user_agent
|
|
93
|
+
|
|
94
|
+
response = OnyxCord::HTTP.request(type, url, body, **headers)
|
|
95
|
+
|
|
96
|
+
if response.code == 403
|
|
97
|
+
noprm = OnyxCord::Errors::NoPermission.new
|
|
98
|
+
noprm.define_singleton_method(:_response) { response }
|
|
99
|
+
raise noprm, "The bot doesn't have the required permission to do this!"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Retry on 502 Bad Gateway
|
|
103
|
+
if response.code == 502
|
|
104
|
+
OnyxCord::LOGGER.warn('Got a 502 while sending a request! Not a big deal, retrying the request')
|
|
105
|
+
return raw_request(type, url, body, **headers)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
response
|
|
98
109
|
end
|
|
99
110
|
|
|
100
111
|
# Make an API request, including rate limit handling.
|
|
101
112
|
def request(key, major_parameter, type, *attributes)
|
|
102
|
-
#
|
|
103
|
-
|
|
113
|
+
# Parse attributes: URL is first, body is second (if present), rest is headers hash
|
|
114
|
+
url = attributes.shift
|
|
115
|
+
headers_or_body = attributes
|
|
116
|
+
|
|
117
|
+
# Separate body and headers from the positional args
|
|
118
|
+
body = nil
|
|
119
|
+
headers = {}
|
|
120
|
+
|
|
121
|
+
headers_or_body.each do |arg|
|
|
122
|
+
if arg.is_a?(Hash)
|
|
123
|
+
headers.merge!(arg)
|
|
124
|
+
elsif body.nil?
|
|
125
|
+
body = arg
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Extract content_type from headers for HTTPX
|
|
130
|
+
content_type = headers.delete(:content_type)
|
|
131
|
+
headers['content-type'] = 'application/json' if content_type == :json
|
|
132
|
+
|
|
133
|
+
# Add user agent
|
|
134
|
+
headers['user-agent'] = user_agent
|
|
104
135
|
|
|
105
136
|
begin
|
|
106
137
|
rate_limiter.before_request(key, major_parameter)
|
|
107
138
|
|
|
108
139
|
response = nil
|
|
109
140
|
begin
|
|
110
|
-
response =
|
|
111
|
-
|
|
112
|
-
response
|
|
141
|
+
response = OnyxCord::HTTP.request(type, url, body, **headers)
|
|
142
|
+
|
|
143
|
+
if response.code == 403
|
|
144
|
+
noprm = OnyxCord::Errors::NoPermission.new
|
|
145
|
+
noprm.define_singleton_method(:_response) { response }
|
|
146
|
+
raise noprm, "The bot doesn't have the required permission to do this!"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Retry on 502
|
|
150
|
+
if response.code == 502
|
|
151
|
+
OnyxCord::LOGGER.warn('Got a 502 while sending a request! Not a big deal, retrying the request')
|
|
152
|
+
return request(key, major_parameter, type, url, body, headers)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Handle error status codes
|
|
156
|
+
if response.code >= 400 && response.code != 429
|
|
157
|
+
data = begin
|
|
158
|
+
JSON.parse(response.body)
|
|
159
|
+
rescue StandardError
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
raise "HTTP #{response.code}: #{response.body}" unless data
|
|
113
164
|
|
|
114
|
-
if response.body && !e.is_a?(RestClient::TooManyRequests)
|
|
115
|
-
data = JSON.parse(response.body)
|
|
116
165
|
err_klass = OnyxCord::Errors.error_class_for(data['code'] || 0)
|
|
117
166
|
e = err_klass.new(data['message'], data['errors'])
|
|
118
|
-
|
|
119
167
|
OnyxCord::LOGGER.error(e.full_message)
|
|
168
|
+
raise e
|
|
120
169
|
end
|
|
121
|
-
|
|
122
|
-
raise e
|
|
123
170
|
rescue OnyxCord::Errors::NoPermission => e
|
|
124
|
-
if e.respond_to?(:
|
|
125
|
-
response = e.
|
|
171
|
+
if e.respond_to?(:_response)
|
|
172
|
+
response = e._response
|
|
126
173
|
else
|
|
127
|
-
OnyxCord::LOGGER.warn("NoPermission doesn't respond_to?
|
|
174
|
+
OnyxCord::LOGGER.warn("NoPermission doesn't respond_to? _response!")
|
|
128
175
|
end
|
|
129
176
|
|
|
130
177
|
raise e
|
|
@@ -135,27 +182,33 @@ module OnyxCord::API
|
|
|
135
182
|
OnyxCord::LOGGER.ratelimit('Response was nil before trying to preemptively rate limit!')
|
|
136
183
|
end
|
|
137
184
|
end
|
|
138
|
-
rescue
|
|
139
|
-
|
|
140
|
-
rate_limiter.handle_rate_limit(key, major_parameter, e.response)
|
|
185
|
+
rescue OnyxCord::Errors::CodeError => e
|
|
186
|
+
raise if e.respond_to?(:code) && e.code != 429_000
|
|
141
187
|
|
|
142
|
-
|
|
188
|
+
raise
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Handle 429 rate limiting
|
|
192
|
+
if response&.code == 429
|
|
193
|
+
trace("429 #{key} #{major_parameter}")
|
|
194
|
+
rate_limiter.handle_rate_limit(key, major_parameter, response)
|
|
195
|
+
return request(key, major_parameter, type, url, body, headers)
|
|
143
196
|
end
|
|
144
197
|
|
|
145
198
|
# Endpoints that use Elasticsearch can return a 202 when the index isn't ready yet. Wait the
|
|
146
199
|
# amount of time indicated by the response body, and then recursively retry and return the request.
|
|
147
200
|
if response&.code == 202 && response&.body
|
|
148
|
-
|
|
201
|
+
body_data = JSON.parse(response.body)
|
|
149
202
|
|
|
150
|
-
if
|
|
151
|
-
case
|
|
203
|
+
if body_data['code'] == 110_000
|
|
204
|
+
case body_data['retry_after']
|
|
152
205
|
when 0, 1, nil
|
|
153
206
|
sleep(rand(4.5..5.0))
|
|
154
207
|
else
|
|
155
|
-
sleep(
|
|
208
|
+
sleep(body_data['retry_after'])
|
|
156
209
|
end
|
|
157
210
|
|
|
158
|
-
return request(key, major_parameter, type,
|
|
211
|
+
return request(key, major_parameter, type, url, body, headers)
|
|
159
212
|
end
|
|
160
213
|
end
|
|
161
214
|
|
data/lib/onyxcord/bot.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'onyxcord/http'
|
|
4
4
|
require 'zlib'
|
|
5
5
|
|
|
6
6
|
require 'onyxcord/configuration'
|
|
@@ -477,9 +477,7 @@ module OnyxCord
|
|
|
477
477
|
# @param enforce_nonce [true, false] Whether the nonce should be enforced and used for message de-duplication.
|
|
478
478
|
# @param poll [Hash, Poll::Builder, Poll, nil] The poll that should be attached to this message.
|
|
479
479
|
def send_temporary_message(channel, content, timeout, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = 0, nonce = nil, enforce_nonce = false, poll = nil)
|
|
480
|
-
|
|
481
|
-
Thread.current[:onyxcord_name] = "#{@current_thread}-temp-msg"
|
|
482
|
-
|
|
480
|
+
Async do
|
|
483
481
|
message = send_message(channel, content, tts, embeds, attachments, allowed_mentions, message_reference, components, flags, nonce, enforce_nonce, poll)
|
|
484
482
|
sleep(timeout)
|
|
485
483
|
message.delete
|
|
@@ -504,7 +502,6 @@ module OnyxCord
|
|
|
504
502
|
filename ||= File.basename(file.path)
|
|
505
503
|
filename = "SPOILER_#{filename}" unless filename.start_with? 'SPOILER_'
|
|
506
504
|
end
|
|
507
|
-
# https://github.com/rest-client/rest-client/blob/v2.0.2/lib/restclient/payload.rb#L160
|
|
508
505
|
file.define_singleton_method(:original_filename) { filename } if filename
|
|
509
506
|
file.define_singleton_method(:path) { filename } if filename
|
|
510
507
|
end
|
data/lib/onyxcord/cache.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'lru_redux'
|
|
3
4
|
require 'onyxcord/api'
|
|
4
5
|
require 'onyxcord/api/server'
|
|
5
6
|
require 'onyxcord/api/invite'
|
|
@@ -26,17 +27,15 @@ module OnyxCord
|
|
|
26
27
|
# Initializes this cache
|
|
27
28
|
def init_cache
|
|
28
29
|
@cache_policy ||= OnyxCord.configuration.normalize_cache(:full)
|
|
30
|
+
sizes = OnyxCord.configuration.cache_sizes
|
|
29
31
|
|
|
30
|
-
@users = cache_enabled?(:users) ?
|
|
31
|
-
|
|
32
|
+
@users = cache_enabled?(:users) ? LruRedux::ThreadSafeCache.new(sizes.users) : nil
|
|
32
33
|
@voice_regions = cache_enabled?(:voice_regions) ? {} : nil
|
|
33
|
-
|
|
34
|
-
@
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
@
|
|
38
|
-
@thread_members = cache_enabled?(:thread_members) ? {} : nil
|
|
39
|
-
@server_previews = cache_enabled?(:server_previews) ? {} : nil
|
|
34
|
+
@servers = cache_enabled?(:servers) ? LruRedux::ThreadSafeCache.new(sizes.servers) : nil
|
|
35
|
+
@channels = cache_enabled?(:channels) ? LruRedux::ThreadSafeCache.new(sizes.channels) : nil
|
|
36
|
+
@pm_channels = cache_enabled?(:pm_channels) ? LruRedux::ThreadSafeCache.new(sizes.pm_channels) : nil
|
|
37
|
+
@thread_members = cache_enabled?(:thread_members) ? LruRedux::ThreadSafeCache.new(sizes.thread_members) : nil
|
|
38
|
+
@server_previews = cache_enabled?(:server_previews) ? LruRedux::ThreadSafeCache.new(sizes.server_previews) : nil
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
def cache_enabled?(key)
|
|
@@ -46,7 +45,7 @@ module OnyxCord
|
|
|
46
45
|
def cache_stats
|
|
47
46
|
CACHE_STORES.each_with_object({}) do |(key, ivar), stats|
|
|
48
47
|
store = instance_variable_get(ivar)
|
|
49
|
-
stats[key] = store.respond_to?(:
|
|
48
|
+
stats[key] = store.respond_to?(:count) ? store.count : 0
|
|
50
49
|
end
|
|
51
50
|
end
|
|
52
51
|
|
|
@@ -56,7 +55,7 @@ module OnyxCord
|
|
|
56
55
|
keys.each_with_object({}) do |key, pruned|
|
|
57
56
|
ivar = CACHE_STORES.fetch(key)
|
|
58
57
|
store = instance_variable_get(ivar)
|
|
59
|
-
pruned[key] = store.respond_to?(:
|
|
58
|
+
pruned[key] = store.respond_to?(:count) ? store.count : 0
|
|
60
59
|
store&.clear
|
|
61
60
|
end
|
|
62
61
|
end
|
|
@@ -480,24 +480,18 @@ module OnyxCord::Commands
|
|
|
480
480
|
end
|
|
481
481
|
|
|
482
482
|
def execute_chain(chain, event)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
event.send_file(event.file, caption: result)
|
|
493
|
-
else
|
|
494
|
-
event.respond result unless result.nil? || result.empty?
|
|
495
|
-
end
|
|
496
|
-
rescue StandardError => e
|
|
497
|
-
log_exception(e)
|
|
498
|
-
ensure
|
|
499
|
-
@event_threads.delete(t)
|
|
483
|
+
Async do
|
|
484
|
+
debug("Parsing command chain #{chain}")
|
|
485
|
+
result = @attributes[:advanced_functionality] ? CommandChain.new(chain, self).execute(event) : simple_execute(chain, event)
|
|
486
|
+
result = event.drain_into(result)
|
|
487
|
+
|
|
488
|
+
if event.file
|
|
489
|
+
event.send_file(event.file, caption: result)
|
|
490
|
+
else
|
|
491
|
+
event.respond result unless result.nil? || result.empty?
|
|
500
492
|
end
|
|
493
|
+
rescue StandardError => e
|
|
494
|
+
log_exception(e)
|
|
501
495
|
end
|
|
502
496
|
end
|
|
503
497
|
|
|
@@ -43,11 +43,53 @@ module OnyxCord
|
|
|
43
43
|
}
|
|
44
44
|
}.freeze
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
# Stores maximum limits for each LRU cache entity type.
|
|
47
|
+
class CacheSizes
|
|
48
|
+
attr_accessor :servers, :channels, :users, :members, :pm_channels, :thread_members, :server_previews
|
|
49
|
+
|
|
50
|
+
def initialize
|
|
51
|
+
@servers = 1000
|
|
52
|
+
@channels = 10_000
|
|
53
|
+
@users = 50_000
|
|
54
|
+
@members = 100_000
|
|
55
|
+
@pm_channels = 1000
|
|
56
|
+
@thread_members = 5000
|
|
57
|
+
@server_previews = 100
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def [](key)
|
|
61
|
+
send(key)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def []=(key, value)
|
|
65
|
+
send("#{key}=", value)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_h
|
|
69
|
+
{
|
|
70
|
+
servers: @servers,
|
|
71
|
+
channels: @channels,
|
|
72
|
+
users: @users,
|
|
73
|
+
members: @members,
|
|
74
|
+
pm_channels: @pm_channels,
|
|
75
|
+
thread_members: @thread_members,
|
|
76
|
+
server_previews: @server_previews
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def dup
|
|
81
|
+
copy = self.class.new
|
|
82
|
+
to_h.each { |k, v| copy[k] = v }
|
|
83
|
+
copy
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
attr_accessor :mode, :cache, :cache_sizes, :event_executor, :event_workers, :event_queue_size
|
|
47
88
|
|
|
48
89
|
def initialize
|
|
49
90
|
@mode = :hybrid
|
|
50
91
|
@cache = :none
|
|
92
|
+
@cache_sizes = CacheSizes.new
|
|
51
93
|
@event_executor = :pool
|
|
52
94
|
@event_workers = 4
|
|
53
95
|
@event_queue_size = nil
|
|
@@ -57,6 +99,7 @@ module OnyxCord
|
|
|
57
99
|
copy = self.class.new
|
|
58
100
|
copy.mode = @mode
|
|
59
101
|
copy.cache = @cache.is_a?(Hash) ? @cache.dup : @cache
|
|
102
|
+
copy.cache_sizes = @cache_sizes.dup
|
|
60
103
|
copy.event_executor = @event_executor
|
|
61
104
|
copy.event_workers = @event_workers
|
|
62
105
|
copy.event_queue_size = @event_queue_size
|
|
@@ -111,19 +111,17 @@ module OnyxCord
|
|
|
111
111
|
@channel_id = data['channel_id']&.to_i
|
|
112
112
|
@channel = bot.ensure_channel(data['channel']) if data['channel']
|
|
113
113
|
@user = begin
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
nil
|
|
126
|
-
end
|
|
114
|
+
if data['member'] && data['member']['user']
|
|
115
|
+
data['member']['guild_id'] = @server_id
|
|
116
|
+
server = bot.servers ? bot.servers[@server_id] : nil
|
|
117
|
+
OnyxCord::Member.new(data['member'], server, bot)
|
|
118
|
+
elsif data['user']
|
|
119
|
+
bot.ensure_user(data['user'])
|
|
120
|
+
end
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
OnyxCord::LOGGER.error("Failed to parse interaction user/member: #{e}")
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
127
125
|
@token = data['token']
|
|
128
126
|
@version = data['version']
|
|
129
127
|
@components = @data['components']&.filter_map { |component| Components.from_data(component, @bot) } || []
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'async'
|
|
4
|
+
|
|
3
5
|
module OnyxCord
|
|
4
6
|
# Event execution strategies used by bot dispatch.
|
|
5
7
|
module EventExecutor
|
|
@@ -18,21 +20,20 @@ module OnyxCord
|
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
#
|
|
23
|
+
# Async-based worker pool for event handlers.
|
|
24
|
+
# Uses Async tasks (fibers) instead of threads for lightweight concurrency.
|
|
22
25
|
class Pool
|
|
23
|
-
attr_reader :
|
|
26
|
+
attr_reader :queue
|
|
24
27
|
|
|
25
28
|
def initialize(size:, queue_size: nil)
|
|
26
29
|
raise ArgumentError, 'Pool size must be greater than zero' unless size.positive?
|
|
27
30
|
|
|
31
|
+
@size = size
|
|
28
32
|
@queue = queue_size ? SizedQueue.new(queue_size) : Queue.new
|
|
29
33
|
@closed = false
|
|
30
|
-
@
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
worker_loop
|
|
34
|
-
end
|
|
35
|
-
end
|
|
34
|
+
@workers = []
|
|
35
|
+
|
|
36
|
+
start_workers
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def post(&block)
|
|
@@ -46,16 +47,34 @@ module OnyxCord
|
|
|
46
47
|
@queue.size
|
|
47
48
|
end
|
|
48
49
|
|
|
50
|
+
# Compatibility with code that checks worker threads.
|
|
51
|
+
def threads
|
|
52
|
+
@workers
|
|
53
|
+
end
|
|
54
|
+
|
|
49
55
|
def shutdown
|
|
50
56
|
return if @closed
|
|
51
57
|
|
|
52
58
|
@closed = true
|
|
53
|
-
@
|
|
54
|
-
@
|
|
59
|
+
@size.times { @queue << STOP }
|
|
60
|
+
@workers.each do |w|
|
|
61
|
+
w.join unless w == Thread.current
|
|
62
|
+
rescue StandardError
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
private
|
|
58
68
|
|
|
69
|
+
def start_workers
|
|
70
|
+
@workers = Array.new(@size) do |index|
|
|
71
|
+
Thread.new do
|
|
72
|
+
Thread.current[:onyxcord_name] = "event-worker-#{index + 1}"
|
|
73
|
+
worker_loop
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
59
78
|
def worker_loop
|
|
60
79
|
loop do
|
|
61
80
|
job = @queue.pop
|