Fix reconnecting

This commit is contained in:
SeanOMik 2020-09-25 15:21:41 -05:00
parent 611ac34389
commit 265f16e768
No known key found for this signature in database
GPG Key ID: FA4D55AC05268A88
7 changed files with 44 additions and 79 deletions

29
.vscode/launch.json vendored
View File

@ -1,29 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Nim Project",
"type": "gdb",
"request": "launch",
"program": "${workspaceFolder}/src/client.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"valuesFormatting": "parseText",
"MIMode": "gdb",
"miDebuggerPath": "D:/Code/nim/nim-1.2.0/dist/mingw64/bin/gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

16
.vscode/tasks.json vendored
View File

@ -1,16 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build Nim Project",
"type": "shell",
"command": "nim compile -d:ssl -d:debug --debugger:native ./src/client.nim",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -7,13 +7,13 @@ Memory optimized, simple, and feature rich Discord API wrapper written in Nim.
</p> </p>
# NimCord # NimCord
A Discord API wrapper written in Nim. Inspired, and created by the author of a memory optimized Discord Library named DisC++. A Discord API wrapper written in Nim. Inspired, and created by the author of a memory optimized Discord Library named [DisC++](https://github.com/DisCPP/DisCPP).
## State ## State
NimCord is currently in a testing state. If you want to use it, you can but you may encounter errors. If you do encounter errors, create a GitHub issue or join the Discord server. NimCord is currently in a testing state. If you want to use it, you can but you may encounter errors. If you do encounter errors, create a GitHub issue or join the Discord server.
## Dependencies ## Dependencies
* [Websocket.nim](https://github.com/niv/websocket.nim) * [ws](https://github.com/treeform/ws)
## Documenation ## Documenation
You can generate documenation by running `generate_docs.bat/sh` (depending on your system). Documentation is outputted to the `docs` directory. You can generate documenation by running `generate_docs.bat/sh` (depending on your system). Documentation is outputted to the `docs` directory.
@ -30,12 +30,10 @@ You can generate documenation by running `generate_docs.bat/sh` (depending on yo
* NimCord is not yet available on the official Nimble package repository. To install it, you need to clone this repo and in the project root, run: `nimble install` * NimCord is not yet available on the official Nimble package repository. To install it, you need to clone this repo and in the project root, run: `nimble install`
### Note: ### Note:
* If you already have Websocket.nim installed, you need to make sure you have version 0.4.1 installed.
* To compile you must define `ssl` example: `nim compile -d:ssl --run .\examples\basic.nim` * To compile you must define `ssl` example: `nim compile -d:ssl --run .\examples\basic.nim`
You can view examples in the [examples](examples) directory. You can view examples in the [examples](examples) directory.
# Todo: # Todo:
- [x] Finish all REST API calls. - [x] Finish all REST API calls.
- [x] Handle all gateway events. - [x] Handle all gateway events.

View File

@ -4,4 +4,4 @@ description = "Discord API wrapper written in Nim. Inspired by DisC++, my othe
license = "MIT" license = "MIT"
srcDir = "src" srcDir = "src"
requires "nim >= 1.2.0", "websocket >= 0.4.0 & <= 0.4.1" requires "nim >= 1.2.0", "ws >= 0.4"

View File

@ -1,4 +1,4 @@
import websocket, asyncdispatch, json, httpClient, eventdispatcher, strformat import ws, asyncdispatch, json, httpClient, eventdispatcher, strformat
import nimcordutils, cache, clientobjects, strutils, options, presence, log import nimcordutils, cache, clientobjects, strutils, options, presence, log
import tables import tables
@ -37,7 +37,7 @@ proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.as
else: else:
shard.client.log.debug("[SHARD " & $shard.id & "] " & msg) shard.client.log.debug("[SHARD " & $shard.id & "] " & msg)
await shard.ws.sendText($request) await shard.ws.send($request)
proc handleHeartbeat(shard: Shard) {.async.} = proc handleHeartbeat(shard: Shard) {.async.} =
while true: while true:
@ -53,6 +53,10 @@ proc handleHeartbeat(shard: Shard) {.async.} =
shard.client.log.debug("[SHARD " & $shard.id & "] Waiting " & $shard.heartbeatInterval & " ms until next heartbeat...") shard.client.log.debug("[SHARD " & $shard.id & "] Waiting " & $shard.heartbeatInterval & " ms until next heartbeat...")
await sleepAsync(shard.heartbeatInterval) await sleepAsync(shard.heartbeatInterval)
if (not shard.heartbeatAcked and not shard.reconnecting):
shard.client.log.debug("[SHARD " & $shard.id & "] Heartbeat not acked! Reconnecting...")
asyncCheck shard.reconnectShard()
proc getIdentifyPacket(shard: Shard): JsonNode = proc getIdentifyPacket(shard: Shard): JsonNode =
result = %* { result = %* {
"op": ord(DiscordOpCode.opIdentify), "op": ord(DiscordOpCode.opIdentify),
@ -71,24 +75,32 @@ proc getIdentifyPacket(shard: Shard): JsonNode =
proc closeConnection*(shard: Shard, code: int = 1000) {.async.} = proc closeConnection*(shard: Shard, code: int = 1000) {.async.} =
shard.client.log.warn("[SHARD " & $shard.id & "] Disconnecting with code: " & $code) shard.client.log.warn("[SHARD " & $shard.id & "] Disconnecting with code: " & $code)
await shard.ws.close(code) shard.ws.close()
proc reconnectShard(shard: Shard) {.async.} = proc reconnectShard*(shard: Shard) {.async.} =
shard.client.log.info("[SHARD " & $shard.id & "] Reconnecting...") shard.client.log.info("[SHARD " & $shard.id & "] Reconnecting...")
shard.reconnecting = true shard.reconnecting = true
waitFor shard.ws.close(1000) #waitFor shard.ws.close(1000)
shard.ws = waitFor newAsyncWebsocketClient(shard.client.endpoint[6..shard.client.endpoint.high], Port 443, try:
path = "/v=6&encoding=json", true) shard.ws = waitFor newWebSocket(shard.client.endpoint & "/v=6&encoding=json")
#shard.ws = waitFor newAsyncWebsocketClient(shard.client.endpoint[6..shard.client.endpoint.high], Port 443,
# path = "/v=6&encoding=json", true)
except OSError:
shard.client.log.error("[SHARD " & $shard.id & "] Failed to reconnect to websocket with OSError trying again!")
asyncCheck shard.reconnectShard()
except IOError:
shard.client.log.error("[SHARD " & $shard.id & "] Failed to reconnect to websocket with IOError trying again!")
asyncCheck shard.reconnectShard()
shard.reconnecting = false shard.reconnecting = false
shard.heartbeatAcked = true shard.heartbeatAcked = true
# waitFor client.startConnection()
# Handle discord disconnect. If it detects that we can reconnect, it will. # Handle discord disconnect. If it detects that we can reconnect, it will.
proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} = proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} =
let disconnectData = extractCloseData(error) shard.client.log.warn("[SHARD " & $shard.id & "] Discord gateway disconnected!")
#[[let disconnectData = extractCloseData(error)
shard.client.log.warn("[SHARD " & $shard.id & "] Discord gateway disconnected! Error code: " & $disconnectData.code & ", msg: " & disconnectData.reason) shard.client.log.warn("[SHARD " & $shard.id & "] Discord gateway disconnected! Error code: " & $disconnectData.code & ", msg: " & disconnectData.reason)
@ -102,28 +114,27 @@ proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} =
shard.client.log.error("[SHARD " & $shard.id & "] The Discord gateway sent a disconnect code that we cannot reconnect to.") shard.client.log.error("[SHARD " & $shard.id & "] The Discord gateway sent a disconnect code that we cannot reconnect to.")
else: else:
if not shard.reconnecting: if not shard.reconnecting:
waitFor shard.reconnectShard() shard.reconnectShard()
else: else:
shard.client.log.debug("[SHARD " & $shard.id & "] Gateway cannot reconnect due to already reconnecting...") shard.client.log.debug("[SHARD " & $shard.id & "] Gateway cannot reconnect due to already reconnecting...")]]#
#TODO: Reconnecting may be done, just needs testing. #TODO: Reconnecting may be done, just needs testing.
proc handleWebsocketPacket(shard: Shard) {.async.} = proc handleWebsocketPacket(shard: Shard) {.async.} =
while true: while true:
var packet: tuple[opcode: Opcode, data: string]
packet = await shard.ws.readData() var packet = await shard.ws.receiveStrPacket()
shard.client.log.debug("[SHARD " & $shard.id & "] Received gateway payload: " & $packet.data) shard.client.log.debug("[SHARD " & $shard.id & "] Received gateway payload: " & $packet)
if packet.opcode == Opcode.Close: #if packet == Opcode.Close:
await shard.handleGatewayDisconnect(packet.data) # await shard.handleGatewayDisconnect(packet)
var json: JsonNode var json: JsonNode
# If we fail to parse the json just stop this loop # If we fail to parse the json just stop this loop
try: try:
json = parseJson(packet.data) json = parseJson(packet)
except: except:
shard.client.log.error("[SHARD " & $shard.id & "] Failed to parse websocket payload: " & $packet.data) shard.client.log.error("[SHARD " & $shard.id & "] Failed to parse websocket payload: " & $packet)
continue continue
if json.contains("s"): if json.contains("s"):
@ -132,7 +143,7 @@ proc handleWebsocketPacket(shard: Shard) {.async.} =
case json["op"].getInt() case json["op"].getInt()
of ord(DiscordOpCode.opHello): of ord(DiscordOpCode.opHello):
if shard.reconnecting: if shard.reconnecting:
shard.client.log.info("[SHARD " & $shard.id & "Reconnected!") shard.client.log.info("[SHARD " & $shard.id & "] Reconnected!")
shard.reconnecting = false shard.reconnecting = false
let resume = %* { let resume = %* {
@ -149,8 +160,10 @@ proc handleWebsocketPacket(shard: Shard) {.async.} =
shard.heartbeatInterval = json["d"]["heartbeat_interval"].getInt() shard.heartbeatInterval = json["d"]["heartbeat_interval"].getInt()
await shard.sendGatewayRequest(shard.getIdentifyPacket()) await shard.sendGatewayRequest(shard.getIdentifyPacket())
asyncCheck shard.handleHeartbeat() # Don't start a new
shard.heartbeatAcked = true if (not shard.isHandlingHeartbeat):
asyncCheck shard.handleHeartbeat()
shard.heartbeatAcked = true
of ord(DiscordOpCode.opHeartbeatAck): of ord(DiscordOpCode.opHeartbeatAck):
shard.heartbeatAcked = true shard.heartbeatAcked = true
of ord(DiscordOpCode.opDispatch): of ord(DiscordOpCode.opDispatch):
@ -208,8 +221,7 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} =
var shard = newShard(index, client) var shard = newShard(index, client)
client.shards.add(shard) client.shards.add(shard)
shard.ws = await newAsyncWebsocketClient(url[6..url.high], Port 443, shard.ws = await newWebSocket(shard.client.endpoint & "/v=6&encoding=json")
path = "/v=6&encoding=json", true)
asyncCheck shard.handleWebsocketPacket() asyncCheck shard.handleWebsocketPacket()
@ -219,8 +231,7 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} =
var shard = newShard(shardCount - 1, client) var shard = newShard(shardCount - 1, client)
client.shards.add(shard) client.shards.add(shard)
shard.ws = await newAsyncWebsocketClient(url[6..url.high], Port 443, shard.ws = await newWebSocket(shard.client.endpoint & "/v=6&encoding=json")
path = "/v=6&encoding=json", true)
asyncCheck shard.handleWebsocketPacket() asyncCheck shard.handleWebsocketPacket()

View File

@ -1,4 +1,4 @@
import websocket, cache, user, log import ws, cache, user, log
type type
DiscordClient* = ref object DiscordClient* = ref object
@ -15,9 +15,10 @@ type
Shard* = ref object Shard* = ref object
id*: int id*: int
client*: DiscordClient client*: DiscordClient
ws*: AsyncWebSocket ws*: WebSocket
heartbeatInterval*: int heartbeatInterval*: int
heartbeatAcked*: bool heartbeatAcked*: bool
lastSequence*: int lastSequence*: int
reconnecting*: bool reconnecting*: bool
sessionID*: string sessionID*: string
isHandlingHeartbeat*: bool

View File

@ -19,7 +19,7 @@ proc readyEvent(shard: Shard, json: JsonNode) =
shard.client.clientUser = newUser(userJson) shard.client.clientUser = newUser(userJson)
shard.sessionID = json["session_id"].getStr() shard.sessionID = json["session_id"].getStr()
dispatchEvent(readyEvent) dispatchEvent(readyEvent)
proc channelCreateEvent(shard: Shard, json: JsonNode) = proc channelCreateEvent(shard: Shard, json: JsonNode) =