Class: DiscordRDA::GatewayClient

Inherits:
Object
  • Object
show all
Defined in:
lib/discord_rda/connection/gateway_client.rb

Overview

WebSocket client for Discord Gateway. Handles connection, heartbeat, resume, and message dispatch.

Examples:

Basic usage

gateway = GatewayClient.new(config, event_bus)
gateway.connect
gateway.run

Constant Summary collapse

DEFAULT_VERSION =

Gateway versions

10
ENCODING =

Gateway encoding

'json'
OPCODES =

Gateway opcodes

{
  dispatch: 0,
  heartbeat: 1,
  identify: 2,
  presence_update: 3,
  voice_state_update: 4,
  resume: 6,
  reconnect: 7,
  request_guild_members: 8,
  invalid_session: 9,
  hello: 10,
  heartbeat_ack: 11
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, event_bus, logger, shard_id: 0, shard_count: 1) ⇒ GatewayClient

Initialize the gateway client

Parameters:

  • config (Configuration)

    Bot configuration

  • event_bus (EventBus)

    Event bus instance

  • logger (Logger)

    Logger instance

  • shard_id (Integer) (defaults to: 0)

    Shard ID

  • shard_count (Integer) (defaults to: 1)

    Total shard count



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/discord_rda/connection/gateway_client.rb', line 67

def initialize(config, event_bus, logger, shard_id: 0, shard_count: 1)
  @config = config
  @event_bus = event_bus
  @logger = logger
  @shard_id = shard_id
  @shard_count = shard_count
  @sequence = 0
  @session_id = nil
  @connected = false
  @heartbeat_interval = nil
  @heartbeat_task = nil
  @websocket = nil
  @zlib = nil
  @buffer = +''
  @last_heartbeat_ack = Time.now
  @resume_gateway_url = nil
end

Instance Attribute Details

#configConfiguration (readonly)

Returns Configuration instance.

Returns:



41
42
43
# File 'lib/discord_rda/connection/gateway_client.rb', line 41

def config
  @config
end

#connectedBoolean (readonly)

Returns Whether connected.

Returns:

  • (Boolean)

    Whether connected



59
60
61
# File 'lib/discord_rda/connection/gateway_client.rb', line 59

def connected
  @connected
end

#event_busEventBus (readonly)

Returns Event bus for dispatching events.

Returns:

  • (EventBus)

    Event bus for dispatching events



44
45
46
# File 'lib/discord_rda/connection/gateway_client.rb', line 44

def event_bus
  @event_bus
end

#loggerLogger (readonly)

Returns Logger instance.

Returns:

  • (Logger)

    Logger instance



47
48
49
# File 'lib/discord_rda/connection/gateway_client.rb', line 47

def logger
  @logger
end

#resume_gateway_urlString? (readonly)

Returns Resume gateway URL.

Returns:

  • (String, nil)

    Resume gateway URL



56
57
58
# File 'lib/discord_rda/connection/gateway_client.rb', line 56

def resume_gateway_url
  @resume_gateway_url
end

#sequenceInteger (readonly)

Returns Current sequence number.

Returns:

  • (Integer)

    Current sequence number



50
51
52
# File 'lib/discord_rda/connection/gateway_client.rb', line 50

def sequence
  @sequence
end

#session_idString (readonly)

Returns Session ID for resuming.

Returns:

  • (String)

    Session ID for resuming



53
54
55
# File 'lib/discord_rda/connection/gateway_client.rb', line 53

def session_id
  @session_id
end

Instance Method Details

#connectAsync::Task

Connect to the Discord Gateway

Returns:

  • (Async::Task)

    Connection task



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/discord_rda/connection/gateway_client.rb', line 87

def connect
  Async do
    gateway_url = @resume_gateway_url || fetch_gateway_url
    endpoint = build_endpoint(gateway_url)

    @logger&.info('Connecting to Gateway', shard: @shard_id, url: gateway_url)

    @zlib = Zlib::Inflate.new(15 + 32) if @config.gateway_compression == :zlib_stream
    @buffer = +''

    begin
      Async::WebSocket::Client.connect(endpoint) do |websocket|
        @websocket = websocket
        @connected = true
        @logger&.info('Gateway connected', shard: @shard_id)

        handle_messages
      end
    rescue => e
      @logger&.error('Gateway connection error', error: e, shard: @shard_id)
      @connected = false
      raise
    end
  end
end

#disconnectvoid

This method returns an undefined value.

Disconnect from the Gateway



121
122
123
124
125
126
127
# File 'lib/discord_rda/connection/gateway_client.rb', line 121

def disconnect
  @connected = false
  @heartbeat_task&.stop
  @websocket&.close
  @zlib&.close
  @logger&.info('Gateway disconnected', shard: @shard_id)
end

#identifyvoid

This method returns an undefined value.

Send an identify payload



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/discord_rda/connection/gateway_client.rb', line 131

def identify
  payload = {
    op: OPCODES[:identify],
    d: {
      token: @config.token,
      properties: {
        os: 'linux',
        browser: 'discord_rda',
        device: 'discord_rda'
      },
      compress: @config.gateway_compression == :zlib_stream,
      large_threshold: 250,
      shard: [@shard_id, @shard_count],
      intents: @config.intents_bitmask
    }
  }

  send_payload(payload)
  @logger&.info('Sent identify', shard: @shard_id)
end

#request_guild_members(guild_id, query: '', limit: 0, presences: false, user_ids: nil, nonce: nil) ⇒ void

This method returns an undefined value.

Request guild members (chunking)

Parameters:

  • guild_id (String)

    Guild ID

  • query (String) (defaults to: '')

    Query string

  • limit (Integer) (defaults to: 0)

    Member limit

  • presences (Boolean) (defaults to: false)

    Include presences

  • user_ids (Array<String>) (defaults to: nil)

    Specific user IDs

  • nonce (String) (defaults to: nil)

    Nonce for response



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/discord_rda/connection/gateway_client.rb', line 197

def request_guild_members(guild_id, query: '', limit: 0, presences: false, user_ids: nil, nonce: nil)
  payload = {
    op: OPCODES[:request_guild_members],
    d: {
      guild_id: guild_id,
      query: query,
      limit: limit,
      presences: presences,
      user_ids: user_ids,
      nonce: nonce
    }.compact
  }

  send_payload(payload)
end

#restore_session_state(session_id:, sequence:, resume_gateway_url: nil) ⇒ Object



213
214
215
216
217
218
# File 'lib/discord_rda/connection/gateway_client.rb', line 213

def restore_session_state(session_id:, sequence:, resume_gateway_url: nil)
  @session_id = session_id
  @sequence = sequence.to_i
  @resume_gateway_url = resume_gateway_url if resume_gateway_url
  @logger&.info('Restored gateway session state', shard: @shard_id, session: @session_id, seq: @sequence)
end

#resumevoid

This method returns an undefined value.

Send a resume payload



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/discord_rda/connection/gateway_client.rb', line 154

def resume
  return unless @session_id && @sequence > 0

  payload = {
    op: OPCODES[:resume],
    d: {
      token: @config.token,
      session_id: @session_id,
      seq: @sequence
    }
  }

  send_payload(payload)
  @logger&.info('Sent resume', shard: @shard_id, session: @session_id, seq: @sequence)
end

#runvoid

This method returns an undefined value.

Run the gateway event loop



115
116
117
# File 'lib/discord_rda/connection/gateway_client.rb', line 115

def run
  connect.wait
end

#update_presence(status: 'online', activity: nil, afk: false) ⇒ void

This method returns an undefined value.

Update presence

Parameters:

  • status (String) (defaults to: 'online')

    online, idle, dnd, invisible

  • activity (Hash) (defaults to: nil)

    Activity data

  • afk (Boolean) (defaults to: false)

    Whether AFK



175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/discord_rda/connection/gateway_client.rb', line 175

def update_presence(status: 'online', activity: nil, afk: false)
  payload = {
    op: OPCODES[:presence_update],
    d: {
      since: afk ? Time.now.to_i * 1000 : nil,
      activities: activity ? [activity] : [],
      status: status,
      afk: afk
    }
  }

  send_payload(payload)
end