onyxcord 3.2.7 → 4.0.0.beta.1
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/lib/onyxcord/application_commands.rb +2 -0
- data/lib/onyxcord/bot.rb +12 -6
- data/lib/onyxcord/cache/manager.rb +9 -9
- data/lib/onyxcord/cogs/bot.rb +28 -0
- data/lib/onyxcord/cogs/cog.rb +264 -0
- data/lib/onyxcord/cogs/cog_meta.rb +61 -0
- data/lib/onyxcord/cogs/extensions.rb +109 -0
- data/lib/onyxcord/commands/bot/channels.rb +1 -0
- data/lib/onyxcord/commands/bot/execution.rb +1 -0
- data/lib/onyxcord/commands/container.rb +51 -0
- data/lib/onyxcord/commands/parser.rb +61 -7
- data/lib/onyxcord/commands/rate_limiter.rb +2 -2
- data/lib/onyxcord/core/bot/application_commands.rb +20 -16
- data/lib/onyxcord/core/bot/messaging.rb +2 -2
- data/lib/onyxcord/core/bot/oauth.rb +1 -1
- data/lib/onyxcord/core/version.rb +1 -1
- data/lib/onyxcord/events/interactions/autocomplete.rb +21 -6
- data/lib/onyxcord/gateway/client.rb +29 -14
- data/lib/onyxcord/interactions/command.rb +27 -2
- data/lib/onyxcord/interactions/internal/application_command.rb +1 -1
- data/lib/onyxcord/interactions/registry.rb +36 -0
- data/lib/onyxcord/internal/event_bus.rb +5 -3
- data/lib/onyxcord/internal/event_executor.rb +7 -0
- data/lib/onyxcord/internal/http.rb +18 -2
- data/lib/onyxcord/internal/json.rb +26 -22
- data/lib/onyxcord/internal/rate_limiter/async_rest.rb +4 -3
- data/lib/onyxcord/internal/rate_limiter/rest.rb +5 -3
- data/lib/onyxcord/light/light_bot.rb +3 -3
- data/lib/onyxcord/models/application.rb +1 -1
- data/lib/onyxcord/models/channel.rb +19 -17
- data/lib/onyxcord/models/component.rb +3 -3
- data/lib/onyxcord/models/interaction.rb +5 -5
- data/lib/onyxcord/models/member.rb +8 -8
- data/lib/onyxcord/models/message.rb +5 -5
- data/lib/onyxcord/models/poll.rb +2 -2
- data/lib/onyxcord/models/profile.rb +4 -4
- data/lib/onyxcord/models/role.rb +1 -1
- data/lib/onyxcord/models/scheduled_event.rb +3 -3
- data/lib/onyxcord/models/server.rb +46 -34
- data/lib/onyxcord/models/user.rb +1 -1
- data/lib/onyxcord/models/webhook.rb +7 -7
- data/lib/onyxcord/rest/client.rb +43 -7
- data/lib/onyxcord/voice/client.rb +2 -0
- data/lib/onyxcord/voice/network/udp.rb +4 -1
- data/lib/onyxcord/voice/network/websocket.rb +8 -3
- data/lib/onyxcord/webhooks/view/container_builder.rb +6 -1
- data/lib/onyxcord.rb +5 -1
- metadata +5 -1
|
@@ -122,6 +122,8 @@ module OnyxCord
|
|
|
122
122
|
|
|
123
123
|
# Whether this server's members have been chunked (resolved using op 8 and GUILD_MEMBERS_CHUNK) yet
|
|
124
124
|
@chunked = false
|
|
125
|
+
@chunk_mutex = Mutex.new
|
|
126
|
+
@chunk_condition = ConditionVariable.new
|
|
125
127
|
end
|
|
126
128
|
|
|
127
129
|
# @return [Member] The server owner.
|
|
@@ -172,7 +174,7 @@ module OnyxCord
|
|
|
172
174
|
# end
|
|
173
175
|
# @return [Hash<Integer => Integer>] A hash mapping role IDs to their respective member counts.
|
|
174
176
|
def role_member_counts
|
|
175
|
-
response = JSON.parse(REST::Server.role_member_counts(@bot.token, @id))
|
|
177
|
+
response = OnyxCord::Internal::JSON.parse(REST::Server.role_member_counts(@bot.token, @id))
|
|
176
178
|
response.transform_keys!(&:to_i)
|
|
177
179
|
response.tap { |hash| hash[@id] = @member_count }
|
|
178
180
|
end
|
|
@@ -202,7 +204,13 @@ module OnyxCord
|
|
|
202
204
|
raise 'The :server_members intent is required to get server members' if @bot.gateway.intents.nobits?(INTENTS[:server_members])
|
|
203
205
|
|
|
204
206
|
@bot.request_chunks(@id)
|
|
205
|
-
|
|
207
|
+
|
|
208
|
+
@chunk_mutex.synchronize do
|
|
209
|
+
@chunk_condition.wait(@chunk_mutex, 30) unless @chunked
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
raise Timeout::Error, "Guild members not received for server #{@id} within 30s" unless @chunked
|
|
213
|
+
|
|
206
214
|
@members.values
|
|
207
215
|
end
|
|
208
216
|
|
|
@@ -226,7 +234,7 @@ module OnyxCord
|
|
|
226
234
|
# @return [Array<Integration>] an array of the integrations in this server.
|
|
227
235
|
# @note If the server has more than 50 integrations, they cannot be accessed.
|
|
228
236
|
def integrations
|
|
229
|
-
integration = JSON.parse(REST::Server.integrations(@bot.token, @id))
|
|
237
|
+
integration = OnyxCord::Internal::JSON.parse(REST::Server.integrations(@bot.token, @id))
|
|
230
238
|
integration.map { |element| Integration.new(element, @bot, self) }
|
|
231
239
|
end
|
|
232
240
|
|
|
@@ -241,14 +249,14 @@ module OnyxCord
|
|
|
241
249
|
action = AuditLogs::ACTIONS.key(action)
|
|
242
250
|
user = user.resolve_id if user
|
|
243
251
|
before = before.resolve_id if before
|
|
244
|
-
AuditLogs.new(self, @bot, JSON.parse(REST::Server.audit_logs(@bot.token, @id, limit, user, action, before)))
|
|
252
|
+
AuditLogs.new(self, @bot, OnyxCord::Internal::JSON.parse(REST::Server.audit_logs(@bot.token, @id, limit, user, action, before)))
|
|
245
253
|
end
|
|
246
254
|
|
|
247
255
|
# @note For internal use only
|
|
248
256
|
# @!visibility private
|
|
249
257
|
def cache_widget_data(data = nil)
|
|
250
258
|
data ||= if bot.permission?(:manage_server)
|
|
251
|
-
JSON.parse(REST::Server.widget(@bot.token, @id))
|
|
259
|
+
OnyxCord::Internal::JSON.parse(REST::Server.widget(@bot.token, @id))
|
|
252
260
|
else
|
|
253
261
|
return update_data(nil)
|
|
254
262
|
end
|
|
@@ -315,7 +323,7 @@ module OnyxCord
|
|
|
315
323
|
def modify_widget(enabled, channel, reason = nil)
|
|
316
324
|
cache_widget_data if @widget_enabled.nil?
|
|
317
325
|
channel_id = channel ? channel.resolve_id : @widget_channel_id
|
|
318
|
-
cache_widget_data(JSON.parse(REST::Server.modify_widget(@bot.token, @id, enabled, channel_id, reason)))
|
|
326
|
+
cache_widget_data(OnyxCord::Internal::JSON.parse(REST::Server.modify_widget(@bot.token, @id, enabled, channel_id, reason)))
|
|
319
327
|
end
|
|
320
328
|
alias_method :modify_embed, :modify_widget
|
|
321
329
|
|
|
@@ -347,7 +355,7 @@ module OnyxCord
|
|
|
347
355
|
response = REST::Server.add_member(@bot.token, @id, user_id, access_token, nick, roles, deaf, mute)
|
|
348
356
|
return nil if response.empty?
|
|
349
357
|
|
|
350
|
-
add_member Member.new(JSON.parse(response), self, @bot)
|
|
358
|
+
add_member Member.new(OnyxCord::Internal::JSON.parse(response), self, @bot)
|
|
351
359
|
end
|
|
352
360
|
|
|
353
361
|
# Returns the amount of members that are candidates for pruning
|
|
@@ -357,7 +365,7 @@ module OnyxCord
|
|
|
357
365
|
def prune_count(days)
|
|
358
366
|
raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
|
|
359
367
|
|
|
360
|
-
response = JSON.parse REST::Server.prune_count(@bot.token, @id, days)
|
|
368
|
+
response = OnyxCord::Internal::JSON.parse REST::Server.prune_count(@bot.token, @id, days)
|
|
361
369
|
response['pruned']
|
|
362
370
|
end
|
|
363
371
|
|
|
@@ -369,7 +377,7 @@ module OnyxCord
|
|
|
369
377
|
def begin_prune(days, reason = nil)
|
|
370
378
|
raise ArgumentError, 'Days must be between 1 and 30' unless days.between?(1, 30)
|
|
371
379
|
|
|
372
|
-
response = JSON.parse REST::Server.begin_prune(@bot.token, @id, days, reason)
|
|
380
|
+
response = OnyxCord::Internal::JSON.parse REST::Server.begin_prune(@bot.token, @id, days, reason)
|
|
373
381
|
response['pruned']
|
|
374
382
|
end
|
|
375
383
|
|
|
@@ -581,7 +589,7 @@ module OnyxCord
|
|
|
581
589
|
total = nil
|
|
582
590
|
|
|
583
591
|
get_messages = lambda do |query|
|
|
584
|
-
data = JSON.parse(REST::Server.search_messages(@bot.token, @id, **options, **query.compact))
|
|
592
|
+
data = OnyxCord::Internal::JSON.parse(REST::Server.search_messages(@bot.token, @id, **options, **query.compact))
|
|
585
593
|
total ||= data['total_results']
|
|
586
594
|
|
|
587
595
|
data['threads']&.each do |thread|
|
|
@@ -621,13 +629,14 @@ module OnyxCord
|
|
|
621
629
|
# @note For internal use only
|
|
622
630
|
# @!visibility private
|
|
623
631
|
def delete_role(role_id)
|
|
624
|
-
|
|
632
|
+
resolved_id = role_id.resolve_id
|
|
633
|
+
@roles.delete(resolved_id)
|
|
625
634
|
@members.each_value do |member|
|
|
626
|
-
new_roles = member.roles.reject { |r| r.id ==
|
|
635
|
+
new_roles = member.roles.reject { |r| r.id == resolved_id }
|
|
627
636
|
member.update_roles(new_roles)
|
|
628
637
|
end
|
|
629
638
|
@channels.each do |channel|
|
|
630
|
-
overwrites = channel.permission_overwrites.reject { |id, _| id ==
|
|
639
|
+
overwrites = channel.permission_overwrites.reject { |id, _| id == resolved_id }
|
|
631
640
|
channel.update_overwrites(overwrites)
|
|
632
641
|
end
|
|
633
642
|
end
|
|
@@ -636,7 +645,7 @@ module OnyxCord
|
|
|
636
645
|
# @note For internal use only
|
|
637
646
|
# @!visibility private
|
|
638
647
|
def update_role_positions(role_positions, reason: nil)
|
|
639
|
-
response = JSON.parse(REST::Server.update_role_positions(@bot.token, @id, role_positions, reason))
|
|
648
|
+
response = OnyxCord::Internal::JSON.parse(REST::Server.update_role_positions(@bot.token, @id, role_positions, reason))
|
|
640
649
|
response.each { |data| role(data['id'].to_i)&.update_data(data) }
|
|
641
650
|
end
|
|
642
651
|
|
|
@@ -739,7 +748,7 @@ module OnyxCord
|
|
|
739
748
|
permission_overwrites.map! { |e| e.is_a?(Overwrite) ? e.to_hash : e } if permission_overwrites.is_a?(Array)
|
|
740
749
|
parent_id = parent.respond_to?(:resolve_id) ? parent.resolve_id : nil
|
|
741
750
|
response = REST::Server.create_channel(@bot.token, @id, name, type, topic, bitrate, user_limit, permission_overwrites, parent_id, nsfw, rate_limit_per_user, position, reason)
|
|
742
|
-
Channel.new(JSON.parse(response), @bot)
|
|
751
|
+
Channel.new(OnyxCord::Internal::JSON.parse(response), @bot)
|
|
743
752
|
end
|
|
744
753
|
|
|
745
754
|
# Creates a role on this server which can then be modified. It will be initialized
|
|
@@ -784,7 +793,7 @@ module OnyxCord
|
|
|
784
793
|
|
|
785
794
|
response = REST::Server.create_role(@bot.token, @id, name, nil, hoist, mentionable, permissions&.to_s, reason, colours, icon, unicode_emoji)
|
|
786
795
|
|
|
787
|
-
role = Role.new(JSON.parse(response), @bot, self)
|
|
796
|
+
role = Role.new(OnyxCord::Internal::JSON.parse(response), @bot, self)
|
|
788
797
|
@roles[role.id] = role
|
|
789
798
|
end
|
|
790
799
|
|
|
@@ -797,7 +806,7 @@ module OnyxCord
|
|
|
797
806
|
def add_emoji(name, image, roles = [], reason: nil)
|
|
798
807
|
image = image.respond_to?(:read) ? OnyxCord.encode64(image) : image
|
|
799
808
|
|
|
800
|
-
data = JSON.parse(REST::Server.add_emoji(@bot.token, @id, image, name, roles.map(&:resolve_id), reason))
|
|
809
|
+
data = OnyxCord::Internal::JSON.parse(REST::Server.add_emoji(@bot.token, @id, image, name, roles.map(&:resolve_id), reason))
|
|
801
810
|
new_emoji = Emoji.new(data, @bot, self)
|
|
802
811
|
@emoji[new_emoji.id] = new_emoji
|
|
803
812
|
end
|
|
@@ -817,7 +826,7 @@ module OnyxCord
|
|
|
817
826
|
# @return [Emoji] The edited emoji.
|
|
818
827
|
def edit_emoji(emoji, name: nil, roles: nil, reason: nil)
|
|
819
828
|
emoji = @emoji[emoji.resolve_id]
|
|
820
|
-
data = JSON.parse(REST::Server.edit_emoji(@bot.token, @id, emoji.resolve_id, name || emoji.name, (roles || emoji.roles).map(&:resolve_id), reason))
|
|
829
|
+
data = OnyxCord::Internal::JSON.parse(REST::Server.edit_emoji(@bot.token, @id, emoji.resolve_id, name || emoji.name, (roles || emoji.roles).map(&:resolve_id), reason))
|
|
821
830
|
new_emoji = Emoji.new(data, @bot, self)
|
|
822
831
|
@emoji[new_emoji.id] = new_emoji
|
|
823
832
|
end
|
|
@@ -842,7 +851,7 @@ module OnyxCord
|
|
|
842
851
|
# @param limit [Integer] The maximum number of members between 1-1000 to return. Returns 1 member by default.
|
|
843
852
|
# @return [Array<Member>, nil] An array of member objects that match the given parameters, or nil for no members.
|
|
844
853
|
def search_members(name:, limit: nil)
|
|
845
|
-
response = JSON.parse(REST::Server.search_guild_members(@bot.token, @id, name, limit))
|
|
854
|
+
response = OnyxCord::Internal::JSON.parse(REST::Server.search_guild_members(@bot.token, @id, name, limit))
|
|
846
855
|
return nil if response.empty?
|
|
847
856
|
|
|
848
857
|
response.map { |mem| Member.new(mem, self, @bot) }
|
|
@@ -854,7 +863,7 @@ module OnyxCord
|
|
|
854
863
|
# @param after_id [Integer] Consider only users after given user id.
|
|
855
864
|
# @return [Array<ServerBan>] a list of banned users on this server and the reason they were banned.
|
|
856
865
|
def bans(limit: nil, before_id: nil, after_id: nil)
|
|
857
|
-
response = JSON.parse(REST::Server.bans(@bot.token, @id, limit, before_id, after_id))
|
|
866
|
+
response = OnyxCord::Internal::JSON.parse(REST::Server.bans(@bot.token, @id, limit, before_id, after_id))
|
|
858
867
|
response.map do |e|
|
|
859
868
|
ServerBan.new(self, @bot.ensure_user(e['user']), e['reason'])
|
|
860
869
|
end
|
|
@@ -876,7 +885,7 @@ module OnyxCord
|
|
|
876
885
|
|
|
877
886
|
get_bans = lambda do |before: nil, after: nil|
|
|
878
887
|
data = REST::Server.bans(@bot.token, @id, f_limit, before&.id || f_before, after&.id || f_after)
|
|
879
|
-
JSON.parse(data).map { |ban| ServerBan.new(self, @bot.ensure_user(ban['user']), ban['reason']) }
|
|
888
|
+
OnyxCord::Internal::JSON.parse(data).map { |ban| ServerBan.new(self, @bot.ensure_user(ban['user']), ban['reason']) }
|
|
880
889
|
end
|
|
881
890
|
|
|
882
891
|
paginator = Paginator.new(limit, before ? :up : :down) do |page|
|
|
@@ -923,7 +932,7 @@ module OnyxCord
|
|
|
923
932
|
return ban(users.first, 0, message_seconds: message_seconds, reason: reason) if users.size == 1
|
|
924
933
|
|
|
925
934
|
response = REST::Server.bulk_ban(@bot.token, @id, users.map(&:resolve_id), message_seconds, reason)
|
|
926
|
-
BulkBan.new(JSON.parse(response), self, reason)
|
|
935
|
+
BulkBan.new(OnyxCord::Internal::JSON.parse(response), self, reason)
|
|
927
936
|
end
|
|
928
937
|
|
|
929
938
|
# Kicks a user from this server.
|
|
@@ -958,7 +967,7 @@ module OnyxCord
|
|
|
958
967
|
|
|
959
968
|
@available_voice_regions = {}
|
|
960
969
|
|
|
961
|
-
data = JSON.parse REST::Server.regions(@bot.token, @id)
|
|
970
|
+
data = OnyxCord::Internal::JSON.parse REST::Server.regions(@bot.token, @id)
|
|
962
971
|
@available_voice_regions = data.map { |e| VoiceRegion.new e }
|
|
963
972
|
end
|
|
964
973
|
|
|
@@ -971,7 +980,7 @@ module OnyxCord
|
|
|
971
980
|
# Moves the server to another region. This will cause a voice interruption of at most a second.
|
|
972
981
|
# @param region [String] The new region the server should be in.
|
|
973
982
|
def region=(region)
|
|
974
|
-
update_data(JSON.parse(REST::Server.update!(@bot.token, @id, region: region.to_s)))
|
|
983
|
+
update_data(OnyxCord::Internal::JSON.parse(REST::Server.update!(@bot.token, @id, region: region.to_s)))
|
|
975
984
|
end
|
|
976
985
|
|
|
977
986
|
# Sets the server's icon.
|
|
@@ -1127,14 +1136,14 @@ module OnyxCord
|
|
|
1127
1136
|
# Requests a list of Webhooks on the server.
|
|
1128
1137
|
# @return [Array<Webhook>] webhooks on the server.
|
|
1129
1138
|
def webhooks
|
|
1130
|
-
webhooks = JSON.parse(REST::Server.webhooks(@bot.token, @id))
|
|
1139
|
+
webhooks = OnyxCord::Internal::JSON.parse(REST::Server.webhooks(@bot.token, @id))
|
|
1131
1140
|
webhooks.map { |webhook| Webhook.new(webhook, @bot) }
|
|
1132
1141
|
end
|
|
1133
1142
|
|
|
1134
1143
|
# Requests a list of Invites to the server.
|
|
1135
1144
|
# @return [Array<Invite>] invites to the server.
|
|
1136
1145
|
def invites
|
|
1137
|
-
invites = JSON.parse(REST::Server.invites(@bot.token, @id))
|
|
1146
|
+
invites = OnyxCord::Internal::JSON.parse(REST::Server.invites(@bot.token, @id))
|
|
1138
1147
|
invites.map { |invite| Invite.new(invite, @bot) }
|
|
1139
1148
|
end
|
|
1140
1149
|
|
|
@@ -1143,7 +1152,7 @@ module OnyxCord
|
|
|
1143
1152
|
# should be ignored and re-fetched via an HTTP request.
|
|
1144
1153
|
# @return [Array<ScheduledEvent>] The scheduled events on the server.
|
|
1145
1154
|
def scheduled_events(bypass_cache: false)
|
|
1146
|
-
process_scheduled_events(JSON.parse(REST::Server.list_scheduled_events(@bot.token, @id, with_user_count: true))) if bypass_cache
|
|
1155
|
+
process_scheduled_events(OnyxCord::Internal::JSON.parse(REST::Server.list_scheduled_events(@bot.token, @id, with_user_count: true))) if bypass_cache
|
|
1147
1156
|
|
|
1148
1157
|
@scheduled_events.values
|
|
1149
1158
|
end
|
|
@@ -1157,7 +1166,7 @@ module OnyxCord
|
|
|
1157
1166
|
return @scheduled_events[id] if @scheduled_events[id]
|
|
1158
1167
|
return nil unless request
|
|
1159
1168
|
|
|
1160
|
-
event = JSON.parse(REST::Server.get_scheduled_event(@bot.token, @id, id, with_user_count: true))
|
|
1169
|
+
event = OnyxCord::Internal::JSON.parse(REST::Server.get_scheduled_event(@bot.token, @id, id, with_user_count: true))
|
|
1161
1170
|
scheduled_event = ScheduledEvent.new(event, self, @bot)
|
|
1162
1171
|
@scheduled_events[scheduled_event.id] = scheduled_event
|
|
1163
1172
|
rescue StandardError
|
|
@@ -1193,7 +1202,7 @@ module OnyxCord
|
|
|
1193
1202
|
recurrence_rule: block_given? ? builder.to_h : recurrence_rule&.to_h
|
|
1194
1203
|
}
|
|
1195
1204
|
|
|
1196
|
-
event = JSON.parse(REST::Server.create_scheduled_event(@bot.token, @id, **options, reason: reason))
|
|
1205
|
+
event = OnyxCord::Internal::JSON.parse(REST::Server.create_scheduled_event(@bot.token, @id, **options, reason: reason))
|
|
1197
1206
|
scheduled_event = ScheduledEvent.new(event, self, @bot)
|
|
1198
1207
|
@scheduled_events[scheduled_event.id] = scheduled_event
|
|
1199
1208
|
end
|
|
@@ -1210,7 +1219,10 @@ module OnyxCord
|
|
|
1210
1219
|
LOGGER.debug("Finished chunking server #{@id}")
|
|
1211
1220
|
|
|
1212
1221
|
# Reset everything to normal
|
|
1213
|
-
@
|
|
1222
|
+
@chunk_mutex.synchronize do
|
|
1223
|
+
@chunked = true
|
|
1224
|
+
end
|
|
1225
|
+
@chunk_condition.broadcast
|
|
1214
1226
|
end
|
|
1215
1227
|
|
|
1216
1228
|
# Get the AFK channel of the server.
|
|
@@ -1305,7 +1317,7 @@ module OnyxCord
|
|
|
1305
1317
|
channel_id: widget_channel == :undef ? widget_channel : widget_channel&.resolve_id
|
|
1306
1318
|
}
|
|
1307
1319
|
|
|
1308
|
-
cache_widget_data(JSON.parse(REST::Server.update_widget(@bot.token, @id, **widget_data, reason: reason)))
|
|
1320
|
+
cache_widget_data(OnyxCord::Internal::JSON.parse(REST::Server.update_widget(@bot.token, @id, **widget_data, reason: reason)))
|
|
1309
1321
|
end
|
|
1310
1322
|
|
|
1311
1323
|
if invites_disabled_until != :undef || dms_disabled_until != :undef
|
|
@@ -1314,12 +1326,12 @@ module OnyxCord
|
|
|
1314
1326
|
invites_disabled_until: invites_disabled_until == :undef ? @invites_disabled_until&.iso8601 : invites_disabled_until&.iso8601
|
|
1315
1327
|
}
|
|
1316
1328
|
|
|
1317
|
-
process_incident_actions(JSON.parse(REST::Server.update_incident_actions(@bot.token, @id, **incidents_data, reason: reason)))
|
|
1329
|
+
process_incident_actions(OnyxCord::Internal::JSON.parse(REST::Server.update_incident_actions(@bot.token, @id, **incidents_data, reason: reason)))
|
|
1318
1330
|
end
|
|
1319
1331
|
|
|
1320
1332
|
return unless data.any? { |_, value| value != :undef }
|
|
1321
1333
|
|
|
1322
|
-
update_data(JSON.parse(REST::Server.update!(@bot.token, @id, **data, reason: reason)))
|
|
1334
|
+
update_data(OnyxCord::Internal::JSON.parse(REST::Server.update!(@bot.token, @id, **data, reason: reason)))
|
|
1323
1335
|
nil
|
|
1324
1336
|
end
|
|
1325
1337
|
|
|
@@ -1327,7 +1339,7 @@ module OnyxCord
|
|
|
1327
1339
|
# @note For internal use only
|
|
1328
1340
|
# @!visibility private
|
|
1329
1341
|
def update_data(new_data = nil)
|
|
1330
|
-
new_data ||= JSON.parse(REST::Server.resolve(@bot.token, @id))
|
|
1342
|
+
new_data ||= OnyxCord::Internal::JSON.parse(REST::Server.resolve(@bot.token, @id))
|
|
1331
1343
|
@name = new_data['name']
|
|
1332
1344
|
@icon_id = new_data['icon']
|
|
1333
1345
|
@splash_id = new_data['splash']
|
data/lib/onyxcord/models/user.rb
CHANGED
|
@@ -193,7 +193,7 @@ module OnyxCord
|
|
|
193
193
|
# @return [String, nil] the ID of this user's current banner, can be used to generate a banner URL.
|
|
194
194
|
# @see #banner_url
|
|
195
195
|
def banner_id
|
|
196
|
-
@banner_id ||= JSON.parse(REST::User.resolve(@bot.token, @id))['banner']
|
|
196
|
+
@banner_id ||= OnyxCord::Internal::JSON.parse(REST::User.resolve(@bot.token, @id))['banner']
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
# Set the user's username
|
|
@@ -153,7 +153,7 @@ module OnyxCord
|
|
|
153
153
|
|
|
154
154
|
resp = REST::Webhook.token_execute_webhook(@token, @id, wait, data[:content], data[:username], data[:avatar_url], data[:tts], data[:file], data[:embeds], data[:allowed_mentions], flags, components.to_a)
|
|
155
155
|
|
|
156
|
-
Message.new(JSON.parse(resp), @bot) if wait
|
|
156
|
+
Message.new(OnyxCord::Internal::JSON.parse(resp), @bot) if wait
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
# Delete a message created by this webhook.
|
|
@@ -192,7 +192,7 @@ module OnyxCord
|
|
|
192
192
|
edit_content = content == OnyxCord::MessagePayload::KEEP || !content.nil? ? content : data[:content]
|
|
193
193
|
edit_embeds = embeds == OnyxCord::MessagePayload::KEEP || !embeds.nil? ? embeds : data[:embeds]
|
|
194
194
|
resp = REST::Webhook.token_edit_message(@token, @id, message.resolve_id, edit_content, edit_embeds, data[:allowed_mentions], components.to_a, nil, flags)
|
|
195
|
-
Message.new(JSON.parse(resp), @bot)
|
|
195
|
+
Message.new(OnyxCord::Internal::JSON.parse(resp), @bot)
|
|
196
196
|
end
|
|
197
197
|
|
|
198
198
|
# Utility function to get a webhook's avatar URL.
|
|
@@ -228,11 +228,11 @@ module OnyxCord
|
|
|
228
228
|
|
|
229
229
|
def update_webhook(new_data)
|
|
230
230
|
reason = new_data.delete(:reason)
|
|
231
|
-
data = JSON.parse(if token?
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
231
|
+
data = OnyxCord::Internal::JSON.parse(if token?
|
|
232
|
+
REST::Webhook.token_update_webhook(@token, @id, new_data, reason)
|
|
233
|
+
else
|
|
234
|
+
REST::Webhook.update_webhook(@bot.token, @id, new_data, reason)
|
|
235
|
+
end)
|
|
236
236
|
# Only update cache if API call worked
|
|
237
237
|
update_internal(data) if data['name']
|
|
238
238
|
end
|
data/lib/onyxcord/rest/client.rb
CHANGED
|
@@ -12,11 +12,17 @@ require_relative '../internal/rate_limiter/async_rest'
|
|
|
12
12
|
# List of methods representing endpoints in Discord's API
|
|
13
13
|
module OnyxCord::REST
|
|
14
14
|
# The base URL of the Discord REST API.
|
|
15
|
-
APIBASE = 'https://discord.com/api/
|
|
15
|
+
APIBASE = 'https://discord.com/api/v10'
|
|
16
16
|
|
|
17
17
|
# The URL of Discord's CDN
|
|
18
18
|
CDN_URL = 'https://cdn.discordapp.com'
|
|
19
19
|
|
|
20
|
+
# Maximum number of retries for 502 Bad Gateway errors
|
|
21
|
+
MAX_RAW_RETRIES = 3
|
|
22
|
+
|
|
23
|
+
# Maximum number of retries for 429 Rate Limit errors
|
|
24
|
+
MAX_429_RETRIES = 5
|
|
25
|
+
|
|
20
26
|
module_function
|
|
21
27
|
|
|
22
28
|
# @return [String] the currently used API base URL.
|
|
@@ -62,8 +68,11 @@ module OnyxCord::REST
|
|
|
62
68
|
|
|
63
69
|
# Resets all rate limit mutexes
|
|
64
70
|
def reset_mutexes
|
|
65
|
-
@
|
|
66
|
-
@
|
|
71
|
+
@rest_mutex ||= Mutex.new
|
|
72
|
+
@rest_mutex.synchronize do
|
|
73
|
+
@rate_limiter = ::OnyxCord::Internal::RateLimiter::Rest.new
|
|
74
|
+
@async_rate_limiter = ::OnyxCord::Internal::RateLimiter::AsyncRest.new
|
|
75
|
+
end
|
|
67
76
|
end
|
|
68
77
|
|
|
69
78
|
def rate_limiter
|
|
@@ -118,9 +127,15 @@ module OnyxCord::REST
|
|
|
118
127
|
raise noprm
|
|
119
128
|
end
|
|
120
129
|
|
|
121
|
-
# Retry on 502 Bad Gateway
|
|
130
|
+
# Retry on 502 Bad Gateway (with limit and backoff)
|
|
122
131
|
if response.code == 502
|
|
132
|
+
retries = caller.count { |c| c.include?('raw_request') }
|
|
133
|
+
if retries >= MAX_RAW_RETRIES
|
|
134
|
+
OnyxCord::LOGGER.warn("Got #{MAX_RAW_RETRIES} consecutive 502s — giving up on #{url}")
|
|
135
|
+
return response
|
|
136
|
+
end
|
|
123
137
|
OnyxCord::LOGGER.warn('Got a 502 while sending a request! Not a big deal, retrying the request')
|
|
138
|
+
sleep(2**retries)
|
|
124
139
|
return raw_request(type, url, body, **headers)
|
|
125
140
|
end
|
|
126
141
|
|
|
@@ -203,7 +218,7 @@ module OnyxCord::REST
|
|
|
203
218
|
|
|
204
219
|
if response.code >= 400 && response.code != 429
|
|
205
220
|
data = begin
|
|
206
|
-
JSON.parse(response.body)
|
|
221
|
+
OnyxCord::Internal::JSON.parse(response.body)
|
|
207
222
|
rescue StandardError
|
|
208
223
|
nil
|
|
209
224
|
end
|
|
@@ -222,7 +237,11 @@ module OnyxCord::REST
|
|
|
222
237
|
|
|
223
238
|
err_klass = OnyxCord::Errors.error_class_for(data['code'] || 0)
|
|
224
239
|
e = err_klass.new(data['message'], data['errors'], status: response.code, headers: response.headers, route: route, body: response.body, response: response)
|
|
225
|
-
OnyxCord::
|
|
240
|
+
if e.is_a?(OnyxCord::Errors::UnknownMessage)
|
|
241
|
+
OnyxCord::LOGGER.warn('Ignoring stale Discord message reference.')
|
|
242
|
+
else
|
|
243
|
+
OnyxCord::LOGGER.error(e.full_message)
|
|
244
|
+
end
|
|
226
245
|
raise e
|
|
227
246
|
end
|
|
228
247
|
rescue OnyxCord::Errors::NoPermission => e
|
|
@@ -233,13 +252,30 @@ module OnyxCord::REST
|
|
|
233
252
|
end
|
|
234
253
|
|
|
235
254
|
if response.code == 429
|
|
255
|
+
retries_for_429 = caller.count { |c| c.include?('request_async') && c.include?('429') }
|
|
236
256
|
trace("429 #{key} #{major_parameter}")
|
|
237
257
|
async_rate_limiter.handle_rate_limit(key, major_parameter, response)
|
|
258
|
+
|
|
259
|
+
if retries_for_429 >= MAX_429_RETRIES
|
|
260
|
+
raise OnyxCord::Errors::HTTPError.new(
|
|
261
|
+
"Rate limited after #{MAX_429_RETRIES} retries: #{key} #{major_parameter}",
|
|
262
|
+
status: 429,
|
|
263
|
+
headers: response.headers,
|
|
264
|
+
body: response.body,
|
|
265
|
+
response: response
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
retry_after = response.headers[:x_ratelimit_reset_after]&.to_f || response.headers[:retry_after]&.to_f
|
|
270
|
+
if retry_after && retry_after.positive?
|
|
271
|
+
OnyxCord::LOGGER.warn("Rate limited — waiting #{retry_after}s before retry ##{retries_for_429 + 1}")
|
|
272
|
+
OnyxCord::Internal::AsyncRuntime.sleep(retry_after)
|
|
273
|
+
end
|
|
238
274
|
return request_async(key, major_parameter, type, url, body, headers)
|
|
239
275
|
end
|
|
240
276
|
|
|
241
277
|
if response.code == 202 && response.body
|
|
242
|
-
body_data = JSON.parse(response.body)
|
|
278
|
+
body_data = OnyxCord::Internal::JSON.parse(response.body)
|
|
243
279
|
|
|
244
280
|
if body_data['code'] == 110_000
|
|
245
281
|
case body_data['retry_after']
|
|
@@ -230,6 +230,8 @@ module OnyxCord::Voice
|
|
|
230
230
|
# Windows does not support TERM as a kill signal, so we use KILL. `Process.waitpid` verifies that our
|
|
231
231
|
# child process has not already completed.
|
|
232
232
|
Process.kill(Gem.win_platform? ? 'KILL' : 'TERM', pid) if Process.waitpid(pid, Process::WNOHANG).nil?
|
|
233
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
234
|
+
# Process already exited or doesn't exist — no action needed
|
|
233
235
|
rescue StandardError => e
|
|
234
236
|
OnyxCord::LOGGER.warn('Failed to kill ffmpeg process! You *might* have a process leak now.')
|
|
235
237
|
OnyxCord::LOGGER.warn("Reason: #{e}")
|
|
@@ -37,8 +37,11 @@ module OnyxCord::Voice
|
|
|
37
37
|
# Waits for a UDP discovery reply, and returns the sent data.
|
|
38
38
|
# @return [Array(String, Integer)] the IP and port received from the discovery reply.
|
|
39
39
|
def receive_discovery_reply
|
|
40
|
-
# Wait for a UDP message
|
|
40
|
+
# Wait for a UDP message with timeout
|
|
41
|
+
raise Timeout::Error, 'Timed out waiting for UDP discovery reply' unless @socket.wait_readable(5)
|
|
42
|
+
|
|
41
43
|
message = @socket.recv(74)
|
|
44
|
+
|
|
42
45
|
ip = message[8..-3].delete("\0")
|
|
43
46
|
port = message[-2..].unpack1('n')
|
|
44
47
|
[ip, port]
|
|
@@ -64,7 +64,7 @@ module OnyxCord::Voice
|
|
|
64
64
|
|
|
65
65
|
# Send a heartbeat (op 3), has to be done every @heartbeat_interval seconds or the connection will terminate
|
|
66
66
|
def send_heartbeat
|
|
67
|
-
millis =
|
|
67
|
+
millis = (Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)).to_i
|
|
68
68
|
@bot.debug("Sending voice heartbeat at #{millis}")
|
|
69
69
|
|
|
70
70
|
send_opcode(
|
|
@@ -110,7 +110,7 @@ module OnyxCord::Voice
|
|
|
110
110
|
# @!visibility private
|
|
111
111
|
def websocket_message(msg)
|
|
112
112
|
@bot.debug("Received VWS message! #{msg}")
|
|
113
|
-
packet = JSON.parse(msg)
|
|
113
|
+
packet = OnyxCord::Internal::JSON.parse(msg)
|
|
114
114
|
|
|
115
115
|
@seq = packet['seq'] if packet['seq']
|
|
116
116
|
|
|
@@ -136,6 +136,7 @@ module OnyxCord::Voice
|
|
|
136
136
|
@ready = true
|
|
137
137
|
@udp.secret_key = @ws_data['secret_key'].pack('C*')
|
|
138
138
|
@udp.mode = @ws_data['mode']
|
|
139
|
+
@ready_condition&.broadcast
|
|
139
140
|
when OnyxCord::Voice::Opcodes::HELLO
|
|
140
141
|
# Opcode 8 contains the heartbeat interval.
|
|
141
142
|
@heartbeat_interval = packet['d']['heartbeat_interval']
|
|
@@ -176,7 +177,11 @@ module OnyxCord::Voice
|
|
|
176
177
|
@bot.debug('Waiting for op 4 now')
|
|
177
178
|
|
|
178
179
|
# Wait for op 4, then finish
|
|
179
|
-
|
|
180
|
+
@ready_mutex ||= Mutex.new
|
|
181
|
+
@ready_condition ||= ConditionVariable.new
|
|
182
|
+
@ready_mutex.synchronize do
|
|
183
|
+
@ready_condition.wait(@ready_mutex, 30) unless @ready
|
|
184
|
+
end
|
|
180
185
|
end
|
|
181
186
|
|
|
182
187
|
# Disconnects the websocket and kills the thread
|
|
@@ -60,7 +60,12 @@ class OnyxCord::Webhooks::View
|
|
|
60
60
|
def colour=(colour)
|
|
61
61
|
@colour = case colour
|
|
62
62
|
when Array
|
|
63
|
-
|
|
63
|
+
raise ArgumentError, 'Colour array must have exactly 3 elements (R, G, B)' unless colour.size == 3
|
|
64
|
+
|
|
65
|
+
r, g, b = colour
|
|
66
|
+
raise ArgumentError, 'RGB values must be between 0 and 255' unless [r, g, b].all? { |v| v.is_a?(Integer) && v.between?(0, 255) }
|
|
67
|
+
|
|
68
|
+
(r << 16) | (g << 8) | b
|
|
64
69
|
when String
|
|
65
70
|
colour.delete('#').to_i(16)
|
|
66
71
|
else
|
data/lib/onyxcord.rb
CHANGED
|
@@ -199,8 +199,10 @@ loader.ignore(
|
|
|
199
199
|
"#{__dir__}/onyxcord/cache",
|
|
200
200
|
"#{__dir__}/onyxcord/gateway",
|
|
201
201
|
"#{__dir__}/onyxcord/container.rb",
|
|
202
|
+
"#{__dir__}/onyxcord/cogs",
|
|
202
203
|
"#{__dir__}/onyxcord/bot.rb",
|
|
203
|
-
"#{__dir__}/onyxcord/commands"
|
|
204
|
+
"#{__dir__}/onyxcord/commands",
|
|
205
|
+
"#{__dir__}/onyxcord/light"
|
|
204
206
|
)
|
|
205
207
|
|
|
206
208
|
loader.setup
|
|
@@ -238,6 +240,7 @@ loader.setup
|
|
|
238
240
|
cache/manager
|
|
239
241
|
await
|
|
240
242
|
container
|
|
243
|
+
cogs/cog
|
|
241
244
|
internal/event_bus
|
|
242
245
|
gateway/client
|
|
243
246
|
application_commands
|
|
@@ -246,6 +249,7 @@ loader.setup
|
|
|
246
249
|
webhooks
|
|
247
250
|
bot
|
|
248
251
|
commands/bot
|
|
252
|
+
light
|
|
249
253
|
].each { |path| require_relative "onyxcord/#{path}" }
|
|
250
254
|
|
|
251
255
|
loader.eager_load if ENV['ONYXCORD_EAGER_LOAD']
|
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:
|
|
4
|
+
version: 4.0.0.beta.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gustavo Silva
|
|
@@ -391,6 +391,10 @@ files:
|
|
|
391
391
|
- lib/onyxcord/bot.rb
|
|
392
392
|
- lib/onyxcord/cache/manager.rb
|
|
393
393
|
- lib/onyxcord/cache/stores/gateway.rb
|
|
394
|
+
- lib/onyxcord/cogs/bot.rb
|
|
395
|
+
- lib/onyxcord/cogs/cog.rb
|
|
396
|
+
- lib/onyxcord/cogs/cog_meta.rb
|
|
397
|
+
- lib/onyxcord/cogs/extensions.rb
|
|
394
398
|
- lib/onyxcord/commands/bot.rb
|
|
395
399
|
- lib/onyxcord/commands/bot/channels.rb
|
|
396
400
|
- lib/onyxcord/commands/bot/execution.rb
|