Push untested ratelimiting.
This commit is contained in:
parent
2e12d71f0a
commit
2bf20854d6
|
@ -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
|
type
|
||||||
DiscordOpCode = enum
|
DiscordOpCode = enum
|
||||||
|
@ -24,6 +24,31 @@ type
|
||||||
heartbeatAcked: bool
|
heartbeatAcked: bool
|
||||||
lastSequence: int
|
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.} =
|
proc sendGatewayRequest*(client: DiscordClient, request: JsonNode, msg: string = "") {.async.} =
|
||||||
if (msg.len == 0):
|
if (msg.len == 0):
|
||||||
echo "Sending gateway payload: ", request
|
echo "Sending gateway payload: ", request
|
||||||
|
@ -74,21 +99,18 @@ proc handleWebsocketPacket(client: DiscordClient) {.async.} =
|
||||||
handleDiscordEvent(json["d"], json["t"].getStr())
|
handleDiscordEvent(json["d"], json["t"].getStr())
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc endpoint*(url: string): string =
|
|
||||||
return fmt("https://discord.com/api/v6{url}")
|
|
||||||
|
|
||||||
proc startConnection*(client: DiscordClient) {.async.} =
|
proc startConnection*(client: DiscordClient) {.async.} =
|
||||||
|
globalClient = client
|
||||||
|
|
||||||
client.httpClient = newAsyncHttpClient()
|
client.httpClient = newAsyncHttpClient()
|
||||||
client.httpClient.headers = newHttpHeaders({"Authorization": fmt("Bot {client.token}")})
|
client.httpClient.headers = newHttpHeaders({"Authorization": fmt("Bot {client.token}")})
|
||||||
|
|
||||||
let urlResult = parseJson(await client.httpClient.getContent(endpoint("/gateway/bot")))
|
let urlResult = sendRequest(endpoint("/gateway/bot"), HttpMethod.HttpGet, client.defaultHeaders())
|
||||||
echo "Got result: ", $urlResult
|
|
||||||
|
|
||||||
if (urlResult.contains("url")):
|
if (urlResult.contains("url")):
|
||||||
let url = urlResult["url"].getStr()
|
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)
|
path = "/v=6&encoding=json", true)
|
||||||
echo "Connected!"
|
echo "Connected!"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
include parseutils, json, httpClient
|
import parseutils, json, httpClient, strformat, tables, times, asyncdispatch
|
||||||
from discordobject import snowflake
|
from discordobject import snowflake
|
||||||
|
|
||||||
proc getIDFromJson*(str: string): uint64 =
|
proc getIDFromJson*(str: string): uint64 =
|
||||||
|
@ -6,9 +6,84 @@ proc getIDFromJson*(str: string): uint64 =
|
||||||
discard parseOct(str, num)
|
discard parseOct(str, num)
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
proc endpoint*(url: string): string =
|
||||||
|
return fmt("https://discord.com/api/v6{url}")
|
||||||
|
|
||||||
type RateLimitBucketType = enum
|
type
|
||||||
CHANNEL,
|
RateLimitBucketType* = enum
|
||||||
GUILD,
|
channel,
|
||||||
WEBHOOK,
|
guild,
|
||||||
GLOBAL
|
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)
|
Reference in New Issue