51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
|
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
export class DiscordRateLimiter {
|
|
private readonly baseUrl: string;
|
|
private readonly botToken: string;
|
|
|
|
constructor(baseUrl: string, botToken: string) {
|
|
this.baseUrl = baseUrl;
|
|
this.botToken = botToken;
|
|
}
|
|
|
|
/**
|
|
* Makes a request to the Discord API, handling rate limits automatically.
|
|
* @param path The API endpoint path (e.g., '/channels/123/messages').
|
|
* @param options Fetch options (method, headers, body, etc.).
|
|
* @returns The JSON response from the Discord API.
|
|
*/
|
|
public async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
const url = `${this.baseUrl}${path}`;
|
|
|
|
const headers: Record<string, string> = {
|
|
'Authorization': `Bot ${this.botToken}`,
|
|
...(options.headers as Record<string, string>),
|
|
};
|
|
|
|
// Only set Content-Type to application/json if body is a string and not explicitly set
|
|
if (typeof options.body === 'string' && !('Content-Type' in headers)) {
|
|
headers['Content-Type'] = 'application/json';
|
|
}
|
|
|
|
const response = await fetch(url, { ...options, headers });
|
|
|
|
if (response.status === 429) {
|
|
const retryAfter = response.headers.get('Retry-After');
|
|
const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 1000; // Default to 1 second if header is missing
|
|
|
|
console.warn(`Discord API rate limit hit. Retrying after ${delay / 1000} seconds.`);
|
|
await sleep(delay);
|
|
return this.request<T>(path, options); // Retry the request
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: response.statusText }));
|
|
throw new Error(`Discord API error: ${response.status} - ${errorData.message || JSON.stringify(errorData)}`);
|
|
}
|
|
|
|
return response.json() as Promise<T>;
|
|
}
|
|
}
|