Push untested ratelimiting.

This commit is contained in:
SeanOMik 2020-06-17 00:34:23 -05:00
parent 2e12d71f0a
commit 2bf20854d6
No known key found for this signature in database
GPG Key ID: FA4D55AC05268A88
2 changed files with 111 additions and 14 deletions

View File

@ -1,4 +1,4 @@
import websocket, asyncnet, asyncdispatch, json, httpClient, strformat, eventdispatcher, eventhandler, streams
import websocket, asyncnet, asyncdispatch, json, httpClient, eventdispatcher, strformat, eventhandler, streams, nimcordutils, discordobject
type
DiscordOpCode = enum
@ -24,6 +24,31 @@ type
heartbeatAcked: bool
lastSequence: int
var globalClient: DiscordClient
proc defaultHeaders*(client: DiscordClient, added: HttpHeaders = newHttpHeaders()): HttpHeaders =
added.add("Authorization", fmt("Bot {client.token}"))
added.add("User-Agent", "NimCord (https://github.com/SeanOMik/nimcord, v0.0.0)")
added.add("X-RateLimit-Precision", "millisecond")
return added;
proc sendRequest*(endpoint: string, httpMethod: HttpMethod, headers: HttpHeaders, objectID: snowflake = 0, bucketType: RateLimitBucketType = global, jsonBody: JsonNode = %*{}): JsonNode =
var client = newHttpClient()
# Add headers
client.headers = headers
var strPayload: string
if ($jsonBody == "{}"):
strPayload = ""
else:
strPayload = $jsonBody
echo "Sending GET request, URL: ", endpoint, ", body: ", strPayload
waitForRateLimits(objectID, bucketType)
return handleResponse(client.request(endpoint, httpMethod, strPayload), objectId, bucketType)
proc sendGatewayRequest*(client: DiscordClient, request: JsonNode, msg: string = "") {.async.} =
if (msg.len == 0):
echo "Sending gateway payload: ", request
@ -74,21 +99,18 @@ proc handleWebsocketPacket(client: DiscordClient) {.async.} =
handleDiscordEvent(json["d"], json["t"].getStr())
else:
discard
proc endpoint*(url: string): string =
return fmt("https://discord.com/api/v6{url}")
proc startConnection*(client: DiscordClient) {.async.} =
globalClient = client
client.httpClient = newAsyncHttpClient()
client.httpClient.headers = newHttpHeaders({"Authorization": fmt("Bot {client.token}")})
let urlResult = parseJson(await client.httpClient.getContent(endpoint("/gateway/bot")))
echo "Got result: ", $urlResult
let urlResult = sendRequest(endpoint("/gateway/bot"), HttpMethod.HttpGet, client.defaultHeaders())
if (urlResult.contains("url")):
let url = urlResult["url"].getStr()
client.ws = await newAsyncWebsocketClient(url[6..url.high], Port 443 ,
client.ws = await newAsyncWebsocketClient(url[6..url.high], Port 443,
path = "/v=6&encoding=json", true)
echo "Connected!"

View File

@ -1,4 +1,4 @@
include parseutils, json, httpClient
import parseutils, json, httpClient, strformat, tables, times, asyncdispatch
from discordobject import snowflake
proc getIDFromJson*(str: string): uint64 =
@ -6,9 +6,84 @@ proc getIDFromJson*(str: string): uint64 =
discard parseOct(str, num)
return num
proc endpoint*(url: string): string =
return fmt("https://discord.com/api/v6{url}")
type RateLimitBucketType = enum
CHANNEL,
GUILD,
WEBHOOK,
GLOBAL
type
RateLimitBucketType* = enum
channel,
guild,
webhook,
global
RateLimit = ref object {.requiresInit.}
limit: int
remainingLimit: int
ratelimitReset: float
proc newRateLimit(lmt: int = 500, remLmnt: int = 500, ratelmtReset: float = 0): RateLimit =
return RateLimit(limit: lmt, remainingLimit: remLmnt, ratelimitReset: ratelmtReset)
# Rate limit buckets
let channelRatelimitBucket = newTable[snowflake, RateLimit]()
let guildRatelimitBucket = newTable[snowflake, RateLimit]()
let webhookRatelimitBucket = newTable[snowflake, RateLimit]()
var globalRateLimit: RateLimit = newRateLimit()
proc handleRateLimits*(headers: HttpHeaders, objectID: snowflake, bucketType: RateLimitBucketType) =
var obj: RateLimit
if (headers.hasKey("x-ratelimit-global")):
obj = globalRateLimit
elif (headers.hasKey("x-ratelimit-limit")):
case bucketType:
of RateLimitBucketType.channel:
obj = channelRatelimitBucket[objectID]
discard
of RateLimitBucketType.guild:
obj = guildRatelimitBucket[objectID]
discard
of RateLimitBucketType.webhook:
obj = webhookRatelimitBucket[objectID]
discard
of RateLimitBucketType.global:
obj = globalRateLimit
discard
else:
return
discard parseInt(headers["x-ratelimit-limit"], obj.limit)
discard parseInt(headers["x-ratelimit-remaining"], obj.remainingLimit)
discard parseFloat(headers["x-ratelimit-reset"], obj.ratelimitReset)
proc handleResponse*(response: Response, objectID: snowflake, bucketType: RateLimitBucketType): JsonNode =
echo fmt("Received requested payload: {response.body}")
handleRateLimits(response.headers, objectID, bucketType)
return parseJson(response.body())
proc waitForRateLimits*(objectID: snowflake, bucketType: RateLimitBucketType) =
var rlmt: RateLimit
if (globalRateLimit.remainingLimit == 0):
rlmt = globalRateLimit
else:
case bucketType:
of RateLimitBucketType.channel:
rlmt = channelRatelimitBucket[objectID]
discard
of RateLimitBucketType.guild:
rlmt = guildRatelimitBucket[objectID]
discard
of RateLimitBucketType.webhook:
rlmt = webhookRatelimitBucket[objectID]
discard
of RateLimitBucketType.global:
rlmt = globalRateLimit
discard
if (rlmt.remainingLimit == 0):
let millisecondTime: float = rlmt.ratelimitReset * 1000 - epochTime() * 1000
if (millisecondTime > 0):
echo fmt("Rate limit wait time: {millisecondTime} miliseconds")
discard sleepAsync(millisecondTime)