discorb 0.19.0 → 0.20.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build_version.yml +2 -2
  3. data/.rubocop.yml +12 -75
  4. data/Changelog.md +10 -0
  5. data/Rakefile +482 -454
  6. data/lib/discorb/allowed_mentions.rb +68 -72
  7. data/lib/discorb/app_command/command.rb +466 -398
  8. data/lib/discorb/app_command/common.rb +65 -25
  9. data/lib/discorb/app_command/handler.rb +304 -266
  10. data/lib/discorb/app_command.rb +5 -5
  11. data/lib/discorb/application.rb +198 -197
  12. data/lib/discorb/asset.rb +101 -101
  13. data/lib/discorb/attachment.rb +134 -119
  14. data/lib/discorb/audit_logs.rb +412 -385
  15. data/lib/discorb/automod.rb +279 -269
  16. data/lib/discorb/channel/base.rb +107 -108
  17. data/lib/discorb/channel/category.rb +32 -32
  18. data/lib/discorb/channel/container.rb +44 -44
  19. data/lib/discorb/channel/dm.rb +26 -28
  20. data/lib/discorb/channel/guild.rb +311 -246
  21. data/lib/discorb/channel/stage.rb +156 -140
  22. data/lib/discorb/channel/text.rb +430 -336
  23. data/lib/discorb/channel/thread.rb +374 -325
  24. data/lib/discorb/channel/voice.rb +85 -79
  25. data/lib/discorb/channel.rb +5 -5
  26. data/lib/discorb/client.rb +635 -621
  27. data/lib/discorb/color.rb +178 -182
  28. data/lib/discorb/common.rb +168 -164
  29. data/lib/discorb/components/button.rb +107 -106
  30. data/lib/discorb/components/select_menu.rb +157 -145
  31. data/lib/discorb/components/text_input.rb +103 -106
  32. data/lib/discorb/components.rb +68 -66
  33. data/lib/discorb/dictionary.rb +135 -135
  34. data/lib/discorb/embed.rb +404 -398
  35. data/lib/discorb/emoji.rb +309 -302
  36. data/lib/discorb/emoji_table.rb +16099 -8857
  37. data/lib/discorb/error.rb +131 -131
  38. data/lib/discorb/event.rb +360 -314
  39. data/lib/discorb/event_handler.rb +39 -39
  40. data/lib/discorb/exe/about.rb +17 -17
  41. data/lib/discorb/exe/irb.rb +72 -67
  42. data/lib/discorb/exe/new.rb +323 -315
  43. data/lib/discorb/exe/run.rb +69 -68
  44. data/lib/discorb/exe/setup.rb +57 -55
  45. data/lib/discorb/exe/show.rb +12 -12
  46. data/lib/discorb/extend.rb +25 -45
  47. data/lib/discorb/extension.rb +89 -83
  48. data/lib/discorb/flag.rb +126 -128
  49. data/lib/discorb/gateway.rb +984 -804
  50. data/lib/discorb/gateway_events.rb +670 -638
  51. data/lib/discorb/gateway_requests.rb +45 -48
  52. data/lib/discorb/guild.rb +2115 -1626
  53. data/lib/discorb/guild_template.rb +280 -241
  54. data/lib/discorb/http.rb +247 -232
  55. data/lib/discorb/image.rb +42 -42
  56. data/lib/discorb/integration.rb +169 -161
  57. data/lib/discorb/intents.rb +161 -163
  58. data/lib/discorb/interaction/autocomplete.rb +76 -62
  59. data/lib/discorb/interaction/command.rb +279 -224
  60. data/lib/discorb/interaction/components.rb +114 -104
  61. data/lib/discorb/interaction/modal.rb +36 -32
  62. data/lib/discorb/interaction/response.rb +379 -336
  63. data/lib/discorb/interaction/root.rb +271 -257
  64. data/lib/discorb/interaction.rb +5 -5
  65. data/lib/discorb/invite.rb +154 -153
  66. data/lib/discorb/member.rb +344 -311
  67. data/lib/discorb/message.rb +615 -544
  68. data/lib/discorb/message_meta.rb +197 -186
  69. data/lib/discorb/modules.rb +371 -290
  70. data/lib/discorb/permission.rb +305 -291
  71. data/lib/discorb/presence.rb +352 -346
  72. data/lib/discorb/rate_limit.rb +81 -76
  73. data/lib/discorb/reaction.rb +55 -54
  74. data/lib/discorb/role.rb +272 -240
  75. data/lib/discorb/shard.rb +76 -74
  76. data/lib/discorb/sticker.rb +193 -171
  77. data/lib/discorb/user.rb +205 -188
  78. data/lib/discorb/utils/colored_puts.rb +16 -16
  79. data/lib/discorb/utils.rb +12 -16
  80. data/lib/discorb/voice_state.rb +305 -281
  81. data/lib/discorb/webhook.rb +537 -507
  82. data/lib/discorb.rb +62 -56
  83. data/sig/discorb/application.rbs +2 -0
  84. data/sig/discorb/automod.rbs +10 -1
  85. data/sig/discorb/guild.rbs +2 -0
  86. data/sig/discorb/message.rbs +2 -0
  87. data/sig/discorb/user.rbs +22 -20
  88. metadata +2 -2
data/lib/discorb/http.rb CHANGED
@@ -1,232 +1,247 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/https"
4
-
5
- module Discorb
6
- #
7
- # A class to handle http requests.
8
- # @private
9
- #
10
- class HTTP
11
- @nil_body = nil
12
-
13
- #
14
- # Initializes the http client.
15
- # @private
16
- #
17
- # @param [Discorb::Client] client The client.
18
- #
19
- def initialize(client)
20
- @client = client
21
- @ratelimit_handler = RatelimitHandler.new(client)
22
- end
23
-
24
- #
25
- # Execute a request.
26
- # @async
27
- #
28
- # @param [Discorb::Route] path The path to the resource.
29
- # @param [String, Hash] body The body of the request. Defaults to an empty string.
30
- # @param [String] audit_log_reason The audit log reason to send with the request.
31
- # @param [Hash] kwargs The keyword arguments.
32
- #
33
- # @return [Async::Task<Array(Net::HTTPResponse, Hash)>] The response and as JSON.
34
- # @return [Async::Task<Array(Net::HTTPResponse, nil)>] The response was 204.
35
- #
36
- # @raise [Discorb::HTTPError] The request was failed.
37
- #
38
- def request(path, body = "", audit_log_reason: nil, **kwargs)
39
- Async do |_task|
40
- @ratelimit_handler.wait(path)
41
- resp = if %i[post patch put].include? path.method
42
- http.send(
43
- path.method,
44
- get_path(path),
45
- get_body(body),
46
- get_headers(body, audit_log_reason),
47
- **kwargs,
48
- )
49
- else
50
- http.send(
51
- path.method,
52
- get_path(path),
53
- get_headers(body, audit_log_reason),
54
- **kwargs,
55
- )
56
- end
57
- data = get_response_data(resp)
58
- @ratelimit_handler.save(path, resp)
59
- handle_response(resp, data, path, body, nil, audit_log_reason, kwargs)
60
- end
61
- end
62
-
63
- #
64
- # Execute a multipart request.
65
- # @async
66
- #
67
- # @param [Discorb::Route] path The path to the resource.
68
- # @param [String, Hash] body The body of the request.
69
- # @param [Array<Discorb::Attachment>] files The files to upload.
70
- # @param [String] audit_log_reason The audit log reason to send with the request.
71
- # @param [Hash] kwargs The keyword arguments.
72
- #
73
- # @return [Async::Task<Array(Net::HTTPResponse, Hash)>] The response and as JSON.
74
- # @return [Async::Task<Array(Net::HTTPResponse, nil)>] The response was 204.
75
- #
76
- # @raise [Discorb::HTTPError] The request was failed.
77
- #
78
- def multipart_request(path, body, files, audit_log_reason: nil, **kwargs)
79
- Async do |_task|
80
- @ratelimit_handler.wait(path)
81
- req = Net::HTTP.const_get(path.method.to_s.capitalize).new(
82
- get_path(path),
83
- get_headers(body, audit_log_reason),
84
- **kwargs,
85
- )
86
- # @type var data: Array[untyped]
87
- data = [
88
- ["payload_json", get_body(body)],
89
- ]
90
- files&.each_with_index do |file, i|
91
- next if file.nil?
92
-
93
- if file.created_by == :discord
94
- request_io = StringIO.new(
95
- cdn_http.get(
96
- (URI.parse(file.url).path || raise("Could not parse url")),
97
- {
98
- "Content-Type" => nil,
99
- "User-Agent" => Discorb::USER_AGENT,
100
- }
101
- ).body
102
- )
103
- data << ["files[#{i}]", request_io, { filename: file.filename, content_type: file.content_type }]
104
- else
105
- data << ["files[#{i}]", file.io, { filename: file.filename, content_type: file.content_type }]
106
- end
107
- end
108
- req.set_form(data, "multipart/form-data")
109
- session = Net::HTTP.new("discord.com", 443)
110
- session.use_ssl = true
111
- resp = session.request(req)
112
- resp_data = get_response_data(resp)
113
- @ratelimit_handler.save(path, resp)
114
- response = handle_response(resp, resp_data, path, body, files, audit_log_reason, kwargs)
115
- files&.then { _1.filter(&:will_close).each { |f| f.io.close } }
116
- response
117
- end
118
- end
119
-
120
- def inspect
121
- "#<#{self.class} client=#{@client}>"
122
- end
123
-
124
- private
125
-
126
- def handle_response(resp, data, path, body, files, audit_log_reason, kwargs)
127
- case resp.code
128
- when "429"
129
- @client.logger.info("Rate limit exceeded for #{path.method} #{path.url}, waiting #{data[:retry_after]} seconds")
130
- sleep(data[:retry_after])
131
- if files
132
- multipart_request(path, body, files, audit_log_reason: audit_log_reason, **kwargs).wait
133
- else
134
- request(path, body, audit_log_reason: audit_log_reason, **kwargs).wait
135
- end
136
- when "400"
137
- raise BadRequestError.new(resp, data)
138
- when "401"
139
- raise UnauthorizedError.new(resp, data)
140
- when "403"
141
- raise ForbiddenError.new(resp, data)
142
- when "404"
143
- raise NotFoundError.new(resp, data)
144
- else
145
- [resp, data]
146
- end
147
- end
148
-
149
- def get_headers(body = "", audit_log_reason = nil)
150
- ret = if body.nil? || body == ""
151
- {
152
- "User-Agent" => USER_AGENT,
153
- "authorization" => "Bot #{@client.token}",
154
- }
155
- else
156
- {
157
- "User-Agent" => USER_AGENT,
158
- "authorization" => "Bot #{@client.token}",
159
- "content-type" => "application/json",
160
- }
161
- end
162
- ret["X-Audit-Log-Reason"] = audit_log_reason unless audit_log_reason.nil?
163
- ret
164
- end
165
-
166
- def get_body(body)
167
- if body.nil?
168
- ""
169
- elsif body.is_a?(String)
170
- body
171
- else
172
- recr_utf8(body).to_json
173
- end
174
- end
175
-
176
- def get_path(path)
177
- full_path = if path.url.start_with?("https://")
178
- path.url
179
- else
180
- API_BASE_URL + path.url
181
- end
182
- uri = URI(full_path)
183
- full_path.sub(uri.scheme + "://" + uri.host, "")
184
- end
185
-
186
- def get_response_data(resp)
187
- begin
188
- data = JSON.parse(resp.body, symbolize_names: true)
189
- rescue JSON::ParserError, TypeError
190
- data = if resp.body.nil? || resp.body.empty?
191
- {}
192
- else
193
- resp.body
194
- end
195
- end
196
- raise CloudFlareBanError.new(resp, @client) if resp["Via"].nil? && resp.code == "429" && data.is_a?(String)
197
-
198
- data
199
- end
200
-
201
- def http
202
- https = Net::HTTP.new("discord.com", 443)
203
- https.use_ssl = true
204
- https
205
- end
206
-
207
- def cdn_http
208
- https = Net::HTTP.new("cdn.discordapp.com", 443)
209
- https.use_ssl = true
210
- https
211
- end
212
-
213
- def recr_utf8(data)
214
- case data
215
- when Hash
216
- data.each do |k, v|
217
- data[k] = recr_utf8(v)
218
- end
219
- data
220
- when Array
221
- data.each_index do |i|
222
- data[i] = recr_utf8(data[i])
223
- end
224
- data
225
- when String
226
- data.dup.force_encoding(Encoding::UTF_8)
227
- else
228
- data
229
- end
230
- end
231
- end
232
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "net/https"
4
+
5
+ module Discorb
6
+ #
7
+ # A class to handle http requests.
8
+ # @private
9
+ #
10
+ class HTTP
11
+ @nil_body = nil
12
+
13
+ #
14
+ # Initializes the http client.
15
+ # @private
16
+ #
17
+ # @param [Discorb::Client] client The client.
18
+ #
19
+ def initialize(client)
20
+ @client = client
21
+ @ratelimit_handler = RatelimitHandler.new(client)
22
+ end
23
+
24
+ #
25
+ # Execute a request.
26
+ # @async
27
+ #
28
+ # @param [Discorb::Route] path The path to the resource.
29
+ # @param [String, Hash] body The body of the request. Defaults to an empty string.
30
+ # @param [String] audit_log_reason The audit log reason to send with the request.
31
+ # @param [Hash] kwargs The keyword arguments.
32
+ #
33
+ # @return [Async::Task<Array(Net::HTTPResponse, Hash)>] The response and as JSON.
34
+ # @return [Async::Task<Array(Net::HTTPResponse, nil)>] The response was 204.
35
+ #
36
+ # @raise [Discorb::HTTPError] The request was failed.
37
+ #
38
+ def request(path, body = "", audit_log_reason: nil, **kwargs)
39
+ Async do |_task|
40
+ @ratelimit_handler.wait(path)
41
+ resp =
42
+ if %i[post patch put].include? path.method
43
+ http.send(
44
+ path.method,
45
+ get_path(path),
46
+ get_body(body),
47
+ get_headers(body, audit_log_reason),
48
+ **kwargs
49
+ )
50
+ else
51
+ http.send(
52
+ path.method,
53
+ get_path(path),
54
+ get_headers(body, audit_log_reason),
55
+ **kwargs
56
+ )
57
+ end
58
+ data = get_response_data(resp)
59
+ @ratelimit_handler.save(path, resp)
60
+ handle_response(resp, data, path, body, nil, audit_log_reason, kwargs)
61
+ end
62
+ end
63
+
64
+ #
65
+ # Execute a multipart request.
66
+ # @async
67
+ #
68
+ # @param [Discorb::Route] path The path to the resource.
69
+ # @param [String, Hash] body The body of the request.
70
+ # @param [Array<Discorb::Attachment>] files The files to upload.
71
+ # @param [String] audit_log_reason The audit log reason to send with the request.
72
+ # @param [Hash] kwargs The keyword arguments.
73
+ #
74
+ # @return [Async::Task<Array(Net::HTTPResponse, Hash)>] The response and as JSON.
75
+ # @return [Async::Task<Array(Net::HTTPResponse, nil)>] The response was 204.
76
+ #
77
+ # @raise [Discorb::HTTPError] The request was failed.
78
+ #
79
+ def multipart_request(path, body, files, audit_log_reason: nil, **kwargs)
80
+ Async do |_task|
81
+ @ratelimit_handler.wait(path)
82
+ req =
83
+ Net::HTTP.const_get(path.method.to_s.capitalize).new(
84
+ get_path(path),
85
+ get_headers(body, audit_log_reason),
86
+ **kwargs
87
+ )
88
+ # @type var data: Array[untyped]
89
+ data = [["payload_json", get_body(body)]]
90
+ files&.each_with_index do |file, i|
91
+ next if file.nil?
92
+
93
+ if file.created_by == :discord
94
+ request_io =
95
+ StringIO.new(
96
+ cdn_http.get(
97
+ (URI.parse(file.url).path || raise("Could not parse url")),
98
+ { "Content-Type" => nil, "User-Agent" => Discorb::USER_AGENT }
99
+ ).body
100
+ )
101
+ data << [
102
+ "files[#{i}]",
103
+ request_io,
104
+ { filename: file.filename, content_type: file.content_type }
105
+ ]
106
+ else
107
+ data << [
108
+ "files[#{i}]",
109
+ file.io,
110
+ { filename: file.filename, content_type: file.content_type }
111
+ ]
112
+ end
113
+ end
114
+ req.set_form(data, "multipart/form-data")
115
+ session = Net::HTTP.new("discord.com", 443)
116
+ session.use_ssl = true
117
+ resp = session.request(req)
118
+ resp_data = get_response_data(resp)
119
+ @ratelimit_handler.save(path, resp)
120
+ response =
121
+ handle_response(
122
+ resp,
123
+ resp_data,
124
+ path,
125
+ body,
126
+ files,
127
+ audit_log_reason,
128
+ kwargs
129
+ )
130
+ files&.then { _1.filter(&:will_close).each { |f| f.io.close } }
131
+ response
132
+ end
133
+ end
134
+
135
+ def inspect
136
+ "#<#{self.class} client=#{@client}>"
137
+ end
138
+
139
+ private
140
+
141
+ def handle_response(resp, data, path, body, files, audit_log_reason, kwargs)
142
+ case resp.code
143
+ when "429"
144
+ @client.logger.info(
145
+ "Rate limit exceeded for #{path.method} #{path.url}, waiting #{data[:retry_after]} seconds"
146
+ )
147
+ sleep(data[:retry_after])
148
+ if files
149
+ multipart_request(
150
+ path,
151
+ body,
152
+ files,
153
+ audit_log_reason: audit_log_reason,
154
+ **kwargs
155
+ ).wait
156
+ else
157
+ request(path, body, audit_log_reason: audit_log_reason, **kwargs).wait
158
+ end
159
+ when "400"
160
+ raise BadRequestError.new(resp, data)
161
+ when "401"
162
+ raise UnauthorizedError.new(resp, data)
163
+ when "403"
164
+ raise ForbiddenError.new(resp, data)
165
+ when "404"
166
+ raise NotFoundError.new(resp, data)
167
+ else
168
+ [resp, data]
169
+ end
170
+ end
171
+
172
+ def get_headers(body = "", audit_log_reason = nil)
173
+ ret =
174
+ if body.nil? || body == ""
175
+ {
176
+ "User-Agent" => USER_AGENT,
177
+ "authorization" => "Bot #{@client.token}"
178
+ }
179
+ else
180
+ {
181
+ "User-Agent" => USER_AGENT,
182
+ "authorization" => "Bot #{@client.token}",
183
+ "content-type" => "application/json"
184
+ }
185
+ end
186
+ ret["X-Audit-Log-Reason"] = audit_log_reason unless audit_log_reason.nil?
187
+ ret
188
+ end
189
+
190
+ def get_body(body)
191
+ if body.nil?
192
+ ""
193
+ elsif body.is_a?(String)
194
+ body
195
+ else
196
+ recr_utf8(body).to_json
197
+ end
198
+ end
199
+
200
+ def get_path(path)
201
+ full_path =
202
+ (path.url.start_with?("https://") ? path.url : API_BASE_URL + path.url)
203
+ uri = URI(full_path)
204
+ full_path.sub("#{uri.scheme}://#{uri.host}", "")
205
+ end
206
+
207
+ def get_response_data(resp)
208
+ begin
209
+ data = JSON.parse(resp.body, symbolize_names: true)
210
+ rescue JSON::ParserError, TypeError
211
+ data = (resp.body.nil? || resp.body.empty? ? {} : resp.body)
212
+ end
213
+ if resp["Via"].nil? && resp.code == "429" && data.is_a?(String)
214
+ raise CloudFlareBanError.new(resp, @client)
215
+ end
216
+
217
+ data
218
+ end
219
+
220
+ def http
221
+ https = Net::HTTP.new("discord.com", 443)
222
+ https.use_ssl = true
223
+ https
224
+ end
225
+
226
+ def cdn_http
227
+ https = Net::HTTP.new("cdn.discordapp.com", 443)
228
+ https.use_ssl = true
229
+ https
230
+ end
231
+
232
+ def recr_utf8(data)
233
+ case data
234
+ when Hash
235
+ data.each { |k, v| data[k] = recr_utf8(v) }
236
+ data
237
+ when Array
238
+ data.each_index { |i| data[i] = recr_utf8(data[i]) }
239
+ data
240
+ when String
241
+ data.dup.force_encoding(Encoding::UTF_8)
242
+ else
243
+ data
244
+ end
245
+ end
246
+ end
247
+ end
data/lib/discorb/image.rb CHANGED
@@ -1,42 +1,42 @@
1
- # frozen_string_literal: true
2
-
3
- require "base64"
4
- require "mime/types"
5
-
6
- module Discorb
7
- #
8
- # Represents an image.
9
- #
10
- class Image
11
- #
12
- # Initializes a new Image.
13
- #
14
- # @param [#read, String] source The IO source or path of the image.
15
- # @param [String] type The MIME type of the image.
16
- #
17
- def initialize(source, type = nil)
18
- if source.respond_to?(:read)
19
- @io = source
20
- @type = type || MIME::Types.type_for(source.path).first.content_type
21
- elsif ::File.exist?(source)
22
- @io = ::File.open(source, "rb")
23
- @type = MIME::Types.type_for(source).first.to_s
24
- else
25
- raise ArgumentError, "Couldn't read file."
26
- end
27
- end
28
-
29
- #
30
- # Formats the image as a Discord style.
31
- #
32
- # @return [String] The image as a Discord style.
33
- #
34
- def to_s
35
- "data:#{@type};base64,#{Base64.strict_encode64(@io.read)}"
36
- end
37
-
38
- def inspect
39
- "#<#{self.class} #{@type}>"
40
- end
41
- end
42
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "mime/types"
5
+
6
+ module Discorb
7
+ #
8
+ # Represents an image.
9
+ #
10
+ class Image
11
+ #
12
+ # Initializes a new Image.
13
+ #
14
+ # @param [#read, String] source The IO source or path of the image.
15
+ # @param [String] type The MIME type of the image.
16
+ #
17
+ def initialize(source, type = nil)
18
+ if source.respond_to?(:read)
19
+ @io = source
20
+ @type = type || MIME::Types.type_for(source.path).first.content_type
21
+ elsif ::File.exist?(source)
22
+ @io = ::File.open(source, "rb")
23
+ @type = MIME::Types.type_for(source).first.to_s
24
+ else
25
+ raise ArgumentError, "Couldn't read file."
26
+ end
27
+ end
28
+
29
+ #
30
+ # Formats the image as a Discord style.
31
+ #
32
+ # @return [String] The image as a Discord style.
33
+ #
34
+ def to_s
35
+ "data:#{@type};base64,#{Base64.strict_encode64(@io.read)}"
36
+ end
37
+
38
+ def inspect
39
+ "#<#{self.class} #{@type}>"
40
+ end
41
+ end
42
+ end