export function setupAniListAPI() {
// Handle graphQL requests
ipcMain.handle("anilist:request", async (_, query, variables, token) => {
try {
console.log("Handling AniList API request in main process");
// Extract and remove bypassCache from variables to avoid sending it to the API
const bypassCache = variables?.bypassCache;
if (variables && "bypassCache" in variables) {
// Create a copy without the bypassCache property
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { bypassCache: removed, ...cleanVariables } = variables;
variables = cleanVariables;
}
// Check if it's a search request and if we should use cache
const isSearchQuery = query.includes("Page(") && variables?.search;
if (isSearchQuery && !bypassCache) {
const cacheKey = generateCacheKey(query, variables);
if (isCacheValid(searchCache, cacheKey)) {
console.log(`Using cached search results for: ${variables.search}`);
return {
success: true,
data: searchCache[cacheKey].data,
};
}
}
if (isSearchQuery && bypassCache) {
console.log(`Bypassing cache for search: ${variables.search}`);
}
const response = await requestAniList(query, variables, token);
// Cache search results only if not bypassing cache
if (isSearchQuery && response.data && !bypassCache) {
const cacheKey = generateCacheKey(query, variables);
searchCache[cacheKey] = {
data: response,
timestamp: Date.now(),
};
}
return {
success: true,
data: response,
};
} catch (error) {
console.error("Error in anilist:request:", error);
return {
success: false,
error,
};
}
});
// Handle token exchange
ipcMain.handle("anilist:exchangeToken", async (_, params) => {
try {
const { clientId, clientSecret, redirectUri, code } = params;
// Format the request body
const tokenRequestBody = {
grant_type: "authorization_code",
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
code: code,
};
const response = await fetch("https://anilist.co/api/v2/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(tokenRequestBody),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(`API error: ${response.status} ${errorData}`);
}
const data = await response.json();
return {
success: true,
token: data,
};
} catch (error) {
console.error("Error in anilist:exchangeToken:", error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
});
// Handle opening external links in the default browser
ipcMain.handle("shell:openExternal", async (_, url) => {
try {
console.log(`Opening external URL in default browser: ${url}`);
await shell.openExternal(url);
return { success: true };
} catch (error) {
console.error("Error opening external URL:", error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
});
// Clear search cache
ipcMain.handle("anilist:clearCache", (_, searchQuery) => {
if (searchQuery) {
// Clear specific cache entries
Object.keys(searchCache).forEach((key) => {
if (key.includes(searchQuery)) {
delete searchCache[key];
}
});
} else {
// Clear all cache
Object.keys(searchCache).forEach((key) => {
delete searchCache[key];
});
}
return { success: true };
});
// Get rate limit status from main process
ipcMain.handle("anilist:getRateLimitStatus", () => {
const now = Date.now();
return {
isRateLimited,
retryAfter: isRateLimited ? rateLimitResetTime : null,
timeRemaining: isRateLimited ? Math.max(0, rateLimitResetTime - now) : 0,
};
});
}
Setup IPC handlers for AniList API requests, token exchange, cache, and shell actions.