/** * Error thrown by every C2 API request (server-26#286). * * `message` is a sentence a user can read — FastAPI's `detail` when the * server sent one, otherwise a plain mapping of the status. The raw status * and body stay on the error for callers that need the structured parts * (the Discord parsers read `invite`/`code` out of `body`). */ export class C2ApiError extends Error { readonly status: number; readonly body: string; constructor(status: number, body: string) { super(humanMessage(status, body)); this.name = "C2ApiError"; this.status = status; this.body = body; } // Call sites render String(e); without this it reads "C2ApiError: …". toString(): string { return this.message; } } function detailOf(body: string): string | null { try { const parsed = JSON.parse(body); const d = parsed?.detail; if (typeof d === "string" && d.trim()) return d; // pydantic validation errors: [{loc, msg, type}, ...] if (Array.isArray(d)) { const msgs = d.map((x: { msg?: unknown }) => (typeof x?.msg === "string" ? x.msg : null)).filter(Boolean); if (msgs.length) return msgs.join("; "); } } catch { /* not JSON */ } return null; } function humanMessage(status: number, body: string): string { const detail = detailOf(body); if (status === 401) return "Your session has expired. Sign in again."; if (status === 403) return detail ? `Not allowed: ${detail}` : "You don't have permission to do that."; if (detail) return detail; if (status === 404) return "Not found. It may have been deleted."; if (status === 429) return "Too many requests. Wait a moment and try again."; if (status >= 500) return "The server hit an error. Try again in a moment."; return `Request failed (${status}).`; } /** Message for any caught value — use in catch blocks instead of String(e). */ export function errorMessage(e: unknown): string { if (e instanceof Error) return e.message; return String(e); } /** Rebuilds the legacy `C2 API error : ` string for the * Discord parsers, which predate C2ApiError and match on that shape. */ export function legacyErrorText(err: unknown): string { if (err instanceof C2ApiError) return `C2 API error ${err.status}: ${err.body}`; return err instanceof Error ? err.message : String(err); }