Handle Patreon API 429 responses (#2881)

This commit is contained in:
Kalle 2026-03-10 18:53:29 +02:00 committed by GitHub
parent 6834c86952
commit 51c9f2ad24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 57 additions and 14 deletions

View File

@ -7,6 +7,14 @@ import {
UNKNOWN_TIER_ID,
} from "./constants";
export const patreonRateLimitSchema = z.object({
errors: z.array(
z.object({
retry_after_seconds: z.number().optional(),
}),
),
});
export const patronResponseSchema = z.object({
data: z.array(
z.object({

View File

@ -13,7 +13,7 @@ import {
TIER_4_ID,
UNKNOWN_TIER_ID,
} from "./constants";
import { patronResponseSchema } from "./schema";
import { patreonRateLimitSchema, patronResponseSchema } from "./schema";
export async function updatePatreonData(): Promise<void> {
const patrons: UserRepository.UpdatePatronDataArgs = [];
@ -47,28 +47,63 @@ export async function updatePatreonData(): Promise<void> {
await UserRepository.updatePatronData(patronsWithMods);
}
const MAX_RETRIES = 10;
const DEFAULT_RETRY_AFTER_SECONDS = 10;
const MAX_RETRY_AFTER_SECONDS = 60;
async function fetchPatronData(urlToFetch: string) {
if (!process.env.PATREON_ACCESS_TOKEN) {
throw new Error("Missing Patreon access token");
}
const response = await fetchWithTimeout(
urlToFetch,
{
headers: {
Authorization: `Bearer ${process.env.PATREON_ACCESS_TOKEN}`,
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
const response = await fetchWithTimeout(
urlToFetch,
{
headers: {
Authorization: `Bearer ${process.env.PATREON_ACCESS_TOKEN}`,
},
},
},
30_000,
);
if (!response.ok) {
throw new Error(
`Patreon response not succesful. Status code was: ${response.status}`,
30_000,
);
if (response.status === 429) {
if (attempt === MAX_RETRIES) {
throw new Error(
`Patreon rate limit exceeded after ${MAX_RETRIES} retries`,
);
}
const parsed = patreonRateLimitSchema.safeParse(await response.json());
const retryAfterSeconds = Math.min(
parsed.success
? (parsed.data.errors[0]?.retry_after_seconds ??
DEFAULT_RETRY_AFTER_SECONDS)
: DEFAULT_RETRY_AFTER_SECONDS,
MAX_RETRY_AFTER_SECONDS,
);
logger.warn(
`Patreon rate limited, retrying in ${retryAfterSeconds}s (attempt ${attempt + 1}/${MAX_RETRIES})`,
);
await sleep(retryAfterSeconds * 1000);
continue;
}
if (!response.ok) {
throw new Error(
`Patreon response not successful. Status code was: ${response.status}`,
);
}
return patronResponseSchema.parse(await response.json());
}
return patronResponseSchema.parse(await response.json());
throw new Error("Unexpected end of fetch retry loop");
}
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function parsePatronData({