Class: OnyxCord::Gateway::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/onyxcord/gateway/client.rb

Overview

Client for the Discord gateway protocol — fully async.

Constant Summary collapse

LARGE_THRESHOLD =
100
GATEWAY_VERSION =
ENV.fetch('DISCORD_GATEWAY_VERSION', '10').to_i
FATAL_CLOSE_CODES =
[4003, 4004, 4011, 4014].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = ALL_INTENTS) ⇒ Client

Returns a new instance of Client.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/onyxcord/gateway/client.rb', line 22

def initialize(bot, token, shard_key = nil, compress_mode = :stream, intents = ALL_INTENTS)
  @token = token
  @bot = bot
  @shard_key = shard_key
  @ws_success = false
  @check_heartbeat_acks = true
  @compress_mode = compress_mode
  @intents = intents
  @send_limiter = Internal::RateLimiter::Gateway.new
  @zlib_mutex = Mutex.new
  @connection = nil
  @closed = true
  @pipe_broken = false
  @missed_heartbeat_acks = 0
end

Instance Attribute Details

#check_heartbeat_acksObject

Returns the value of attribute check_heartbeat_acks.



19
20
21
# File 'lib/onyxcord/gateway/client.rb', line 19

def check_heartbeat_acks
  @check_heartbeat_acks
end

#intentsObject (readonly)

Returns the value of attribute intents.



20
21
22
# File 'lib/onyxcord/gateway/client.rb', line 20

def intents
  @intents
end

#latencyObject (readonly)

Returns the value of attribute latency.



20
21
22
# File 'lib/onyxcord/gateway/client.rb', line 20

def latency
  @latency
end

Instance Method Details

#heartbeatObject



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/onyxcord/gateway/client.rb', line 92

def heartbeat
  if check_heartbeat_acks
    if @last_heartbeat_acked
      @missed_heartbeat_acks = 0
    else
      @missed_heartbeat_acks += 1
      if @missed_heartbeat_acks >= 2
        LOGGER.warn('Last heartbeats were not acked — zombie connection! Reconnecting')
        @pipe_broken = true
        reconnect
        return
      end

      LOGGER.warn('Last heartbeat was not acked — waiting one more interval before reconnecting')
    end
    @last_heartbeat_acked = false
  end
  send_heartbeat(@session ? @session.sequence : 0)
end

#identifyObject



117
118
119
120
121
122
123
124
# File 'lib/onyxcord/gateway/client.rb', line 117

def identify
  compress = @compress_mode == :large
  send_identify(@token, {
                  os: RUBY_PLATFORM,
                  browser: 'onyxcord',
                  device: 'onyxcord'
                }, compress, LARGE_THRESHOLD, @shard_key, @intents)
end

#inject_error(e) ⇒ Object



88
89
90
# File 'lib/onyxcord/gateway/client.rb', line 88

def inject_error(e)
  handle_internal_close(e)
end

#inject_reconnect(url = nil) ⇒ Object



79
80
81
82
# File 'lib/onyxcord/gateway/client.rb', line 79

def inject_reconnect(url = nil)
  data = url ? { op: Internal::Gateway::Opcodes::RECONNECT, d: { url: url } } : { op: Internal::Gateway::Opcodes::RECONNECT, d: nil }
  handle_message(data.to_json)
end

#inject_resume(seq) ⇒ Object



84
85
86
# File 'lib/onyxcord/gateway/client.rb', line 84

def inject_resume(seq)
  send_resume(raw_token, @session_id, seq || @sequence)
end

#killObject



71
72
73
# File 'lib/onyxcord/gateway/client.rb', line 71

def kill
  @task&.stop
end

#notify_readyObject



75
76
77
# File 'lib/onyxcord/gateway/client.rb', line 75

def notify_ready
  @ws_success = true
end

#open?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/onyxcord/gateway/client.rb', line 60

def open?
  !@closed && @connection
end

#reconnect(attempt_resume = true) ⇒ Object



153
154
155
156
157
158
159
160
161
# File 'lib/onyxcord/gateway/client.rb', line 153

def reconnect(attempt_resume = true)
  @session.suspend if @session && attempt_resume
  @session.invalidate if attempt_resume && @session && !@received_hello
  @instant_reconnect = true
  @should_reconnect = true
  @heartbeat_task&.stop
  @heartbeat_task = nil
  close(4000)
end

#resumeObject



149
150
151
# File 'lib/onyxcord/gateway/client.rb', line 149

def resume
  send_resume(@token, @session.session_id, @session.sequence)
end

#runObject



50
51
52
53
54
# File 'lib/onyxcord/gateway/client.rb', line 50

def run
  @reactor_task = Async::Task.current
  connect_loop
  LOGGER.warn('The gateway loop exited!')
end

#run_asyncObject

Connect to the gateway server inside an Async reactor.



39
40
41
42
43
44
45
46
47
48
# File 'lib/onyxcord/gateway/client.rb', line 39

def run_async
  @task = Internal::AsyncRuntime.async { run }

  LOGGER.debug('Gateway task created! Waiting for confirmation...')
  loop do
    Internal::AsyncRuntime.sleep(0.5)
    break if @ws_success
    break if @should_reconnect == false
  end
end

#send_heartbeat(sequence) ⇒ Object



112
113
114
115
# File 'lib/onyxcord/gateway/client.rb', line 112

def send_heartbeat(sequence)
  @last_heartbeat_sent_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  send_packet(Internal::Gateway::Opcodes::HEARTBEAT, sequence)
end

#send_identify(token, properties, compress, large_threshold, shard_key = nil, intents = ALL_INTENTS) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/onyxcord/gateway/client.rb', line 126

def send_identify(token, properties, compress, large_threshold, shard_key = nil, intents = ALL_INTENTS)
  data = {
    token: token,
    properties: properties,
    compress: compress,
    large_threshold: large_threshold,
    intents: intents
  }
  data[:shard] = shard_key if shard_key
  send_packet(Internal::Gateway::Opcodes::IDENTIFY, data)
end

#send_packet(opcode, packet) ⇒ Object



171
172
173
# File 'lib/onyxcord/gateway/client.rb', line 171

def send_packet(opcode, packet)
  send({ op: opcode, d: packet }.to_json)
end

#send_raw(data, _type = :text) ⇒ Object



175
176
177
# File 'lib/onyxcord/gateway/client.rb', line 175

def send_raw(data, _type = :text)
  send(data)
end

#send_request_members(server_id, query, limit) ⇒ Object



167
168
169
# File 'lib/onyxcord/gateway/client.rb', line 167

def send_request_members(server_id, query, limit)
  send_packet(Internal::Gateway::Opcodes::REQUEST_MEMBERS, { guild_id: server_id, query: query, limit: limit })
end

#send_resume(token, session_id, seq) ⇒ Object



163
164
165
# File 'lib/onyxcord/gateway/client.rb', line 163

def send_resume(token, session_id, seq)
  send_packet(Internal::Gateway::Opcodes::RESUME, { token: token, session_id: session_id, seq: seq })
end

#send_status_update(status, since, game, afk) ⇒ Object



138
139
140
# File 'lib/onyxcord/gateway/client.rb', line 138

def send_status_update(status, since, game, afk)
  send_packet(Internal::Gateway::Opcodes::PRESENCE, { status: status, since: since, game: game, afk: afk })
end

#send_voice_state_update(server_id, channel_id, self_mute, self_deaf) ⇒ Object



142
143
144
145
146
147
# File 'lib/onyxcord/gateway/client.rb', line 142

def send_voice_state_update(server_id, channel_id, self_mute, self_deaf)
  send_packet(Internal::Gateway::Opcodes::VOICE_STATE, {
                guild_id: server_id, channel_id: channel_id,
                self_mute: self_mute, self_deaf: self_deaf
              })
end

#stopObject



64
65
66
67
68
69
# File 'lib/onyxcord/gateway/client.rb', line 64

def stop
  @should_reconnect = false
  @intentional_shutdown = true
  close
  nil
end

#syncObject



56
57
58
# File 'lib/onyxcord/gateway/client.rb', line 56

def sync
  @task&.wait
end