import type { Unicord } from '.'; import { exists, log } from './utils'; export class UnicordDiscordAPI { private readonly base_url: string = 'https://discord.com/api'; private readonly version: number = 10; constructor(private self: Unicord) {} async request(method: string, path: string, data?: any): Promise { const url = `${this.base_url}/v${this.version.toString()}${path.startsWith('/') ? '' : '/'}${path}`; const headers = { Authorization: `Bot ${this.self.config.token}`, Accept: 'application/json', 'Content-Type': 'application/json', }; const response = await fetch(url, { method, headers, body: JSON.stringify(data), }); if (!response.ok) { // console.error(`Failed to fetch: ${response.statusText} ${response.url} ${response.status}`); console.log('Failed Request Response: ', await response.text()); throw new Error(`Failed to fetch: ${response.statusText}`); } const body = await response.json(); // console.log(`API Request: ${method} ${url}`); // console.log(`API Body: ${JSON.stringify(data, null, 4)}`); // console.log('API Response:', JSON.stringify(body, null, 4)); // Extract rate limit headers const remaining = response.headers.get('X-RateLimit-Remaining'); const limit = response.headers.get('X-RateLimit-Limit'); const reset = response.headers.get('X-RateLimit-Reset'); const resetSeconds = response.headers.get('X-RateLimit-Reset-After'); const limitBucket = response.headers.get('X-RateLimit-Bucket'); // Log rate limit info for debugging log(`Rate Limit: ${remaining}/${limit} - Reset: ${reset} (${resetSeconds}s) - Bucket: ${limitBucket}`); // If status is 429, we are being rate limited if (response.status === 429) { // Look for the retry-after header const retryAfter = response.headers.get('Retry-After'); if (exists(retryAfter)) { // Wait for the retry-after time await new Promise((resolve) => setTimeout(resolve, parseInt(retryAfter) * 1000)); } else { // TODO: Handle this case better // Maybe wait some arbitrary time?? throw new Error('Rate limited without Retry-After header'); } } return body; } async get(path: string): Promise { return this.request('GET', path); } async post(path: string, data: any): Promise { return this.request('POST', path, data); } async patch(path: string, data: any): Promise { return this.request('PATCH', path, data); } async delete(path: string): Promise { return this.request('DELETE', path); } }