discorb 0.19.0 → 0.20.0

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