discorb 0.10.1 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 711fc9fac1407a6de44da571555d6b5eb0575983453a8371bbb1df960ae376ac
4
- data.tar.gz: 33defa63e7f65c82eee318a4d3783c2640fc1dcb54ffdf4941694cef7e72b1a9
3
+ metadata.gz: 6b979da1f1325d5d9cae229e0262cce1d6cf16b6aca91feba731ec6a0146fe42
4
+ data.tar.gz: e6e1fcbe4265cfb54828fef5c7e2d6304dd306544e941cb787233ad563d9abaf
5
5
  SHA512:
6
- metadata.gz: 6a58931cea651763d030c14a61f1f463b7959449bcd70599819c0140884a2a0d86cde1706f97fb3af81013eb6fe2d5372df743a5275dc86f454d01618ba1a90a
7
- data.tar.gz: 884bdcfa59ff7612bcf9f0f12186c5e4cf51313b22a71dac308edc2abaaf45f33cf5e6f85c48681beb976309a245610d16dfd5c781bc6e50c90c0f9e215bc11a
6
+ metadata.gz: bb35c0736b2e549964030103dfb1202ef7711fd3c36323710d3f94631b958a9976f7c111f340d03cf2ed67032a8f74f5165f66b5ff92c59d0628af25b4a95e79
7
+ data.tar.gz: 01d59859fb5e84beaafdc48e333ba841398a8fe5867c52bb9621b2d676493dbc776297c94e303297ffeef591c7f2e527ea30aaae381323e4ed8e8b1ce632b1ef
data/Changelog.md CHANGED
@@ -2,8 +2,38 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## v0.11
6
+
7
+ ### v0.11.1
8
+
9
+ - Improve: Improve rate limit handling
10
+ - Fix: Fix bug in Integration initalization
11
+ - Change: Change log style
12
+ - Add: Support OP code 7
13
+
14
+ ### v0.11.0
15
+
16
+ - Add: Improve documents
17
+ - Add: Implement global rate limits
18
+ - Add: Add support autocomplete
19
+ - Add: Add role icon editting
20
+ - Change: Use `include Discorb::Extension` instead of `< Discorb::Extension`
21
+ - Fix: Fix role operation
22
+
5
23
  ## v0.10
6
24
 
25
+ ### v0.10.3
26
+
27
+ - Add: Support role icons
28
+ - Fix: Fix version order
29
+ - Change: Use `exec` instead of `system` in `discorb run`
30
+ - Add: Add `Extension.loaded`
31
+
32
+ ### v0.10.2
33
+
34
+ - Change: `discorb init` is now `discorb new`
35
+ - Add: Add `:channel_types` parameter to `ApplicationCommand::Handler#slash` and some
36
+
7
37
  ### v0.10.1
8
38
 
9
39
  - Add: Add `Client#extensions`
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/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
  {
@@ -401,6 +401,9 @@ module Discorb
401
401
  cmd.define_singleton_method(:extension) { ins.name }
402
402
  @commands << cmd
403
403
  end
404
+
405
+ cls = ins.class
406
+ cls.loaded(self, ...) if cls.respond_to? :loaded
404
407
  @bottom_commands += ins.class.bottom_commands
405
408
  @extensions[ins.class.name] = ins
406
409
  ins
@@ -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.1"
7
+ VERSION = "0.11.1"
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
@@ -71,9 +71,9 @@ ENV["DISCORB_CLI_TITLE"] = options[:title]
71
71
 
72
72
  if File.exist? script
73
73
  if options[:bundler]
74
- system "bundle exec ruby #{script}"
74
+ exec "bundle exec ruby #{script}"
75
75
  else
76
- system "ruby #{script}"
76
+ exec "ruby #{script}"
77
77
  end
78
78
  else
79
79
  eputs "Could not load script: \e[31m#{script}\e[91m"
@@ -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, {})
@@ -495,6 +495,7 @@ module Discorb
495
495
  @connection = connection
496
496
  @zlib_stream = Zlib::Inflate.new(Zlib::MAX_WBITS)
497
497
  @buffer = +""
498
+
498
499
  while (message = @connection.read)
499
500
  @buffer << message
500
501
  if message.end_with?((+"\x00\x00\xff\xff").force_encoding("ASCII-8BIT"))
@@ -505,7 +506,7 @@ module Discorb
505
506
  rescue JSON::ParserError
506
507
  @buffer = +""
507
508
  @log.error "Received invalid JSON from gateway."
508
- @log.debug data
509
+ @log.debug "#{data}"
509
510
  else
510
511
  handle_gateway(message)
511
512
  end
@@ -524,7 +525,10 @@ module Discorb
524
525
  @log.error "Discord WebSocket closed: #{e.message}"
525
526
  connect_gateway(false)
526
527
  end
527
- rescue EOFError, Async::Wrapper::Cancelled
528
+ rescue EOFError, Async::Wrapper::Cancelled, Async::Wrapper::WaitError
529
+ connect_gateway(false)
530
+ rescue => e
531
+ @log.error "Discord WebSocket error: #{e.message}"
528
532
  connect_gateway(false)
529
533
  end
530
534
  end
@@ -533,7 +537,7 @@ module Discorb
533
537
  def send_gateway(opcode, **value)
534
538
  @connection.write({ op: opcode, d: value }.to_json)
535
539
  @connection.flush
536
- @log.debug "Sent message: #{{ op: opcode, d: value }.to_json.gsub(@token, "[Token]")}"
540
+ @log.debug "Sent message #{{ op: opcode, d: value }.to_json.gsub(@token, "[Token]")}"
537
541
  end
538
542
 
539
543
  def handle_gateway(payload)
@@ -568,6 +572,11 @@ module Discorb
568
572
  }
569
573
  send_gateway(6, **payload)
570
574
  end
575
+ when 7
576
+ @log.info "Received opcode 7, reconnecting"
577
+ @tasks.map(&:stop)
578
+ @connection.close
579
+ connect_gateway(false)
571
580
  when 9
572
581
  @log.warn "Received opcode 9, closed connection"
573
582
  @tasks.map(&:stop)
@@ -1024,7 +1033,7 @@ module Discorb
1024
1033
  if respond_to?("event_" + event_name.downcase)
1025
1034
  __send__("event_" + event_name.downcase, data)
1026
1035
  else
1027
- @log.debug "Received unknown event: #{event_name}\n#{data.inspect}"
1036
+ @log.debug "#{event_name}\n#{data.inspect}"
1028
1037
  end
1029
1038
  end
1030
1039
  end
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,16 @@ 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
+ @client.log.info("Rate limit exceeded for #{method} #{path}, waiting #{data[:retry_after]} seconds")
179
+ sleep(data[:retry_after])
180
+ if body
181
+ __send__(method, path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
182
+ else
183
+ __send__(method, path, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
184
+ end
204
185
  when "400"
205
186
  raise BadRequestError.new(resp, data)
206
187
  when "401"
@@ -247,15 +228,19 @@ module Discorb
247
228
  end
248
229
 
249
230
  def get_response_data(resp)
250
- if resp["Via"].nil? && resp.code == "429"
251
- raise CloudFlareBanError.new(resp, @client)
231
+ begin
232
+ data = JSON.parse(resp.body, symbolize_names: true)
233
+ rescue JSON::ParserError, TypeError
234
+ if resp.body.nil? || resp.body.empty?
235
+ data = nil
236
+ else
237
+ data = resp.body
238
+ end
252
239
  end
253
- rd = resp.body
254
- if rd.nil? || rd.empty?
255
- nil
256
- else
257
- JSON.parse(rd, symbolize_names: true)
240
+ if resp["Via"].nil? && resp.code == "429" && data.is_a?(String)
241
+ raise CloudFlareBanError.new(resp, @client)
258
242
  end
243
+ data
259
244
  end
260
245
 
261
246
  def http
@@ -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
 
@@ -80,7 +80,7 @@ module Discorb
80
80
  @enable_emoticons = data[:enable_emoticons]
81
81
  @expire_behavior = self.class.expire_behavior[data[:expire_behavior]]
82
82
  @expire_grace_period = data[:expire_grace_period]
83
- @user = @client.users[data[:user].to_i]
83
+ @user = @client.users[data[:user][:id]] or Discorb::User.new(@client, data[:user])
84
84
  @account = Account.new(data[:account])
85
85
  @subscriber_count = data[:subscriber_count]
86
86
  @revoked = data[:revoked]
@@ -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
 
@@ -9,8 +9,9 @@ module Discorb
9
9
  # @private
10
10
  def initialize(client)
11
11
  @client = client
12
- @ratelimit_hash = {}
13
- @path_ratelimit_hash = {}
12
+ @current_ratelimits = {}
13
+ @path_ratelimit_bucket = {}
14
+ @global = false
14
15
  end
15
16
 
16
17
  #
@@ -22,18 +23,26 @@ module Discorb
22
23
  def wait(method, path)
23
24
  return if path.start_with?("https://")
24
25
 
25
- return unless hash = @path_ratelimit_hash[method + path]
26
+ if @global
27
+ time = b[:reset_at] - Time.now.to_i
28
+ @client.log.info("global rate limit reached, waiting #{time} seconds")
29
+ sleep(time)
30
+ @global = false
31
+ end
32
+
33
+ return unless hash = @path_ratelimit_bucket[method + path]
26
34
 
27
- return unless b = @ratelimit_hash[hash]
35
+ return unless b = @current_ratelimits[hash]
28
36
 
29
- if b[:reset_at] < Time.now.to_i
30
- @ratelimit_hash.delete(hash)
37
+ if b[:reset_at] < Time.now.to_f
38
+ @current_ratelimits.delete(hash)
31
39
  return
32
40
  end
33
41
  return if b[:remaining] > 0
34
42
 
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)
43
+ time = b[:reset_at] - Time.now.to_f
44
+ @client.log.info("rate limit for #{method} #{path} reached, waiting #{time} seconds")
45
+ sleep(time)
37
46
  end
38
47
 
39
48
  #
@@ -44,12 +53,15 @@ module Discorb
44
53
  # @param [Net::HTTPResponse] resp The response.
45
54
  #
46
55
  def save(method, path, resp)
56
+ if resp["X-Ratelimit-Global"] == "true"
57
+ @global = Time.now.to_i + JSON.parse(resp.body, symbolize_names: true)[:retry_after]
58
+ end
47
59
  return unless resp["X-RateLimit-Remaining"]
48
60
 
49
- @path_ratelimit_hash[method + path] = resp["X-RateLimit-Bucket"]
50
- @ratelimit_hash[resp["X-RateLimit-Bucket"]] = {
61
+ @path_ratelimit_bucket[method + path] = resp["X-RateLimit-Bucket"]
62
+ @current_ratelimits[resp["X-RateLimit-Bucket"]] = {
51
63
  remaining: resp["X-RateLimit-Remaining"].to_i,
52
- reset_at: resp["X-RateLimit-Reset"].to_i,
64
+ reset_at: Time.now.to_f + resp["X-RateLimit-Reset-After"].to_f,
53
65
  }
54
66
  end
55
67
  end
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.1
4
+ version: 0.11.1
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-14 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