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(path: string, options: RequestInit = {}): Promise { const url = `${this.baseUrl}${path}`; const headers: Record = { 'Authorization': `Bot ${this.botToken}`, ...(options.headers as Record), }; // 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(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; } }