You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
5.6 KiB
Lua
258 lines
5.6 KiB
Lua
local json = require('json')
|
|
local timer = require('timer')
|
|
|
|
local EventHandler = require('client/EventHandler')
|
|
local WebSocket = require('client/WebSocket')
|
|
|
|
local constants = require('constants')
|
|
local enums = require('enums')
|
|
|
|
local logLevel = enums.logLevel
|
|
local min, max, random = math.min, math.max, math.random
|
|
local null = json.null
|
|
local format = string.format
|
|
local sleep = timer.sleep
|
|
local setInterval, clearInterval = timer.setInterval, timer.clearInterval
|
|
local wrap = coroutine.wrap
|
|
|
|
local ID_DELAY = constants.ID_DELAY
|
|
|
|
local DISPATCH = 0
|
|
local HEARTBEAT = 1
|
|
local IDENTIFY = 2
|
|
local STATUS_UPDATE = 3
|
|
local VOICE_STATE_UPDATE = 4
|
|
-- local VOICE_SERVER_PING = 5 -- TODO
|
|
local RESUME = 6
|
|
local RECONNECT = 7
|
|
local REQUEST_GUILD_MEMBERS = 8
|
|
local INVALID_SESSION = 9
|
|
local HELLO = 10
|
|
local HEARTBEAT_ACK = 11
|
|
local GUILD_SYNC = 12
|
|
|
|
local ignore = {
|
|
['CALL_DELETE'] = true,
|
|
['CHANNEL_PINS_ACK'] = true,
|
|
['GUILD_INTEGRATIONS_UPDATE'] = true,
|
|
['MESSAGE_ACK'] = true,
|
|
['PRESENCES_REPLACE'] = true,
|
|
['USER_SETTINGS_UPDATE'] = true,
|
|
['USER_GUILD_SETTINGS_UPDATE'] = true,
|
|
['SESSIONS_REPLACE'] = true,
|
|
['INVITE_CREATE'] = true,
|
|
['INVITE_DELETE'] = true,
|
|
['INTEGRATION_CREATE'] = true,
|
|
['INTEGRATION_UPDATE'] = true,
|
|
['INTEGRATION_DELETE'] = true,
|
|
}
|
|
|
|
local Shard = require('class')('Shard', WebSocket)
|
|
|
|
function Shard:__init(id, client)
|
|
WebSocket.__init(self, client)
|
|
self._id = id
|
|
self._client = client
|
|
self._backoff = 1000
|
|
end
|
|
|
|
for name in pairs(logLevel) do
|
|
Shard[name] = function(self, fmt, ...)
|
|
local client = self._client
|
|
return client[name](client, format('Shard %i : %s', self._id, fmt), ...)
|
|
end
|
|
end
|
|
|
|
function Shard:__tostring()
|
|
return format('Shard: %i', self._id)
|
|
end
|
|
|
|
local function getReconnectTime(self, n, m)
|
|
return self._backoff * (n + random() * (m - n))
|
|
end
|
|
|
|
local function incrementReconnectTime(self)
|
|
self._backoff = min(self._backoff * 2, 60000)
|
|
end
|
|
|
|
local function decrementReconnectTime(self)
|
|
self._backoff = max(self._backoff / 2, 1000)
|
|
end
|
|
|
|
function Shard:handleDisconnect(url, path)
|
|
self._client:emit('shardDisconnect', self._id)
|
|
if self._reconnect then
|
|
self:info('Reconnecting...')
|
|
return self:connect(url, path)
|
|
elseif self._reconnect == nil and self._client._options.autoReconnect then
|
|
local backoff = getReconnectTime(self, 0.9, 1.1)
|
|
incrementReconnectTime(self)
|
|
self:info('Reconnecting after %i ms...', backoff)
|
|
sleep(backoff)
|
|
return self:connect(url, path)
|
|
end
|
|
end
|
|
|
|
function Shard:handlePayload(payload)
|
|
|
|
local client = self._client
|
|
|
|
local s = payload.s
|
|
local t = payload.t
|
|
local d = payload.d
|
|
local op = payload.op
|
|
|
|
if t ~= null then
|
|
self:debug('WebSocket OP %s : %s : %s', op, t, s)
|
|
else
|
|
self:debug('WebSocket OP %s', op)
|
|
end
|
|
|
|
if op == DISPATCH then
|
|
|
|
self._seq = s
|
|
if not ignore[t] then
|
|
EventHandler[t](d, client, self)
|
|
end
|
|
|
|
elseif op == HEARTBEAT then
|
|
|
|
self:heartbeat()
|
|
|
|
elseif op == RECONNECT then
|
|
|
|
self:info('Discord has requested a reconnection')
|
|
self:disconnect(true)
|
|
|
|
elseif op == INVALID_SESSION then
|
|
|
|
if payload.d and self._session_id then
|
|
self:info('Session invalidated, resuming...')
|
|
self:resume()
|
|
else
|
|
self:info('Session invalidated, re-identifying...')
|
|
sleep(random(1000, 5000))
|
|
self:identify()
|
|
end
|
|
|
|
elseif op == HELLO then
|
|
|
|
self:info('Received HELLO')
|
|
self:startHeartbeat(d.heartbeat_interval)
|
|
if self._session_id then
|
|
self:resume()
|
|
else
|
|
self:identify()
|
|
end
|
|
|
|
elseif op == HEARTBEAT_ACK then
|
|
|
|
client:emit('heartbeat', self._id, self._sw.milliseconds)
|
|
|
|
elseif op then
|
|
|
|
self:warning('Unhandled WebSocket payload OP %i', op)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local function loop(self)
|
|
decrementReconnectTime(self)
|
|
return wrap(self.heartbeat)(self)
|
|
end
|
|
|
|
function Shard:startHeartbeat(interval)
|
|
if self._heartbeat then
|
|
clearInterval(self._heartbeat)
|
|
end
|
|
self._heartbeat = setInterval(interval, loop, self)
|
|
end
|
|
|
|
function Shard:stopHeartbeat()
|
|
if self._heartbeat then
|
|
clearInterval(self._heartbeat)
|
|
end
|
|
self._heartbeat = nil
|
|
end
|
|
|
|
function Shard:identifyWait()
|
|
if self:waitFor('READY', 1.5 * ID_DELAY) then
|
|
return sleep(ID_DELAY)
|
|
end
|
|
end
|
|
|
|
function Shard:heartbeat()
|
|
self._sw:reset()
|
|
return self:_send(HEARTBEAT, self._seq or json.null)
|
|
end
|
|
|
|
function Shard:identify()
|
|
|
|
local client = self._client
|
|
local mutex = client._mutex
|
|
local options = client._options
|
|
|
|
mutex:lock()
|
|
wrap(function()
|
|
self:identifyWait()
|
|
mutex:unlock()
|
|
end)()
|
|
|
|
self._seq = nil
|
|
self._session_id = nil
|
|
self._ready = false
|
|
self._loading = {guilds = {}, chunks = {}, syncs = {}}
|
|
|
|
return self:_send(IDENTIFY, {
|
|
token = client._token,
|
|
properties = {
|
|
['$os'] = jit.os,
|
|
['$browser'] = 'Discordia',
|
|
['$device'] = 'Discordia',
|
|
['$referrer'] = '',
|
|
['$referring_domain'] = '',
|
|
},
|
|
compress = options.compress,
|
|
large_threshold = options.largeThreshold,
|
|
shard = {self._id, client._total_shard_count},
|
|
presence = next(client._presence) and client._presence,
|
|
}, true)
|
|
|
|
end
|
|
|
|
function Shard:resume()
|
|
return self:_send(RESUME, {
|
|
token = self._client._token,
|
|
session_id = self._session_id,
|
|
seq = self._seq
|
|
})
|
|
end
|
|
|
|
function Shard:requestGuildMembers(id)
|
|
return self:_send(REQUEST_GUILD_MEMBERS, {
|
|
guild_id = id,
|
|
query = '',
|
|
limit = 0,
|
|
})
|
|
end
|
|
|
|
function Shard:updateStatus(presence)
|
|
return self:_send(STATUS_UPDATE, presence)
|
|
end
|
|
|
|
function Shard:updateVoice(guild_id, channel_id, self_mute, self_deaf)
|
|
return self:_send(VOICE_STATE_UPDATE, {
|
|
guild_id = guild_id,
|
|
channel_id = channel_id or null,
|
|
self_mute = self_mute or false,
|
|
self_deaf = self_deaf or false,
|
|
})
|
|
end
|
|
|
|
function Shard:syncGuilds(ids)
|
|
return self:_send(GUILD_SYNC, ids)
|
|
end
|
|
|
|
return Shard
|