snowtransfer
Advanced tools
Comparing version 0.5.0 to 0.5.1
@@ -0,1 +1,2 @@ | ||
/// <reference types="node" /> | ||
/** | ||
@@ -19,3 +20,3 @@ * Bucket used for saving ratelimits | ||
*/ | ||
protected _remaining: number; | ||
remaining: number; | ||
/** | ||
@@ -26,5 +27,5 @@ * Timeframe in milliseconds until the ratelimit resets | ||
/** | ||
* The Date time in which the bucket will reset | ||
* Timeout that calls the reset function once the timeframe passed | ||
*/ | ||
resetAt: number | null; | ||
resetTimeout: NodeJS.Timeout | null; | ||
/** | ||
@@ -35,8 +36,10 @@ * ratelimiter used for ratelimiting requests | ||
/** | ||
* Key used internally to routify requests | ||
*/ | ||
routeKey: string; | ||
/** | ||
* Create a new bucket | ||
* @param ratelimiter ratelimiter used for ratelimiting requests | ||
*/ | ||
constructor(ratelimiter: import("./Ratelimiter")); | ||
get remaining(): number; | ||
set remaining(value: number); | ||
constructor(ratelimiter: import("./Ratelimiter"), routeKey: string); | ||
/** | ||
@@ -47,3 +50,4 @@ * Queue a function to be executed | ||
*/ | ||
queue(fn: (bucket: LocalBucket) => any | Promise<any>): Promise<any>; | ||
queue(fn: (bucket: LocalBucket) => any): Promise<any>; | ||
runTimer(): void; | ||
/** | ||
@@ -54,3 +58,3 @@ * Check if there are any functions in the queue that haven't been executed yet | ||
/** | ||
* Reset the remaining tokens to the base limit | ||
* Reset the remaining tokens to the base limit and removes the bucket from the rate limiter to save memory | ||
*/ | ||
@@ -57,0 +61,0 @@ resetRemaining(): void; |
@@ -10,20 +10,26 @@ "use strict"; | ||
*/ | ||
constructor(ratelimiter) { | ||
constructor(ratelimiter, routeKey) { | ||
/** | ||
* array of functions waiting to be executed | ||
*/ | ||
this.fnQueue = []; | ||
/** | ||
* Number of functions that may be executed during the timeframe set in limitReset | ||
*/ | ||
this.limit = 5; | ||
this._remaining = 1; | ||
/** | ||
* Remaining amount of executions during the current timeframe | ||
*/ | ||
this.remaining = 1; | ||
/** | ||
* Timeframe in milliseconds until the ratelimit resets | ||
*/ | ||
this.reset = 5000; | ||
this.resetAt = null; | ||
/** | ||
* Timeout that calls the reset function once the timeframe passed | ||
*/ | ||
this.resetTimeout = null; | ||
this.ratelimiter = ratelimiter; | ||
this.routeKey = routeKey; | ||
} | ||
get remaining() { | ||
if (this.resetAt && this.resetAt <= Date.now()) { | ||
this._remaining = this.limit; | ||
this.resetAt = null; | ||
} | ||
return this._remaining; | ||
} | ||
set remaining(value) { | ||
this._remaining = value; | ||
} | ||
/** | ||
@@ -35,6 +41,4 @@ * Queue a function to be executed | ||
queue(fn) { | ||
return new Promise((res, rej) => { | ||
return new Promise(res => { | ||
const wrapFn = () => { | ||
if (fn instanceof Promise) | ||
return fn.then(res).catch(rej); | ||
return res(fn(this)); | ||
@@ -46,2 +50,7 @@ }; | ||
} | ||
runTimer() { | ||
if (this.resetTimeout) | ||
clearTimeout(this.resetTimeout); | ||
this.resetTimeout = setTimeout(() => this.resetRemaining(), this.ratelimiter.global ? this.ratelimiter.globalReset : this.reset); | ||
} | ||
/** | ||
@@ -53,6 +62,9 @@ * Check if there are any functions in the queue that haven't been executed yet | ||
this.reset = 100; | ||
if (this.ratelimiter.global && this.ratelimiter.globalResetAt > Date.now()) | ||
return; | ||
if (this.ratelimiter.global) | ||
return this.runTimer(); | ||
if (this.remaining === 0) | ||
this.runTimer(); | ||
if (this.fnQueue.length > 0 && this.remaining !== 0) { | ||
const queuedFunc = this.fnQueue.splice(0, 1)[0]; | ||
this.remaining--; | ||
queuedFunc.callback(); | ||
@@ -63,8 +75,10 @@ this.checkQueue(); | ||
/** | ||
* Reset the remaining tokens to the base limit | ||
* Reset the remaining tokens to the base limit and removes the bucket from the rate limiter to save memory | ||
*/ | ||
resetRemaining() { | ||
this._remaining = this.limit; | ||
this.resetAt = null; | ||
this.checkQueue(); | ||
this.remaining = this.limit; | ||
if (this.resetTimeout) | ||
clearTimeout(this.resetTimeout); | ||
this.resetTimeout = null; | ||
delete this.ratelimiter.buckets[this.routeKey]; | ||
} | ||
@@ -71,0 +85,0 @@ /** |
@@ -192,15 +192,2 @@ /// <reference types="node" /> | ||
/** | ||
* Batch edits permissions for all commands in a guild. Takes an Array of partial [guild application command permission](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure) objects. | ||
* You can only add up to 10 permission overwrites for a command | ||
* @param appId The Id of the application | ||
* @param guildId The Id of the guild | ||
* @param permissions New application command permissions data Array | ||
* @returns An Array of [guild application command permission](https://discord.com/developers/docs/interactions/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure) objects | ||
* | ||
* @example | ||
* const client = new SnowTransfer("TOKEN") | ||
* const permissions = await client.interaction.bulkEditGuildApplicationCommandPermissions("appId", "guildId", [{ id: "cmdId", permissions: [{ type: 2, id: "userId", permission: true }] }]) | ||
*/ | ||
batchEditGuildApplicationCommandPermissions(appId: string, guildId: string, permissions: Array<Pick<import("discord-typings").GuildApplicationCommandPermission, "id" | "permissions">>): Promise<Array<import("discord-typings").GuildApplicationCommandPermission>>; | ||
/** | ||
* Create a response to an Interaction | ||
@@ -207,0 +194,0 @@ * |
@@ -209,17 +209,2 @@ "use strict"; | ||
/** | ||
* Batch edits permissions for all commands in a guild. Takes an Array of partial [guild application command permission](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure) objects. | ||
* You can only add up to 10 permission overwrites for a command | ||
* @param appId The Id of the application | ||
* @param guildId The Id of the guild | ||
* @param permissions New application command permissions data Array | ||
* @returns An Array of [guild application command permission](https://discord.com/developers/docs/interactions/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure) objects | ||
* | ||
* @example | ||
* const client = new SnowTransfer("TOKEN") | ||
* const permissions = await client.interaction.bulkEditGuildApplicationCommandPermissions("appId", "guildId", [{ id: "cmdId", permissions: [{ type: 2, id: "userId", permission: true }] }]) | ||
*/ | ||
batchEditGuildApplicationCommandPermissions(appId, guildId, permissions) { | ||
return this.requestHandler.request(Endpoints_1.default.APPLICATION_GUILD_COMMANDS_PERMISSIONS(appId, guildId), "put", "json", permissions); | ||
} | ||
/** | ||
* Create a response to an Interaction | ||
@@ -226,0 +211,0 @@ * |
@@ -1,2 +0,1 @@ | ||
/// <reference types="node" /> | ||
import LocalBucket from "./LocalBucket"; | ||
@@ -12,12 +11,3 @@ /** | ||
global: boolean; | ||
globalResetAt: number; | ||
/** | ||
* This is an interval to constantly check Buckets which should be reset or unreferenced from the RateLimiter to be swept by the garbage collector. | ||
* This 1 timeout is more performant as compared to potentially many more ticking timers to reset individual bucket remaining values. | ||
* | ||
* YOU SHOULD NEVER OVERRIDE THIS UNLESS YOU KNOW WHAT YOU'RE DOING. REQUESTS MAY POSSIBLY NEVER EXECUTE WITHOUT THIS AND/OR MEMORY MAY SLOWLY CLIMB OVER TIME. | ||
*/ | ||
protected _timeout: NodeJS.Timeout; | ||
protected _timeoutFN: () => void; | ||
protected _timeoutDuration: number; | ||
globalReset: number; | ||
constructor(); | ||
@@ -24,0 +14,0 @@ /** |
@@ -12,24 +12,5 @@ "use strict"; | ||
constructor() { | ||
this._timeoutDuration = 1000; | ||
this.buckets = {}; | ||
this.global = false; | ||
this.globalResetAt = 0; | ||
this._timeoutFN = () => { | ||
for (const routeKey of Object.keys(this.buckets)) { | ||
const bkt = this.buckets[routeKey]; | ||
if (bkt.resetAt && bkt.resetAt < Date.now()) { | ||
if (bkt.fnQueue.length) | ||
bkt.resetRemaining(); | ||
else | ||
delete this.buckets[routeKey]; | ||
} | ||
else if (!bkt.resetAt && this.global && this.globalResetAt < Date.now()) { | ||
if (bkt.fnQueue.length) | ||
bkt.checkQueue(); | ||
else | ||
delete this.buckets[routeKey]; | ||
} | ||
} | ||
}; | ||
this._timeout = setInterval(() => { this._timeoutFN(); }, this._timeoutDuration); | ||
this.globalReset = 0; | ||
} | ||
@@ -60,3 +41,3 @@ /** | ||
if (!this.buckets[routeKey]) | ||
this.buckets[routeKey] = new LocalBucket_1.default(this); | ||
this.buckets[routeKey] = new LocalBucket_1.default(this, routeKey); | ||
this.buckets[routeKey].queue(fn); | ||
@@ -63,0 +44,0 @@ } |
@@ -76,6 +76,5 @@ /// <reference types="node" /> | ||
* @param data data to send, if any | ||
* @param amount amount of requests previously executed | ||
* @returns Result of the request | ||
*/ | ||
request(endpoint: string, method: HTTPMethod, dataType?: "json" | "multipart", data?: any | undefined, amount?: number): Promise<any>; | ||
request(endpoint: string, method: HTTPMethod, dataType?: "json" | "multipart", data?: any | undefined): Promise<any>; | ||
/** | ||
@@ -92,3 +91,2 @@ * Apply the received ratelimit headers to the ratelimit bucket | ||
* @param useParams Whether to send the data in the body or use query params | ||
* @param amount amount of requests previously executed | ||
* @returns Result of the request | ||
@@ -95,0 +93,0 @@ */ |
@@ -74,6 +74,5 @@ "use strict"; | ||
* @param data data to send, if any | ||
* @param amount amount of requests previously executed | ||
* @returns Result of the request | ||
*/ | ||
request(endpoint, method, dataType = "json", data = {}, amount = 0) { | ||
request(endpoint, method, dataType = "json", data = {}) { | ||
if (typeof data === "number") | ||
@@ -90,23 +89,20 @@ data = String(data); | ||
if (dataType == "json") | ||
request = await this._request(endpoint, method, data, (method === "get" || endpoint.includes("/bans") || endpoint.includes("/prune")), amount); | ||
request = await this._request(endpoint, method, data, (method === "get" || endpoint.includes("/bans") || endpoint.includes("/prune"))); | ||
else if (dataType == "multipart") | ||
request = await this._multiPartRequest(endpoint, method, data, amount); | ||
else { | ||
const e = new Error("Forbidden dataType. Use json or multipart"); | ||
e.stack = stack; | ||
throw e; | ||
request = await this._multiPartRequest(endpoint, method, data); | ||
else | ||
throw new Error("Forbidden dataType. Use json or multipart"); | ||
if (request.statusCode && !Constants_1.default.OK_STATUS_CODES.includes(request.statusCode) && request.statusCode !== 429) | ||
throw new DiscordAPIError(endpoint, ((_a = request.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.startsWith("application/json")) ? await request.json() : request.body.toString(), method, request.statusCode); | ||
this._applyRatelimitHeaders(bkt, request.headers); | ||
if (request.statusCode === 429) { | ||
const b = JSON.parse(request.body.toString()); // Discord says it will be a JSON, so if there's an error, sucks | ||
if (b.global) | ||
this.ratelimiter.global = true; | ||
if (b.reset_after) | ||
bkt.reset = b.reset_after * 1000; | ||
bkt.runTimer(); | ||
this.emit("rateLimit", { timeout: bkt.reset, limit: bkt.limit, method: method, path: endpoint, route: this.ratelimiter.routify(endpoint, method) }); | ||
throw new DiscordAPIError(endpoint, b.message || "unknnown", method, request.statusCode); | ||
} | ||
// 429 and 502 are recoverable and will be re-tried automatically with 3 attempts max. | ||
if (request.statusCode && !Constants_1.default.OK_STATUS_CODES.includes(request.statusCode) && ![429, 502].includes(request.statusCode)) { | ||
const e = new DiscordAPIError(endpoint, ((_a = request.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.startsWith("application/json")) ? await request.json() : request.body.toString(), method, request.statusCode); | ||
e.stack = stack; | ||
throw e; | ||
} | ||
if (request.statusCode && [429, 502].includes(request.statusCode)) { | ||
if (request.statusCode === 429) { | ||
this._applyRatelimitHeaders(bkt, request.headers); | ||
this.emit("rateLimit", { timeout: bkt.reset, limit: bkt.limit, method: method, path: endpoint, route: this.ratelimiter.routify(endpoint, method) }); | ||
} | ||
return this.request(endpoint, method, dataType, data, amount++); | ||
} | ||
this.emit("done", reqID, request); | ||
@@ -141,17 +137,10 @@ if (request.body) { | ||
_applyRatelimitHeaders(bkt, headers) { | ||
if (headers["x-ratelimit-global"]) { | ||
bkt.ratelimiter.global = true; | ||
bkt.ratelimiter.globalResetAt = Date.now() + (parseFloat(headers["retry-after"]) * 1000); | ||
} | ||
if (headers["x-ratelimit-remaining"]) { | ||
if (headers["x-ratelimit-remaining"]) | ||
bkt.remaining = parseInt(headers["x-ratelimit-remaining"]); | ||
if (bkt.remaining === 0) | ||
bkt.resetAt = Date.now() + bkt.reset; | ||
} | ||
else | ||
bkt.remaining = 1; | ||
if (headers["x-ratelimit-limit"]) | ||
bkt.limit = parseInt(headers["x-ratelimit-limit"]); | ||
if (headers["retry-after"] && !headers["x-ratelimit-global"]) | ||
bkt.resetAt = Date.now() + (parseInt(headers["retry-after"]) * 1000); // The ms precision is not strictly necessary. It always rounds up, which is safe. | ||
if (headers["x-ratelimit-reset"]) { | ||
bkt.reset = parseInt(headers["x-ratelimit-reset"]) - Date.now(); | ||
bkt.runTimer(); | ||
} | ||
} | ||
@@ -163,8 +152,5 @@ /** | ||
* @param useParams Whether to send the data in the body or use query params | ||
* @param amount amount of requests previously executed | ||
* @returns Result of the request | ||
*/ | ||
async _request(endpoint, method, data, useParams = false, amount = 0) { | ||
if (amount >= 3) | ||
throw new Error("Max amount of rety attempts reached"); | ||
async _request(endpoint, method, data, useParams = false) { | ||
const headers = {}; | ||
@@ -193,5 +179,3 @@ if (typeof data != "string" && data.reason) { | ||
*/ | ||
async _multiPartRequest(endpoint, method, data, amount = 0) { | ||
if (amount >= 3) | ||
throw new Error("Max amount of rety attempts reached"); | ||
async _multiPartRequest(endpoint, method, data) { | ||
const form = new form_data_1.default(); | ||
@@ -198,0 +182,0 @@ if (data.files && Array.isArray(data.files) && data.files.every(f => !!f.name && !!f.file)) { |
@@ -20,8 +20,2 @@ import Ratelimiter from "./Ratelimiter"; | ||
disableEveryone: boolean; | ||
sentryOptions: { | ||
extra: { | ||
snowtransferVersion: string; | ||
}; | ||
}; | ||
useRedis: boolean; | ||
}; | ||
@@ -28,0 +22,0 @@ token: string | undefined; |
@@ -21,3 +21,2 @@ "use strict"; | ||
const Endpoints_1 = __importDefault(require("./Endpoints")); | ||
const { version } = require("../package.json"); | ||
class SnowTransfer { | ||
@@ -32,5 +31,5 @@ /** | ||
throw new Error("Missing token"); | ||
if (token && !token.startsWith("Bot")) | ||
if (token && (!token.startsWith("Bot") && !token.startsWith("Bearer"))) | ||
token = `Bot ${token}`; | ||
this.options = { baseHost: Endpoints_1.default.BASE_HOST, disableEveryone: false, sentryOptions: { extra: { snowtransferVersion: version } }, useRedis: false }; | ||
this.options = { baseHost: Endpoints_1.default.BASE_HOST, disableEveryone: false }; | ||
this.token = token; | ||
@@ -37,0 +36,0 @@ Object.assign(this.options, options); |
{ | ||
"name": "snowtransfer", | ||
"version": "0.5.0", | ||
"version": "0.5.1", | ||
"description": "Minimalistic Rest client for the Discord Api", | ||
@@ -33,3 +33,3 @@ "main": "./dist/index.js", | ||
"centra": "^2.5.0", | ||
"discord-typings": "^10.1.0", | ||
"discord-typings": "^10.1.2", | ||
"form-data": "~4.0.0" | ||
@@ -40,5 +40,5 @@ }, | ||
"@types/node": "^16.0.1", | ||
"@typescript-eslint/eslint-plugin": "^5.18.0", | ||
"@typescript-eslint/parser": "^5.18.0", | ||
"eslint": "^8.13.0", | ||
"@typescript-eslint/eslint-plugin": "^5.21.0", | ||
"@typescript-eslint/parser": "^5.21.0", | ||
"eslint": "^8.14.0", | ||
"typedoc": "^0.22.15", | ||
@@ -45,0 +45,0 @@ "typedoc-plugin-mdn-links": "^1.0.6", |
@@ -15,2 +15,3 @@ # A minimalistic rest client for the discord api | ||
- Well documented | ||
- Supports both Bot and Bearer tokens (Bearer tokens will have much more limited access than Bot tokens) | ||
@@ -17,0 +18,0 @@ ### General Usecase: |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
48
364398
7251
Updateddiscord-typings@^10.1.2