onyxcord 3.2.8 → 4.0.0.beta.2
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 +6 -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 +38 -6
- 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
|
@@ -83,6 +83,27 @@ module OnyxCord::Commands
|
|
|
83
83
|
@commands.delete name
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
# Creates a command group. Commands defined inside the block will be prefixed with the group name.
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# group :admin do
|
|
90
|
+
# command(:ban) { |event, *args| "Banned #{args.first}" }
|
|
91
|
+
# command(:kick) { |event, *args| "Kicked #{args.first}" }
|
|
92
|
+
# end
|
|
93
|
+
# # Creates :admin_ban and :admin_kick commands
|
|
94
|
+
#
|
|
95
|
+
# @param group_name [Symbol] The group prefix.
|
|
96
|
+
# @param attributes [Hash] Shared attributes applied to all commands in the group.
|
|
97
|
+
# @yield The block defines commands within the group.
|
|
98
|
+
# @return [self]
|
|
99
|
+
def group(group_name, attributes = {}, &block)
|
|
100
|
+
raise ArgumentError, 'Block required for group' unless block_given?
|
|
101
|
+
|
|
102
|
+
proxy = GroupProxy.new(self, group_name, attributes)
|
|
103
|
+
proxy.instance_exec(&block)
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
86
107
|
# Adds all commands from another container into this one. Existing commands will be overwritten.
|
|
87
108
|
# @param container [Module] A module that `extend`s {CommandContainer} from which the commands will be added.
|
|
88
109
|
def include_commands(container)
|
|
@@ -93,6 +114,21 @@ module OnyxCord::Commands
|
|
|
93
114
|
@commands.merge! handlers
|
|
94
115
|
end
|
|
95
116
|
|
|
117
|
+
# Yields each command in this container, flattening subcommands.
|
|
118
|
+
# @yieldparam name [Symbol] The command name.
|
|
119
|
+
# @yieldparam cmd [Command] The command object.
|
|
120
|
+
# @return [self]
|
|
121
|
+
def walk_commands
|
|
122
|
+
return self unless block_given?
|
|
123
|
+
|
|
124
|
+
@commands&.each do |name, cmd|
|
|
125
|
+
next if cmd.is_a?(CommandAlias)
|
|
126
|
+
|
|
127
|
+
yield name, cmd
|
|
128
|
+
end
|
|
129
|
+
self
|
|
130
|
+
end
|
|
131
|
+
|
|
96
132
|
# Registers a before or after hook on an existing command.
|
|
97
133
|
# @param command_name [Symbol] The name of the command to hook.
|
|
98
134
|
# @param type [:before, :after] The hook type.
|
|
@@ -128,4 +164,19 @@ module OnyxCord::Commands
|
|
|
128
164
|
end
|
|
129
165
|
end
|
|
130
166
|
end
|
|
167
|
+
|
|
168
|
+
# @private
|
|
169
|
+
# Proxy object used inside a {CommandContainer#group} block to prefix command names.
|
|
170
|
+
class GroupProxy
|
|
171
|
+
def initialize(container, group_name, shared_attributes)
|
|
172
|
+
@container = container
|
|
173
|
+
@group_name = group_name
|
|
174
|
+
@shared_attributes = shared_attributes
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def command(name, attributes = {}, &block)
|
|
178
|
+
merged = @shared_attributes.merge(attributes)
|
|
179
|
+
@container.command(:"#{@group_name}_#{name}", merged, &block)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
131
182
|
end
|
|
@@ -12,6 +12,7 @@ module OnyxCord::Commands
|
|
|
12
12
|
# @!visibility private
|
|
13
13
|
def initialize(name, attributes = {}, &block)
|
|
14
14
|
@name = name
|
|
15
|
+
@subcommands = {}
|
|
15
16
|
@attributes = {
|
|
16
17
|
# The lowest permission level that can use the command
|
|
17
18
|
permission_level: attributes[:permission_level] || 0,
|
|
@@ -65,6 +66,9 @@ module OnyxCord::Commands
|
|
|
65
66
|
# Block for handling internal exceptions, or a string to respond with
|
|
66
67
|
rescue: attributes[:rescue],
|
|
67
68
|
|
|
69
|
+
# Whether the command executes even when a subcommand is provided
|
|
70
|
+
invoke_without_command: attributes[:invoke_without_command] || false,
|
|
71
|
+
|
|
68
72
|
# A list of aliases that reference this command
|
|
69
73
|
aliases: attributes[:aliases] || []
|
|
70
74
|
}
|
|
@@ -94,6 +98,43 @@ module OnyxCord::Commands
|
|
|
94
98
|
self
|
|
95
99
|
end
|
|
96
100
|
|
|
101
|
+
# Registers a subcommand on this command.
|
|
102
|
+
# @param name [Symbol] The subcommand name.
|
|
103
|
+
# @param attributes [Hash] Attributes forwarded to the subcommand.
|
|
104
|
+
# @yieldparam event [CommandEvent] The event.
|
|
105
|
+
# @yieldparam args [Array<String>] Remaining arguments.
|
|
106
|
+
# @return [Command] The newly created subcommand.
|
|
107
|
+
def subcommand(name, attributes = {}, &block)
|
|
108
|
+
sub = self.class.new(name, attributes, &block)
|
|
109
|
+
@subcommands[name] = sub
|
|
110
|
+
sub
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return [Hash{Symbol => Command}] subcommands registered on this command.
|
|
114
|
+
attr_reader :subcommands
|
|
115
|
+
|
|
116
|
+
# Finds a subcommand by name.
|
|
117
|
+
# @param name [Symbol, String] The subcommand name.
|
|
118
|
+
# @return [Command, nil]
|
|
119
|
+
def find_subcommand(name)
|
|
120
|
+
@subcommands[name.to_sym]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def copy_for(receiver = nil)
|
|
124
|
+
block = @block
|
|
125
|
+
copied = self.class.new(@name, @attributes.dup) do |event, *arguments|
|
|
126
|
+
receiver ? receiver.instance_exec(event, *arguments, &block) : block.call(event, *arguments)
|
|
127
|
+
end
|
|
128
|
+
@before_hooks.each { |hook| copied.before { |event, *args| receiver ? receiver.instance_exec(event, *args, &hook) : hook.call(event, *args) } }
|
|
129
|
+
@after_hooks.each { |hook| copied.after { |event, *args| receiver ? receiver.instance_exec(event, *args, &hook) : hook.call(event, *args) } }
|
|
130
|
+
@subcommands.each { |name, sub| copied.subcommand(name) { |event, *args| sub.call(event, args) } }
|
|
131
|
+
copied.instance_variable_set(:@check, proc { |event| receiver.__send__(:passed_cog_command_check?, event) }) if receiver
|
|
132
|
+
copied.instance_variable_set(:@error_handler, proc { |event, error| receiver.__send__(:handled_cog_command_error?, event, error) }) if receiver
|
|
133
|
+
copied.instance_variable_set(:@cog_before_invoke, proc { |event| receiver.__send__(:invoke_cog_before_invoke, event) }) if receiver
|
|
134
|
+
copied.instance_variable_set(:@cog_after_invoke, proc { |event, result| receiver.__send__(:invoke_cog_after_invoke, event, result) }) if receiver
|
|
135
|
+
copied
|
|
136
|
+
end
|
|
137
|
+
|
|
97
138
|
# Calls this command and executes the code inside.
|
|
98
139
|
# @param event [CommandEvent] The event to call the command with.
|
|
99
140
|
# @param arguments [Array<String>] The attributes for the command.
|
|
@@ -127,6 +168,10 @@ module OnyxCord::Commands
|
|
|
127
168
|
end
|
|
128
169
|
end
|
|
129
170
|
|
|
171
|
+
return unless @check.nil? || @check.call(event)
|
|
172
|
+
|
|
173
|
+
@cog_before_invoke&.call(event)
|
|
174
|
+
|
|
130
175
|
cancelled = @before_hooks.any? { |hook| hook.call(event, *arguments).is_a?(FalseClass) }
|
|
131
176
|
return if cancelled
|
|
132
177
|
|
|
@@ -134,20 +179,29 @@ module OnyxCord::Commands
|
|
|
134
179
|
event.drain_into(result)
|
|
135
180
|
|
|
136
181
|
@after_hooks.each { |hook| hook.call(event, *arguments, result) }
|
|
182
|
+
@cog_after_invoke&.call(event, result)
|
|
137
183
|
|
|
138
184
|
result
|
|
139
185
|
rescue LocalJumpError => e # occurs when breaking
|
|
140
186
|
result = e.exit_value
|
|
141
187
|
event.drain_into(result)
|
|
142
188
|
rescue StandardError => e # Something went wrong inside our @block!
|
|
143
|
-
rescue_value = @attributes[:rescue]
|
|
144
|
-
if rescue_value
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
rescue_value = @attributes[:rescue]
|
|
190
|
+
return handle_rescue_value(rescue_value, event, e) if rescue_value
|
|
191
|
+
return if @error_handler&.call(event, e)
|
|
192
|
+
|
|
193
|
+
bot_rescue = event.bot.attributes[:rescue]
|
|
194
|
+
return handle_rescue_value(bot_rescue, event, e) if bot_rescue
|
|
148
195
|
|
|
149
196
|
raise e
|
|
150
197
|
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
def handle_rescue_value(rescue_value, event, error)
|
|
202
|
+
event.respond(rescue_value.gsub('%exception%', error.message)) if rescue_value.is_a?(String)
|
|
203
|
+
rescue_value.call(event, error) if rescue_value.respond_to?(:call)
|
|
204
|
+
end
|
|
151
205
|
end
|
|
152
206
|
|
|
153
207
|
# A command that references another command
|
|
@@ -190,10 +244,10 @@ module OnyxCord::Commands
|
|
|
190
244
|
|
|
191
245
|
@chain.each_char.with_index do |char, index|
|
|
192
246
|
# Escape character
|
|
193
|
-
if char == '\\' && !escaped
|
|
247
|
+
if char == '\\' && !escaped && b_level <= 0
|
|
194
248
|
escaped = true
|
|
195
249
|
next
|
|
196
|
-
elsif escaped
|
|
250
|
+
elsif escaped
|
|
197
251
|
result += char
|
|
198
252
|
escaped = false
|
|
199
253
|
next
|
|
@@ -25,10 +25,10 @@ module OnyxCord::Commands
|
|
|
25
25
|
|
|
26
26
|
@bucket.delete_if do |_, limit_hash|
|
|
27
27
|
# Time limit has not run out
|
|
28
|
-
|
|
28
|
+
next false if @time_span && rate_limit_time < (limit_hash[:set_time] + @time_span)
|
|
29
29
|
|
|
30
30
|
# Delay has not run out
|
|
31
|
-
|
|
31
|
+
next false if @delay && rate_limit_time < (limit_hash[:last_time] + @delay)
|
|
32
32
|
|
|
33
33
|
true
|
|
34
34
|
end
|
|
@@ -13,7 +13,7 @@ module OnyxCord
|
|
|
13
13
|
REST::Application.get_global_commands(@token, profile.id)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
JSON.parse(resp).map do |command_data|
|
|
16
|
+
OnyxCord::Internal::JSON.parse(resp).map do |command_data|
|
|
17
17
|
ApplicationCommand.new(command_data, self, server_id)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -27,38 +27,42 @@ module OnyxCord
|
|
|
27
27
|
else
|
|
28
28
|
REST::Application.get_global_command(@token, profile.id, command_id)
|
|
29
29
|
end
|
|
30
|
-
ApplicationCommand.new(JSON.parse(resp), self, server_id)
|
|
30
|
+
ApplicationCommand.new(OnyxCord::Internal::JSON.parse(resp), self, server_id)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# @return [OnyxCord::Interactions::Registry]
|
|
34
34
|
def commands
|
|
35
|
-
@commands ||=
|
|
35
|
+
@commands ||= application_command_registry
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def application_command_registry
|
|
39
|
+
@application_command_registry ||= ::OnyxCord::ApplicationCommands::Registry.new(self)
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
def slash(name, description: nil, **attributes, &block)
|
|
39
|
-
|
|
43
|
+
application_command_registry.slash(name, description: description, **attributes, &block)
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def user_command(name, **attributes, &block)
|
|
43
|
-
|
|
47
|
+
application_command_registry.user(name, **attributes, &block)
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
def message_command(name, **attributes, &block)
|
|
47
|
-
|
|
51
|
+
application_command_registry.message(name, **attributes, &block)
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
def sync_application_commands!(server_id: nil, delete_unknown: false)
|
|
51
|
-
|
|
55
|
+
application_command_registry.sync!(server_id: server_id, delete_unknown: delete_unknown)
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
def bulk_overwrite_global_application_commands(commands)
|
|
55
59
|
response = REST::Application.bulk_overwrite_global_commands(@token, profile.id, commands)
|
|
56
|
-
JSON.parse(response).map { |data| ApplicationCommand.new(data, self) }
|
|
60
|
+
OnyxCord::Internal::JSON.parse(response).map { |data| ApplicationCommand.new(data, self) }
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
def bulk_overwrite_guild_application_commands(server_id, commands)
|
|
60
64
|
response = REST::Application.bulk_overwrite_guild_commands(@token, profile.id, server_id.resolve_id, commands)
|
|
61
|
-
JSON.parse(response).map { |data| ApplicationCommand.new(data, self, server_id) }
|
|
65
|
+
OnyxCord::Internal::JSON.parse(response).map { |data| ApplicationCommand.new(data, self, server_id) }
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
# @yieldparam [OptionBuilder]
|
|
@@ -91,7 +95,7 @@ module OnyxCord
|
|
|
91
95
|
else
|
|
92
96
|
REST::Application.create_global_command(@token, profile.id, name, description, builder.to_a, default_permission, type, default_member_permissions&.to_s, contexts, nsfw, integration_types)
|
|
93
97
|
end
|
|
94
|
-
cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)
|
|
98
|
+
cmd = ApplicationCommand.new(OnyxCord::Internal::JSON.parse(resp), self, server_id)
|
|
95
99
|
|
|
96
100
|
if permission_builder.to_a.any?
|
|
97
101
|
raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id
|
|
@@ -121,7 +125,7 @@ module OnyxCord
|
|
|
121
125
|
else
|
|
122
126
|
REST::Application.edit_global_command(@token, profile.id, command_id, name, description, builder.to_a, default_permission, type, default_member_permissions&.to_s, contexts, nsfw, integration_types)
|
|
123
127
|
end
|
|
124
|
-
cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)
|
|
128
|
+
cmd = ApplicationCommand.new(OnyxCord::Internal::JSON.parse(resp), self, server_id)
|
|
125
129
|
|
|
126
130
|
if permission_builder.to_a.any?
|
|
127
131
|
raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id
|
|
@@ -163,14 +167,14 @@ module OnyxCord
|
|
|
163
167
|
# @return [Array<ApplicationCommand::Permission>] The permissions for all of the application commands in the given server.
|
|
164
168
|
def application_command_permissions(server_id:)
|
|
165
169
|
response = REST::Application.get_guild_application_command_permissions(@token, profile.id, server_id.resolve_id)
|
|
166
|
-
JSON.parse(response).flat_map { |data| data['permissions'].map { |inner| ApplicationCommand::Permission.new(inner, data, self) } }
|
|
170
|
+
OnyxCord::Internal::JSON.parse(response).flat_map { |data| data['permissions'].map { |inner| ApplicationCommand::Permission.new(inner, data, self) } }
|
|
167
171
|
end
|
|
168
172
|
|
|
169
173
|
# Fetches all the application emojis that the bot can use.
|
|
170
174
|
# @return [Array<Emoji>] Returns an array of emoji objects.
|
|
171
175
|
def application_emojis
|
|
172
176
|
response = REST::Application.list_application_emojis(@token, profile.id)
|
|
173
|
-
JSON.parse(response)['items'].map { |emoji| Emoji.new(emoji, self) }
|
|
177
|
+
OnyxCord::Internal::JSON.parse(response)['items'].map { |emoji| Emoji.new(emoji, self) }
|
|
174
178
|
end
|
|
175
179
|
|
|
176
180
|
# Fetches a single application emoji from its ID.
|
|
@@ -178,7 +182,7 @@ module OnyxCord
|
|
|
178
182
|
# @return [Emoji] The application emoji.
|
|
179
183
|
def application_emoji(emoji_id)
|
|
180
184
|
response = REST::Application.get_application_emoji(@token, profile.id, emoji_id.resolve_id)
|
|
181
|
-
Emoji.new(JSON.parse(response), self)
|
|
185
|
+
Emoji.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
182
186
|
end
|
|
183
187
|
|
|
184
188
|
# Creates a new custom emoji that can be used by this application.
|
|
@@ -188,7 +192,7 @@ module OnyxCord
|
|
|
188
192
|
def create_application_emoji(name:, image:)
|
|
189
193
|
image = image.respond_to?(:read) ? OnyxCord.encode64(image) : image
|
|
190
194
|
response = REST::Application.create_application_emoji(@token, profile.id, name, image)
|
|
191
|
-
Emoji.new(JSON.parse(response), self)
|
|
195
|
+
Emoji.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
192
196
|
end
|
|
193
197
|
|
|
194
198
|
# Edits an existing application emoji.
|
|
@@ -197,7 +201,7 @@ module OnyxCord
|
|
|
197
201
|
# @return [Emoji] Returns the updated emoji object on success.
|
|
198
202
|
def edit_application_emoji(emoji_id, name:)
|
|
199
203
|
response = REST::Application.edit_application_emoji(@token, profile.id, emoji_id.resolve_id, name)
|
|
200
|
-
Emoji.new(JSON.parse(response), self)
|
|
204
|
+
Emoji.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
201
205
|
end
|
|
202
206
|
|
|
203
207
|
# Deletes an existing application emoji.
|
|
@@ -25,7 +25,7 @@ module OnyxCord
|
|
|
25
25
|
flags = OnyxCord::MessageComponents.apply_v2_flag(flags, components)
|
|
26
26
|
|
|
27
27
|
response = REST::Channel.create_message(token, channel, content, tts, embeds, nonce, attachments, allowed_mentions&.to_hash, message_reference, components, flags, enforce_nonce, poll&.to_h)
|
|
28
|
-
Message.new(JSON.parse(response), self)
|
|
28
|
+
Message.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Sends a text message to a channel given its ID and the message's content,
|
|
@@ -75,7 +75,7 @@ module OnyxCord
|
|
|
75
75
|
|
|
76
76
|
channel = channel.resolve_id
|
|
77
77
|
response = REST::Channel.upload_file(token, channel, file, caption: caption, tts: tts)
|
|
78
|
-
Message.new(JSON.parse(response), self)
|
|
78
|
+
Message.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
# Gets the users, channels, roles and emoji from a string.
|
|
@@ -9,7 +9,7 @@ module OnyxCord
|
|
|
9
9
|
# @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
|
|
10
10
|
# @return [Array(String, String)] your applications' client ID and client secret to be used in OAuth authorization.
|
|
11
11
|
def create_oauth_application(name, redirect_uris)
|
|
12
|
-
response = JSON.parse(REST.create_oauth_application(@token, name, redirect_uris))
|
|
12
|
+
response = OnyxCord::Internal::JSON.parse(REST.create_oauth_application(@token, name, redirect_uris))
|
|
13
13
|
[response['id'], response['secret']]
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -28,15 +28,13 @@ module OnyxCord::Events
|
|
|
28
28
|
# @return [String] Name of the currently focused option.
|
|
29
29
|
attr_reader :focused
|
|
30
30
|
|
|
31
|
-
# @return [
|
|
32
|
-
attr_reader :
|
|
31
|
+
# @return [String, Integer, Float, nil] The current value typed by the user (if any).
|
|
32
|
+
attr_reader :value
|
|
33
33
|
|
|
34
34
|
# @!visibility private
|
|
35
35
|
def initialize(data, bot)
|
|
36
36
|
super
|
|
37
37
|
|
|
38
|
-
@choices = {}
|
|
39
|
-
|
|
40
38
|
options = data['data']['options']
|
|
41
39
|
|
|
42
40
|
options = case options[0]['type']
|
|
@@ -48,14 +46,31 @@ module OnyxCord::Events
|
|
|
48
46
|
options
|
|
49
47
|
end
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
focused_option = options.find { |opt| opt.key?('focused') }
|
|
50
|
+
@focused = focused_option['name']
|
|
51
|
+
@value = focused_option['value']
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Respond to this interaction with autocomplete choices.
|
|
55
|
-
# @param choices [Array<Hash>, Hash
|
|
55
|
+
# @param choices [Array<Hash>, Hash] Autocomplete choices to return.
|
|
56
|
+
# When a Hash, keys are names and values are the choice values.
|
|
57
|
+
# When an Array, each element is a Hash with :name and :value keys.
|
|
56
58
|
def respond(choices:)
|
|
57
59
|
@interaction.show_autocomplete_choices(choices || [])
|
|
58
60
|
end
|
|
61
|
+
|
|
62
|
+
# Convenience method to add a single choice and respond.
|
|
63
|
+
# @param name [String] The display name of the choice.
|
|
64
|
+
# @param value [String, Integer, Float] The value sent to the bot when selected.
|
|
65
|
+
def add_choice(name, value)
|
|
66
|
+
respond(choices: [{ name: name.to_s, value: value }])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Convenience method to respond with multiple name/value pairs.
|
|
70
|
+
# @param pairs [Hash{String => String, Integer, Float}] Name => Value pairs.
|
|
71
|
+
def add_choices(pairs)
|
|
72
|
+
respond(choices: pairs.map { |n, v| { name: n.to_s, value: v } })
|
|
73
|
+
end
|
|
59
74
|
end
|
|
60
75
|
|
|
61
76
|
# An event for whenever an application command's permissions are updated.
|
|
@@ -28,6 +28,7 @@ module OnyxCord
|
|
|
28
28
|
@compress_mode = compress_mode
|
|
29
29
|
@intents = intents
|
|
30
30
|
@send_limiter = Internal::RateLimiter::Gateway.new
|
|
31
|
+
@zlib_mutex = Mutex.new
|
|
31
32
|
@connection = nil
|
|
32
33
|
@closed = true
|
|
33
34
|
@pipe_broken = false
|
|
@@ -62,6 +63,7 @@ module OnyxCord
|
|
|
62
63
|
|
|
63
64
|
def stop
|
|
64
65
|
@should_reconnect = false
|
|
66
|
+
@intentional_shutdown = true
|
|
65
67
|
close
|
|
66
68
|
nil
|
|
67
69
|
end
|
|
@@ -75,8 +77,8 @@ module OnyxCord
|
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
def inject_reconnect(url = nil)
|
|
78
|
-
data = url ? { url: url } : nil
|
|
79
|
-
handle_message(
|
|
80
|
+
data = url ? { op: Internal::Gateway::Opcodes::RECONNECT, d: { url: url } } : { op: Internal::Gateway::Opcodes::RECONNECT, d: nil }
|
|
81
|
+
handle_message(data.to_json)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def inject_resume(seq)
|
|
@@ -153,6 +155,8 @@ module OnyxCord
|
|
|
153
155
|
@session.invalidate if attempt_resume && @session && !@received_hello
|
|
154
156
|
@instant_reconnect = true
|
|
155
157
|
@should_reconnect = true
|
|
158
|
+
@heartbeat_task&.stop
|
|
159
|
+
@heartbeat_task = nil
|
|
156
160
|
close(4000)
|
|
157
161
|
end
|
|
158
162
|
|
|
@@ -226,7 +230,7 @@ module OnyxCord
|
|
|
226
230
|
|
|
227
231
|
def find_gateway
|
|
228
232
|
response = REST.gateway(@token)
|
|
229
|
-
JSON.parse(response)['url']
|
|
233
|
+
OnyxCord::Internal::JSON.parse(response)['url']
|
|
230
234
|
end
|
|
231
235
|
|
|
232
236
|
def process_gateway
|
|
@@ -267,10 +271,12 @@ module OnyxCord
|
|
|
267
271
|
end
|
|
268
272
|
rescue Protocol::WebSocket::ClosedError, EOFError => e
|
|
269
273
|
LOGGER.warn("Gateway websocket closed (#{e.class}); reconnecting.")
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
unless @intentional_shutdown
|
|
275
|
+
begin
|
|
276
|
+
@bot.__send__(:raise_event, Events::DisconnectEvent.new(@bot))
|
|
277
|
+
rescue StandardError
|
|
278
|
+
nil
|
|
279
|
+
end
|
|
274
280
|
end
|
|
275
281
|
rescue StandardError => e
|
|
276
282
|
@session&.invalidate unless @received_hello
|
|
@@ -303,15 +309,17 @@ module OnyxCord
|
|
|
303
309
|
def handle_message(msg)
|
|
304
310
|
case @compress_mode
|
|
305
311
|
when :large
|
|
306
|
-
msg = Zlib::Inflate.inflate(msg) if msg.byteslice(0) == 'x'
|
|
312
|
+
msg = @zlib_mutex.synchronize { Zlib::Inflate.inflate(msg) } if msg.byteslice(0) == 'x'
|
|
307
313
|
when :stream
|
|
308
|
-
@
|
|
309
|
-
|
|
314
|
+
@zlib_mutex.synchronize do
|
|
315
|
+
@zlib_reader << msg
|
|
316
|
+
return if msg.bytesize < 4 || msg.byteslice(-4, 4) != ZLIB_SUFFIX
|
|
310
317
|
|
|
311
|
-
|
|
318
|
+
msg = @zlib_reader.inflate('')
|
|
319
|
+
end
|
|
312
320
|
end
|
|
313
321
|
|
|
314
|
-
packet = JSON.parse(msg)
|
|
322
|
+
packet = OnyxCord::Internal::JSON.parse(msg)
|
|
315
323
|
op = packet['op'].to_i
|
|
316
324
|
|
|
317
325
|
LOGGER.in(packet)
|
|
@@ -374,11 +382,12 @@ module OnyxCord
|
|
|
374
382
|
reconnect
|
|
375
383
|
else
|
|
376
384
|
@session.invalidate
|
|
385
|
+
identify
|
|
377
386
|
end
|
|
378
387
|
else
|
|
379
388
|
LOGGER.warn('Op 9 without session!')
|
|
389
|
+
identify
|
|
380
390
|
end
|
|
381
|
-
identify
|
|
382
391
|
end
|
|
383
392
|
|
|
384
393
|
# Op 10
|
|
@@ -414,7 +423,7 @@ module OnyxCord
|
|
|
414
423
|
end
|
|
415
424
|
|
|
416
425
|
def handle_close(e)
|
|
417
|
-
@bot.__send__(:raise_event, Events::DisconnectEvent.new(@bot))
|
|
426
|
+
@bot.__send__(:raise_event, Events::DisconnectEvent.new(@bot)) unless @intentional_shutdown
|
|
418
427
|
|
|
419
428
|
if e.respond_to?(:code)
|
|
420
429
|
LOGGER.error("WebSocket close frame! Code: #{e.code}")
|
|
@@ -454,6 +463,12 @@ module OnyxCord
|
|
|
454
463
|
# Ignore close errors
|
|
455
464
|
end
|
|
456
465
|
|
|
466
|
+
begin
|
|
467
|
+
@zlib_reader&.finish
|
|
468
|
+
rescue StandardError
|
|
469
|
+
nil
|
|
470
|
+
end
|
|
471
|
+
@zlib_reader = nil
|
|
457
472
|
@connection = nil
|
|
458
473
|
handle_close(nil)
|
|
459
474
|
end
|
|
@@ -53,13 +53,38 @@ module OnyxCord
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def execute(&block)
|
|
56
|
-
@executor = block
|
|
56
|
+
@executor = @receiver ? proc { |context| @receiver.instance_exec(context, &block) } : block
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def call(context)
|
|
60
60
|
return unless @executor
|
|
61
|
+
return unless @check.nil? || @check.call(context, self)
|
|
61
62
|
|
|
62
|
-
@
|
|
63
|
+
@cog_before_invoke&.call(context, self)
|
|
64
|
+
result = @executor.call(context)
|
|
65
|
+
@cog_after_invoke&.call(context, result, self)
|
|
66
|
+
result
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
return if @error_handler&.call(context, e, self)
|
|
69
|
+
|
|
70
|
+
raise e
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def copy_for(receiver = nil)
|
|
74
|
+
copied = self.class.new(@name, description: @description, type: @type, **@attributes, &@block)
|
|
75
|
+
copied.instance_variable_set(:@receiver, receiver)
|
|
76
|
+
copied.instance_variable_set(:@check, proc { |context, command| receiver.__send__(:passed_cog_application_command_check?, context, command) }) if receiver
|
|
77
|
+
copied.instance_variable_set(:@error_handler, proc { |context, error, command| receiver.__send__(:handled_cog_application_command_error?, context, error, command) }) if receiver
|
|
78
|
+
copied.instance_variable_set(:@cog_before_invoke, proc { |context, command| receiver.__send__(:invoke_cog_application_before_invoke, context, command) }) if receiver
|
|
79
|
+
copied.instance_variable_set(:@cog_after_invoke, proc { |context, result, command| receiver.__send__(:invoke_cog_application_after_invoke, context, result, command) }) if receiver
|
|
80
|
+
copied.instance_variable_set(:@options, @options.map(&:dup))
|
|
81
|
+
|
|
82
|
+
if @executor
|
|
83
|
+
executor = @executor
|
|
84
|
+
copied.execute { |context| receiver ? receiver.instance_exec(context, &executor) : executor.call(context) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
copied
|
|
63
88
|
end
|
|
64
89
|
|
|
65
90
|
def to_h
|
|
@@ -97,7 +97,7 @@ module OnyxCord
|
|
|
97
97
|
def permissions(server_id: nil)
|
|
98
98
|
raise ArgumentError, 'A server ID must be provided for global application commands' if @server_id.nil? && server_id.nil?
|
|
99
99
|
|
|
100
|
-
response = JSON.parse(REST::Application.get_application_command_permissions(@bot.token, @bot.profile.id, @server_id || server_id&.resolve_id, @id))
|
|
100
|
+
response = OnyxCord::Internal::JSON.parse(REST::Application.get_application_command_permissions(@bot.token, @bot.profile.id, @server_id || server_id&.resolve_id, @id))
|
|
101
101
|
response['permissions'].map { |permission| Permission.new(permission, response, @bot) }
|
|
102
102
|
rescue OnyxCord::Errors::UnknownError
|
|
103
103
|
# If there aren't any explicit overwrites configured for the command, the response is a 400.
|
|
@@ -8,6 +8,8 @@ module OnyxCord
|
|
|
8
8
|
def initialize(bot)
|
|
9
9
|
@bot = bot
|
|
10
10
|
@commands = {}
|
|
11
|
+
@pending_sync = nil
|
|
12
|
+
@sync_mutex = Mutex.new
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def slash(name, description:, **attributes, &block)
|
|
@@ -23,11 +25,16 @@ module OnyxCord
|
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def register(command)
|
|
28
|
+
command.parse(&command.block) if command.block && command.options.empty?
|
|
26
29
|
@commands[command.name] = command
|
|
27
30
|
wire_handler(command)
|
|
28
31
|
command
|
|
29
32
|
end
|
|
30
33
|
|
|
34
|
+
# Immediately sync all commands to Discord.
|
|
35
|
+
# @param server_id [Integer, String, nil] Guild ID for guild commands, or nil for global.
|
|
36
|
+
# @param delete_unknown [Boolean] Whether to remove commands not in the registry.
|
|
37
|
+
# @return [void]
|
|
31
38
|
def sync!(server_id: nil, delete_unknown: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
32
39
|
payload = @commands.values.map(&:to_h)
|
|
33
40
|
|
|
@@ -38,6 +45,35 @@ module OnyxCord
|
|
|
38
45
|
end
|
|
39
46
|
end
|
|
40
47
|
|
|
48
|
+
# Sync global application commands.
|
|
49
|
+
# @return [void]
|
|
50
|
+
def sync_global_commands
|
|
51
|
+
sync!(server_id: nil)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Sync guild application commands.
|
|
55
|
+
# @param server_id [Integer, String] The guild ID.
|
|
56
|
+
# @return [void]
|
|
57
|
+
def sync_server_commands(server_id)
|
|
58
|
+
sync!(server_id: server_id)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Schedule a debounced sync. Multiple calls within the delay window
|
|
62
|
+
# are collapsed into a single sync. Default delay is 2 seconds.
|
|
63
|
+
# @param delay [Numeric] Seconds to wait before syncing.
|
|
64
|
+
# @param server_id [Integer, String, nil] Guild ID, or nil for global.
|
|
65
|
+
# @return [Thread, nil] The timer thread, or nil if cancelled.
|
|
66
|
+
def schedule_delayed_command_sync(delay: 2, server_id: nil)
|
|
67
|
+
@sync_mutex.synchronize do
|
|
68
|
+
@pending_sync&.kill if @pending_sync.is_a?(Thread)
|
|
69
|
+
@pending_sync = Thread.new do
|
|
70
|
+
sleep(delay)
|
|
71
|
+
@sync_mutex.synchronize { @pending_sync = nil }
|
|
72
|
+
sync!(server_id: server_id)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
41
77
|
private
|
|
42
78
|
|
|
43
79
|
def wire_handler(command)
|