discorb 0.3.1 → 0.5.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.
@@ -0,0 +1,49 @@
1
+ # description: Run a client.
2
+ require "optparse"
3
+ require "json"
4
+ require "discorb/utils/colored_puts"
5
+
6
+ ARGV.delete_at 0
7
+ # @!visibility private
8
+ LOG_LEVELS = %w[none debug info warn error fatal]
9
+
10
+ opt = OptionParser.new <<~BANNER
11
+ This command will run a client.
12
+
13
+ Usage: discorb run [options] [script]
14
+
15
+ script The script to run. Defaults to 'main.rb'.
16
+ BANNER
17
+ options = {
18
+ deamon: false,
19
+ log_level: nil,
20
+ log_file: nil,
21
+ log_color: nil,
22
+ setup: nil,
23
+ }
24
+ opt.on("-d", "--deamon", "Run as a daemon.") { |v| options[:daemon] = v }
25
+ opt.on("-l", "--log-level LEVEL", "Log level.") do |v|
26
+ unless LOG_LEVELS.include? v.downcase
27
+ eputs "Invalid log level: \e[31m#{v}\e[91m"
28
+ eputs "Valid log levels: \e[31m#{LOG_LEVELS.join("\e[91m, \e[31m")}\e[91m"
29
+ exit 1
30
+ end
31
+ options[:log_level] = v.downcase
32
+ end
33
+ opt.on("-f", "--log-file FILE", "File to write log to.") { |v| options[:log_file] = v }
34
+ opt.on("-c", "--[no-]log-color", "Whether to colorize log output.") { |v| options[:log_color] = v }
35
+ opt.on("-s", "--setup", "Whether to setup application commands.") { |v| options[:setup] = v }
36
+ opt.parse!(ARGV)
37
+
38
+ script = ARGV[0]
39
+
40
+ script ||= "main.rb"
41
+
42
+ ENV["DISCORB_CLI_FLAG"] = "run"
43
+ ENV["DISCORB_CLI_OPTIONS"] = JSON.generate(options)
44
+
45
+ begin
46
+ load script
47
+ rescue LoadError
48
+ eputs "Could not load script: \e[31m#{script}\e[91m"
49
+ end
@@ -0,0 +1,26 @@
1
+ # description: Setup application commands.
2
+ require "optparse"
3
+ require "discorb/utils/colored_puts"
4
+
5
+ ARGV.delete_at 0
6
+
7
+ opt = OptionParser.new <<~BANNER
8
+ This command will setup application commands.
9
+
10
+ Usage: discorb setup [script]
11
+
12
+ script The script to setup.
13
+ BANNER
14
+ opt.parse!(ARGV)
15
+
16
+ script = ARGV[0]
17
+ script ||= "main.rb"
18
+ ENV["DISCORB_CLI_FLAG"] = "setup"
19
+
20
+ begin
21
+ load script
22
+ rescue LoadError
23
+ eputs "Could not load script: \e[31m#{script}\e[m"
24
+ else
25
+ sputs "Successfully set up commands for \e[32m#{script}\e[m."
26
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "async/http"
4
+ require "async/websocket"
5
+ require "json"
6
+ require "zlib"
4
7
 
5
8
  module Discorb
6
9
  #
@@ -464,13 +467,20 @@ module Discorb
464
467
  @first = first
465
468
  _, gateway_response = @http.get("/gateway").wait
466
469
  gateway_url = gateway_response[:url]
467
- endpoint = Async::HTTP::Endpoint.parse("#{gateway_url}?v=9&encoding=json",
470
+ endpoint = Async::HTTP::Endpoint.parse("#{gateway_url}?v=9&encoding=json&compress=zlib-stream",
468
471
  alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
469
472
  begin
470
- Async::WebSocket::Client.connect(endpoint, headers: [["User-Agent", Discorb::USER_AGENT]]) do |connection|
473
+ Async::WebSocket::Client.connect(endpoint, headers: [["User-Agent", Discorb::USER_AGENT]], handler: RawConnection) do |connection|
471
474
  @connection = connection
475
+ @zlib_stream = Zlib::Inflate.new(Zlib::MAX_WBITS)
476
+ @buffer = +""
472
477
  while (message = @connection.read)
473
- handle_gateway(message)
478
+ @buffer << message
479
+ if message.end_with?((+"\x00\x00\xff\xff").force_encoding("ASCII-8BIT"))
480
+ message = JSON.parse(@zlib_stream.inflate(@buffer), symbolize_names: true)
481
+ @buffer = +""
482
+ handle_gateway(message)
483
+ end
474
484
  end
475
485
  end
476
486
  rescue Protocol::WebSocket::ClosedError => e
@@ -490,7 +500,7 @@ module Discorb
490
500
  end
491
501
 
492
502
  def send_gateway(opcode, **value)
493
- @connection.write({ op: opcode, d: value })
503
+ @connection.write({ op: opcode, d: value }.to_json)
494
504
  @connection.flush
495
505
  @log.debug "Sent message with opcode #{opcode}: #{value.to_json.gsub(@token, "[Token]")}"
496
506
  end
@@ -548,7 +558,7 @@ module Discorb
548
558
  task.sleep((interval / 1000.0 - 1) * rand)
549
559
  loop do
550
560
  @heartbeat_before = Time.now.to_f
551
- @connection.write({ op: 1, d: @last_s })
561
+ @connection.write({ op: 1, d: @last_s }.to_json)
552
562
  @connection.flush
553
563
  @log.debug "Sent opcode 1."
554
564
  @log.debug "Waiting for heartbeat."
@@ -574,7 +584,6 @@ module Discorb
574
584
  @uncached_guilds.delete(data[:id])
575
585
  if @uncached_guilds == []
576
586
  @ready = true
577
- setup_commands.wait if @overwrite_application_commands
578
587
  dispatch(:ready)
579
588
  @log.info("Guilds were cached")
580
589
  end
@@ -974,5 +983,25 @@ module Discorb
974
983
  end
975
984
  end
976
985
  end
986
+
987
+ #
988
+ # A class for connecting websocket with raw bytes data.
989
+ # @private
990
+ #
991
+ class RawConnection < Async::WebSocket::Connection
992
+ def initialize(...)
993
+ super
994
+ end
995
+
996
+ def parse(buffer)
997
+ # noop
998
+ buffer.to_s
999
+ end
1000
+
1001
+ def dump(object)
1002
+ # noop
1003
+ object.to_s
1004
+ end
1005
+ end
977
1006
  end
978
1007
  end
data/lib/discorb/http.rb CHANGED
@@ -13,6 +13,7 @@ module Discorb
13
13
  # @!visibility private
14
14
  def initialize(client)
15
15
  @client = client
16
+ @ratelimit_handler = RatelimitHandler.new(client)
16
17
  end
17
18
 
18
19
  #
@@ -31,13 +32,10 @@ module Discorb
31
32
  #
32
33
  def get(path, headers: nil, audit_log_reason: nil, **kwargs)
33
34
  Async do |task|
35
+ @ratelimit_handler.wait("GET", path)
34
36
  resp = http.get(get_path(path), get_headers(headers, "", audit_log_reason), **kwargs)
35
- rd = resp.body
36
- data = if rd.nil? || rd.empty?
37
- nil
38
- else
39
- JSON.parse(rd, symbolize_names: true)
40
- end
37
+ data = get_response_data(resp)
38
+ @ratelimit_handler.save("GET", path, resp)
41
39
  test_error(if resp.code == "429"
42
40
  @client.log.warn "Ratelimit exceeded for #{path}, trying again in #{data[:retry_after]} seconds."
43
41
  task.sleep(data[:retry_after])
@@ -65,13 +63,10 @@ module Discorb
65
63
  #
66
64
  def post(path, body = "", headers: nil, audit_log_reason: nil, **kwargs)
67
65
  Async do |task|
66
+ @ratelimit_handler.wait("POST", path)
68
67
  resp = http.post(get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
69
- rd = resp.body
70
- data = if rd.nil? || rd.empty?
71
- nil
72
- else
73
- JSON.parse(rd, symbolize_names: true)
74
- end
68
+ data = get_response_data(resp)
69
+ @ratelimit_handler.save("POST", path, resp)
75
70
  test_error(if resp.code == "429"
76
71
  task.sleep(data[:retry_after])
77
72
  post(path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
@@ -98,13 +93,10 @@ module Discorb
98
93
  #
99
94
  def patch(path, body = "", headers: nil, audit_log_reason: nil, **kwargs)
100
95
  Async do |task|
96
+ @ratelimit_handler.wait("PATCH", path)
101
97
  resp = http.patch(get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
102
- rd = resp.body
103
- data = if rd.nil? || rd.empty?
104
- nil
105
- else
106
- JSON.parse(rd, symbolize_names: true)
107
- end
98
+ data = get_response_data(resp)
99
+ @ratelimit_handler.save("PATCH", path, resp)
108
100
  test_error(if resp.code == "429"
109
101
  task.sleep(data[:retry_after])
110
102
  patch(path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
@@ -131,13 +123,10 @@ module Discorb
131
123
  #
132
124
  def put(path, body = "", headers: nil, audit_log_reason: nil, **kwargs)
133
125
  Async do |task|
126
+ @ratelimit_handler.wait("PUT", path)
134
127
  resp = http.put(get_path(path), get_body(body), get_headers(headers, body, audit_log_reason), **kwargs)
135
- rd = resp.body
136
- data = if rd.nil? || rd.empty?
137
- nil
138
- else
139
- JSON.parse(rd, symbolize_names: true)
140
- end
128
+ data = get_response_data(resp)
129
+ @ratelimit_handler.save("PUT", path, resp)
141
130
  test_error(if resp.code == "429"
142
131
  task.sleep(data[:retry_after])
143
132
  put(path, body, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
@@ -163,13 +152,10 @@ module Discorb
163
152
  #
164
153
  def delete(path, headers: nil, audit_log_reason: nil, **kwargs)
165
154
  Async do |task|
155
+ @ratelimit_handler.wait("DELETE", path)
166
156
  resp = http.delete(get_path(path), get_headers(headers, "", audit_log_reason))
167
- rd = resp.body
168
- data = if rd.nil? || rd.empty?
169
- nil
170
- else
171
- JSON.parse(rd, symbolize_names: true)
172
- end
157
+ data = get_response_data(resp)
158
+ @ratelimit_handler.save("DELETE", path, resp)
173
159
  test_error(if resp.code == "429"
174
160
  task.sleep(data[:retry_after])
175
161
  delete(path, headers: headers, audit_log_reason: audit_log_reason, **kwargs).wait
@@ -257,6 +243,18 @@ module Discorb
257
243
  URI(full_path).path
258
244
  end
259
245
 
246
+ def get_response_data(resp)
247
+ if resp["Via"].nil?
248
+ raise CloudFlareBanError.new(@client, resp)
249
+ end
250
+ rd = resp.body
251
+ if rd.nil? || rd.empty?
252
+ nil
253
+ else
254
+ JSON.parse(rd, symbolize_names: true)
255
+ end
256
+ end
257
+
260
258
  def http
261
259
  https = Net::HTTP.new("discord.com", 443)
262
260
  https.use_ssl = true
data/lib/discorb/log.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Discorb
4
4
  # @!visibility private
5
5
  class Logger
6
- attr_reader :out, :colorize_log
6
+ attr_accessor :out, :colorize_log
7
7
 
8
8
  @levels = %i[debug info warn error fatal].freeze
9
9
 
@@ -14,11 +14,12 @@ module Discorb
14
14
  end
15
15
 
16
16
  def level
17
- @levels[@level]
17
+ self.class.levels[@level]
18
18
  end
19
19
 
20
20
  def level=(level)
21
- @level = @levels.index(level)
21
+ @level = self.class.levels.index(level)
22
+ raise ArgumentError, "Invalid log level: #{level}" unless @level
22
23
  end
23
24
 
24
25
  def debug(message)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # Class to handle rate limiting.
6
+ #
7
+ class RatelimitHandler
8
+ def initialize(client)
9
+ @client = client
10
+ @ratelimit_hash = {}
11
+ @path_ratelimit_hash = {}
12
+ end
13
+
14
+ def wait(method, path)
15
+ return if path.start_with?("https://")
16
+
17
+ return unless hash = @path_ratelimit_hash[method + path]
18
+
19
+ return unless b = @ratelimit_hash[hash]
20
+
21
+ if b[:reset_at] < Time.now.to_i
22
+ @ratelimit_hash.delete(hash)
23
+ return
24
+ end
25
+ return if b[:remaining] > 0
26
+
27
+ @client.log.info("Ratelimit reached, waiting for #{b[:reset_at] - Time.now.to_i} seconds")
28
+ sleep(b[:reset_at] - Time.now.to_i)
29
+ end
30
+
31
+ def save(method, path, resp)
32
+ return unless resp["X-RateLimit-Remaining"]
33
+
34
+ @path_ratelimit_hash[method + path] = resp["X-RateLimit-Bucket"]
35
+ @ratelimit_hash[resp["X-RateLimit-Bucket"]] = {
36
+ remaining: resp["X-RateLimit-Remaining"].to_i,
37
+ reset_at: resp["X-RateLimit-Reset"].to_i,
38
+ }
39
+ end
40
+ end
41
+ end
data/lib/discorb.rb CHANGED
@@ -39,7 +39,7 @@ module Discorb
39
39
  end
40
40
  end
41
41
 
42
- require_order = %w[common flag dictionary error http intents emoji_table modules] +
42
+ require_order = %w[common flag dictionary error rate_limit http intents emoji_table modules] +
43
43
  %w[user member guild emoji channel embed message] +
44
44
  %w[application audit_logs color components event extension] +
45
45
  %w[file guild_template image integration interaction invite log permission] +
@@ -216,8 +216,8 @@ tt {
216
216
  font-family: var(--monospace-font);
217
217
  }
218
218
 
219
- pre.code {
220
- color: var(--header-secondary);
219
+ pre.code code {
220
+ color: var(--header-secondary) !important;
221
221
  tab-size: 2;
222
222
  }
223
223
 
@@ -310,6 +310,11 @@ li.even {
310
310
  background: var(--background-secondary);
311
311
  }
312
312
 
313
+ li.current {
314
+ background: var(--background-tertiary);
315
+ }
316
+
317
+
313
318
  .item:hover {
314
319
  background: var(--background-secondary-alt);
315
320
  }
@@ -516,4 +521,10 @@ li.r2 {
516
521
  border-color: var(--background-tertiary);
517
522
  color: var(--interactive-active);
518
523
  fill: var(--interactive-active);
524
+ }
525
+
526
+ dd pre.code {
527
+ padding: 9px 14px;
528
+ border-radius: 4px;
529
+ border: 1px solid var(--background-tertiary);
519
530
  }
@@ -47,9 +47,9 @@
47
47
  <ul id="full_list" class="method">
48
48
  <!--replace-->
49
49
  <!--template-->
50
- <li class="!eo!">
50
+ <li class="!class!">
51
51
  <div class="item">
52
- <span class='object_link'><a href="../!version!/index.html" title="!version!">!version!</a></span>
52
+ <span class='object_link'><a href="!path!/index.html" title="!version!">!version!</a></span>
53
53
  <small class='git_sha'>!sha!</small>
54
54
  </div>
55
55
  </li>
@@ -1,12 +1,24 @@
1
- def build_version_sidebar(dir)
1
+ def build_version_sidebar(dir, version)
2
2
  raw = File.read("template-replace/resources/version_list.html")
3
3
  template = raw.match(/<!--template-->(.*)<!--endtemplate-->/m)[1]
4
4
  raw.gsub!(template, "")
5
5
  res = +""
6
- `git tag`.force_encoding("utf-8").split("\n").each.with_index do |tag, i|
6
+ i = 0
7
+ `git tag`.force_encoding("utf-8").split("\n").each.with_index do |tag|
8
+ i += 1
7
9
  sha = `git rev-parse #{tag}`.force_encoding("utf-8").strip
8
10
  version = tag.delete_prefix("v")
9
- res += template.gsub("!version!", version).gsub("!eo!", i % 2 == 0 ? "even" : "odd").gsub("!sha!", sha)
11
+ cls = i % 2 == 0 ? "even" : "odd"
12
+ if version == "."
13
+ cls += " current"
14
+ end
15
+ res += template.gsub("!version!", version).gsub("!path!", "../" + version).gsub("!class!", cls).gsub("!sha!", sha)
10
16
  end
17
+ i += 1
18
+ cls = i % 2 == 0 ? "even" : "odd"
19
+ if version == "main"
20
+ cls += " current"
21
+ end
22
+ res += template.gsub("!version!", "main").gsub("!path!", "../main").gsub("!class!", cls).gsub("!sha!", "(Latest on GitHub)")
11
23
  File.write(dir + "/version_list.html", raw.gsub("<!--replace-->", res))
12
24
  end
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discorb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.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-09-05 00:00:00.000000000 Z
11
+ date: 2021-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 1.30.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 1.30.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: async-http
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.56.5
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.56.5
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: async-websocket
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 0.19.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 0.19.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: mime-types
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -74,6 +74,7 @@ executables:
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - ".github/workflows/build_main.yml"
77
78
  - ".gitignore"
78
79
  - ".yardopts"
79
80
  - Changelog.md
@@ -88,11 +89,23 @@ files:
88
89
  - discorb.gemspec
89
90
  - docs/Examples.md
90
91
  - docs/application_command.md
92
+ - docs/assets/01_new_app.png
93
+ - docs/assets/02_bot_name.png
94
+ - docs/assets/03_add_bot.png
95
+ - docs/assets/04_token_copy.png
96
+ - docs/assets/05_oauth.png
97
+ - docs/assets/06_online.png
98
+ - docs/assets/07_hello_infinite.png
99
+ - docs/assets/08_hello_once.png
100
+ - docs/assets/unused_ping_pong.png
91
101
  - docs/cli.md
92
102
  - docs/cli/init.md
93
103
  - docs/cli/irb.md
104
+ - docs/cli/run.md
105
+ - docs/cli/setup.md
94
106
  - docs/events.md
95
107
  - docs/extension.md
108
+ - docs/tutorial.md
96
109
  - docs/voice_events.md
97
110
  - examples/commands/bookmarker.rb
98
111
  - examples/commands/hello.rb
@@ -122,8 +135,11 @@ files:
122
135
  - lib/discorb/emoji_table.rb
123
136
  - lib/discorb/error.rb
124
137
  - lib/discorb/event.rb
138
+ - lib/discorb/exe/about.rb
125
139
  - lib/discorb/exe/init.rb
126
140
  - lib/discorb/exe/irb.rb
141
+ - lib/discorb/exe/run.rb
142
+ - lib/discorb/exe/setup.rb
127
143
  - lib/discorb/exe/show.rb
128
144
  - lib/discorb/extend.rb
129
145
  - lib/discorb/extension.rb
@@ -145,6 +161,7 @@ files:
145
161
  - lib/discorb/modules.rb
146
162
  - lib/discorb/permission.rb
147
163
  - lib/discorb/presence.rb
164
+ - lib/discorb/rate_limit.rb
148
165
  - lib/discorb/reaction.rb
149
166
  - lib/discorb/role.rb
150
167
  - lib/discorb/sticker.rb
@@ -167,7 +184,7 @@ metadata:
167
184
  allowed_push_host: https://rubygems.org
168
185
  homepage_uri: https://github.com/discorb-lib/discorb
169
186
  source_code_uri: https://github.com/discorb-lib/discorb
170
- changelog_uri: https://github.com/discorb-lib/discorb/blob/main/Changelog.md
187
+ changelog_uri: https://discorb-lib.github.io/file.Changelog.html
171
188
  documentation_uri: https://discorb-lib.github.io
172
189
  post_install_message:
173
190
  rdoc_options: []