discordrb 3.3.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|