discorb 0.15.1 → 0.16.0

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: 4bc9c7d6433e9e093a0d58d821ed97e3f001d63224c9f65eb1693a2ee3d16f7d
4
- data.tar.gz: daff87553c1996cbc1a68765ee3df54947f46b7660fc1a16f0a17a0e0660e2c7
3
+ metadata.gz: 6537aa3c45f64e6b0e8040a5a0d989934fb58d766ee6de305bf04132630a7b94
4
+ data.tar.gz: 68512bc0d2abd0738cc6e81ba2b794feb87480d6d84197a51a6deba739d31d5a
5
5
  SHA512:
6
- metadata.gz: dceaba51436368feeb9e99bc4afda5cea9ac45aa0525bb44cdd33c427fef6c6617d17bf1a56b27acac137df05b4d6f27395e22883ce574e4e48d3e7a8ca05a13
7
- data.tar.gz: 81622ca789eee2f8c55b4fd98e7c0bdc602d164247602508c8b041757d61fcb77f659d01142db8a8c16b9dd6e6cc402ebf9f45cf881e787a77449ba4831b8376
6
+ metadata.gz: 239e4658a2ade0ea8382655970a1eecbb3c62c7c3619e3de8fdf932c1c0a4053e35b722bef5dcdbd67477754388cc3c157c9be6360ef7625fe9f84504b5ecf90
7
+ data.tar.gz: d830a06c7a505ee31aeb3242e7aa59dadf6abab24a26c357b9e29e5f94d5fef7a889b114d4b7793ffda3f50b19a6be0c3f87921934393b03e71c5f50bfa96de4
data/Changelog.md CHANGED
@@ -4,6 +4,15 @@
4
4
 
5
5
  # Changelog
6
6
 
7
+ ## v0.16
8
+
9
+ ### v0.16.0
10
+
11
+ - Change!: Use built-in Logger instead of custom Logger.
12
+ - Delete!: `--log-level`, `--[no-]log-color` is deleted.
13
+ - Add: Support sharding
14
+ - Add: Use Mutex for preventing connection duplications.
15
+
7
16
  ## v0.15
8
17
 
9
18
  ### v0.15.1
data/docs/events.md CHANGED
@@ -30,13 +30,13 @@ class << client
30
30
  end
31
31
  ```
32
32
 
33
- If you want to seperate event handlers from the client, consider using {Discorb::Extension}. {file:docs/extension.md Learn more about extensions}.
33
+ If you want to separate event handlers from the client, consider using {Discorb::Extension}. {file:docs/extension.md Learn more about extensions}.
34
34
 
35
- Since v0.6.1, you can set `:override` to `true` to register overridable event handlers.
35
+ Since v0.6.1, you can set `:override` to `true` to register event handlers that can be overridden.
36
36
 
37
37
  ```ruby
38
38
  client.on :message, override: true do |event|
39
- puts "This event handler is overrideable!"
39
+ puts "This event handler can be overridden."
40
40
  end
41
41
 
42
42
  client.on :message do |event|
@@ -44,8 +44,8 @@ client.on :message do |event|
44
44
  end
45
45
  ```
46
46
 
47
- This example will print `Override!`, but not `This event handler is overrideable!`.
48
- This is useful for registering event handlers as default behaviour, such as error handlers.
47
+ This example will print `Override!`, but not `This event handler can be overridden.`.
48
+ This is useful for registering event handlers as default behavior, such as error handlers.
49
49
 
50
50
  ```ruby
51
51
  # In the library...
@@ -95,6 +95,22 @@ Defaults to printing the error to stderr, override to handle it yourself.
95
95
  Fires when `discorb setup` is run.
96
96
  This is useful for setting up some dependencies, such as the database.
97
97
 
98
+ #### `shard_standby(shard)`
99
+
100
+ Fires when a shard is standby.
101
+
102
+ | Parameter | Type | Description |
103
+ | ---------- | ----- | ----------- |
104
+ |`shard` | {Discorb::Shard} | The shard that is standby. |
105
+
106
+ #### `shard_resumed(shard)`
107
+
108
+ Fires when a shard is resumed connection.
109
+
110
+ | Parameter | Type | Description |
111
+ | ---------- | ----- | ----------- |
112
+ |`shard` | {Discorb::Shard} | The shard that is standby. |
113
+
98
114
  ### Guild events
99
115
 
100
116
  #### `guild_join(guild)`
@@ -2,7 +2,7 @@
2
2
  require "discorb"
3
3
  intents = Discorb::Intents.new
4
4
  intents.members = true
5
- client = Discorb::Client.new(intents: intents, log: $stdout, colorize_log: true)
5
+ client = Discorb::Client.new(intents: intents)
6
6
 
7
7
  def convert_role(guild, string)
8
8
  guild.roles.find do |role|
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require "discorb"
3
+
4
+ client = Discorb::Client.new(log: Logger.new($stdout))
5
+
6
+ client.once :standby do
7
+ puts "Logged in as #{client.user}"
8
+ end
9
+
10
+ client.on :message do |message|
11
+ next if message.author.bot?
12
+ next unless message.content == "!inspect"
13
+
14
+ message.channel.post("I'm #{client.user}, running on shard #{client.shard_id}!")
15
+ end
16
+
17
+ client.run(ENV["DISCORD_BOT_TOKEN"], shards: [0, 1], shard_count: 2)
@@ -160,7 +160,7 @@ module Discorb
160
160
  end
161
161
  end
162
162
  end
163
- @log.info "Successfully setup commands"
163
+ @logger.info "Successfully setup commands"
164
164
  end
165
165
  end
166
166
  end
@@ -67,7 +67,7 @@ module Discorb
67
67
  descendants.each do |klass|
68
68
  return klass.new(client, data, no_cache: no_cache) if !klass.channel_type.nil? && klass.channel_type == data[:type]
69
69
  end
70
- client.log.warn("Unknown channel type #{data[:type]}, initialized GuildChannel")
70
+ client.logger.warn("Unknown channel type #{data[:type]}, initialized GuildChannel")
71
71
  GuildChannel.new(client, data)
72
72
  end
73
73
 
@@ -40,8 +40,6 @@ module Discorb
40
40
  attr_reader :emojis
41
41
  # @return [Discorb::Dictionary{Discorb::Snowflake => Discorb::Message}] A dictionary of messages.
42
42
  attr_reader :messages
43
- # @return [Discorb::Logger] The logger.
44
- attr_reader :log
45
43
  # @return [Array<Discorb::ApplicationCommand::Command>] The commands that the client is using.
46
44
  attr_reader :commands
47
45
  # @return [Float] The ping of the client.
@@ -50,15 +48,28 @@ module Discorb
50
48
  attr_reader :ping
51
49
  # @return [:initialized, :running, :closed] The status of the client.
52
50
  attr_reader :status
53
- # @return [Integer] The session ID of connection.
54
- attr_reader :session_id
55
51
  # @return [Hash{String => Discorb::Extension}] The loaded extensions.
56
52
  attr_reader :extensions
57
- #
53
+ # @return [Hash{Integer => Discorb::Shard}] The shards of the client.
54
+ attr_reader :shards
58
55
  # @private
59
56
  # @return [Hash{Discorb::Snowflake => Discorb::ApplicationCommand::Command}] The commands on the top level.
60
- #
61
57
  attr_reader :bottom_commands
58
+ # @private
59
+ # @return [{String => Thread::Mutex}] A hash of mutexes.
60
+ attr_reader :mutex
61
+
62
+ # @!attribute [r] session_id
63
+ # @return [String] The session ID of the client or current shard.
64
+ # @return [nil] If not connected to the gateway.
65
+ # @!attribute [r] shard
66
+ # @return [Discorb::Shard] The current shard. This is implemented with Thread variables.
67
+ # @return [nil] If client has no shard.
68
+ # @!attribute [r] shard_id
69
+ # @return [Discorb::Shard] The current shard ID. This is implemented with Thread variables.
70
+ # @return [nil] If client has no shard.
71
+ # @!attribute [r] logger
72
+ # @return [Logger] The logger.
62
73
 
63
74
  #
64
75
  # Initializes a new client.
@@ -66,8 +77,7 @@ module Discorb
66
77
  # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions that the client is using.
67
78
  # @param [Discorb::Intents] intents The intents that the client is currently using.
68
79
  # @param [Integer] message_caches The number of messages to cache.
69
- # @param [#write] log The IO object to use for logging.
70
- # @param [Boolean] colorize_log Whether to colorize the log.
80
+ # @param [Logger] logger The IO object to use for logging.
71
81
  # @param [:debug, :info, :warn, :error, :critical] log_level The log level.
72
82
  # @param [Boolean] wait_until_ready Whether to delay event dispatch until ready.
73
83
  # @param [Boolean] fetch_member Whether to fetch member on ready. This may slow down the client. Default to `false`.
@@ -75,7 +85,7 @@ module Discorb
75
85
  #
76
86
  def initialize(
77
87
  allowed_mentions: nil, intents: nil, message_caches: 1000,
78
- log: nil, colorize_log: false, log_level: :info,
88
+ logger: nil,
79
89
  wait_until_ready: true, fetch_member: false,
80
90
  title: nil
81
91
  )
@@ -83,7 +93,7 @@ module Discorb
83
93
  @intents = (intents or Intents.default)
84
94
  @events = {}
85
95
  @api_version = nil
86
- @log = Logger.new(log, colorize_log, log_level)
96
+ @logger = logger || Logger.new($stdout, progname: "discorb")
87
97
  @user = nil
88
98
  @users = Discorb::Dictionary.new
89
99
  @channels = Discorb::Dictionary.new
@@ -103,6 +113,8 @@ module Discorb
103
113
  @fetch_member = fetch_member
104
114
  @title = title
105
115
  @extensions = {}
116
+ @mutex = {}
117
+ @shards = {}
106
118
  set_default_events
107
119
  end
108
120
 
@@ -183,16 +195,16 @@ module Discorb
183
195
  events << event_method
184
196
  end
185
197
  if events.nil?
186
- @log.debug "Event #{event_name} doesn't have any proc, skipping"
198
+ logger.debug "Event #{event_name} doesn't have any proc, skipping"
187
199
  next
188
200
  end
189
- @log.debug "Dispatching event #{event_name}"
201
+ logger.debug "Dispatching event #{event_name}"
190
202
  events.each do |block|
191
203
  Async do
192
204
  Async(annotation: "Discorb event: #{event_name}") do |_task|
193
205
  @events[event_name].delete(block) if block.is_a?(Discorb::EventHandler) && block.metadata[:once]
194
206
  block.call(*args)
195
- @log.debug "Dispatched proc with ID #{block.id.inspect}"
207
+ logger.debug "Dispatched proc with ID #{block.id.inspect}"
196
208
  rescue StandardError, ScriptError => e
197
209
  dispatch(:error, event_name, args, e)
198
210
  end
@@ -316,7 +328,7 @@ module Discorb
316
328
  }
317
329
  payload[:activities] = [activity.to_hash] unless activity.nil?
318
330
  payload[:status] = status unless status.nil?
319
- if @connection
331
+ if connection
320
332
  Async do
321
333
  send_gateway(3, **payload)
322
334
  end
@@ -427,15 +439,15 @@ module Discorb
427
439
  #
428
440
  # @note If the token is nil, you should use `discorb run` with the `-e` or `--env` option.
429
441
  #
430
- def run(token = nil)
442
+ def run(token = nil, shards: nil, shard_count: nil)
431
443
  token ||= ENV["DISCORB_CLI_TOKEN"]
432
444
  raise ArgumentError, "Token is not specified, and -e/--env is not specified" if token.nil?
433
445
  case ENV["DISCORB_CLI_FLAG"]
434
446
  when nil
435
- start_client(token)
447
+ start_client(token, shards: shards, shard_count: shard_count)
436
448
  when "run"
437
449
  before_run(token)
438
- start_client(token)
450
+ start_client(token, shards: shards, shard_count: shard_count)
439
451
  when "setup"
440
452
  run_setup(token)
441
453
  end
@@ -445,10 +457,33 @@ module Discorb
445
457
  # Stops the client.
446
458
  #
447
459
  def close!
448
- @connection.send_close
460
+ if @shards.any?
461
+ @shards.each(&:close!)
462
+ else
463
+ @connection.send_close
464
+ end
449
465
  @tasks.each(&:stop)
450
466
  @status = :closed
451
- @close_condition.signal
467
+ end
468
+
469
+ def session_id
470
+ if shard
471
+ shard.session_id
472
+ else
473
+ @session_id
474
+ end
475
+ end
476
+
477
+ def logger
478
+ shard&.logger || @logger
479
+ end
480
+
481
+ def shard
482
+ Thread.current.thread_variable_get("shard")
483
+ end
484
+
485
+ def shard_id
486
+ Thread.current.thread_variable_get("shard_id")
452
487
  end
453
488
 
454
489
  private
@@ -457,22 +492,6 @@ module Discorb
457
492
  require "json"
458
493
  options = JSON.parse(ENV["DISCORB_CLI_OPTIONS"], symbolize_names: true)
459
494
  setup_commands(token) if options[:setup]
460
- if options[:log_level]
461
- if options[:log_level] == "none"
462
- @log.out = nil
463
- else
464
- @log.out = case options[:log_file]
465
- when nil, "stderr"
466
- $stderr
467
- when "stdout"
468
- $stdout
469
- else
470
- ::File.open(options[:log_file], "a")
471
- end
472
- @log.level = options[:log_level].to_sym
473
- @log.colorize_log = options[:log_color].nil? ? @log.out.isatty : options[:log_color]
474
- end
475
- end
476
495
  end
477
496
 
478
497
  def run_setup(token)
@@ -490,32 +509,93 @@ module Discorb
490
509
  end
491
510
  end
492
511
 
493
- def start_client(token)
494
- Async do |_task|
495
- Signal.trap(:SIGINT) do
496
- @log.info "SIGINT received, closing..."
497
- Signal.trap(:SIGINT, "DEFAULT")
498
- close!
512
+ def set_status(status, shard)
513
+ if shard.nil?
514
+ @status = status
515
+ else
516
+ @shards[shard].status = status
517
+ end
518
+ end
519
+
520
+ def connection
521
+ if shard_id
522
+ @shards[shard_id].connection
523
+ else
524
+ @connection
525
+ end
526
+ end
527
+
528
+ def connection=(value)
529
+ if shard_id
530
+ @shards[shard_id].connection = value
531
+ else
532
+ @connection = value
533
+ end
534
+ end
535
+
536
+ def session_id=(value)
537
+ if shard_id
538
+ @shards[shard_id].session_id = value
539
+ else
540
+ @session_id = value
541
+ end
542
+ end
543
+
544
+ def start_client(token, shards: nil, shard_count: nil)
545
+ @token = token.to_s
546
+ @shard_count = shard_count
547
+ Signal.trap(:SIGINT) do
548
+ logger.info "SIGINT received, closing..."
549
+ Signal.trap(:SIGINT, "DEFAULT")
550
+ close!
551
+ end
552
+ if shards.nil?
553
+ main_loop(nil)
554
+ else
555
+ @shards = shards.map.with_index do |shard, i|
556
+ Shard.new(self, shard, shard_count, i)
499
557
  end
500
- @token = token.to_s
501
- @close_condition = Async::Condition.new
502
- @main_task = Async do
503
- @status = :running
504
- connect_gateway(false).wait
505
- rescue StandardError
506
- @status = :stopped
507
- @close_condition.signal
508
- raise
558
+ @shards[..-1].each_with_index do |shard, i|
559
+ shard.next_shard = @shards[i + 1]
509
560
  end
510
- @close_condition.wait
511
- @main_task.stop
561
+ @shards.each { |s| s.thread.join }
562
+ end
563
+ end
564
+
565
+ def main_loop(shard)
566
+ close_condition = Async::Condition.new
567
+ self.main_task = Async do
568
+ set_status(:running, shard)
569
+ connect_gateway(false).wait
570
+ rescue StandardError
571
+ set_status(:running, shard)
572
+ close_condition.signal
573
+ raise
574
+ end
575
+ close_condition.wait
576
+ main_task.stop
577
+ end
578
+
579
+ def main_task
580
+ if shard_id
581
+ shard.main_task
582
+ else
583
+ @main_task
584
+ end
585
+ end
586
+
587
+ def main_task=(value)
588
+ if shard_id
589
+ shard.main_task = value
590
+ else
591
+ @main_task = value
512
592
  end
513
593
  end
514
594
 
515
595
  def set_default_events
516
596
  on :error, override: true do |event_name, _args, e|
517
597
  message = "An error occurred while dispatching #{event_name}:\n#{e.full_message}"
518
- @log.error message, fallback: $stderr
598
+ logger.error message, fallback: $stderr
519
599
  end
520
600
 
521
601
  once :standby do
@@ -4,7 +4,7 @@ module Discorb
4
4
  # @return [String] The API base URL.
5
5
  API_BASE_URL = "https://discord.com/api/v10"
6
6
  # @return [String] The version of discorb.
7
- VERSION = "0.15.1"
7
+ VERSION = "0.16.0"
8
8
  # @return [Array<Integer>] The version array of discorb.
9
9
  VERSION_ARRAY = VERSION.split(".").map(&:to_i).freeze
10
10
  # @return [String] The user agent for the bot.
@@ -164,8 +164,8 @@ def git_init
164
164
  system "git add ."
165
165
  system "git commit -m \"Initial commit\""
166
166
  sputs "Initialized repository, use " \
167
- "\e[32mgit commit --amend -m '...'\e[92m" \
168
- " to change commit message of initial commit.\n"
167
+ "\e[32mgit commit --amend -m '...'\e[92m" \
168
+ " to change commit message of initial commit.\n"
169
169
  end
170
170
 
171
171
  # @private
@@ -228,9 +228,9 @@ if (dir = ARGV[0])
228
228
  iputs "Found \e[30m#{dir}\e[90m and empty, using this directory."
229
229
  elsif $values[:force]
230
230
  iputs "Found \e[30m#{dir}\e[90m and not empty, but force is on, using this directory."
231
- else
232
- eputs "Directory \e[31m#{dir}\e[91m already exists and not empty. Use \e[31m-f\e[91m to force."
233
- exit
231
+ else
232
+ eputs "Directory \e[31m#{dir}\e[91m already exists and not empty. Use \e[31m-f\e[91m to force."
233
+ exit
234
234
  end
235
235
  else
236
236
  Dir.mkdir($path)
@@ -4,10 +4,9 @@ require "optparse"
4
4
  require "json"
5
5
  require "discorb/utils/colored_puts"
6
6
  require "io/console"
7
+ require "discorb"
7
8
 
8
9
  ARGV.delete_at 0
9
- # @private
10
- LOG_LEVELS = %w[none debug info warn error fatal].freeze
11
10
 
12
11
  opt = OptionParser.new <<~BANNER
13
12
  This command will run a client.
@@ -18,23 +17,10 @@ opt = OptionParser.new <<~BANNER
18
17
  BANNER
19
18
  options = {
20
19
  title: nil,
21
- log_level: nil,
22
- log_file: nil,
23
- log_color: nil,
24
20
  setup: nil,
25
21
  token: false,
26
22
  bundler: :default,
27
23
  }
28
- opt.on("-l", "--log-level LEVEL", "Log level.") do |v|
29
- unless LOG_LEVELS.include? v.downcase
30
- eputs "Invalid log level: \e[31m#{v}\e[91m"
31
- eputs "Valid log levels: \e[31m#{LOG_LEVELS.join("\e[91m, \e[31m")}\e[91m"
32
- exit 1
33
- end
34
- options[:log_level] = v.downcase
35
- end
36
- opt.on("-f", "--log-file FILE", "File to write log to.") { |v| options[:log_file] = v }
37
- opt.on("-c", "--[no-]log-color", "Whether to colorize log output.") { |v| options[:log_color] = v }
38
24
  opt.on("-s", "--setup", "Whether to setup application commands.") { |v| options[:setup] = v }
39
25
  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 }
40
26
  opt.on("-t", "--title TITLE", "The title of process.") { |v| options[:title] = v }