diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 1f54545..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -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 - } - ] - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 90e8776..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -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 - } - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index 20978a5..a456b8f 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ Memory optimized, simple, and feature rich Discord API wrapper written in Nim.

# 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,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` ### 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. diff --git a/nimcord.nimble b/nimcord.nimble index 27c36e1..167b1ac 100644 --- a/nimcord.nimble +++ b/nimcord.nimble @@ -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" diff --git a/src/nimcord/client.nim b/src/nimcord/client.nim index ecd97c4..ad780ec 100644 --- a/src/nimcord/client.nim +++ b/src/nimcord/client.nim @@ -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,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.") 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.} = 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) + 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 +143,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 +160,10 @@ proc handleWebsocketPacket(shard: Shard) {.async.} = shard.heartbeatInterval = json["d"]["heartbeat_interval"].getInt() await shard.sendGatewayRequest(shard.getIdentifyPacket()) - asyncCheck shard.handleHeartbeat() - shard.heartbeatAcked = true + # Don't start a new + if (not shard.isHandlingHeartbeat): + asyncCheck shard.handleHeartbeat() + shard.heartbeatAcked = true of ord(DiscordOpCode.opHeartbeatAck): shard.heartbeatAcked = true of ord(DiscordOpCode.opDispatch): @@ -208,8 +221,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,8 +231,7 @@ 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() diff --git a/src/nimcord/clientobjects.nim b/src/nimcord/clientobjects.nim index 76e7db4..bbfe552 100644 --- a/src/nimcord/clientobjects.nim +++ b/src/nimcord/clientobjects.nim @@ -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 \ No newline at end of file + sessionID*: string + isHandlingHeartbeat*: bool \ No newline at end of file diff --git a/src/nimcord/eventdispatcher.nim b/src/nimcord/eventdispatcher.nim index 17732d0..ff671cc 100644 --- a/src/nimcord/eventdispatcher.nim +++ b/src/nimcord/eventdispatcher.nim @@ -19,7 +19,7 @@ proc readyEvent(shard: Shard, json: JsonNode) = shard.client.clientUser = newUser(userJson) shard.sessionID = json["session_id"].getStr() - + dispatchEvent(readyEvent) proc channelCreateEvent(shard: Shard, json: JsonNode) =