discorb 0.13.2 → 0.15.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -0
  3. data/.github/workflows/build_version.yml +1 -1
  4. data/.github/workflows/codeql-analysis.yml +70 -0
  5. data/.github/workflows/lint-push.yml +20 -0
  6. data/.github/workflows/lint.yml +16 -0
  7. data/.rubocop.yml +70 -0
  8. data/CODE_OF_CONDUCT.md +128 -0
  9. data/Changelog.md +34 -0
  10. data/Gemfile +7 -3
  11. data/README.md +1 -1
  12. data/Rakefile +22 -22
  13. data/discorb.gemspec +13 -1
  14. data/docs/faq.md +8 -8
  15. data/examples/commands/bookmarker.rb +2 -1
  16. data/examples/commands/hello.rb +1 -0
  17. data/examples/commands/inspect.rb +3 -2
  18. data/examples/components/authorization_button.rb +2 -1
  19. data/examples/components/select_menu.rb +2 -1
  20. data/examples/extension/main.rb +1 -0
  21. data/examples/extension/message_expander.rb +1 -0
  22. data/examples/simple/eval.rb +3 -2
  23. data/examples/simple/ping_pong.rb +1 -0
  24. data/examples/simple/rolepanel.rb +1 -0
  25. data/examples/simple/wait_for_message.rb +4 -3
  26. data/exe/discorb +8 -7
  27. data/lib/discorb/allowed_mentions.rb +64 -0
  28. data/lib/discorb/app_command/command.rb +274 -0
  29. data/lib/discorb/app_command/handler.rb +168 -0
  30. data/lib/discorb/app_command.rb +2 -404
  31. data/lib/discorb/asset.rb +3 -1
  32. data/lib/discorb/{file.rb → attachment.rb} +42 -35
  33. data/lib/discorb/audit_logs.rb +3 -3
  34. data/lib/discorb/channel.rb +65 -61
  35. data/lib/discorb/client.rb +36 -33
  36. data/lib/discorb/common.rb +29 -22
  37. data/lib/discorb/components/button.rb +106 -0
  38. data/lib/discorb/components/select_menu.rb +157 -0
  39. data/lib/discorb/components/text_input.rb +96 -0
  40. data/lib/discorb/components.rb +11 -276
  41. data/lib/discorb/dictionary.rb +13 -2
  42. data/lib/discorb/embed.rb +40 -33
  43. data/lib/discorb/emoji.rb +21 -5
  44. data/lib/discorb/emoji_table.rb +1 -1
  45. data/lib/discorb/error.rb +4 -6
  46. data/lib/discorb/event.rb +13 -11
  47. data/lib/discorb/exe/about.rb +1 -0
  48. data/lib/discorb/exe/irb.rb +4 -3
  49. data/lib/discorb/exe/new.rb +6 -7
  50. data/lib/discorb/exe/run.rb +2 -1
  51. data/lib/discorb/exe/setup.rb +8 -5
  52. data/lib/discorb/exe/show.rb +1 -0
  53. data/lib/discorb/extend.rb +19 -14
  54. data/lib/discorb/extension.rb +5 -1
  55. data/lib/discorb/gateway.rb +82 -29
  56. data/lib/discorb/guild.rb +58 -80
  57. data/lib/discorb/guild_template.rb +5 -5
  58. data/lib/discorb/http.rb +52 -170
  59. data/lib/discorb/integration.rb +32 -3
  60. data/lib/discorb/intents.rb +9 -4
  61. data/lib/discorb/interaction/autocomplete.rb +5 -4
  62. data/lib/discorb/interaction/command.rb +34 -9
  63. data/lib/discorb/interaction/components.rb +5 -2
  64. data/lib/discorb/interaction/modal.rb +33 -0
  65. data/lib/discorb/interaction/response.rb +41 -12
  66. data/lib/discorb/interaction/root.rb +1 -0
  67. data/lib/discorb/interaction.rb +2 -1
  68. data/lib/discorb/invite.rb +1 -1
  69. data/lib/discorb/log.rb +4 -3
  70. data/lib/discorb/member.rb +4 -6
  71. data/lib/discorb/message.rb +38 -241
  72. data/lib/discorb/message_meta.rb +157 -0
  73. data/lib/discorb/modules.rb +47 -23
  74. data/lib/discorb/permission.rb +2 -2
  75. data/lib/discorb/presence.rb +6 -3
  76. data/lib/discorb/rate_limit.rb +15 -21
  77. data/lib/discorb/role.rb +3 -3
  78. data/lib/discorb/sticker.rb +2 -2
  79. data/lib/discorb/user.rb +3 -3
  80. data/lib/discorb/utils/colored_puts.rb +1 -0
  81. data/lib/discorb/voice_state.rb +7 -2
  82. data/lib/discorb/webhook.rb +9 -6
  83. data/lib/discorb.rb +2 -1
  84. data/sig/discorb.rbs +5836 -6714
  85. data/template-replace/scripts/arrow.rb +1 -0
  86. data/template-replace/scripts/favicon.rb +1 -0
  87. data/template-replace/scripts/index.rb +2 -1
  88. data/template-replace/scripts/locale_ja.rb +5 -4
  89. data/template-replace/scripts/sidebar.rb +1 -0
  90. data/template-replace/scripts/version.rb +7 -10
  91. data/template-replace/scripts/yard_replace.rb +5 -4
  92. metadata +30 -5
data/lib/discorb/embed.rb CHANGED
@@ -45,45 +45,46 @@ module Discorb
45
45
  # @param [Discorb::Embed::Thumbnail, String] thumbnail The thumbnail of embed.
46
46
  #
47
47
  def initialize(title = nil, description = nil, color: nil, url: nil, timestamp: nil, author: nil,
48
- fields: nil, footer: nil, image: nil, thumbnail: nil, data: nil)
49
- if data.nil?
50
- @title = title
51
- @description = description
52
- @url = url
53
- @timestamp = timestamp
54
- @color = color
55
- @author = author
56
- @fields = fields || []
57
- @footer = footer
58
- @image = image && (image.is_a?(String) ? Image.new(image) : image)
59
- @thumbnail = thumbnail && (thumbnail.is_a?(String) ? Thumbnail.new(thumbnail) : thumbnail)
60
- @type = "rich"
61
- else
62
- @title = data[:title]
63
- @description = data[:description]
64
- @url = data[:url]
65
- @timestamp = data[:timestamp] && Time.iso8601(data[:timestamp])
66
- @type = data[:type]
67
- @color = data[:color] && Color.new(data[:color])
68
- @footer = data[:footer] && Footer.new(data[:footer][:text], icon: data[:footer][:icon_url])
69
- @author = if data[:author]
70
- Author.new(data[:author][:name], icon: data[:author][:icon_url],
71
- url: data[:author][:url])
72
- end
73
- @thumbnail = data[:thumbnail] && Thumbnail.new(data[:thumbnail])
74
- @image = data[:image] && Image.new(data[:image])
75
- @video = data[:video] && Video.new(data[:video])
76
- @provider = data[:provider] && Provider.new(data[:provider])
77
- @fields = data[:fields] ? data[:fields].map { |f| Field.new(f[:name], f[:value], inline: f[:inline]) } : []
78
- end
48
+ fields: nil, footer: nil, image: nil, thumbnail: nil)
49
+ @title = title
50
+ @description = description
51
+ @url = url
52
+ @timestamp = timestamp
53
+ @color = color
54
+ @author = author
55
+ @fields = fields || []
56
+ @footer = footer
57
+ @image = image && (image.is_a?(String) ? Image.new(image) : image)
58
+ @thumbnail = thumbnail && (thumbnail.is_a?(String) ? Thumbnail.new(thumbnail) : thumbnail)
59
+ @type = "rich"
60
+ end
61
+
62
+ # @private
63
+ def initialize_hash(data)
64
+ @title = data[:title]
65
+ @description = data[:description]
66
+ @url = data[:url]
67
+ @timestamp = data[:timestamp] && Time.iso8601(data[:timestamp])
68
+ @type = data[:type]
69
+ @color = data[:color] && Color.new(data[:color])
70
+ @footer = data[:footer] && Footer.new(data[:footer][:text], icon: data[:footer][:icon_url])
71
+ @author = if data[:author]
72
+ Author.new(data[:author][:name], icon: data[:author][:icon_url],
73
+ url: data[:author][:url])
74
+ end
75
+ @thumbnail = data[:thumbnail] && Thumbnail.new(data[:thumbnail])
76
+ @image = data[:image] && Image.new(data[:image])
77
+ @video = data[:video] && Video.new(data[:video])
78
+ @provider = data[:provider] && Provider.new(data[:provider])
79
+ @fields = data[:fields] ? data[:fields].map { |f| Field.new(f[:name], f[:value], inline: f[:inline]) } : []
79
80
  end
80
81
 
81
82
  def image=(value)
82
- @image = (value.is_a?(String)) ? Image.new(value) : value
83
+ @image = value.is_a?(String) ? Image.new(value) : value
83
84
  end
84
85
 
85
86
  def thumbnail=(value)
86
- @thumbnail = (value.is_a?(String)) ? Thumbnail.new(value) : value
87
+ @thumbnail = value.is_a?(String) ? Thumbnail.new(value) : value
87
88
  end
88
89
 
89
90
  def inspect
@@ -111,6 +112,12 @@ module Discorb
111
112
  ret
112
113
  end
113
114
 
115
+ def self.from_hash(data)
116
+ inst = allocate
117
+ inst.initialize_hash(data)
118
+ inst
119
+ end
120
+
114
121
  #
115
122
  # Represents an author of embed.
116
123
  #
data/lib/discorb/emoji.rb CHANGED
@@ -96,7 +96,7 @@ module Discorb
96
96
  payload = {}
97
97
  payload[:name] = name if name != Discorb::Unset
98
98
  payload[:roles] = roles.map { |r| Discorb::Utils.try(r, :id) } if roles != Discorb::Unset
99
- @client.http.patch("/guilds/#{@guild.id}/emojis/#{@id}", payload, audit_log_reason: reason)
99
+ @client.http.request(Route.new("/guilds/#{@guild.id}/emojis/#{@id}", "//guilds/:guild_id/emojis/:emoji_id", :patch), payload, audit_log_reason: reason)
100
100
  self
101
101
  end
102
102
  end
@@ -113,7 +113,7 @@ module Discorb
113
113
  #
114
114
  def delete!(reason: nil)
115
115
  Async do
116
- @client.http.delete("/guilds/#{@guild.id}/emojis/#{@id}", audit_log_reason: reason).wait
116
+ @client.http.request(Route.new("/guilds/#{@guild.id}/emojis/#{@id}", "//guilds/:guild_id/emojis/:emoji_id", :delete), audit_log_reason: reason).wait
117
117
  @available = false
118
118
  self
119
119
  end
@@ -121,6 +121,15 @@ module Discorb
121
121
 
122
122
  alias destroy! delete!
123
123
 
124
+ # @private
125
+ def to_hash
126
+ {
127
+ name: @name,
128
+ id: @id,
129
+ animated: @animated,
130
+ }
131
+ end
132
+
124
133
  private
125
134
 
126
135
  def _set_data(data)
@@ -202,9 +211,7 @@ module Discorb
202
211
  else
203
212
  raise ArgumentError, "No such emoji: #{name}"
204
213
  end
205
- if tone > 0
206
- @value += EmojiTable::SKIN_TONES[tone]
207
- end
214
+ @value += EmojiTable::SKIN_TONES[tone] if tone.positive?
208
215
  end
209
216
 
210
217
  # @return [String] The unicode string of the emoji.
@@ -225,6 +232,15 @@ module Discorb
225
232
  "#<#{self.class} :#{@name}:>"
226
233
  end
227
234
 
235
+ # @private
236
+ def to_hash
237
+ {
238
+ name: @value,
239
+ id: nil,
240
+ animated: false,
241
+ }
242
+ end
243
+
228
244
  class << self
229
245
  alias [] new
230
246
  end
@@ -3885,7 +3885,7 @@ module Discorb
3885
3885
  "\xf0\x9f\x8f\xbc",
3886
3886
  "\xf0\x9f\x8f\xbd",
3887
3887
  "\xf0\x9f\x8f\xbe",
3888
- "\xf0\x9f\x8f\xbf",
3888
+ "\xf0\x9f\x8f\xbf"
3889
3889
  ].freeze
3890
3890
  end
3891
3891
  end
data/lib/discorb/error.rb CHANGED
@@ -11,9 +11,7 @@ module Discorb
11
11
  def enumerate_errors(hash)
12
12
  res = {}
13
13
  _recr_items([], hash, res)
14
- if res == { "" => nil }
15
- res = {}
16
- end
14
+ res = {} if res == { "" => nil }
17
15
  res
18
16
  end
19
17
 
@@ -64,7 +62,7 @@ module Discorb
64
62
  [
65
63
  data[:message] + " (#{@code})", enumerate_errors(data[:errors])
66
64
  .map { |ek, ev| "#{ek}=>#{ev}" }
67
- .join("\n"),
65
+ .join("\n")
68
66
  ].join("\n")
69
67
  )
70
68
  end
@@ -92,14 +90,14 @@ module Discorb
92
90
  # Represents a error because of a cloudflare ban.
93
91
  #
94
92
  class CloudFlareBanError < HTTPError
95
- def initialize(resp, client)
93
+ def initialize(_resp, client)
96
94
  @client = client
97
95
  @client.close!
98
96
  message = <<~MESSAGE
99
97
  The client is banned from CloudFlare.
100
98
  Hint: Try to decrease the number of requests per second, e.g. Use sleep in between requests.
101
99
  MESSAGE
102
- $stderr.puts message
100
+ warn message
103
101
  DiscorbError.instance_method(:initialize).bind(self).call(message)
104
102
  end
105
103
  end
data/lib/discorb/event.rb CHANGED
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  module Discorb
3
+ #
4
+ # Represents an event in guild.
5
+ #
2
6
  class ScheduledEvent < DiscordModel
3
7
  @privacy_level = {
4
8
  2 => :guild_only,
@@ -120,7 +124,7 @@ module Discorb
120
124
  status: Discorb::Unset
121
125
  )
122
126
  Async do
123
- payload = case (type == Discorb::Unset) ? @entity_type : type
127
+ payload = case type == Discorb::Unset ? @entity_type : type
124
128
  when :stage_instance
125
129
  raise ArgumentError, "channel must be provided for stage_instance events" unless channel
126
130
  {
@@ -164,7 +168,7 @@ module Discorb
164
168
  else
165
169
  raise ArgumentError, "Invalid scheduled event type: #{type}"
166
170
  end
167
- @client.http.patch("/guilds/#{@guild_id}/scheduled-events/#{@id}", payload).wait
171
+ @client.http.request(Route.new("/guilds/#{@guild_id}/scheduled-events/#{@id}", "//guilds/:guild_id/scheduled-events/:scheduled_event_id", :patch), payload).wait
168
172
  end
169
173
  end
170
174
 
@@ -201,7 +205,7 @@ module Discorb
201
205
  #
202
206
  def delete!
203
207
  Async do
204
- @client.http.delete("/guilds/#{@guild_id}/scheduled-events/#{@id}").wait
208
+ @client.http.request(Route.new("/guilds/#{@guild_id}/scheduled-events/#{@id}", "//guilds/:guild_id/scheduled-events/:scheduled_event_id", :delete)).wait
205
209
  end
206
210
  end
207
211
 
@@ -226,11 +230,9 @@ module Discorb
226
230
  if limit.nil?
227
231
  after = 0
228
232
  res = []
229
- while true
230
- _resp, users = @client.http.get("/guilds/#{@guild_id}/scheduled-events/#{@id}/users?limit=100&after=#{after}&with_member=true").wait
231
- if users.empty?
232
- break
233
- end
233
+ loop do
234
+ _resp, users = @client.http.request(Route.new("/guilds/#{@guild_id}/scheduled-events/#{@id}/users?limit=100&after=#{after}&with_member=true", "//guilds/:guild_id/scheduled-events/:scheduled_event_id/users", :get)).wait
235
+ break if users.empty?
234
236
  res += users.map { |u| Member.new(@client, @guild_id, u[:user], u[:member]) }
235
237
  after = users.last[:user][:id]
236
238
  end
@@ -238,11 +240,11 @@ module Discorb
238
240
  else
239
241
  params = {
240
242
  limit: limit,
241
- before: Discorb::Utils.try(after, :id),
242
- after: Discorb::Utils.try(around, :id),
243
+ before: Discorb::Utils.try(before, :id),
244
+ after: Discorb::Utils.try(after, :id),
243
245
  with_member: with_member,
244
246
  }.filter { |_k, v| !v.nil? }.to_h
245
- _resp, messages = @client.http.get("/channels/#{channel_id.wait}/messages?#{URI.encode_www_form(params)}").wait
247
+ _resp, messages = @client.http.request(Route.new("/channels/#{channel_id.wait}/messages?#{URI.encode_www_form(params)}", "//channels/:channel_id/messages", :get)).wait
246
248
  messages.map { |m| Message.new(@client, m.merge({ guild_id: @guild_id.to_s })) }
247
249
  end
248
250
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # description: Show information of discorb.
2
3
 
3
4
  puts " disco\e[31mrb\e[m - A new Discord API wrapper in Ruby. \n\n"
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # description: Connect to discord and start IRB.
4
5
 
@@ -31,13 +32,13 @@ client.on :standby do
31
32
  end
32
33
 
33
34
  def dirb_help
34
- puts <<~EOS
35
+ puts <<~MESSAGE
35
36
  \e[96mDiscord-IRB\e[m
36
37
  This is a debug client for Discord.
37
38
  \e[90mmessage\e[m to get latest message.
38
39
 
39
40
  \e[36mhttps://discorb-lib.github.io/#{Discorb::VERSION}/file.irb.html\e[m for more information.
40
- EOS
41
+ MESSAGE
41
42
  end
42
43
 
43
44
  puts <<~FIRST_MESSAGE
@@ -56,7 +57,7 @@ end
56
57
 
57
58
  token = ENV["DISCORD_BOT_TOKEN"] || ENV["DISCORD_TOKEN"]
58
59
  if token.nil?
59
- if File.exists?(token_file)
60
+ if File.exist?(token_file)
60
61
  token = File.read(token_file)
61
62
  else
62
63
  print "\e[90mToken?\e[m : "
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # description: Make files for the discorb project.
2
3
 
3
4
  require "optparse"
@@ -131,7 +132,7 @@ FILES = {
131
132
  https://rubygems.org/gems/license-cli may be useful.
132
133
 
133
134
  MARKDOWN
134
- }
135
+ }.freeze
135
136
 
136
137
  # @private
137
138
  def create_file(name)
@@ -162,8 +163,8 @@ def git_init
162
163
  system "git init"
163
164
  system "git add ."
164
165
  system "git commit -m \"Initial commit\""
165
- sputs "Initialized repository, use " +
166
- "\e[32mgit commit --amend -m '...'\e[92m" +
166
+ sputs "Initialized repository, use " \
167
+ "\e[32mgit commit --amend -m '...'\e[92m" \
167
168
  " to change commit message of initial commit.\n"
168
169
  end
169
170
 
@@ -225,13 +226,11 @@ if (dir = ARGV[0])
225
226
  if Dir.exist?($path)
226
227
  if Dir.empty?($path)
227
228
  iputs "Found \e[30m#{dir}\e[90m and empty, using this directory."
228
- else
229
- if $values[:force]
230
- iputs "Found \e[30m#{dir}\e[90m and not empty, but force is on, using this directory."
229
+ elsif $values[:force]
230
+ iputs "Found \e[30m#{dir}\e[90m and not empty, but force is on, using this directory."
231
231
  else
232
232
  eputs "Directory \e[31m#{dir}\e[91m already exists and not empty. Use \e[31m-f\e[91m to force."
233
233
  exit
234
- end
235
234
  end
236
235
  else
237
236
  Dir.mkdir($path)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # description: Run a client.
2
3
  require "optparse"
3
4
  require "json"
@@ -6,7 +7,7 @@ require "io/console"
6
7
 
7
8
  ARGV.delete_at 0
8
9
  # @private
9
- LOG_LEVELS = %w[none debug info warn error fatal]
10
+ LOG_LEVELS = %w[none debug info warn error fatal].freeze
10
11
 
11
12
  opt = OptionParser.new <<~BANNER
12
13
  This command will run a client.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # description: Setup application commands.
2
3
  require "optparse"
3
4
  require "discorb/utils/colored_puts"
@@ -6,6 +7,7 @@ ARGV.delete_at 0
6
7
 
7
8
  options = {
8
9
  guilds: nil,
10
+ script: true,
9
11
  }
10
12
 
11
13
  opt = OptionParser.new <<~BANNER
@@ -16,20 +18,21 @@ opt = OptionParser.new <<~BANNER
16
18
  script The script to setup.
17
19
  BANNER
18
20
  opt.on("-g", "--guild ID", Array, "The guild ID to setup, use comma for setup commands in multiple guilds, or `global` for setup global commands.") { |v| options[:guilds] = v }
21
+ opt.on("-s", "--[no-]script", "Whether to run `:setup` event. This may be useful if setup script includes operation that shouldn't run twice. Default to true.") { |v| options[:script] = v }
19
22
  opt.parse!(ARGV)
20
23
 
21
24
  script = ARGV[0]
22
25
  script ||= "main.rb"
23
26
  ENV["DISCORB_CLI_FLAG"] = "setup"
24
27
 
25
- if options[:guilds] == ["global"]
26
- ENV["DISCORB_SETUP_GUILDS"] = "global"
28
+ ENV["DISCORB_SETUP_GUILDS"] = if options[:guilds] == ["global"]
29
+ "global"
27
30
  elsif options[:guilds]
28
- ENV["DISCORB_SETUP_GUILDS"] = options[:guilds].join(",")
29
- else
30
- ENV["DISCORB_SETUP_GUILDS"] = nil
31
+ options[:guilds].join(",")
31
32
  end
32
33
 
34
+ ENV["DISCORB_SETUP_SCRIPT"] = options[:script].to_s if options[:script]
35
+
33
36
  begin
34
37
  load script
35
38
  rescue LoadError
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # description: Show information of your environment.
2
3
 
3
4
  require "etc"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # rubocop: disable Style/Documentation
2
3
 
3
4
  class Time
4
5
  #
@@ -18,22 +19,26 @@ class Time
18
19
  end
19
20
 
20
21
  # @private
21
- class Async::Node
22
- def description
23
- @object_name ||= "#{self.class}:0x#{object_id.to_s(16)}#{@transient ? " transient" : nil}"
22
+ module Async
23
+ class Node
24
+ def description
25
+ @object_name ||= "#{self.class}:0x#{object_id.to_s(16)}#{@transient ? " transient" : nil}"
24
26
 
25
- if @annotation
26
- "#{@object_name} #{@annotation}"
27
- elsif line = self.backtrace(0, 1)&.first
28
- "#{@object_name} #{line}"
29
- else
30
- @object_name
27
+ if @annotation
28
+ "#{@object_name} #{@annotation}"
29
+ elsif line = self.backtrace(0, 1)&.first
30
+ "#{@object_name} #{line}"
31
+ else
32
+ @object_name
33
+ end
31
34
  end
32
- end
33
35
 
34
- def to_s
35
- "\#<#{self.description}>"
36
- end
36
+ def to_s
37
+ "\#<#{self.description}>"
38
+ end
37
39
 
38
- alias inspect to_s
40
+ alias inspect to_s
41
+ end
39
42
  end
43
+
44
+ # rubocop: enable Style/Documentation
@@ -17,7 +17,7 @@ module Discorb
17
17
  ret = {}
18
18
  self.class.events.each do |event, handlers|
19
19
  ret[event] = handlers.map do |handler|
20
- Discorb::EventHandler.new(Proc.new { |*args, **kwargs| instance_exec(*args, **kwargs, &handler[2]) }, handler[0], handler[1])
20
+ Discorb::EventHandler.new(proc { |*args, **kwargs| instance_exec(*args, **kwargs, &handler[2]) }, handler[0], handler[1])
21
21
  end
22
22
  end
23
23
  @events = ret
@@ -27,6 +27,10 @@ module Discorb
27
27
  base.extend(ClassMethods)
28
28
  end
29
29
 
30
+ #
31
+ # @private
32
+ # Module for adding class methods to the extension class.
33
+ #
30
34
  module ClassMethods
31
35
  include Discorb::ApplicationCommand::Handler
32
36
  undef setup_commands