Merge branch 'master' into deglobalize

This commit is contained in:
SeanOMik 2020-08-20 19:14:07 -05:00
commit 733b004fab
No known key found for this signature in database
GPG Key ID: FA4D55AC05268A88
9 changed files with 60 additions and 64 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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)

View File

@ -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,7 +222,7 @@ 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()
@ -253,15 +252,17 @@ proc getClientInstance*(instanceID: uint8): DiscordClient =
## Get a client instance with instance id. Mainly used internally.
return clientInstances[instanceID]
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, log: log)
result = DiscordClient(token: tkn, cache: cac, commandPrefix: commandPrefix, instanceID: nextInstanceId)
clientInstances.add(nextInstanceId, result)
nextInstanceId++

View File

@ -1,4 +1,4 @@
import websocket, cache, user
import websocket, cache, user, log
type
DiscordClient* = ref object
@ -11,6 +11,7 @@ type
endpoint*: string
commandPrefix*: string
instanceID*: uint8
log*: Log
Shard* = ref object
id*: int

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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)