discorb 0.10.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1b87a672806fd34b5a1a3308bd96db493c13a645a2f4ae7cd6a3b9622cf7a01
4
- data.tar.gz: fc1b8e0b433489e1390b958b7b4ebeb9c39c1f2d38ff80c05334dda5f19f3bd8
3
+ metadata.gz: 3c80c426387c9e5ba44589c5851e7c1a4d54a5de99c82d5b3571c4200dcaf135
4
+ data.tar.gz: 2aa6db0900cafe2120793b295b68e5de357e97e0797cd416a06caca9491b09e1
5
5
  SHA512:
6
- metadata.gz: 4c666466d4c10d93e974ecd843d14d9e92b424c18e6fab784dba54ff8c0caf9c5f54feaaa7c03e32f640a237af4ee6795e653691035c3e80e3c7a4af79740143
7
- data.tar.gz: 344ecd871e7c1e98a1ad2b68f75f988ee2aabfbe78e24ecf4e8a58da069e04b8e3327f5348414c33a619381c520ea7dbb4fb173cbea6fa153cb2dd5b07b9f9e2
6
+ metadata.gz: afe14b35796820b8b7af5baaab6a79a5a770c55d11d30d25b2a9647034658e27346e918be42fb8d4a30edbf27873bd367cf3551cf1b091e82c49150c886ef03e
7
+ data.tar.gz: 9c817738b2a4b3763d94182e2b47f583aaa7129a8769cdc9b8f7fc7e10f0656f43183f0d416d72aa20112b6b34addfdd38e36b7710c6405d2f29f658cb238c4c
data/Changelog.md CHANGED
@@ -2,8 +2,37 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## v0.11
6
+
7
+ ### v0.11.0
8
+
9
+ - Add: Improve documents
10
+ - Add: Implement global rate limits
11
+ - Add: Add support autocomplete
12
+ - Add: Add role icon editting
13
+ - Change: Use `include Discorb::Extension` instead of `< Discorb::Extension`
14
+ - Fix: Fix role operation
15
+
5
16
  ## v0.10
6
17
 
18
+ ### v0.10.3
19
+
20
+ - Add: Support role icons
21
+ - Fix: Fix version order
22
+ - Change: Use `exec` instead of `system` in `discorb run`
23
+ - Add: Add `Extension.loaded`
24
+
25
+ ### v0.10.2
26
+
27
+ - Change: `discorb init` is now `discorb new`
28
+ - Add: Add `:channel_types` parameter to `ApplicationCommand::Handler#slash` and some
29
+
30
+ ### v0.10.1
31
+
32
+ - Add: Add `Client#extensions`
33
+ - Change: `Client#load_extension` allows instance of `Extension`
34
+ - Add: Add `-b` option to `discorb run`
35
+
7
36
  ### v0.10.0
8
37
 
9
38
  - Change: Sort versions
data/Rakefile CHANGED
@@ -156,7 +156,7 @@ namespace :document do
156
156
  Rake::Task["document:replace:html"].execute
157
157
  Rake::Task["document:replace:css"].execute
158
158
  Rake::Task["document:replace:eol"].execute
159
- tags = `git tag`.force_encoding("utf-8").split("\n")
159
+ tags = `git tag`.force_encoding("utf-8").split("\n").sort_by { |t| t[1..].split(".").map(&:to_i) }
160
160
  tags.each do |tag|
161
161
  sh "git checkout #{tag} -f"
162
162
  iputs "Building #{tag}"
@@ -68,6 +68,8 @@ In `options`, hash should be like this:
68
68
  | `:type` | `Object` | Type of the option. |
69
69
  | `:choice` | `Hash{String => String, Integer, Float}` | Type of the option. |
70
70
  | `:default` | `Object` | Default value of the option. |
71
+ | `:channel_types` | `Array<Class<Discorb::Channel>>` | Type of the channel option. |
72
+ | `:autocomplete` | `Proc` | Autocomplete function. |
71
73
 
72
74
  `choices` should be unspecified if you don't want to use it.
73
75
  `choices` is hash like this:
@@ -106,7 +108,7 @@ In `type`, You must use one of the following:
106
108
  | `:channel` | Channel argument. | None |
107
109
  | `:role` | Role argument. | None |
108
110
 
109
- ### Group Slash Commands
111
+ #### Group Slash Commands
110
112
 
111
113
  To register a group of slash commands, use {Discorb::ApplicationCommand::Handler#slash_group}.
112
114
 
@@ -238,6 +240,31 @@ end
238
240
 
239
241
  Same as above, you can use block for register commands since v0.5.1.
240
242
 
243
+ #### Use Auto Completing
244
+
245
+ Since v0.11.0, you can use auto completion by setting Proc to `:autocomplete` in options.
246
+ The proc will be called with interaction object and the argument.
247
+ The proc should return an hash of the autocomplete result.
248
+
249
+ ```ruby
250
+ client.slash("hello2", "Greet for you", {
251
+ "target" => {
252
+ type: :string,
253
+ description: "Person to greet",
254
+ autocomplete: ->(interaction, target) {
255
+ {
256
+ "You" => interaction.target.to_s
257
+ }
258
+ },
259
+ },
260
+ }) do |interaction, target|
261
+ interaction.post("Hello, #{target}!")
262
+ end
263
+ ```
264
+
265
+ In the above example, `You` will be displayed in the user menu.
266
+ Due to the limitation of Discord API, the proc must return the result in less than 3 second.
267
+
241
268
  ### Register User Context Menu Command
242
269
 
243
270
  ```ruby
data/docs/cli/init.md CHANGED
@@ -1,13 +1,13 @@
1
- # @title CLI: discorb init
1
+ # @title CLI: discorb new
2
2
 
3
- # discorb init
3
+ # discorb new
4
4
 
5
5
  This command will create a new project in the directory.
6
6
 
7
7
  ## Usage
8
8
 
9
9
  ```bash
10
- discorb init [options] [dir]
10
+ discorb new [options] [dir]
11
11
  ```
12
12
 
13
13
  ## Options
data/docs/cli/run.md CHANGED
@@ -53,4 +53,8 @@ Whether to setup application commands.
53
53
 
54
54
  #### `-e`, `--env`
55
55
 
56
- The name of the environment variable to use for token, or just `-t` or `--token` for intractive prompt.
56
+ The name of the environment variable to use for token, or just `-t` or `--token` for intractive prompt.
57
+
58
+ #### `-b`, `--bundler`
59
+
60
+ Whether to use bundler to load the script.
data/docs/extension.md CHANGED
@@ -2,16 +2,18 @@
2
2
 
3
3
  # Extension
4
4
 
5
- Extension allows you to seperate your code from the main application.
5
+ Extension allows you to seperate events.
6
6
 
7
7
  # @since
8
8
 
9
9
  ## Make a new extension
10
10
 
11
- Make a new class that extends Extension.
11
+ Make a new class that includes Extension.
12
12
 
13
13
  ```ruby
14
- class MyExtension < Discorb::Extension
14
+ class MyExtension
15
+ include Discorb::Extension
16
+
15
17
  # ...
16
18
  end
17
19
  ```
@@ -21,7 +23,9 @@ end
21
23
  Use {Discorb::Extension.event} to register event, or {Discorb::Extension.once_event} to register event only once.
22
24
 
23
25
  ```ruby
24
- class MyExtension < Discorb::Extension
26
+ class MyExtension
27
+ include Discorb::Extension
28
+
25
29
  event :message do |message|
26
30
  # ...
27
31
  end
@@ -39,7 +43,9 @@ Note block will be binded to the extension instance.
39
43
  Use `Discorb::Extension.command` to register command, see {Discorb::ApplicationCommand::Handler} for more information.
40
44
 
41
45
  ```ruby
42
- class MyExtension < Discorb::Extension
46
+ class MyExtension
47
+ include Discorb::Extension
48
+
43
49
  slash("command", "Command") do |interaction|
44
50
  # ...
45
51
  end
@@ -64,7 +70,9 @@ end
64
70
  Use {Discorb::Client#load_extension} to load extension.
65
71
 
66
72
  ```ruby
67
- class MyExtension < Discorb::Extension
73
+ class MyExtension
74
+ include Discorb::Extension
75
+
68
76
  event :message do |message|
69
77
  # ...
70
78
  end
@@ -78,9 +86,45 @@ client.load_extension(MyExtension)
78
86
  You can access {Discorb::Client} from extension with `@client`.
79
87
 
80
88
  ```ruby
81
- class MyExtension < Discorb::Extension
89
+ class MyExtension
90
+ include Discorb::Extension
91
+
82
92
  event :standby do |message|
83
93
  puts "Logged in as #{@client.user}"
84
94
  end
85
95
  end
86
96
  ```
97
+
98
+ ## Receiving Arguments on load
99
+
100
+ You can receive arguments by adding some arguments to `#initialize`.
101
+
102
+ ```ruby
103
+ class MyExtension
104
+ include Discorb::Extension
105
+
106
+ def initialize(client, arg1, arg2)
107
+ super(client)
108
+ # @client = client will also work, but it's not recommended.
109
+ @arg1 = arg1
110
+ @arg2 = arg2
111
+ end
112
+ end
113
+
114
+ client.load_extension(MyExtension, "arg1", "arg2")
115
+
116
+ ```
117
+
118
+ ## Do something on load
119
+
120
+ You can do something on load by overriding `.loaded`. Client and arguments will be passed to it.
121
+
122
+ ```ruby
123
+ class MyExtension
124
+ include Discorb::Extension
125
+
126
+ def self.loaded(client)
127
+ puts "This extension is loaded to #{client}"
128
+ end
129
+ end
130
+ ```
data/docs/faq.md CHANGED
@@ -2,13 +2,25 @@
2
2
 
3
3
  # Fequently asked questions
4
4
 
5
- ## What is `Async::Task`?
5
+ ## What is ...?
6
+
7
+ ### What is `Async::Task`?
6
8
 
7
9
  Async::Task is a object for asynchronous tasks.
8
10
 
9
11
  https://socketry.github.io/async/ for more information.
10
12
 
11
- ## How do I do something with sent messages?
13
+ ### What is `Guild`?
14
+
15
+ It means a `server` of Discord.
16
+
17
+ ### What is difference between `User` and `Member`?
18
+
19
+ `User` is a object for account, `Member` is a object for user in guild.
20
+
21
+ ## How can I ...?
22
+
23
+ ### How can I do something with sent messages?
12
24
 
13
25
  Use `Async::Task#wait` method.
14
26
 
@@ -22,11 +34,12 @@ message = channel.post("Hello world!").wait # => Message
22
34
  message.pin
23
35
  ```
24
36
 
25
- ## How can I send DM to a user?
37
+
38
+ ### How can I send DM to a user?
26
39
 
27
40
  Use {Discorb::User#post} method, {Discorb::User} includes {Discorb::Messageable}.
28
41
 
29
- ## How can I edit status?
42
+ ### How can I edit status?
30
43
 
31
44
  Use {Discorb::Client#update_presence} method.
32
45
 
@@ -47,7 +60,7 @@ client.on :ready do
47
60
  end
48
61
  ```
49
62
 
50
- ## How can I send files?
63
+ ### How can I send files?
51
64
 
52
65
  Use {Discorb::File} class.
53
66
 
@@ -62,6 +75,15 @@ message.channel.post "File!", files: [Discorb::File.new(File.open("./README.md")
62
75
  message.channel.post file: Discorb::File.from_string("Hello world!", "hello.txt")
63
76
  ```
64
77
 
78
+ ### How can I add reactions?
79
+
80
+ Use {Discorb::Message#add_reaction} method.
81
+
82
+ ```ruby
83
+ message.add_reaction Discorb::UnicodeEmoji["🤔"]
84
+ message.add_reaction Discorb::UnicodeEmoji["thinking"]
85
+ ```
86
+
65
87
  # Not fequently asked questions
66
88
 
67
89
  ## How can I pronounce `discorb`?
data/docs/tutorial.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  # Tutorial
4
4
 
5
- Welcome to discorb! This lib allows you to create a discord bot with ease. So, let's get started!
5
+ Welcome to discorb! This lib allows you to create a discord bot with Ruby.
6
+ Let's get started!
6
7
 
7
8
  ## Requirements
8
9
 
@@ -22,6 +23,7 @@ Welcome to discorb! This lib allows you to create a discord bot with ease. So, l
22
23
  - [Sublime Text](https://www.sublimetext.com/)
23
24
  - [Brackets](https://brackets.io/)
24
25
  - [Notepad++](https://notepad-plus-plus.org/)
26
+ - [Vim](https://www.vim.org/)
25
27
  - Git
26
28
  - Bundler
27
29
 
@@ -74,7 +76,7 @@ Create a new directory and go to it.
74
76
  Open terminal and type:
75
77
 
76
78
  ```
77
- discorb init
79
+ discorb new
78
80
  ```
79
81
 
80
82
  Specify `--git` if you want to use git.
@@ -1,6 +1,8 @@
1
1
  require "discorb"
2
2
 
3
- class MessageExpander < Discorb::Extension
3
+ class MessageExpander
4
+ include Discorb::Extension
5
+
4
6
  @@message_regex = Regexp.new(
5
7
  '(?!<)https://(?:ptb\.|canary\.)?discord(?:app)?\.com/channels/' \
6
8
  "(?<guild>[0-9]{18})/(?<channel>[0-9]{18})/(?<message>[0-9]{18})(?!>)"
@@ -25,6 +25,8 @@ module Discorb
25
25
  # | `:type` | `Object` | Type of the option. |
26
26
  # | `:choice` | `Hash{String => String, Integer, Float}` | Type of the option. |
27
27
  # | `:default` | `Object` | Default value of the option. |
28
+ # | `:channel_types` | `Array<Class<Discorb::Channel>>` | Type of the channel option. |
29
+ # | `:autocomplete` | `Proc` | Autocomplete function. |
28
30
  #
29
31
  # @param [Array<#to_s>, false, nil] guild_ids Guild IDs to set the command to. `false` to global command, `nil` to use default.
30
32
  # @param [Proc] block Command block.
@@ -235,9 +237,12 @@ module Discorb
235
237
  description: value[:description],
236
238
  required: value[:required].nil? ? !value[:optional] : value[:required],
237
239
  }
238
- if value[:choices]
239
- ret[:choices] = value[:choices].map { |t| { name: t[0], value: t[1] } }
240
- end
240
+
241
+ ret[:choices] = value[:choices].map { |t| { name: t[0], value: t[1] } } if value[:choices]
242
+
243
+ ret[:channel_types] = value[:channel_types].map(&:channel_type) if value[:channel_types]
244
+
245
+ ret[:autocomplete] = !!value[:autocomplete] if value[:autocomplete]
241
246
  ret
242
247
  end
243
248
  {
@@ -52,6 +52,8 @@ module Discorb
52
52
  attr_reader :status
53
53
  # @return [Integer] The session ID of connection.
54
54
  attr_reader :session_id
55
+ # @return [Hash{String => Discorb::Extension}] The loaded extensions.
56
+ attr_reader :extensions
55
57
  # @private
56
58
  attr_reader :bottom_commands
57
59
 
@@ -97,6 +99,7 @@ module Discorb
97
99
  @status = :initialized
98
100
  @fetch_member = fetch_member
99
101
  @title = title
102
+ @extensions = {}
100
103
  set_default_events
101
104
  end
102
105
 
@@ -369,12 +372,19 @@ module Discorb
369
372
  #
370
373
  # Load the extension.
371
374
  #
372
- # @param [Class] ext The extension to load.
375
+ # @param [Class, Discorb::Extension] ext The extension to load.
373
376
  # @param [Object] ... The arguments to pass to the `ext#initialize`.
374
377
  #
375
378
  def load_extension(ext, ...)
376
- raise ArgumentError, "#{ext} is not a extension" unless ext.is_a?(Class) && ext < Discorb::Extension
377
- ins = ext.new(self, ...)
379
+ if ext.is_a?(Class)
380
+ raise ArgumentError, "#{ext} is not a extension" unless ext < Discorb::Extension
381
+ ins = ext.new(self, ...)
382
+ elsif ext.is_a?(Discorb::Extension)
383
+ ins = ext
384
+ else
385
+ raise ArgumentError, "#{ext} is not a extension"
386
+ end
387
+
378
388
  @events.each_value do |event|
379
389
  event.delete_if { |c| c.metadata[:extension] == ins.class.name }
380
390
  end
@@ -391,7 +401,11 @@ module Discorb
391
401
  cmd.define_singleton_method(:extension) { ins.name }
392
402
  @commands << cmd
393
403
  end
404
+
405
+ cls = ins.class
406
+ cls.loaded(self, ...) if cls.respond_to? :loaded
394
407
  @bottom_commands += ins.class.bottom_commands
408
+ @extensions[ins.class.name] = ins
395
409
  ins
396
410
  end
397
411
 
@@ -4,7 +4,7 @@ module Discorb
4
4
  # @return [String] The API base URL.
5
5
  API_BASE_URL = "https://discord.com/api/v9"
6
6
  # @return [String] The version of discorb.
7
- VERSION = "0.10.0"
7
+ VERSION = "0.11.0"
8
8
  # @return [String] The user agent for the bot.
9
9
  USER_AGENT = "DiscordBot (https://discorb-lib.github.io #{VERSION}) Ruby/#{RUBY_VERSION}"
10
10
 
@@ -214,6 +214,7 @@ module Discorb
214
214
  placeholder: @placeholder,
215
215
  min_values: @min_values,
216
216
  max_values: @max_values,
217
+ disabled: @disabled,
217
218
  }
218
219
  end
219
220
 
@@ -178,7 +178,7 @@ end
178
178
  opt = OptionParser.new <<~BANNER
179
179
  A tool to make a new project.
180
180
 
181
- Usage: discorb init [options] [dir]
181
+ Usage: discorb new [options] [dir]
182
182
 
183
183
  dir The directory to make the files in.
184
184
  BANNER
@@ -22,6 +22,7 @@ options = {
22
22
  log_color: nil,
23
23
  setup: nil,
24
24
  token: false,
25
+ bundler: :default,
25
26
  }
26
27
  opt.on("-l", "--log-level LEVEL", "Log level.") do |v|
27
28
  unless LOG_LEVELS.include? v.downcase
@@ -36,6 +37,7 @@ opt.on("-c", "--[no-]log-color", "Whether to colorize log output.") { |v| option
36
37
  opt.on("-s", "--setup", "Whether to setup application commands.") { |v| options[:setup] = v }
37
38
  opt.on("-e", "--env [ENV]", "The name of the environment variable to use for token, or just `-e` or `--env` for intractive prompt.") { |v| options[:token] = v }
38
39
  opt.on("-t", "--title TITLE", "The title of process.") { |v| options[:title] = v }
40
+ opt.on("-b", "--[no-]bundler", "Whether to use bundler. Default to true if Gemfile exists, otherwise false.") { |v| options[:bundler] = v }
39
41
  opt.parse!(ARGV)
40
42
 
41
43
  script = ARGV[0]
@@ -54,10 +56,25 @@ elsif options[:token].nil? || options[:token] == "-"
54
56
  puts ""
55
57
  end
56
58
 
59
+ if options[:bundler] == :default
60
+ dir = Dir.pwd.split("/")
61
+ options[:bundler] = false
62
+ dir.length.times.reverse_each do |i|
63
+ if File.exist? "#{dir[0..i].join("/")}/Gemfile"
64
+ options[:bundler] = true
65
+ break
66
+ end
67
+ end
68
+ end
69
+
57
70
  ENV["DISCORB_CLI_TITLE"] = options[:title]
58
71
 
59
- begin
60
- load script
61
- rescue LoadError
72
+ if File.exist? script
73
+ if options[:bundler]
74
+ exec "bundle exec ruby #{script}"
75
+ else
76
+ exec "ruby #{script}"
77
+ end
78
+ else
62
79
  eputs "Could not load script: \e[31m#{script}\e[91m"
63
80
  end
@@ -3,15 +3,11 @@
3
3
  module Discorb
4
4
  #
5
5
  # Abstract class to make extension.
6
- # Inherit from this class to make your own extension.
6
+ # Include from this module to make your own extension.
7
7
  # @see file:docs/extension.md
8
8
  # @abstract
9
9
  #
10
- class Extension
11
- extend Discorb::ApplicationCommand::Handler
12
-
13
- @events = {}
14
-
10
+ module Extension
15
11
  def initialize(client)
16
12
  @client = client
17
13
  end
@@ -27,7 +23,12 @@ module Discorb
27
23
  @events = ret
28
24
  end
29
25
 
30
- class << self
26
+ def self.included(base)
27
+ base.extend(ClassMethods)
28
+ end
29
+
30
+ module ClassMethods
31
+ include Discorb::ApplicationCommand::Handler
31
32
  undef setup_commands
32
33
 
33
34
  #
@@ -69,7 +70,7 @@ module Discorb
69
70
  # @private
70
71
  attr_reader :bottom_commands
71
72
 
72
- def inherited(klass)
73
+ def self.extended(klass)
73
74
  klass.instance_variable_set(:@commands, [])
74
75
  klass.instance_variable_set(:@bottom_commands, [])
75
76
  klass.instance_variable_set(:@events, {})
data/lib/discorb/http.rb CHANGED
@@ -36,13 +36,7 @@ module Discorb
36
36
  resp = http.get(get_path(path), get_headers(headers, "", audit_log_reason), **kwargs)
37
37
  data = get_response_data(resp)
38
38
  @ratelimit_handler.save("GET", path, resp)
39
- test_error(if resp.code == "429"
40
- @client.log.warn "Ratelimit exceeded for #{path}, trying again in #{data[:retry_after]} seconds."
41
- task.sleep(data[:retry_after])
42
- get(path, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
43
- else
44
- [resp, data]
45
- end)
39
+ handle_response(:patch, resp, data, path, nil, headers, audit_log_reason, kwargs)
46
40
  end
47
41
  end
48
42
 
@@ -67,12 +61,7 @@ module Discorb
67
61
  resp = http.post(get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
68
62
  data = get_response_data(resp)
69
63
  @ratelimit_handler.save("POST", path, resp)
70
- test_error(if resp.code == "429"
71
- task.sleep(data[:retry_after])
72
- post(path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
73
- else
74
- [resp, data]
75
- end)
64
+ handle_response(:post, resp, data, path, body, headers, audit_log_reason, kwargs)
76
65
  end
77
66
  end
78
67
 
@@ -97,12 +86,7 @@ module Discorb
97
86
  resp = http.patch(get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
98
87
  data = get_response_data(resp)
99
88
  @ratelimit_handler.save("PATCH", path, resp)
100
- test_error(if resp.code == "429"
101
- task.sleep(data[:retry_after])
102
- patch(path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
103
- else
104
- [resp, data]
105
- end)
89
+ handle_response(:patch, resp, data, path, body, headers, audit_log_reason, kwargs)
106
90
  end
107
91
  end
108
92
 
@@ -127,12 +111,7 @@ module Discorb
127
111
  resp = http.put(get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
128
112
  data = get_response_data(resp)
129
113
  @ratelimit_handler.save("PUT", path, resp)
130
- test_error(if resp.code == "429"
131
- task.sleep(data[:retry_after])
132
- put(path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
133
- else
134
- [resp, data]
135
- end)
114
+ handle_response(:put, resp, data, path, body, headers, audit_log_reason, kwargs)
136
115
  end
137
116
  end
138
117
 
@@ -151,17 +130,12 @@ module Discorb
151
130
  # @raise [Discorb::HTTPError] The request was failed.
152
131
  #
153
132
  def delete(path, headers: nil, audit_log_reason: nil, **kwargs)
154
- Async do |task|
133
+ Async do
155
134
  @ratelimit_handler.wait("DELETE", path)
156
135
  resp = http.delete(get_path(path), get_headers(headers, "", audit_log_reason))
157
136
  data = get_response_data(resp)
158
137
  @ratelimit_handler.save("DELETE", path, resp)
159
- test_error(if resp.code == "429"
160
- task.sleep(data[:retry_after])
161
- delete(path, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
162
- else
163
- [resp, data]
164
- end)
138
+ handle_response(:delete, resp, data, path, nil, headers, audit_log_reason, kwargs)
165
139
  end
166
140
  end
167
141
 
@@ -198,9 +172,15 @@ module Discorb
198
172
 
199
173
  private
200
174
 
201
- def test_error(ary)
202
- resp, data = *ary
175
+ def handle_response(method, resp, data, path, body, headers, audit_log_reason, kwargs)
203
176
  case resp.code
177
+ when "429"
178
+ sleep(data[:retry_after])
179
+ if body
180
+ __send__(method, path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
181
+ else
182
+ __send__(method, path, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
183
+ end
204
184
  when "400"
205
185
  raise BadRequestError.new(resp, data)
206
186
  when "401"
@@ -63,7 +63,7 @@ module Discorb
63
63
  #
64
64
  def delete!(reason: nil)
65
65
  Async do
66
- @client.http.delete("/guilds/#{@guild}/integrations/#{@id}", reason: reason).wait
66
+ @client.http.delete("/guilds/#{@guild}/integrations/#{@id}", audit_log_reason: reason).wait
67
67
  end
68
68
  end
69
69
 
@@ -364,7 +364,7 @@ module Discorb
364
364
  end
365
365
 
366
366
  unless (command = @client.bottom_commands.find { |c| c.to_s == name && c.type_raw == 1 })
367
- @client.log.warn "Unknown command name #{name}, ignoreing"
367
+ @client.log.warn "Unknown command name #{name}, ignoring"
368
368
  next
369
369
  end
370
370
 
@@ -451,6 +451,87 @@ module Discorb
451
451
  end
452
452
  end
453
453
 
454
+ #
455
+ # Represents auto complete interaction.
456
+ #
457
+ class AutoComplete < Interaction
458
+ @interaction_type = 4
459
+ @interaction_name = :auto_complete
460
+
461
+ def _set_data(data)
462
+ super
463
+ Sync do
464
+ name = data[:name]
465
+ options = nil
466
+ if (option = data[:options]&.first)
467
+ case option[:type]
468
+ when 1
469
+ name += " #{option[:name]}"
470
+ options = option[:options]
471
+ when 2
472
+ name += " #{option[:name]}"
473
+ if (option_sub = option[:options]&.first)
474
+ if option_sub[:type] == 1
475
+ name += " #{option_sub[:name]}"
476
+ options = option_sub[:options]
477
+ else
478
+ options = option[:options]
479
+ end
480
+ end
481
+ else
482
+ options = data[:options]
483
+ end
484
+ end
485
+
486
+ unless (command = @client.bottom_commands.find { |c| c.to_s == name && c.type_raw == 1 })
487
+ @client.log.warn "Unknown command name #{name}, ignoring"
488
+ next
489
+ end
490
+
491
+ option_map = command.options.map { |k, v| [k.to_s, v[:default]] }.to_h
492
+ options ||= []
493
+ options.each_with_index do |option|
494
+ val = case option[:type]
495
+ when 3, 4, 5, 10
496
+ option[:value]
497
+ when 6
498
+ guild.members[option[:value]] || guild.fetch_member(option[:value]).wait
499
+ when 7
500
+ guild.channels[option[:value]] || guild.fetch_channels.wait.find { |channel| channel.id == option[:value] }
501
+ when 8
502
+ guild.roles[option[:value]] || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
503
+ when 9
504
+ guild.members[option[:value]] || guild.roles[option[:value]] || guild.fetch_member(option[:value]).wait || guild.fetch_roles.wait.find { |role| role.id == option[:value] }
505
+ end
506
+ option_map[option[:name]] = val
507
+ end
508
+ focused_index = options.find_index { |o| o[:focused] }
509
+ val = command.options.values[focused_index][:autocomplete]&.call(self, *command.options.map { |k, v| option_map[k.to_s] })
510
+ send_complete_result(val)
511
+ end
512
+ end
513
+
514
+ def send_complete_result(val)
515
+ @client.http.post("/interactions/#{@id}/#{@token}/callback", {
516
+ type: 8,
517
+ data: {
518
+ choices: val.map do |vk, vv|
519
+ {
520
+ name: vk,
521
+ value: vv,
522
+ }
523
+ end,
524
+ },
525
+ }).wait
526
+ rescue Discorb::NotFoundError
527
+ @client.log.warn "Failed to send auto complete result, This may be caused by the suggestion is taking too long (over 3 seconds) to respond", fallback: $stderr
528
+ end
529
+
530
+ class << self
531
+ alias make_interaction new
532
+ end
533
+ end
534
+
454
535
  #
455
536
  # Represents a message component interaction.
456
537
  # @abstract
@@ -71,21 +71,12 @@ module Discorb
71
71
  _set_data(user_data, member_data)
72
72
  end
73
73
 
74
- #
75
- # Format the member to `@name` style.
76
- #
77
- # @return [String] The formatted member.
78
- #
79
- def to_s
80
- "@#{name}"
81
- end
82
-
83
74
  #
84
75
  # Format the member to `Username#Discriminator` style.
85
76
  #
86
77
  # @return [String] The formatted member.
87
78
  #
88
- def to_s_user
79
+ def to_s
89
80
  "#{username}##{discriminator}"
90
81
  end
91
82
 
@@ -94,7 +94,7 @@ module Discorb
94
94
  #
95
95
  def delete_message!(message_id, reason: nil)
96
96
  Async do
97
- @client.http.delete("/channels/#{channel_id.wait}/messages/#{message_id}", reason: reason).wait
97
+ @client.http.delete("/channels/#{channel_id.wait}/messages/#{message_id}", audit_log_reason: reason).wait
98
98
  end
99
99
  end
100
100
 
@@ -11,6 +11,7 @@ module Discorb
11
11
  @client = client
12
12
  @ratelimit_hash = {}
13
13
  @path_ratelimit_hash = {}
14
+ @global = false
14
15
  end
15
16
 
16
17
  #
@@ -22,6 +23,14 @@ module Discorb
22
23
  def wait(method, path)
23
24
  return if path.start_with?("https://")
24
25
 
26
+ if @global
27
+ time = b[:reset_at] - Time.now.to_i
28
+ @client.log.info("Global ratelimit reached, waiting #{time} seconds")
29
+ sleep(time)
30
+ @global = false
31
+
32
+ end
33
+
25
34
  return unless hash = @path_ratelimit_hash[method + path]
26
35
 
27
36
  return unless b = @ratelimit_hash[hash]
@@ -32,8 +41,9 @@ module Discorb
32
41
  end
33
42
  return if b[:remaining] > 0
34
43
 
35
- @client.log.info("Ratelimit reached, waiting for #{b[:reset_at] - Time.now.to_i} seconds")
36
- sleep(b[:reset_at] - Time.now.to_i)
44
+ time = b[:reset_at] - Time.now.to_i
45
+ @client.log.info("Ratelimit reached, waiting #{time} seconds")
46
+ sleep(time)
37
47
  end
38
48
 
39
49
  #
@@ -44,6 +54,9 @@ module Discorb
44
54
  # @param [Net::HTTPResponse] resp The response.
45
55
  #
46
56
  def save(method, path, resp)
57
+ if resp["X-Ratelimit-Global"] == "true"
58
+ @global = Time.now.to_i + JSON.parse(resp.body, symbolize_names: true)[:retry_after]
59
+ end
47
60
  return unless resp["X-RateLimit-Remaining"]
48
61
 
49
62
  @path_ratelimit_hash[method + path] = resp["X-RateLimit-Bucket"]
data/lib/discorb/role.rb CHANGED
@@ -26,6 +26,10 @@ module Discorb
26
26
  # @return [Boolean] Whether the role is a default role.
27
27
  attr_reader :mentionable
28
28
  alias mentionable? mentionable
29
+ # @return [Discorb::Asset, nil] The icon of the role.
30
+ attr_reader :custom_icon
31
+ # @return [Discorb::Emoji, nil] The emoji of the role.
32
+ attr_reader :emoji
29
33
 
30
34
  # @!attribute [r] mention
31
35
  # @return [String] The mention of the role.
@@ -33,6 +37,8 @@ module Discorb
33
37
  # @return [Boolean] Whether the role has a color.
34
38
  # @!attribute [r] tag
35
39
  # @return [Discorb::Role::Tag] The tag of the role.
40
+ # @!attribute [r] icon
41
+ # @return [Discorb::Asset, Discorb::Emoji] The icon of the role.
36
42
 
37
43
  include Comparable
38
44
 
@@ -44,6 +50,10 @@ module Discorb
44
50
  _set_data(data)
45
51
  end
46
52
 
53
+ def icon
54
+ @custom_icon || @emoji
55
+ end
56
+
47
57
  #
48
58
  # Compares two roles by their position.
49
59
  #
@@ -88,7 +98,7 @@ module Discorb
88
98
  #
89
99
  def move(position, reason: nil)
90
100
  Async do
91
- @client.http.patch("/guilds/#{@guild_id}/roles", { id: @id, position: position }, reason: reason).wait
101
+ @client.http.patch("/guilds/#{@guild.id}/roles", { id: @id, position: position }, audit_log_reason: reason).wait
92
102
  end
93
103
  end
94
104
 
@@ -103,9 +113,10 @@ module Discorb
103
113
  # @param [Discorb::Color] color The new color of the role.
104
114
  # @param [Boolean] hoist Whether the role should be hoisted.
105
115
  # @param [Boolean] mentionable Whether the role should be mentionable.
116
+ # @param [Discorb::Image, Discorb::UnicodeEmoji] icon The new icon or emoji of the role.
106
117
  # @param [String] reason The reason for editing the role.
107
118
  #
108
- def edit(name: :unset, position: :unset, color: :unset, hoist: :unset, mentionable: :unset, reason: nil)
119
+ def edit(name: :unset, position: :unset, color: :unset, hoist: :unset, mentionable: :unset, icon: :unset, reason: nil)
109
120
  Async do
110
121
  payload = {}
111
122
  payload[:name] = name if name != :unset
@@ -113,7 +124,14 @@ module Discorb
113
124
  payload[:color] = color.to_i if color != :unset
114
125
  payload[:hoist] = hoist if hoist != :unset
115
126
  payload[:mentionable] = mentionable if mentionable != :unset
116
- @client.http.patch("/guilds/#{@guild_id}/roles/#{@id}", payload, reason: reason).wait
127
+ if icon != :unset
128
+ if icon.is_a?(Discorb::Image)
129
+ payload[:icon] = icon.to_s
130
+ else
131
+ payload[:unicode_emoji] = icon.to_s
132
+ end
133
+ end
134
+ @client.http.patch("/guilds/#{@guild.id}/roles/#{@id}", payload, audit_log_reason: reason).wait
117
135
  end
118
136
  end
119
137
 
@@ -126,7 +144,7 @@ module Discorb
126
144
  #
127
145
  def delete!(reason: nil)
128
146
  Async do
129
- @client.http.delete("/guilds/#{@guild_id}/roles/#{@id}", reason: reason).wait
147
+ @client.http.delete("/guilds/#{@guild.id}/roles/#{@id}", audit_log_reason: reason).wait
130
148
  end
131
149
  end
132
150
 
@@ -182,6 +200,8 @@ module Discorb
182
200
  @managed = data[:managed]
183
201
  @mentionable = data[:mentionable]
184
202
  @tags = data[:tags] || {}
203
+ @custom_icon = data[:icon] ? Asset.new(self, data[:icon], path: "role-icons/#{@id}") : nil
204
+ @emoji = data[:unicode_emoji] ? UnicodeEmoji.new(data[:unicode_emoji]) : nil
185
205
  @guild.roles[@id] = self unless data[:no_cache]
186
206
  @data.update(data)
187
207
  end
@@ -193,7 +193,7 @@ module Discorb
193
193
  #
194
194
  def delete!(reason: nil)
195
195
  Async do
196
- @client.http.delete("/stage-instances/#{@channel_id}", reason: reason).wait
196
+ @client.http.delete("/stage-instances/#{@channel_id}", audit_log_reason: reason).wait
197
197
  self
198
198
  end
199
199
  end
@@ -4,7 +4,12 @@ def build_version_sidebar(dir, version)
4
4
  raw.gsub!(template, "")
5
5
  res = +""
6
6
  i = 0
7
- `git tag`.force_encoding("utf-8").split("\n").reverse.each.with_index do |tag|
7
+ `git tag`
8
+ .force_encoding("utf-8")
9
+ .split("\n")
10
+ .sort_by { |v| Gem::Version.new(v[1..]) }
11
+ .reverse
12
+ .each.with_index do |tag|
8
13
  i += 1
9
14
  sha = `git rev-parse #{tag}`.force_encoding("utf-8").strip
10
15
  tag_version = tag.delete_prefix("v")
@@ -32,7 +37,7 @@ def build_version_sidebar(dir, version)
32
37
  cls = i % 2 == 0 ? "even" : "odd"
33
38
  res.insert 0, template
34
39
  .gsub("!version!", "Latest")
35
- .gsub("!path!", "/")
40
+ .gsub("!path!", "")
36
41
  .gsub("!class!", cls)
37
42
  .gsub("!sha!", "Latest on RubyGems")
38
43
  File.write(dir + "/version_list.html", raw.gsub("<!--replace-->", res))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discorb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sevenc-nanashi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-02 00:00:00.000000000 Z
11
+ date: 2021-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -142,8 +142,8 @@ files:
142
142
  - lib/discorb/error.rb
143
143
  - lib/discorb/event.rb
144
144
  - lib/discorb/exe/about.rb
145
- - lib/discorb/exe/init.rb
146
145
  - lib/discorb/exe/irb.rb
146
+ - lib/discorb/exe/new.rb
147
147
  - lib/discorb/exe/run.rb
148
148
  - lib/discorb/exe/setup.rb
149
149
  - lib/discorb/exe/show.rb