discorb 0.15.1 → 0.16.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: 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 }