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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4fac344c79bce79ade61b9d7e1d40a756581380e31945ffb93470b5df4ced0c8
|
|
4
|
+
data.tar.gz: c74bbc605deb09a8e19d2a904a4b10e11b27b2a1909403113a2c589ff04de3fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d28b1b62c8e2b5bba98094e9f43d25feb77afbe9b004f3c642be4efe2cd03e487ce0a42d14e413bc840c62aee9ebced716107ef01f01eea530900acfa66d2508
|
|
7
|
+
data.tar.gz: db0532185853f82710423f20df5b1136f22c8d325782da62642c0855a0398e5585aa15ecc8ab34fd1ad7d7d2a88555c588b7468735b088d73b36c748462c8f56
|
data/lib/onyxcord/bot.rb
CHANGED
|
@@ -9,6 +9,8 @@ require_relative 'core/bot/oauth'
|
|
|
9
9
|
require_relative 'core/bot/presence'
|
|
10
10
|
require_relative 'core/bot/awaits'
|
|
11
11
|
require_relative 'core/bot/application_commands'
|
|
12
|
+
require_relative 'cogs/bot'
|
|
13
|
+
require_relative 'cogs/extensions'
|
|
12
14
|
|
|
13
15
|
module OnyxCord
|
|
14
16
|
# Represents a Discord bot, including servers, users, etc.
|
|
@@ -59,6 +61,8 @@ module OnyxCord
|
|
|
59
61
|
include Presence
|
|
60
62
|
include Awaits
|
|
61
63
|
include ApplicationCommands
|
|
64
|
+
include Cogs
|
|
65
|
+
include Extensions
|
|
62
66
|
|
|
63
67
|
# Makes a new bot with the given authentication data. It will be ready to be added event handlers to and can
|
|
64
68
|
# eventually be run with {#run}.
|
|
@@ -117,7 +121,8 @@ module OnyxCord
|
|
|
117
121
|
executor_queue_size = config.normalize_event_queue_size(event_queue_size)
|
|
118
122
|
|
|
119
123
|
LOGGER.mode = log_mode
|
|
120
|
-
|
|
124
|
+
@redact_token = redact_token
|
|
125
|
+
LOGGER.token = token if @redact_token
|
|
121
126
|
|
|
122
127
|
@should_parse_self = parse_self
|
|
123
128
|
|
|
@@ -168,6 +173,7 @@ module OnyxCord
|
|
|
168
173
|
|
|
169
174
|
@application_commands = {}
|
|
170
175
|
@request_members_rl = {}
|
|
176
|
+
@cogs = {}
|
|
171
177
|
end
|
|
172
178
|
|
|
173
179
|
# The list of users the bot shares a server with.
|
|
@@ -225,7 +231,7 @@ module OnyxCord
|
|
|
225
231
|
# @return [GlobalEmoji, nil] the emoji identified by the name, or `nil` if it couldn't be found.
|
|
226
232
|
def find_emoji(name)
|
|
227
233
|
LOGGER.out("Resolving emoji #{name}")
|
|
228
|
-
emoji.find { |element| element.name == name }
|
|
234
|
+
emoji.values.find { |element| element.name == name }
|
|
229
235
|
end
|
|
230
236
|
|
|
231
237
|
# The bot's user profile. This special user object can be used
|
|
@@ -235,7 +241,7 @@ module OnyxCord
|
|
|
235
241
|
return @profile if @profile
|
|
236
242
|
|
|
237
243
|
response = OnyxCord::REST::User.profile(@token)
|
|
238
|
-
@profile = Profile.new(JSON.parse(response), self)
|
|
244
|
+
@profile = Profile.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
239
245
|
end
|
|
240
246
|
|
|
241
247
|
alias_method :bot_user, :profile
|
|
@@ -244,7 +250,7 @@ module OnyxCord
|
|
|
244
250
|
# @return [Application] The bot's application info.
|
|
245
251
|
def bot_application
|
|
246
252
|
response = REST.oauth_application(token)
|
|
247
|
-
Application.new(JSON.parse(response), self)
|
|
253
|
+
Application.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
248
254
|
end
|
|
249
255
|
|
|
250
256
|
alias_method :bot_app, :bot_application
|
|
@@ -253,8 +259,8 @@ module OnyxCord
|
|
|
253
259
|
# Get the role connection metadata records associated with this application.
|
|
254
260
|
# @return [Array<RoleConnectionMetadata>] the role connection metadata records associated with this application.
|
|
255
261
|
def role_connection_metadata_records
|
|
256
|
-
response = REST::Application.get_application_role_connection_metadata_records(
|
|
257
|
-
JSON.parse(response).map { |role_connection| RoleConnectionMetadata.new(role_connection,
|
|
262
|
+
response = REST::Application.get_application_role_connection_metadata_records(token, @client_id)
|
|
263
|
+
OnyxCord::Internal::JSON.parse(response).map { |role_connection| RoleConnectionMetadata.new(role_connection, self) }
|
|
258
264
|
end
|
|
259
265
|
|
|
260
266
|
# The Discord API token received when logging in. Useful to explicitly call
|
|
@@ -26,7 +26,7 @@ module OnyxCord
|
|
|
26
26
|
|
|
27
27
|
# Initializes this cache
|
|
28
28
|
def init_cache
|
|
29
|
-
@cache_policy ||= OnyxCord.configuration.normalize_cache(
|
|
29
|
+
@cache_policy ||= OnyxCord.configuration.normalize_cache(OnyxCord.configuration.cache)
|
|
30
30
|
sizes = OnyxCord.configuration.cache_sizes
|
|
31
31
|
|
|
32
32
|
@users = cache_enabled?(:users) ? LruRedux::ThreadSafeCache.new(sizes.users) : nil
|
|
@@ -73,7 +73,7 @@ module OnyxCord
|
|
|
73
73
|
def fetch_voice_regions
|
|
74
74
|
regions_by_id = {}
|
|
75
75
|
|
|
76
|
-
regions = JSON.parse REST.voice_regions(token)
|
|
76
|
+
regions = OnyxCord::Internal::JSON.parse REST.voice_regions(token)
|
|
77
77
|
regions.each do |data|
|
|
78
78
|
regions_by_id[data['id']] = VoiceRegion.new(data)
|
|
79
79
|
end
|
|
@@ -99,7 +99,7 @@ module OnyxCord
|
|
|
99
99
|
rescue OnyxCord::Errors::UnknownChannel
|
|
100
100
|
return nil
|
|
101
101
|
end
|
|
102
|
-
channel = Channel.new(JSON.parse(response), self, server)
|
|
102
|
+
channel = Channel.new(OnyxCord::Internal::JSON.parse(response), self, server)
|
|
103
103
|
@channels[id] = channel if cache_enabled?(:channels)
|
|
104
104
|
channel
|
|
105
105
|
end
|
|
@@ -120,7 +120,7 @@ module OnyxCord
|
|
|
120
120
|
rescue OnyxCord::Errors::UnknownUser
|
|
121
121
|
return nil
|
|
122
122
|
end
|
|
123
|
-
user = User.new(JSON.parse(response), self)
|
|
123
|
+
user = User.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
124
124
|
@users[id] = user if cache_enabled?(:users)
|
|
125
125
|
user
|
|
126
126
|
end
|
|
@@ -139,7 +139,7 @@ module OnyxCord
|
|
|
139
139
|
rescue OnyxCord::Errors::NoPermission
|
|
140
140
|
return nil
|
|
141
141
|
end
|
|
142
|
-
server = Server.new(JSON.parse(response), self)
|
|
142
|
+
server = Server.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
143
143
|
@servers[id] = server if cache_enabled?(:servers)
|
|
144
144
|
server
|
|
145
145
|
end
|
|
@@ -161,7 +161,7 @@ module OnyxCord
|
|
|
161
161
|
rescue OnyxCord::Errors::UnknownUser, OnyxCord::Errors::UnknownMember
|
|
162
162
|
return nil
|
|
163
163
|
end
|
|
164
|
-
member = Member.new(JSON.parse(response), server, self)
|
|
164
|
+
member = Member.new(OnyxCord::Internal::JSON.parse(response), server, self)
|
|
165
165
|
server.cache_member(member)
|
|
166
166
|
end
|
|
167
167
|
|
|
@@ -176,7 +176,7 @@ module OnyxCord
|
|
|
176
176
|
|
|
177
177
|
debug("Creating pm channel with user id #{id}")
|
|
178
178
|
response = REST::User.create_pm(token, id)
|
|
179
|
-
channel = Channel.new(JSON.parse(response), self)
|
|
179
|
+
channel = Channel.new(OnyxCord::Internal::JSON.parse(response), self)
|
|
180
180
|
@pm_channels[id] = channel if cache_enabled?(:pm_channels)
|
|
181
181
|
channel
|
|
182
182
|
end
|
|
@@ -190,7 +190,7 @@ module OnyxCord
|
|
|
190
190
|
id = id.resolve_id
|
|
191
191
|
return @server_previews[id] if @server_previews&.[](id)
|
|
192
192
|
|
|
193
|
-
response = JSON.parse(REST::Server.preview(token, id))
|
|
193
|
+
response = OnyxCord::Internal::JSON.parse(REST::Server.preview(token, id))
|
|
194
194
|
preview = ServerPreview.new(response, self)
|
|
195
195
|
@server_previews[id] = preview if cache_enabled?(:server_previews)
|
|
196
196
|
preview
|
|
@@ -301,7 +301,7 @@ module OnyxCord
|
|
|
301
301
|
# @return [Invite] The invite with information about the given invite URL.
|
|
302
302
|
def invite(invite)
|
|
303
303
|
code = resolve_invite_code(invite)
|
|
304
|
-
Invite.new(JSON.parse(REST::Invite.resolve(token, code)), self)
|
|
304
|
+
Invite.new(OnyxCord::Internal::JSON.parse(REST::Invite.resolve(token, code)), self)
|
|
305
305
|
end
|
|
306
306
|
|
|
307
307
|
# Finds a channel given its name and optionally the name of the server it is in.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
class Bot
|
|
5
|
+
module Cogs
|
|
6
|
+
attr_reader :cogs
|
|
7
|
+
|
|
8
|
+
def add_cog(cog)
|
|
9
|
+
@cogs ||= {}
|
|
10
|
+
raise ArgumentError, "Cog #{cog.qualified_name} is already loaded" if @cogs.key?(cog.qualified_name)
|
|
11
|
+
|
|
12
|
+
cog._inject(self)
|
|
13
|
+
@cogs[cog.qualified_name] = cog
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def remove_cog(name)
|
|
17
|
+
@cogs ||= {}
|
|
18
|
+
cog = @cogs.delete(name.to_s)
|
|
19
|
+
cog&._eject(self)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get_cog(name)
|
|
23
|
+
@cogs ||= {}
|
|
24
|
+
@cogs[name.to_s]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord; end
|
|
4
|
+
|
|
5
|
+
require 'onyxcord/commands/container'
|
|
6
|
+
require 'onyxcord/cogs/cog_meta'
|
|
7
|
+
|
|
8
|
+
module OnyxCord
|
|
9
|
+
class Cog
|
|
10
|
+
include CogMeta
|
|
11
|
+
|
|
12
|
+
attr_reader :bot, :commands, :application_commands
|
|
13
|
+
|
|
14
|
+
def self.inherited(child)
|
|
15
|
+
child.include CogMeta
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(bot)
|
|
20
|
+
@bot = bot
|
|
21
|
+
_update_copy
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def cog_load; end
|
|
25
|
+
|
|
26
|
+
def cog_unload; end
|
|
27
|
+
|
|
28
|
+
def cog_command_error(_event, _error); end
|
|
29
|
+
|
|
30
|
+
def cog_slash_command_error(_context, _error); end
|
|
31
|
+
|
|
32
|
+
def cog_user_command_error(_context, _error); end
|
|
33
|
+
|
|
34
|
+
def cog_message_command_error(_context, _error); end
|
|
35
|
+
|
|
36
|
+
def cog_check(_event)
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cog_slash_command_check(_context)
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def cog_user_command_check(_context)
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def cog_message_command_check(_context)
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cog_before_invoke(_event); end
|
|
53
|
+
|
|
54
|
+
def cog_after_invoke(_event, _result); end
|
|
55
|
+
|
|
56
|
+
def cog_slash_before_invoke(_context); end
|
|
57
|
+
|
|
58
|
+
def cog_slash_after_invoke(_context, _result); end
|
|
59
|
+
|
|
60
|
+
def cog_user_before_invoke(_context); end
|
|
61
|
+
|
|
62
|
+
def cog_user_after_invoke(_context, _result); end
|
|
63
|
+
|
|
64
|
+
def cog_message_before_invoke(_context); end
|
|
65
|
+
|
|
66
|
+
def cog_message_after_invoke(_context, _result); end
|
|
67
|
+
|
|
68
|
+
def _inject(bot)
|
|
69
|
+
@bot = bot
|
|
70
|
+
old_commands = (bot.instance_variable_get(:@commands) || {}).dup
|
|
71
|
+
old_application_commands = (bot.application_command_registry.commands || {}).dup
|
|
72
|
+
old_application_handlers = (bot.instance_variable_get(:@application_commands) || {}).dup
|
|
73
|
+
old_event_handlers = clone_event_handlers(bot.instance_variable_get(:@event_handlers))
|
|
74
|
+
old_raw_handlers = (bot.instance_variable_get(:@raw_handlers) || []).dup
|
|
75
|
+
|
|
76
|
+
inject_commands(bot)
|
|
77
|
+
inject_listeners(bot)
|
|
78
|
+
cog_load
|
|
79
|
+
self
|
|
80
|
+
rescue StandardError
|
|
81
|
+
bot.instance_variable_set(:@commands, old_commands)
|
|
82
|
+
bot.application_command_registry.commands.replace(old_application_commands)
|
|
83
|
+
bot.instance_variable_set(:@application_commands, old_application_handlers)
|
|
84
|
+
bot.instance_variable_set(:@event_handlers, old_event_handlers)
|
|
85
|
+
bot.instance_variable_set(:@raw_handlers, old_raw_handlers)
|
|
86
|
+
raise
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _eject(bot)
|
|
90
|
+
eject_commands(bot)
|
|
91
|
+
eject_listeners(bot)
|
|
92
|
+
cog_unload
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def _update_copy
|
|
97
|
+
@commands = copy_prefix_commands
|
|
98
|
+
@application_commands = self.class.application_commands.transform_values do |command|
|
|
99
|
+
copy = command.copy_for(self)
|
|
100
|
+
copy.parse(©.block) if copy.block && copy.options.empty?
|
|
101
|
+
copy
|
|
102
|
+
end
|
|
103
|
+
@listeners = self.class.listeners.map { |listener| listener.merge(names: listener[:names].dup) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
107
|
+
def get_commands
|
|
108
|
+
@commands.values.grep(Commands::Command)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def get_application_commands
|
|
112
|
+
@application_commands.values
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def get_listeners
|
|
116
|
+
@listeners
|
|
117
|
+
end
|
|
118
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
119
|
+
|
|
120
|
+
def walk_commands
|
|
121
|
+
get_commands + get_application_commands.flat_map { |command| [command, *walk_options(command.options)] }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def handled_cog_command_error?(event, error)
|
|
125
|
+
return false unless overrides?(:cog_command_error)
|
|
126
|
+
|
|
127
|
+
cog_command_error(event, error)
|
|
128
|
+
true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def handled_cog_application_command_error?(context, error, command)
|
|
132
|
+
method_name = case command.type
|
|
133
|
+
when :chat_input, :primary_entry_point then :cog_slash_command_error
|
|
134
|
+
when :user then :cog_user_command_error
|
|
135
|
+
when :message then :cog_message_command_error
|
|
136
|
+
end
|
|
137
|
+
return false unless method_name && overrides?(method_name)
|
|
138
|
+
|
|
139
|
+
public_send(method_name, context, error)
|
|
140
|
+
true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def passed_cog_command_check?(event)
|
|
144
|
+
return true unless overrides?(:cog_check)
|
|
145
|
+
|
|
146
|
+
cog_check(event)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def passed_cog_application_command_check?(context, command)
|
|
150
|
+
method_name = case command.type
|
|
151
|
+
when :chat_input, :primary_entry_point then :cog_slash_command_check
|
|
152
|
+
when :user then :cog_user_command_check
|
|
153
|
+
when :message then :cog_message_command_check
|
|
154
|
+
end
|
|
155
|
+
return true unless method_name && overrides?(method_name)
|
|
156
|
+
|
|
157
|
+
public_send(method_name, context)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def invoke_cog_before_invoke(event)
|
|
161
|
+
return unless overrides?(:cog_before_invoke)
|
|
162
|
+
|
|
163
|
+
cog_before_invoke(event)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def invoke_cog_after_invoke(event, result)
|
|
167
|
+
return unless overrides?(:cog_after_invoke)
|
|
168
|
+
|
|
169
|
+
cog_after_invoke(event, result)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def invoke_cog_application_before_invoke(context, command)
|
|
173
|
+
method_name = case command.type
|
|
174
|
+
when :chat_input, :primary_entry_point then :cog_slash_before_invoke
|
|
175
|
+
when :user then :cog_user_before_invoke
|
|
176
|
+
when :message then :cog_message_before_invoke
|
|
177
|
+
end
|
|
178
|
+
return unless method_name && overrides?(method_name)
|
|
179
|
+
|
|
180
|
+
public_send(method_name, context)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def invoke_cog_application_after_invoke(context, result, command)
|
|
184
|
+
method_name = case command.type
|
|
185
|
+
when :chat_input, :primary_entry_point then :cog_slash_after_invoke
|
|
186
|
+
when :user then :cog_user_after_invoke
|
|
187
|
+
when :message then :cog_message_after_invoke
|
|
188
|
+
end
|
|
189
|
+
return unless method_name && overrides?(method_name)
|
|
190
|
+
|
|
191
|
+
public_send(method_name, context, result)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def overrides?(method_name)
|
|
197
|
+
method(method_name).owner != OnyxCord::Cog
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def copy_prefix_commands
|
|
201
|
+
copied = {}
|
|
202
|
+
|
|
203
|
+
(self.class.commands || {}).each_value do |command|
|
|
204
|
+
next unless command.is_a?(Commands::Command)
|
|
205
|
+
|
|
206
|
+
copy = command.copy_for(self)
|
|
207
|
+
copied[copy.name] = copy
|
|
208
|
+
copy.attributes[:aliases].each { |name| copied[name] = Commands::CommandAlias.new(name, copy) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
copied
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def walk_options(options)
|
|
215
|
+
options.flat_map { |option| [option, *walk_options(option.options)] }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def inject_commands(bot)
|
|
219
|
+
@__onyxcord_injected_commands = @commands.keys
|
|
220
|
+
bot_commands = bot.instance_variable_get(:@commands) || {}
|
|
221
|
+
bot.instance_variable_set(:@commands, bot_commands.merge(@commands))
|
|
222
|
+
|
|
223
|
+
@__onyxcord_injected_application_commands = @application_commands.keys
|
|
224
|
+
@application_commands.each_value { |command| bot.application_command_registry.register(command) }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def inject_listeners(bot)
|
|
228
|
+
@__onyxcord_injected_listeners = []
|
|
229
|
+
@listeners.each do |listener|
|
|
230
|
+
listener[:names].each do |name|
|
|
231
|
+
raise ArgumentError, "Unknown listener event #{name}" unless bot.respond_to?(name)
|
|
232
|
+
|
|
233
|
+
handler = bot.public_send(name, listener[:attributes]) { |event| instance_exec(event, &listener[:block]) }
|
|
234
|
+
@__onyxcord_injected_listeners << handler
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def eject_commands(bot)
|
|
240
|
+
bot_commands = bot.instance_variable_get(:@commands)
|
|
241
|
+
@__onyxcord_injected_commands&.each do |name|
|
|
242
|
+
bot_commands&.delete(name) if bot_commands[name].equal?(@commands[name])
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
registry_commands = bot.application_command_registry.commands
|
|
246
|
+
application_handlers = bot.instance_variable_get(:@application_commands)
|
|
247
|
+
@__onyxcord_injected_application_commands&.each do |name|
|
|
248
|
+
registry_commands.delete(name) if registry_commands[name].equal?(@application_commands[name])
|
|
249
|
+
application_handlers&.delete(name.to_sym)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def eject_listeners(bot)
|
|
254
|
+
@__onyxcord_injected_listeners&.each { |handler| bot.remove_handler(handler) }
|
|
255
|
+
@__onyxcord_injected_listeners = []
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def clone_event_handlers(handlers)
|
|
259
|
+
return {} unless handlers
|
|
260
|
+
|
|
261
|
+
handlers.transform_values(&:dup)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
module CogMeta
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend Commands::CommandContainer
|
|
7
|
+
base.extend ClassMethods
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def qualified_name
|
|
11
|
+
self.class.name || self.class.to_s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def description
|
|
15
|
+
self.class.instance_variable_get(:@description)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
19
|
+
def description(value = nil)
|
|
20
|
+
@description = value unless value.nil?
|
|
21
|
+
@description
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def command_attrs(attributes = nil)
|
|
25
|
+
@command_attrs = attributes.dup if attributes
|
|
26
|
+
@command_attrs ||= {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def command(name, attributes = {}, &block)
|
|
30
|
+
super(name, command_attrs.merge(attributes), &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def slash(name, description:, **attributes, &block)
|
|
34
|
+
application_commands[name.to_s] = Interactions::Command.chat_input(name, description: description, **attributes, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def user_command(name, **attributes, &block)
|
|
38
|
+
application_commands[name.to_s] = Interactions::Command.user(name, **attributes, &block)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def message_command(name, **attributes, &block)
|
|
42
|
+
application_commands[name.to_s] = Interactions::Command.message(name, **attributes, &block)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def application_commands
|
|
46
|
+
@application_commands ||= {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def listener(*names, **attributes, &block)
|
|
50
|
+
raise ArgumentError, 'Listener requires at least one event name' if names.empty?
|
|
51
|
+
raise ArgumentError, 'Listener requires a block' unless block
|
|
52
|
+
|
|
53
|
+
listeners << { names: names.map(&:to_sym), attributes: attributes, block: block }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def listeners
|
|
57
|
+
@listeners ||= []
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
class Bot
|
|
5
|
+
# Extension management: load, unload, and reload cog modules from files.
|
|
6
|
+
module Extensions
|
|
7
|
+
# @return [Hash{String => Hash}] loaded extensions keyed by name
|
|
8
|
+
attr_reader :extensions
|
|
9
|
+
|
|
10
|
+
# Load an extension from a file. The file must define a +setup(bot)+ method
|
|
11
|
+
# that receives the bot and returns a cog instance (or an array of cog instances).
|
|
12
|
+
# Optionally, the file can define a +teardown(bot)+ method for cleanup.
|
|
13
|
+
#
|
|
14
|
+
# @param name [String, Symbol] The extension name (used as key and to resolve file path).
|
|
15
|
+
# @param path [String, nil] Optional explicit file path. When nil, resolves +extensions/<name>.rb+.
|
|
16
|
+
# @return [Array<Cog>] the cogs created by the extension.
|
|
17
|
+
# @raise [ArgumentError] if the extension is already loaded.
|
|
18
|
+
# @raise [LoadError] if the file cannot be found or does not define +setup+.
|
|
19
|
+
def load_extension(name, path: nil)
|
|
20
|
+
@extensions ||= {}
|
|
21
|
+
name = name.to_s
|
|
22
|
+
raise ArgumentError, "Extension '#{name}' is already loaded" if @extensions.key?(name)
|
|
23
|
+
|
|
24
|
+
path ||= resolve_extension_path(name)
|
|
25
|
+
raise LoadError, "Extension file not found: #{path}" unless File.exist?(path)
|
|
26
|
+
|
|
27
|
+
mod = load_extension_module(path, name)
|
|
28
|
+
|
|
29
|
+
raise LoadError, "Extension '#{name}' must define a self.setup(bot) method" unless mod.respond_to?(:setup)
|
|
30
|
+
|
|
31
|
+
result = mod.setup(self)
|
|
32
|
+
cogs = Array(result).select { |obj| obj.is_a?(Cog) }
|
|
33
|
+
|
|
34
|
+
@extensions[name] = {
|
|
35
|
+
path: path,
|
|
36
|
+
module: mod,
|
|
37
|
+
cogs: cogs.map { |c| c.respond_to?(:qualified_name) ? c.qualified_name : c.class.name }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
cogs
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Unload an extension by name. Calls +teardown(bot)+ if defined.
|
|
44
|
+
#
|
|
45
|
+
# @param name [String, Symbol] The extension name.
|
|
46
|
+
# @return [true, false] whether the extension was found and unloaded.
|
|
47
|
+
def unload_extension(name) # rubocop:disable Naming/PredicateMethod
|
|
48
|
+
@extensions ||= {}
|
|
49
|
+
name = name.to_s
|
|
50
|
+
ext = @extensions.delete(name)
|
|
51
|
+
return false unless ext
|
|
52
|
+
|
|
53
|
+
mod = ext[:module]
|
|
54
|
+
mod.teardown(self) if mod.respond_to?(:teardown)
|
|
55
|
+
|
|
56
|
+
ext[:cogs].each { |cog_name| remove_cog(cog_name) }
|
|
57
|
+
|
|
58
|
+
true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Reload an extension: unload then load.
|
|
62
|
+
#
|
|
63
|
+
# @param name [String, Symbol] The extension name.
|
|
64
|
+
# @return [Array<Cog>] the cogs created by the reloaded extension.
|
|
65
|
+
# @raise [ArgumentError] if the extension was not previously loaded.
|
|
66
|
+
def reload_extension(name)
|
|
67
|
+
@extensions ||= {}
|
|
68
|
+
name = name.to_s
|
|
69
|
+
ext = @extensions[name]
|
|
70
|
+
raise ArgumentError, "Extension '#{name}' is not loaded" unless ext
|
|
71
|
+
|
|
72
|
+
path = ext[:path]
|
|
73
|
+
unload_extension(name)
|
|
74
|
+
load_extension(name, path: path)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Load all +.rb+ files from a directory as extensions.
|
|
78
|
+
#
|
|
79
|
+
# @param dir [String] The directory path containing extension files.
|
|
80
|
+
# @return [Array<Cog>] all cogs created from the loaded extensions.
|
|
81
|
+
def load_extensions(dir)
|
|
82
|
+
raise ArgumentError, "Extensions directory not found: #{dir}" unless Dir.exist?(dir)
|
|
83
|
+
|
|
84
|
+
cogs = []
|
|
85
|
+
Dir.glob(File.join(dir, '*.rb')).each do |file|
|
|
86
|
+
name = File.basename(file, '.rb')
|
|
87
|
+
cogs.concat(load_extension(name, path: file))
|
|
88
|
+
rescue LoadError, StandardError => e
|
|
89
|
+
OnyxCord::LOGGER.warn("Failed to load extension '#{name}': #{e.message}") if defined?(OnyxCord::LOGGER)
|
|
90
|
+
end
|
|
91
|
+
cogs
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def resolve_extension_path(name)
|
|
97
|
+
File.join('extensions', "#{name}.rb")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def load_extension_module(path, name)
|
|
101
|
+
# Create a fresh module for the extension to avoid constant collisions
|
|
102
|
+
mod = Module.new
|
|
103
|
+
mod.define_singleton_method(:name) { "OnyxCord::Extensions::#{name.capitalize}" }
|
|
104
|
+
mod.module_eval(File.read(path), path)
|
|
105
|
+
mod
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|