discordrb 3.3.0 → 3.5.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/.circleci/config.yml +152 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- data/.github/pull_request_template.md +37 -0
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +39 -36
- data/CHANGELOG.md +874 -552
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +80 -86
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/discordrb-webhooks.gemspec +9 -6
- data/discordrb.gemspec +21 -18
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +236 -47
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +5 -5
- data/lib/discordrb/api/server.rb +94 -66
- data/lib/discordrb/api/user.rb +17 -11
- data/lib/discordrb/api/webhook.rb +63 -6
- data/lib/discordrb/api.rb +55 -16
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +480 -93
- data/lib/discordrb/cache.rb +31 -24
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +35 -12
- data/lib/discordrb/commands/container.rb +21 -24
- data/lib/discordrb/commands/parser.rb +20 -20
- data/lib/discordrb/commands/rate_limiter.rb +4 -3
- data/lib/discordrb/container.rb +209 -20
- data/lib/discordrb/data/activity.rb +271 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +71 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +993 -0
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +122 -0
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +372 -0
- data/lib/discordrb/data/message.rb +414 -0
- data/lib/discordrb/data/overwrite.rb +108 -0
- data/lib/discordrb/data/profile.rb +91 -0
- data/lib/discordrb/data/reaction.rb +33 -0
- data/lib/discordrb/data/recipient.rb +34 -0
- data/lib/discordrb/data/role.rb +248 -0
- data/lib/discordrb/data/server.rb +1004 -0
- data/lib/discordrb/data/user.rb +264 -0
- data/lib/discordrb/data/voice_region.rb +45 -0
- data/lib/discordrb/data/voice_state.rb +41 -0
- data/lib/discordrb/data/webhook.rb +238 -0
- data/lib/discordrb/data.rb +28 -4180
- data/lib/discordrb/errors.rb +46 -4
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +3 -1
- data/lib/discordrb/events/guilds.rb +16 -9
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +72 -27
- data/lib/discordrb/events/presence.rb +35 -18
- data/lib/discordrb/events/raw.rb +1 -3
- data/lib/discordrb/events/reactions.rb +49 -4
- data/lib/discordrb/events/threads.rb +96 -0
- data/lib/discordrb/events/typing.rb +6 -4
- data/lib/discordrb/events/voice_server_update.rb +47 -0
- data/lib/discordrb/events/voice_state_update.rb +15 -10
- data/lib/discordrb/events/webhooks.rb +9 -6
- data/lib/discordrb/gateway.rb +99 -71
- data/lib/discordrb/id_object.rb +39 -0
- data/lib/discordrb/light/integrations.rb +1 -1
- data/lib/discordrb/light/light_bot.rb +1 -1
- data/lib/discordrb/logger.rb +4 -4
- data/lib/discordrb/paginator.rb +57 -0
- data/lib/discordrb/permissions.rb +159 -39
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +16 -7
- data/lib/discordrb/voice/network.rb +99 -47
- data/lib/discordrb/voice/sodium.rb +98 -0
- data/lib/discordrb/voice/voice_bot.rb +33 -25
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +107 -1
- metadata +126 -54
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -33
- data/bin/travis_build_docs.sh +0 -17
- /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
data/lib/discordrb/errors.rb
CHANGED
@@ -20,6 +20,9 @@ module Discordrb
|
|
20
20
|
# Raised when the bot gets a HTTP 502 error, which is usually caused by Cloudflare.
|
21
21
|
class CloudflareError < RuntimeError; end
|
22
22
|
|
23
|
+
# Raised when using a webhook method without an associated token.
|
24
|
+
class UnauthorizedWebhook < RuntimeError; end
|
25
|
+
|
23
26
|
# Generic class for errors denoted by API error codes
|
24
27
|
class CodeError < RuntimeError
|
25
28
|
class << self
|
@@ -29,8 +32,11 @@ module Discordrb
|
|
29
32
|
|
30
33
|
# Create a new error with a particular message (the code should be defined by the class instance variable)
|
31
34
|
# @param message [String] the message to use
|
32
|
-
|
35
|
+
# @param errors [Hash] API errors
|
36
|
+
def initialize(message, errors = nil)
|
33
37
|
@message = message
|
38
|
+
|
39
|
+
@errors = errors ? flatten_errors(errors) : []
|
34
40
|
end
|
35
41
|
|
36
42
|
# @return [Integer] The error code represented by this error.
|
@@ -38,26 +44,62 @@ module Discordrb
|
|
38
44
|
self.class.code
|
39
45
|
end
|
40
46
|
|
47
|
+
# @return [String] A message including the message and flattened errors.
|
48
|
+
def full_message(*)
|
49
|
+
error_list = @errors.collect { |err| "\t- #{err}" }
|
50
|
+
|
51
|
+
"#{@message}\n#{error_list.join("\n")}"
|
52
|
+
end
|
53
|
+
|
41
54
|
# @return [String] This error's represented message
|
42
55
|
attr_reader :message
|
56
|
+
|
57
|
+
# @return [Hash] More precise errors
|
58
|
+
attr_reader :errors
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @!visibility hidden
|
63
|
+
# Flattens errors into a more easily read format.
|
64
|
+
# @example Flattening errors of a bad field
|
65
|
+
# flatten_errors(data['errors'])
|
66
|
+
# # => ["embed.fields[0].name: This field is required", "embed.fields[0].value: This field is required"]
|
67
|
+
def flatten_errors(err, prev_key = nil)
|
68
|
+
err.collect do |key, sub_err|
|
69
|
+
if prev_key
|
70
|
+
key = /\A\d+\Z/.match?(key) ? "#{prev_key}[#{key}]" : "#{prev_key}.#{key}"
|
71
|
+
end
|
72
|
+
|
73
|
+
if (errs = sub_err['_errors'])
|
74
|
+
"#{key}: #{errs.map { |e| e['message'] }.join(' ')}"
|
75
|
+
elsif sub_err['message'] || sub_err['code']
|
76
|
+
"#{sub_err['code'] ? "#{sub_err['code']}: " : nil}#{err_msg}"
|
77
|
+
elsif sub_err.is_a? String
|
78
|
+
sub_err
|
79
|
+
else
|
80
|
+
flatten_errors(sub_err, key)
|
81
|
+
end
|
82
|
+
end.flatten
|
83
|
+
end
|
43
84
|
end
|
44
85
|
|
45
86
|
# Create a new code error class
|
46
|
-
# rubocop:disable
|
87
|
+
# rubocop:disable Naming/MethodName
|
47
88
|
def self.Code(code)
|
48
89
|
classy = Class.new(CodeError)
|
49
|
-
classy.instance_variable_set(
|
90
|
+
classy.instance_variable_set(:@code, code)
|
50
91
|
|
51
92
|
@code_classes ||= {}
|
52
93
|
@code_classes[code] = classy
|
53
94
|
|
54
95
|
classy
|
55
96
|
end
|
97
|
+
# rubocop:enable Naming/MethodName
|
56
98
|
|
57
99
|
# @param code [Integer] The code to check
|
58
100
|
# @return [Class] the error class for the given code
|
59
101
|
def self.error_class_for(code)
|
60
|
-
@code_classes[code]
|
102
|
+
@code_classes[code] || UnknownError
|
61
103
|
end
|
62
104
|
|
63
105
|
# Used when Discord doesn't provide a more specific code
|
@@ -27,20 +27,22 @@ module Discordrb::Events
|
|
27
27
|
|
28
28
|
[
|
29
29
|
matches_all(@attributes[:user], event.user) do |a, e|
|
30
|
-
|
30
|
+
case a
|
31
|
+
when String
|
31
32
|
a == e.name
|
32
|
-
|
33
|
+
when Integer
|
33
34
|
a == e.id
|
34
|
-
|
35
|
+
when :bot
|
35
36
|
e.current_bot?
|
36
37
|
else
|
37
38
|
a == e
|
38
39
|
end
|
39
40
|
end,
|
40
41
|
matches_all(@attributes[:server], event.server) do |a, e|
|
41
|
-
a ==
|
42
|
+
a == case a
|
43
|
+
when String
|
42
44
|
e.name
|
43
|
-
|
45
|
+
when Integer
|
44
46
|
e.id
|
45
47
|
else
|
46
48
|
e
|
@@ -31,7 +31,7 @@ module Discordrb::Events
|
|
31
31
|
|
32
32
|
def initialize(data, bot)
|
33
33
|
@bot = bot
|
34
|
-
@channel = bot.channel(data['id'].to_i)
|
34
|
+
@channel = data.is_a?(Discordrb::Channel) ? data : bot.channel(data['id'].to_i)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -126,10 +126,12 @@ module Discordrb::Events
|
|
126
126
|
class ChannelRecipientEvent < Event
|
127
127
|
# @return [Channel] the channel in question.
|
128
128
|
attr_reader :channel
|
129
|
+
|
129
130
|
delegate :name, :server, :type, :owner_id, :recipients, :topic, :user_limit, :position, :permission_overwrites, to: :channel
|
130
131
|
|
131
132
|
# @return [Recipient] the recipient that was added/removed from the group
|
132
133
|
attr_reader :recipient
|
134
|
+
|
133
135
|
delegate :id, to: :recipient
|
134
136
|
|
135
137
|
def initialize(data, bot)
|
@@ -30,9 +30,10 @@ module Discordrb::Events
|
|
30
30
|
|
31
31
|
[
|
32
32
|
matches_all(@attributes[:server], event.server) do |a, e|
|
33
|
-
a ==
|
33
|
+
a == case a
|
34
|
+
when String
|
34
35
|
e.name
|
35
|
-
|
36
|
+
when Integer
|
36
37
|
e.id
|
37
38
|
else
|
38
39
|
e
|
@@ -56,12 +57,16 @@ module Discordrb::Events
|
|
56
57
|
# Event handler for {ServerUpdateEvent}
|
57
58
|
class ServerUpdateEventHandler < ServerEventHandler; end
|
58
59
|
|
59
|
-
# Server is deleted
|
60
|
+
# Server is deleted, the server was left because the bot was kicked, or the
|
61
|
+
# bot made itself leave the server.
|
60
62
|
# @see Discordrb::EventContainer#server_delete
|
61
63
|
class ServerDeleteEvent < ServerEvent
|
64
|
+
# @return [Integer] The ID of the server that was left.
|
65
|
+
attr_reader :server
|
66
|
+
|
62
67
|
# Override init_server to account for the deleted server
|
63
|
-
def init_server(data,
|
64
|
-
@server =
|
68
|
+
def init_server(data, _bot)
|
69
|
+
@server = data['id'].to_i
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
@@ -141,9 +146,10 @@ module Discordrb::Events
|
|
141
146
|
|
142
147
|
[
|
143
148
|
matches_all(@attributes[:server], event.server) do |a, e|
|
144
|
-
a ==
|
149
|
+
a == case a
|
150
|
+
when String
|
145
151
|
e.name
|
146
|
-
|
152
|
+
when Integer
|
147
153
|
e.id
|
148
154
|
else
|
149
155
|
e
|
@@ -169,9 +175,10 @@ module Discordrb::Events
|
|
169
175
|
|
170
176
|
[
|
171
177
|
matches_all(@attributes[:server], event.server) do |a, e|
|
172
|
-
a ==
|
178
|
+
a == case a
|
179
|
+
when String
|
173
180
|
e.name
|
174
|
-
|
181
|
+
when Integer
|
175
182
|
e.id
|
176
183
|
else
|
177
184
|
e
|
@@ -0,0 +1,482 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'discordrb/events/generic'
|
4
|
+
require 'discordrb/data'
|
5
|
+
|
6
|
+
module Discordrb::Events
|
7
|
+
# Generic subclass for interaction events
|
8
|
+
class InteractionCreateEvent < Event
|
9
|
+
# @return [Interaction] The interaction for this event.
|
10
|
+
attr_reader :interaction
|
11
|
+
|
12
|
+
# @!attribute [r] type
|
13
|
+
# @return [Integer]
|
14
|
+
# @see Interaction#type
|
15
|
+
# @!attribute [r] server
|
16
|
+
# @return [Server, nil]
|
17
|
+
# @see Interaction#server
|
18
|
+
# @!attribute [r] server_id
|
19
|
+
# @return [Integer]
|
20
|
+
# @see Interaction#server_id
|
21
|
+
# @!attribute [r] channel
|
22
|
+
# @return [Channel]
|
23
|
+
# @see Interaction#channel
|
24
|
+
# @!attribute [r] channel_id
|
25
|
+
# @return [Integer]
|
26
|
+
# @see Interaction#channel_id
|
27
|
+
# @!attribute [r] user
|
28
|
+
# @return [User]
|
29
|
+
# @see Interaction#user
|
30
|
+
delegate :type, :server, :server_id, :channel, :channel_id, :user, to: :interaction
|
31
|
+
|
32
|
+
def initialize(data, bot)
|
33
|
+
@interaction = Discordrb::Interaction.new(data, bot)
|
34
|
+
@bot = bot
|
35
|
+
end
|
36
|
+
|
37
|
+
# (see Interaction#respond)
|
38
|
+
def respond(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, &block)
|
39
|
+
@interaction.respond(
|
40
|
+
content: content, tts: tts, embeds: embeds, allowed_mentions: allowed_mentions,
|
41
|
+
flags: flags, ephemeral: ephemeral, wait: wait, components: components, &block
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# (see Interaction#defer)
|
46
|
+
def defer(flags: 0, ephemeral: true)
|
47
|
+
@interaction.defer(flags: flags, ephemeral: ephemeral)
|
48
|
+
end
|
49
|
+
|
50
|
+
# (see Interaction#update_message)
|
51
|
+
def update_message(content: nil, tts: nil, embeds: nil, allowed_mentions: nil, flags: 0, ephemeral: nil, wait: false, components: nil, &block)
|
52
|
+
@interaction.update_message(
|
53
|
+
content: content, tts: tts, embeds: embeds, allowed_mentions: allowed_mentions,
|
54
|
+
flags: flags, ephemeral: ephemeral, wait: wait, components: components, &block
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# (see Interaction#show_modal)
|
59
|
+
def show_modal(title:, custom_id:, components: nil, &block)
|
60
|
+
@interaction.show_modal(title: title, custom_id: custom_id, components: components, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
# (see Interaction#edit_response)
|
64
|
+
def edit_response(content: nil, embeds: nil, allowed_mentions: nil, components: nil, &block)
|
65
|
+
@interaction.edit_response(content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# (see Interaction#delete_response)
|
69
|
+
def delete_response
|
70
|
+
@interaction.delete_response
|
71
|
+
end
|
72
|
+
|
73
|
+
# (see Interaction#send_message)
|
74
|
+
def send_message(content: nil, embeds: nil, tts: false, allowed_mentions: nil, flags: 0, ephemeral: nil, components: nil, &block)
|
75
|
+
@interaction.send_message(content: content, embeds: embeds, tts: tts, allowed_mentions: allowed_mentions, flags: flags, ephemeral: ephemeral, components: components, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
# (see Interaction#edit_message)
|
79
|
+
def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, &block)
|
80
|
+
@interaction.edit_message(message, content: content, embeds: embeds, allowed_mentions: allowed_mentions, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
# (see Interaction#delete_message)
|
84
|
+
def delete_message(message)
|
85
|
+
@interaction.delete_message(message)
|
86
|
+
end
|
87
|
+
|
88
|
+
# (see Interaction#defer_update)
|
89
|
+
def defer_update
|
90
|
+
@interaction.defer_update
|
91
|
+
end
|
92
|
+
|
93
|
+
# (see Interaction#get_component)
|
94
|
+
def get_component(custom_id)
|
95
|
+
@interaction.get_component(custom_id)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Event handler for INTERACTION_CREATE events.
|
100
|
+
class InteractionCreateEventHandler < EventHandler
|
101
|
+
# @!visibility private
|
102
|
+
def matches?(event)
|
103
|
+
return false unless event.is_a? InteractionCreateEvent
|
104
|
+
|
105
|
+
[
|
106
|
+
matches_all(@attributes[:type], event.type) do |a, e|
|
107
|
+
a == case a
|
108
|
+
when String, Symbol
|
109
|
+
Discordrb::Interactions::TYPES[e.to_sym]
|
110
|
+
else
|
111
|
+
e
|
112
|
+
end
|
113
|
+
end,
|
114
|
+
|
115
|
+
matches_all(@attributes[:server], event.interaction) do |a, e|
|
116
|
+
a.resolve_id == e.server_id
|
117
|
+
end,
|
118
|
+
|
119
|
+
matches_all(@attributes[:channel], event.interaction) do |a, e|
|
120
|
+
a.resolve_id == e.channel_id
|
121
|
+
end,
|
122
|
+
|
123
|
+
matches_all(@attributes[:user], event.user) do |a, e|
|
124
|
+
a.resolve_id == e.id
|
125
|
+
end
|
126
|
+
].reduce(true, &:&)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Event for ApplicationCommand interactions.
|
131
|
+
class ApplicationCommandEvent < InteractionCreateEvent
|
132
|
+
# Struct to allow accessing data via [] or methods.
|
133
|
+
Resolved = Struct.new('Resolved', :channels, :members, :messages, :roles, :users, :attachments) # rubocop:disable Lint/StructNewOverride
|
134
|
+
|
135
|
+
# @return [String] The name of the command.
|
136
|
+
attr_reader :command_name
|
137
|
+
|
138
|
+
# @return [Integer] The ID of the command.
|
139
|
+
attr_reader :command_id
|
140
|
+
|
141
|
+
# @return [String, nil] The name of the subcommand group relevant to this event.
|
142
|
+
attr_reader :subcommand_group
|
143
|
+
|
144
|
+
# @return [String, nil] The name of the subcommand relevant to this event.
|
145
|
+
attr_reader :subcommand
|
146
|
+
|
147
|
+
# @return [Resolved]
|
148
|
+
attr_reader :resolved
|
149
|
+
|
150
|
+
# @return [Hash<Symbol, Object>] Arguments provided to the command, mapped as `Name => Value`.
|
151
|
+
attr_reader :options
|
152
|
+
|
153
|
+
# @return [Integer, nil] The target of this command when it is a context command.
|
154
|
+
attr_reader :target_id
|
155
|
+
|
156
|
+
def initialize(data, bot)
|
157
|
+
super
|
158
|
+
|
159
|
+
command_data = data['data']
|
160
|
+
|
161
|
+
@command_id = command_data['id']
|
162
|
+
@command_name = command_data['name'].to_sym
|
163
|
+
|
164
|
+
@target_id = command_data['target_id']&.to_i
|
165
|
+
@resolved = Resolved.new({}, {}, {}, {}, {}, {})
|
166
|
+
process_resolved(command_data['resolved']) if command_data['resolved']
|
167
|
+
|
168
|
+
options = command_data['options'] || []
|
169
|
+
|
170
|
+
if options.empty?
|
171
|
+
@options = {}
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
case options[0]['type']
|
176
|
+
when 2
|
177
|
+
options = options[0]
|
178
|
+
@subcommand_group = options['name'].to_sym
|
179
|
+
@subcommand = options['options'][0]['name'].to_sym
|
180
|
+
options = options['options'][0]['options']
|
181
|
+
when 1
|
182
|
+
options = options[0]
|
183
|
+
@subcommand = options['name'].to_sym
|
184
|
+
options = options['options']
|
185
|
+
end
|
186
|
+
|
187
|
+
@options = transform_options_hash(options || {})
|
188
|
+
end
|
189
|
+
|
190
|
+
# @return [Message, User, nil] The target of this command, for context commands.
|
191
|
+
def target
|
192
|
+
return nil unless @target_id
|
193
|
+
|
194
|
+
@resolved.find { |data| data.key?(@target_id) }[@target_id]
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def process_resolved(resolved_data)
|
200
|
+
resolved_data['users']&.each do |id, data|
|
201
|
+
@resolved[:users][id.to_i] = @bot.ensure_user(data)
|
202
|
+
end
|
203
|
+
|
204
|
+
resolved_data['roles']&.each do |id, data|
|
205
|
+
@resolved[:roles][id.to_i] = Discordrb::Role.new(data, @bot)
|
206
|
+
end
|
207
|
+
|
208
|
+
resolved_data['channels']&.each do |id, data|
|
209
|
+
data['guild_id'] = @interaction.server_id
|
210
|
+
@resolved[:channels][id.to_i] = Discordrb::Channel.new(data, @bot)
|
211
|
+
end
|
212
|
+
|
213
|
+
resolved_data['members']&.each do |id, data|
|
214
|
+
data['user'] = resolved_data['users'][id]
|
215
|
+
data['guild_id'] = @interaction.server_id
|
216
|
+
@resolved[:members][id.to_i] = Discordrb::Member.new(data, nil, @bot)
|
217
|
+
end
|
218
|
+
|
219
|
+
resolved_data['messages']&.each do |id, data|
|
220
|
+
@resolved[:messages][id.to_i] = Discordrb::Message.new(data, @bot)
|
221
|
+
end
|
222
|
+
|
223
|
+
resolved_data['attachments']&.each do |id, data|
|
224
|
+
@resolved[:attachments][id.to_i] = Discordrb::Attachment.new(data, nil, @bot)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def transform_options_hash(hash)
|
229
|
+
hash.to_h { |opt| [opt['name'], opt['options'] || opt['value']] }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Event handler for ApplicationCommandEvents.
|
234
|
+
class ApplicationCommandEventHandler < EventHandler
|
235
|
+
# @return [Hash]
|
236
|
+
attr_reader :subcommands
|
237
|
+
|
238
|
+
# @!visibility private
|
239
|
+
def initialize(attributes, block)
|
240
|
+
super
|
241
|
+
|
242
|
+
@subcommands = {}
|
243
|
+
end
|
244
|
+
|
245
|
+
# @param name [Symbol, String]
|
246
|
+
# @yieldparam [SubcommandBuilder]
|
247
|
+
# @return [ApplicationCommandEventHandler]
|
248
|
+
def group(name)
|
249
|
+
raise ArgumentError, 'Unable to mix subcommands and groups' if @subcommands.any? { |_, v| v.is_a? Proc }
|
250
|
+
|
251
|
+
builder = SubcommandBuilder.new(name)
|
252
|
+
yield builder
|
253
|
+
|
254
|
+
@subcommands.merge!(builder.to_h)
|
255
|
+
self
|
256
|
+
end
|
257
|
+
|
258
|
+
# @param name [String, Symbol]
|
259
|
+
# @yieldparam [SubcommandBuilder]
|
260
|
+
# @return [ApplicationCommandEventHandler]
|
261
|
+
def subcommand(name, &block)
|
262
|
+
raise ArgumentError, 'Unable to mix subcommands and groups' if @subcommands.any? { |_, v| v.is_a? Hash }
|
263
|
+
|
264
|
+
@subcommands[name.to_sym] = block
|
265
|
+
|
266
|
+
self
|
267
|
+
end
|
268
|
+
|
269
|
+
# @!visibility private
|
270
|
+
# @param event [Event]
|
271
|
+
def call(event)
|
272
|
+
return unless matches?(event)
|
273
|
+
|
274
|
+
if event.subcommand_group
|
275
|
+
unless (cmd = @subcommands.dig(event.subcommand_group, event.subcommand))
|
276
|
+
Discordrb::LOGGER.debug("Received an event for an unhandled subcommand `#{event.command_name} #{event.subcommand_group} #{event.subcommand}'")
|
277
|
+
return
|
278
|
+
end
|
279
|
+
|
280
|
+
cmd.call(event)
|
281
|
+
elsif event.subcommand
|
282
|
+
unless (cmd = @subcommands[event.subcommand])
|
283
|
+
Discordrb::LOGGER.debug("Received an event for an unhandled subcommand `#{event.command_name} #{event.subcommand}'")
|
284
|
+
return
|
285
|
+
end
|
286
|
+
|
287
|
+
cmd.call(event)
|
288
|
+
else
|
289
|
+
@block.call(event)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# @!visibility private
|
294
|
+
def matches?(event)
|
295
|
+
return false unless event.is_a? ApplicationCommandEvent
|
296
|
+
|
297
|
+
[
|
298
|
+
matches_all(@attributes[:name], event.command_name) do |a, e|
|
299
|
+
a.to_sym == e.to_sym
|
300
|
+
end
|
301
|
+
].reduce(true, &:&)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Builder for adding subcommands to an ApplicationCommandHandler
|
306
|
+
class SubcommandBuilder
|
307
|
+
# @!visibility private
|
308
|
+
# @param group [String, Symbol, nil]
|
309
|
+
def initialize(group = nil)
|
310
|
+
@group = group&.to_sym
|
311
|
+
@subcommands = {}
|
312
|
+
end
|
313
|
+
|
314
|
+
# @param name [Symbol, String]
|
315
|
+
# @yieldparam [ApplicationCommandEvent]
|
316
|
+
def subcommand(name, &block)
|
317
|
+
@subcommands[name.to_sym] = block
|
318
|
+
end
|
319
|
+
|
320
|
+
# @!visibility private
|
321
|
+
def to_h
|
322
|
+
@group ? { @group => @subcommands } : @subcommands
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# An event for when a user interacts with a component.
|
327
|
+
class ComponentEvent < InteractionCreateEvent
|
328
|
+
# @return [String] User provided data for this button.
|
329
|
+
attr_reader :custom_id
|
330
|
+
|
331
|
+
# @return [Interactions::Message, nil] The message the button originates from.
|
332
|
+
attr_reader :message
|
333
|
+
|
334
|
+
# @!visibility private
|
335
|
+
def initialize(data, bot)
|
336
|
+
super
|
337
|
+
|
338
|
+
@message = Discordrb::Interactions::Message.new(data['message'], bot, @interaction) if data['message']
|
339
|
+
@custom_id = data['data']['custom_id']
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Generic handler for component events.
|
344
|
+
class ComponentEventHandler < InteractionCreateEventHandler
|
345
|
+
def matches?(event)
|
346
|
+
return false unless super
|
347
|
+
return false unless event.is_a? ComponentEvent
|
348
|
+
|
349
|
+
[
|
350
|
+
matches_all(@attributes[:custom_id], event.custom_id) do |a, e|
|
351
|
+
# Match regexp and strings
|
352
|
+
case a
|
353
|
+
when Regexp
|
354
|
+
a.match?(e)
|
355
|
+
else
|
356
|
+
a == e
|
357
|
+
end
|
358
|
+
end,
|
359
|
+
matches_all(@attributes[:message], event.message) do |a, e|
|
360
|
+
case a
|
361
|
+
when String, Integer
|
362
|
+
a.resolve_id == e.id
|
363
|
+
else
|
364
|
+
a.id == e.id
|
365
|
+
end
|
366
|
+
end
|
367
|
+
].reduce(&:&)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# An event for when a user interacts with a button component.
|
372
|
+
class ButtonEvent < ComponentEvent
|
373
|
+
end
|
374
|
+
|
375
|
+
# Event handler for a Button interaction event.
|
376
|
+
class ButtonEventHandler < ComponentEventHandler
|
377
|
+
end
|
378
|
+
|
379
|
+
# Event for when a user interacts with a select string component.
|
380
|
+
class StringSelectEvent < ComponentEvent
|
381
|
+
# @return [Array<String>] Selected values.
|
382
|
+
attr_reader :values
|
383
|
+
|
384
|
+
# @!visibility private
|
385
|
+
def initialize(data, bot)
|
386
|
+
super
|
387
|
+
|
388
|
+
@values = data['data']['values']
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Event handler for a select string component.
|
393
|
+
class StringSelectEventHandler < ComponentEventHandler
|
394
|
+
end
|
395
|
+
|
396
|
+
# An event for when a user submits a modal.
|
397
|
+
class ModalSubmitEvent < ComponentEvent
|
398
|
+
# @return [Array<TextInputComponent>]
|
399
|
+
attr_reader :components
|
400
|
+
|
401
|
+
# Get the value of an input passed to the modal.
|
402
|
+
# @param custom_id [String] The custom ID of the component to look for.
|
403
|
+
# @return [String, nil]
|
404
|
+
def value(custom_id)
|
405
|
+
get_component(custom_id)&.value
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Event handler for a modal submission.
|
410
|
+
class ModalSubmitEventHandler < ComponentEventHandler
|
411
|
+
end
|
412
|
+
|
413
|
+
# Event for when a user interacts with a select user component.
|
414
|
+
class UserSelectEvent < ComponentEvent
|
415
|
+
# @return [Array<User>] Selected values.
|
416
|
+
attr_reader :values
|
417
|
+
|
418
|
+
# @!visibility private
|
419
|
+
def initialize(data, bot)
|
420
|
+
super
|
421
|
+
|
422
|
+
@values = data['data']['values'].map { |e| bot.user(e) }
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# Event handler for a select user component.
|
427
|
+
class UserSelectEventHandler < ComponentEventHandler
|
428
|
+
end
|
429
|
+
|
430
|
+
# Event for when a user interacts with a select role component.
|
431
|
+
class RoleSelectEvent < ComponentEvent
|
432
|
+
# @return [Array<Role>] Selected values.
|
433
|
+
attr_reader :values
|
434
|
+
|
435
|
+
# @!visibility private
|
436
|
+
def initialize(data, bot)
|
437
|
+
super
|
438
|
+
|
439
|
+
@values = data['data']['values'].map { |e| bot.server(data['guild_id']).role(e) }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# Event handler for a select role component.
|
444
|
+
class RoleSelectEventHandler < ComponentEventHandler
|
445
|
+
end
|
446
|
+
|
447
|
+
# Event for when a user interacts with a select mentionable component.
|
448
|
+
class MentionableSelectEvent < ComponentEvent
|
449
|
+
# @return [Hash<Symbol => Array<User>, Symbol => Array<Role>>] Selected values.
|
450
|
+
attr_reader :values
|
451
|
+
|
452
|
+
# @!visibility private
|
453
|
+
def initialize(data, bot)
|
454
|
+
super
|
455
|
+
|
456
|
+
users = data['data']['resolved']['users'].keys.map { |e| bot.user(e) }
|
457
|
+
roles = data['data']['resolved']['roles'] ? data['data']['resolved']['roles'].keys.map { |e| bot.server(data['guild_id']).role(e) } : []
|
458
|
+
@values = { users: users, roles: roles }
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# Event handler for a select mentionable component.
|
463
|
+
class MentionableSelectEventHandler < ComponentEventHandler
|
464
|
+
end
|
465
|
+
|
466
|
+
# Event for when a user interacts with a select channel component.
|
467
|
+
class ChannelSelectEvent < ComponentEvent
|
468
|
+
# @return [Array<Channel>] Selected values.
|
469
|
+
attr_reader :values
|
470
|
+
|
471
|
+
# @!visibility private
|
472
|
+
def initialize(data, bot)
|
473
|
+
super
|
474
|
+
|
475
|
+
@values = data['data']['values'].map { |e| bot.channel(e, bot.server(data['guild_id'])) }
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Event handler for a select channel component.
|
480
|
+
class ChannelSelectEventHandler < ComponentEventHandler
|
481
|
+
end
|
482
|
+
end
|