Merge pull request #2 from SeanOMik/feature/fix-reconnecting
Fix Reconnecting
This commit is contained in:
commit
9637568de9
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
10
README.md
10
README.md
|
@ -7,13 +7,13 @@ Memory optimized, simple, and feature rich Discord API wrapper written in Nim.
|
|||
</p>
|
||||
|
||||
# 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
|
||||
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
|
||||
* [Websocket.nim](https://github.com/niv/websocket.nim)
|
||||
* [ws](https://github.com/treeform/ws)
|
||||
|
||||
## Documenation
|
||||
You can generate documenation by running `generate_docs.bat/sh` (depending on your system). Documentation is outputted to the `docs` directory.
|
||||
|
@ -30,16 +30,16 @@ 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`
|
||||
|
||||
### 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`
|
||||
|
||||
You can view examples in the [examples](examples) directory.
|
||||
|
||||
|
||||
# Todo:
|
||||
- [x] Finish all REST API calls.
|
||||
- [x] Handle all gateway events.
|
||||
- [x] Reconnecting
|
||||
- [x] Reconnecting.
|
||||
- [ ] Configurable Logger.
|
||||
- [ ] Add library to official Nimble package repository.
|
||||
- [ ] Memory optimizations.
|
||||
- [ ] Member
|
||||
- [ ] Channel
|
||||
|
|
|
@ -4,4 +4,4 @@ description = "Discord API wrapper written in Nim. Inspired by DisC++, my othe
|
|||
license = "MIT"
|
||||
srcDir = "src"
|
||||
|
||||
requires "nim >= 1.2.0", "websocket >= 0.4.0 & <= 0.4.1"
|
||||
requires "nim >= 1.2.0", "ws >= 0.4"
|
||||
|
|
|
@ -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 tables
|
||||
|
||||
|
@ -37,7 +37,7 @@ proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.as
|
|||
else:
|
||||
shard.client.log.debug("[SHARD " & $shard.id & "] " & msg)
|
||||
|
||||
await shard.ws.sendText($request)
|
||||
await shard.ws.send($request)
|
||||
|
||||
proc handleHeartbeat(shard: Shard) {.async.} =
|
||||
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...")
|
||||
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 =
|
||||
result = %* {
|
||||
"op": ord(DiscordOpCode.opIdentify),
|
||||
|
@ -71,24 +75,32 @@ proc getIdentifyPacket(shard: Shard): JsonNode =
|
|||
|
||||
proc closeConnection*(shard: Shard, code: int = 1000) {.async.} =
|
||||
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.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,
|
||||
path = "/v=6&encoding=json", true)
|
||||
try:
|
||||
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.heartbeatAcked = true
|
||||
# waitFor client.startConnection()
|
||||
|
||||
# Handle discord disconnect. If it detects that we can reconnect, it will.
|
||||
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)
|
||||
|
||||
|
@ -102,28 +114,30 @@ 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.")
|
||||
else:
|
||||
if not shard.reconnecting:
|
||||
waitFor shard.reconnectShard()
|
||||
shard.reconnectShard()
|
||||
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.
|
||||
proc handleWebsocketPacket(shard: Shard) {.async.} =
|
||||
var hasStartedHeartbeatThread = false;
|
||||
while true:
|
||||
var packet: tuple[opcode: Opcode, data: string]
|
||||
|
||||
packet = await shard.ws.readData()
|
||||
shard.client.log.debug("[SHARD " & $shard.id & "] Received gateway payload: " & $packet.data)
|
||||
# Skip if the websocket isn't open
|
||||
if shard.ws.readyState == Open:
|
||||
var packet = await shard.ws.receiveStrPacket()
|
||||
shard.client.log.debug("[SHARD " & $shard.id & "] Received gateway payload: " & $packet)
|
||||
|
||||
if packet.opcode == Opcode.Close:
|
||||
await shard.handleGatewayDisconnect(packet.data)
|
||||
#if packet == Opcode.Close:
|
||||
# await shard.handleGatewayDisconnect(packet)
|
||||
|
||||
var json: JsonNode
|
||||
|
||||
# If we fail to parse the json just stop this loop
|
||||
try:
|
||||
json = parseJson(packet.data)
|
||||
json = parseJson(packet)
|
||||
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
|
||||
|
||||
if json.contains("s"):
|
||||
|
@ -132,7 +146,7 @@ proc handleWebsocketPacket(shard: Shard) {.async.} =
|
|||
case json["op"].getInt()
|
||||
of ord(DiscordOpCode.opHello):
|
||||
if shard.reconnecting:
|
||||
shard.client.log.info("[SHARD " & $shard.id & "Reconnected!")
|
||||
shard.client.log.info("[SHARD " & $shard.id & "] Reconnected!")
|
||||
shard.reconnecting = false
|
||||
|
||||
let resume = %* {
|
||||
|
@ -149,8 +163,14 @@ proc handleWebsocketPacket(shard: Shard) {.async.} =
|
|||
shard.heartbeatInterval = json["d"]["heartbeat_interval"].getInt()
|
||||
await shard.sendGatewayRequest(shard.getIdentifyPacket())
|
||||
|
||||
# Don't start a new heartbeat thread if one is already started
|
||||
echo "About to start a heartbeat thread! shard.heartbeatAcked is ", shard.heartbeatAcked
|
||||
if not hasStartedHeartbeatThread:
|
||||
echo "Starting new heartbeat thread! shard.heartbeatAcked is ", shard.heartbeatAcked
|
||||
asyncCheck shard.handleHeartbeat()
|
||||
shard.heartbeatAcked = true
|
||||
hasStartedHeartbeatThread = true
|
||||
else:
|
||||
echo "Not gonna start a new heartbeat thread since. shard.heartbeatAcked is ", shard.heartbeatAcked
|
||||
of ord(DiscordOpCode.opHeartbeatAck):
|
||||
shard.heartbeatAcked = true
|
||||
of ord(DiscordOpCode.opDispatch):
|
||||
|
@ -208,8 +228,7 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} =
|
|||
var shard = newShard(index, client)
|
||||
client.shards.add(shard)
|
||||
|
||||
shard.ws = await newAsyncWebsocketClient(url[6..url.high], Port 443,
|
||||
path = "/v=6&encoding=json", true)
|
||||
shard.ws = await newWebSocket(shard.client.endpoint & "/v=6&encoding=json")
|
||||
|
||||
asyncCheck shard.handleWebsocketPacket()
|
||||
|
||||
|
@ -219,15 +238,17 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} =
|
|||
var shard = newShard(shardCount - 1, client)
|
||||
client.shards.add(shard)
|
||||
|
||||
shard.ws = await newAsyncWebsocketClient(url[6..url.high], Port 443,
|
||||
path = "/v=6&encoding=json", true)
|
||||
shard.ws = await newWebSocket(shard.client.endpoint & "/v=6&encoding=json")
|
||||
|
||||
asyncCheck shard.handleWebsocketPacket()
|
||||
await shard.handleWebsocketPacket()
|
||||
|
||||
# Just wait. Don't poll while we're reconnecting
|
||||
while true:
|
||||
#[ while true:
|
||||
if not shard.reconnecting:
|
||||
try:
|
||||
poll()
|
||||
except WebSocketError:
|
||||
echo "WebSocketError" ]#
|
||||
else:
|
||||
raise newException(IOError, "Failed to get gateway url, token may of been incorrect!")
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import websocket, cache, user, log
|
||||
import ws, cache, user, log
|
||||
|
||||
type
|
||||
DiscordClient* = ref object
|
||||
|
@ -15,9 +15,10 @@ type
|
|||
Shard* = ref object
|
||||
id*: int
|
||||
client*: DiscordClient
|
||||
ws*: AsyncWebSocket
|
||||
ws*: WebSocket
|
||||
heartbeatInterval*: int
|
||||
heartbeatAcked*: bool
|
||||
lastSequence*: int
|
||||
reconnecting*: bool
|
||||
sessionID*: string
|
||||
isHandlingHeartbeat*: bool
|
Reference in New Issue