diff --git a/examples/basic.nim b/examples/basic.nim index cb55b37..06f8a4a 100644 --- a/examples/basic.nim +++ b/examples/basic.nim @@ -4,11 +4,10 @@ var tokenStream = newFileStream("token.txt", fmRead) var tkn: string if (not isNil(tokenStream)): discard tokenStream.readLine(tkn) - echo "Read token from the file: ", tkn tokenStream.close() -var bot = newDiscordClient(tkn, "?") +var bot = newDiscordClient(tkn, "?", newLog(ord(LoggerFlags.loggerFlagDebugSeverity))) let pingCommand = Command(name: "ping", commandBody: proc(ctx: CommandContext) = discard ctx.channel.sendMessage("PONG") @@ -88,7 +87,7 @@ let sendImageCommand = Command(name: "sendImage", commandBody: proc(ctx: Command # You can even register commands like this: registerCommand(Command(name: "ping2", commandBody: proc(ctx: CommandContext) = - discard ctx.channel.sendMessage("PONG3") + discard ctx.channel.sendMessage("PONG 2") )) # Listen for the ready event. @@ -96,12 +95,13 @@ registerEventListener(EventType.evtReady, proc(bEvt: BaseEvent) = # Cast the BaseEvent to the ReadyEvent which is what we're listening to. let event = ReadyEvent(bEvt) - echo "Ready! (v", 0, ".", 0, ".", 1, ")" - echo "Logged in as: ", bot.clientUser.username, "#", bot.clientUser.discriminator - echo "ID: ", bot.clientUser.id - echo "--------------------" + event.shard.client.log.info("Ready!") + event.shard.client.log.info("Logged in as: " & bot.clientUser.username & "#" & $bot.clientUser.discriminator) + event.shard.client.log.info("ID: " & $bot.clientUser.id) + event.shard.client.log.info("--------------------") - let presence = newPresence("with Nimcord", activityTypeGame, clientStatusIdle, false) + let presence = newPresence("with Nimcord", ActivityType.activityTypeGame, + ClientStatus.clientStatusIdle, false) asyncCheck event.shard.updateClientPresence(presence) # Register commands. You don't need to register them in EventReady. diff --git a/src/nimcord.nim b/src/nimcord.nim index e738b12..25c4594 100644 --- a/src/nimcord.nim +++ b/src/nimcord.nim @@ -16,11 +16,11 @@ import nimcord/[cache, channel, client, clientobjects, discordobject] import nimcord/[embed, emoji, eventdispatcher, eventhandler, guild] import nimcord/[image, member, message, nimcordutils, permission] -import nimcord/[presence, role, user, commandsystem] +import nimcord/[presence, role, user, commandsystem, log] export cache, channel, client, clientobjects, discordobject export embed, emoji, eventdispatcher, eventhandler, guild export image, member, message, nimcordutils, permission -export presence, role, user, commandsystem +export presence, role, user, commandsystem, log const NimCordVersion = "v0.0.1" \ No newline at end of file diff --git a/src/nimcord/channel.nim b/src/nimcord/channel.nim index 4292dbf..36ee168 100644 --- a/src/nimcord/channel.nim +++ b/src/nimcord/channel.nim @@ -163,7 +163,8 @@ proc sendMessage*(channel: Channel, content: string, tts: bool = false, embed: E raise newException(IOError, "Failed to open file for sending: " & file.filePath) multipart.add("payload_json", $messagePayload, "", "application/json", false) - echo "Sending POST request, URL: ", endpoint, ", headers: ", client.headers, " payload_json: ", messagePayload + # TODO: Send this through the logger: + #echo "Sending POST request, URL: ", endpoint, ", headers: ", client.headers, " payload_json: ", messagePayload waitForRateLimits(channel.id, RateLimitBucketType.channel) let response: Response = client.post(endpoint, "", multipart) diff --git a/src/nimcord/client.nim b/src/nimcord/client.nim index 212255f..6bb5aeb 100644 --- a/src/nimcord/client.nim +++ b/src/nimcord/client.nim @@ -1,6 +1,5 @@ import websocket, asyncdispatch, json, httpClient, eventdispatcher, strformat -import nimcordutils, cache, clientobjects -import strutils, options, presence +import nimcordutils, cache, clientobjects, strutils, options, presence, log type DiscordOpCode = enum @@ -22,7 +21,7 @@ proc getIdentifyPacket(shard: Shard): JsonNode proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} proc handleHeartbeat(shard: Shard) {.async.} proc handleWebsocketPacket(shard: Shard) {.async.} -proc newDiscordClient*(tkn: string, commandPrefix: string): DiscordClient +proc newDiscordClient*(tkn: string, commandPrefix: string, log: Log = newLog(ord(LoggerFlags.loggerFlagWarnSeverity) or ord(LoggerFlags.loggerFlagInfoSeverity) or ord(LoggerFlags.loggerFlagErrorSeverity))): DiscordClient proc newShard(shardID: int, client: DiscordClient): Shard proc reconnectShard(shard: Shard) {.async.} proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.async.} @@ -33,11 +32,11 @@ proc sendGatewayRequest*(shard: Shard, request: JsonNode, msg: string = "") {.as ## Send a gateway request. ## Don't use this unless you know what you're doing! if msg.len == 0: - echo "Sending gateway payload: ", request + shard.client.log.debug("[SHARD " & $shard.id & "] Sending gateway payload: " & $request) else: - echo msg + shard.client.log.debug(msg) - await shard.ws.sendText($request) + await shard.ws.sendText("[SHARD " & $shard.id & "] " & $request) proc handleHeartbeat(shard: Shard) {.async.} = while true: @@ -50,7 +49,7 @@ proc handleHeartbeat(shard: Shard) {.async.} = await shard.sendGatewayRequest(heartbeatPayload, fmt("Sending heartbeat payload: {$heartbeatPayload}")) shard.heartbeatAcked = true - echo "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) proc getIdentifyPacket(shard: Shard): JsonNode = @@ -70,11 +69,11 @@ proc getIdentifyPacket(shard: Shard): JsonNode = result.add("shard", %*[shard.id, shard.client.shardCount]) proc closeConnection*(shard: Shard, code: int = 1000) {.async.} = - echo "Disconnecting with code: ", code + shard.client.log.warn("[SHARD " & $shard.id & "] Disconnecting with code: " & $code) await shard.ws.close(code) proc reconnectShard(shard: Shard) {.async.} = - echo "Reconnecting..." + shard.client.log.info("[SHARD " & $shard.id & "] Reconnecting...") shard.reconnecting = true await shard.ws.close(1000) @@ -89,7 +88,7 @@ proc reconnectShard(shard: Shard) {.async.} = proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} = let disconnectData = extractCloseData(error) - echo "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) shard.heartbeatAcked = false @@ -98,12 +97,12 @@ proc handleGatewayDisconnect(shard: Shard, error: string) {.async.} = # 4003, 4004, 4005, 4007, 4010, 4011, 4012, 4013 are not reconnectable. if (c >= 4003 and c <= 4005) or c == 4007 or (c >= 4010 and c <= 4013): - echo "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: if not shard.reconnecting: waitFor shard.reconnectShard() else: - echo "Gateway is 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.} = @@ -111,7 +110,7 @@ proc handleWebsocketPacket(shard: Shard) {.async.} = var packet: tuple[opcode: Opcode, data: string] packet = await shard.ws.readData() - echo "[SHARD ", $shard.id, "] Received gateway payload: ", packet.data + shard.client.log.debug("[SHARD " & $shard.id & "] Received gateway payload: " & $packet.data) if packet.opcode == Opcode.Close: await shard.handleGatewayDisconnect(packet.data) @@ -122,7 +121,7 @@ proc handleWebsocketPacket(shard: Shard) {.async.} = try: json = parseJson(packet.data) except: - echo "Failed to parse websocket payload: ", packet.data + shard.client.log.error("[SHARD " & $shard.id & "] Failed to parse websocket payload: " & $packet.data) continue if json.contains("s"): @@ -131,7 +130,7 @@ proc handleWebsocketPacket(shard: Shard) {.async.} = case json["op"].getInt() of ord(DiscordOpCode.opHello): if shard.reconnecting: - echo "Reconnected!" + shard.client.log.info("[SHARD " & $shard.id & "Reconnected!") shard.reconnecting = false let resume = %* { @@ -189,7 +188,7 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} = ## tokenStream.close() ## ## var bot = newDiscordClient(tkn) - echo "Connecting..." + client.log.info("[CLIENT] Connecting...") # let urlResult = sendRequest(endpoint("/gateway/bot"), HttpMethod.HttpGet, defaultHeaders()) let urlResult = sendRequest(endpoint("/gateway"), HttpMethod.HttpGet, defaultHeaders()) @@ -223,19 +222,10 @@ proc startConnection*(client: DiscordClient, shardAmount: int = 1) {.async.} = asyncCheck shard.handleWebsocketPacket() - # Now just wait. Dont poll while we're reconnecting + # Just wait. Don't poll while we're reconnecting while true: if not shard.reconnecting: poll() - - #[ client.ws = await newAsyncWebsocketClient(url[6..url.high], Port 443, - path = "/v=6&encoding=json", true) - - asyncCheck client.handleWebsocketPacket() - # Now just wait. Dont poll for new events while we're reconnecting - while true: - if not client.reconnecting: - poll() ]# else: raise newException(IOError, "Failed to get gateway url, token may of been incorrect!") @@ -247,13 +237,14 @@ proc updateClientPresence*(shard: Shard, presence: Presence) {.async.} = await shard.sendGatewayRequest(jsonPayload) -proc newDiscordClient*(tkn: string, commandPrefix: string): DiscordClient = +proc newDiscordClient*(tkn: string, commandPrefix: string, log: Log = newLog(ord(LoggerFlags.loggerFlagWarnSeverity) or ord(LoggerFlags.loggerFlagInfoSeverity) or ord(LoggerFlags.loggerFlagErrorSeverity))): DiscordClient = ## Create a DiscordClient using a token. ## - ## Sets globalDiscordClient to the newly created client. + ## Sets globalToken to the newly created client's token. globalToken = tkn + globalLog = log var cac: Cache new(cac) - result = DiscordClient(token: tkn, cache: cac, commandPrefix: commandPrefix) \ No newline at end of file + result = DiscordClient(token: tkn, cache: cac, commandPrefix: commandPrefix, log: log) \ No newline at end of file diff --git a/src/nimcord/clientobjects.nim b/src/nimcord/clientobjects.nim index cb0f634..76e7db4 100644 --- a/src/nimcord/clientobjects.nim +++ b/src/nimcord/clientobjects.nim @@ -1,4 +1,4 @@ -import websocket, cache, user +import websocket, cache, user, log type DiscordClient* = ref object @@ -10,6 +10,7 @@ type shardCount*: int endpoint*: string commandPrefix*: string + log*: Log Shard* = ref object id*: int diff --git a/src/nimcord/eventdispatcher.nim b/src/nimcord/eventdispatcher.nim index 0ea5b63..7a5566a 100644 --- a/src/nimcord/eventdispatcher.nim +++ b/src/nimcord/eventdispatcher.nim @@ -1,7 +1,7 @@ import eventhandler, json, tables, message, emoji, user, member, role import guild, channel, nimcordutils, httpClient, strformat, cache import sequtils, asyncdispatch, clientobjects, discordobject, presence -import commandsystem +import commandsystem, log proc readyEvent(shard: Shard, json: JsonNode) = var readyEvent = ReadyEvent(shard: shard, readyPayload: json, name: $EventType.evtReady) @@ -12,7 +12,7 @@ proc readyEvent(shard: Shard, json: JsonNode) = client.headers = newHttpHeaders({"Authorization": fmt("Bot {shard.client.token}"), "User-Agent": "NimCord (https://github.com/SeanOMik/nimcord, v0.0.0)", "X-RateLimit-Precision": "millisecond"}) - echo "Sending GET request, URL: body: {}" + shard.client.log.debug("[SHARD " & $shard.id & "] Sending GET request, URL: body: {}") waitForRateLimits(0, RateLimitBucketType.global) var userJson = handleResponse(client.request(endpoint("/users/@me"), HttpGet, ""), 0, RateLimitBucketType.global) @@ -512,5 +512,5 @@ proc handleDiscordEvent*(shard: Shard, json: JsonNode, eventName: string) {.asyn let eventProc: proc(shard: Shard, json: JsonNode) = internalEventTable[eventName] eventProc(shard, json) else: - echo "Failed to find event: ", eventName + shard.client.log.error("[SHARD " & $shard.id & "] Failed to find event: " & eventName) diff --git a/src/nimcord/eventhandler.nim b/src/nimcord/eventhandler.nim index 10f927b..f00c4a7 100644 --- a/src/nimcord/eventhandler.nim +++ b/src/nimcord/eventhandler.nim @@ -245,29 +245,22 @@ proc registerEventListener*(event: EventType, listener: proc(event: BaseEvent)) ## .. code-block:: nim ## registerEventListener(EventType.evtReady, proc(bEvt: BaseEvent) = ## let event = ReadyEvent(bEvt) - ## bot.clientUser = event.clientUser ## - ## echo "Ready! (v", nimcordMajor, ".", nimcordMinor, ".", nimcordMicro, ")" - ## echo "Logged in as: ", bot.clientUser.username, "#", bot.clientUser.discriminator - ## echo "ID: ", bot.clientUser.id - ## echo "--------------------" + ## event.shard.client.log.info("Ready!") + ## event.shard.client.log.info("Logged in as: " & bot.clientUser.username & "#" & $bot.clientUser.discriminator) + ## event.shard.client.log.info("ID: " & $bot.clientUser.id) + ## event.shard.client.log.info("--------------------") ## ) if eventListeners.hasKey($event): eventListeners[$event].add(cast[proc(event: BaseEvent)](listener)) - - echo "Added other event listener: ", $event else: let tmp = @[listener] eventListeners.add($event, tmp) - echo "Added new event listener: ", $event - proc dispatchEvent*[T: BaseEvent](event: T) = ## Dispatches an event so something can listen to it. if eventListeners.hasKey(event.name): let listeners = eventListeners[event.name] - echo "Dispatching event: ", event.name + for index, eventListener in listeners.pairs: eventListener(event) - else: - echo "No event listeners for event: ", event.name diff --git a/src/nimcord/log.nim b/src/nimcord/log.nim index 3bac7ee..5c17dbb 100644 --- a/src/nimcord/log.nim +++ b/src/nimcord/log.nim @@ -21,7 +21,7 @@ type logSevDebug = 3 proc newLog*(flags: int, filePath: string = ""): Log = - ## Create a new file. Colors in a file is printed as "fgYellow". + ## Create a new log. Colors in a file is printed as "fgYellow". var log = Log(flags: flags) if filePath.len > 0: log.logFile = newFileStream(filePath, fmWrite) @@ -59,37 +59,36 @@ proc severityToString(sev: LogSeverity): string = of LogSeverity.logSevDebug: return "DEBUG" -#TODO: Remove colors from file. -template autoLog(log: Log, sev: LogSeverity, args: varargs[untyped]) = +proc autoLog(log: Log, sev: LogSeverity, text: string) = if log.canLog(sev): let timeFormated = getTime().format("[HH:mm:ss]") let sevStr = "[" & severityToString(sev) & "]" let logHeader = timeFormated & " " & sevStr & " " - terminal.styledEcho(logHeader, args) + terminal.styledEcho(logHeader, text) if log.logFile != nil: - log.logFile.writeLine(logHeader, args) + log.logFile.writeLine(logHeader, text) -template debug*(log: Log, args: varargs[untyped]) = +proc debug*(log: Log, text: string) = ## Log debug severity. Example output: `[22:34:31] [DEBUG] Test` - log.autoLog(logSevDebug, args) + log.autoLog(logSevDebug, text) -template warn*(log: Log, args: varargs[untyped]) = +proc warn*(log: Log, text: string) = ## Log warning severity. Example output: `[22:34:31] [WARN] Test` - log.autoLog(logSevWarn, args) + log.autoLog(logSevWarn, text) -template error*(log: Log, args: varargs[untyped]) = +proc error*(log: Log, text: string) = ## Log error severity. Example output: `[22:34:31] [ERROR] Test` - log.autoLog(logSevError, args) + log.autoLog(logSevError, text) -template info*(log: Log, args: varargs[untyped]) = +proc info*(log: Log, text: string) = ## Log info severity. Example output: `[22:34:31] [INFO] Test` - log.autoLog(logSevInfo, args) + log.autoLog(logSevInfo, text) proc closeLog*(log: Log) = ## Close log file if it was ever open. if log.logFile != nil: - log.info(fgYellow, "Closing log...") + log.info("Closing log...") log.logFile.close() diff --git a/src/nimcord/nimcordutils.nim b/src/nimcord/nimcordutils.nim index 29a57e0..42281c3 100644 --- a/src/nimcord/nimcordutils.nim +++ b/src/nimcord/nimcordutils.nim @@ -1,4 +1,4 @@ -import parseutils, json, httpClient, strformat, tables, times, asyncdispatch, strutils +import parseutils, json, httpClient, strformat, tables, times, asyncdispatch, strutils, log from discordobject import Snowflake type ImageType* = enum @@ -22,6 +22,7 @@ proc endpoint*(url: string): string = return fmt("https://discord.com/api/v6{url}") var globalToken*: string +var globalLog*: Log proc defaultHeaders*(added: HttpHeaders = newHttpHeaders()): HttpHeaders = added.add("Authorization", fmt("Bot {globalToken}")) @@ -87,7 +88,7 @@ proc handleRateLimits*(headers: HttpHeaders, objectID: Snowflake, bucketType: Ra proc handleResponse*(response: Response, objectID: Snowflake, bucketType: RateLimitBucketType): JsonNode = - echo fmt("Received requested payload: {response.body}") + globalLog.debug(fmt("Received requested payload: {response.body}")) handleRateLimits(response.headers, objectID, bucketType) @@ -124,7 +125,7 @@ proc waitForRateLimits*(objectID: Snowflake, bucketType: RateLimitBucketType) = let millisecondTime: float = rlmt.ratelimitReset * 1000 - epochTime() * 1000 if millisecondTime > 0: - echo fmt("Rate limit wait time: {millisecondTime} miliseconds") + globalLog.debug(fmt("Rate limit wait time: {millisecondTime} miliseconds")) waitFor sleepAsync(millisecondTime) proc sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders, objectID: Snowflake = 0, @@ -138,7 +139,7 @@ proc sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders strPayload = "" else: strPayload = $jsonBody - echo "Sending ", httpMethod, " request, URL: ", endpoint, ", headers: ", $headers, " body: ", strPayload + globalLog.debug("Sending " & $httpMethod & " request, URL: " & endpoint & ", headers: " & $headers & " body: " & strPayload) waitForRateLimits(objectID, bucketType) let response = client.request(endpoint, httpMethod, strPayload)