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,
};
});
// Comick API handlers
ipcMain.handle(
"comick:search",
async (_, query: string, limit: number = 10) => {
try {
console.log(
`🔍 Comick API: Searching for "${query}" with limit ${limit}`,
);
const encodedQuery = encodeURIComponent(query);
const response = await fetch(
`https://api.comick.fun/v1.0/search?q=${encodedQuery}&limit=${limit}&t=false`,
{
method: "GET",
headers: {
Accept: "application/json",
"User-Agent": "KenmeiToAniList/1.0",
},
},
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = (await response.json()) as ComickManga[];
console.log(
`📦 Comick API: Found ${Array.isArray(data) ? data.length : 0} results for "${query}"`,
);
return data || [];
} catch (error) {
console.error(`❌ Comick search failed for "${query}":`, error);
throw error;
}
},
);
ipcMain.handle("comick:getMangaDetail", async (_, slug: string) => {
try {
console.log(`📖 Comick API: Getting manga details for "${slug}"`);
const response = await fetch(`https://api.comick.fun/comic/${slug}`, {
method: "GET",
headers: {
Accept: "application/json",
"User-Agent": "KenmeiToAniList/1.0",
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = (await response.json()) as ComickMangaDetail;
console.log(`📖 Comick API: Retrieved details for "${slug}"`);
return data || null;
} catch (error) {
console.error(`❌ Comick manga detail failed for "${slug}":`, error);
throw error;
}
});
}
Setup IPC handlers for AniList API requests, token exchange, cache, and shell actions.