diff options
Diffstat (limited to 'apps/proxy/src')
| -rw-r--r-- | apps/proxy/src/rawkuma.js | 90 |
1 files changed, 77 insertions, 13 deletions
diff --git a/apps/proxy/src/rawkuma.js b/apps/proxy/src/rawkuma.js index 89f20870..1a533143 100644 --- a/apps/proxy/src/rawkuma.js +++ b/apps/proxy/src/rawkuma.js @@ -2,6 +2,8 @@ const KLMANGA_ORIGIN = "https://klmanga.mom"; const RAWKUMA_ORIGIN = "https://rawkuma.net"; const DEFAULT_CACHE_TTL_MS = 30 * 60 * 1000; const DEFAULT_CONCURRENCY = 8; +const DEFAULT_FETCH_TIMEOUT_MS = 5000; +const DEFAULT_RAWKUMA_NONCE_TTL_MS = 10 * 60 * 1000; const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0"; const MIN_MATCH_SCORE = 0.75; @@ -9,6 +11,11 @@ const MIN_MATCH_MARGIN = 0.1; const nativeChapterCache = new Map(); const nativeChapterInFlight = new Map(); +const rawkumaNonceCache = { + value: null, + expiresAt: 0, + promise: null, +}; const cacheTtlMs = (env) => { const milliseconds = Number.parseInt(env.RAWKUMA_CACHE_TTL_MS || "", 10); @@ -26,6 +33,22 @@ const concurrencyLimit = (env) => { : DEFAULT_CONCURRENCY; }; +const fetchTimeoutMs = (env) => { + const milliseconds = Number.parseInt(env.RAWKUMA_FETCH_TIMEOUT_MS || "", 10); + + return Number.isFinite(milliseconds) && milliseconds > 0 + ? milliseconds + : DEFAULT_FETCH_TIMEOUT_MS; +}; + +const rawkumaNonceTtlMs = (env) => { + const milliseconds = Number.parseInt(env.RAWKUMA_NONCE_TTL_MS || "", 10); + + return Number.isFinite(milliseconds) && milliseconds > 0 + ? milliseconds + : DEFAULT_RAWKUMA_NONCE_TTL_MS; +}; + const getCachedChapterCount = (title) => { const cached = nativeChapterCache.get(title); @@ -49,10 +72,12 @@ const setCachedChapterCount = (env, title, chapter) => { }); }; -const fetchText = async (requestHeaders, url, init = {}) => { +const fetchText = async (env, requestHeaders, url, init = {}) => { const headers = new Headers(requestHeaders); const targetUrl = new URL(url); const initHeaders = new Headers(init.headers); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), fetchTimeoutMs(env)); for (const [key, value] of initHeaders.entries()) headers.set(key, value); @@ -66,7 +91,17 @@ const fetchText = async (requestHeaders, url, init = {}) => { if (!headers.has("User-Agent")) headers.set("User-Agent", DEFAULT_USER_AGENT); headers.delete("Content-Length"); - return await (await fetch(url, { ...init, headers })).text(); + try { + return await ( + await fetch(url, { + ...init, + headers, + signal: init.signal || controller.signal, + }) + ).text(); + } finally { + clearTimeout(timeout); + } }; const decodeHtml = (value) => @@ -189,17 +224,43 @@ const parseRawkumaChapterListUrl = (text) => )?.[1] || "", ).trim() || null; -const fetchRawkumaChapterCount = async (requestHeaders, entry) => { - const nonceText = await fetchText( +const getRawkumaNonce = async (env, requestHeaders) => { + if (rawkumaNonceCache.value && Date.now() < rawkumaNonceCache.expiresAt) + return rawkumaNonceCache.value; + + if (rawkumaNonceCache.promise) return rawkumaNonceCache.promise; + + const promise = fetchText( + env, requestHeaders, `${RAWKUMA_ORIGIN}/wp-admin/admin-ajax.php?type=search_form&action=get_nonce`, - ); - const nonce = parseRawkumaNonce(nonceText); + ) + .then((nonceText) => parseRawkumaNonce(nonceText)) + .then((nonce) => { + if (!nonce) return null; + + rawkumaNonceCache.value = nonce; + rawkumaNonceCache.expiresAt = Date.now() + rawkumaNonceTtlMs(env); + + return nonce; + }) + .finally(() => { + rawkumaNonceCache.promise = null; + }); + + rawkumaNonceCache.promise = promise; + + return promise; +}; + +const fetchRawkumaChapterCount = async (env, requestHeaders, entry) => { + const nonce = await getRawkumaNonce(env, requestHeaders); if (!nonce) return null; for (const candidate of titleCandidates(entry)) { const searchText = await fetchText( + env, requestHeaders, `${RAWKUMA_ORIGIN}/wp-admin/admin-ajax.php?nonce=${encodeURIComponent( nonce, @@ -221,10 +282,10 @@ const fetchRawkumaChapterCount = async (requestHeaders, entry) => { if (!bestMatch) continue; - const mangaText = await fetchText(requestHeaders, bestMatch.url); + const mangaText = await fetchText(env, requestHeaders, bestMatch.url); const chapterListUrl = parseRawkumaChapterListUrl(mangaText); const chapterListText = chapterListUrl - ? await fetchText(requestHeaders, chapterListUrl) + ? await fetchText(env, requestHeaders, chapterListUrl) : mangaText; const chapters = parseRawkumaChapterNumbers(chapterListText); @@ -257,9 +318,10 @@ const parseKlmangaChapterNumbers = (text) => .filter((value) => Number.isFinite(value)) .sort((left, right) => right - left); -const fetchKlmangaChapterCount = async (requestHeaders, entry) => { +const fetchKlmangaChapterCount = async (env, requestHeaders, entry) => { for (const candidate of titleCandidates(entry)) { const searchText = await fetchText( + env, requestHeaders, `${KLMANGA_ORIGIN}/?s=${encodeURIComponent(candidate)}`, ); @@ -270,7 +332,7 @@ const fetchKlmangaChapterCount = async (requestHeaders, entry) => { if (!bestMatch) continue; - const mangaText = await fetchText(requestHeaders, bestMatch.url); + const mangaText = await fetchText(env, requestHeaders, bestMatch.url); const chapters = parseKlmangaChapterNumbers(mangaText); if (!chapters.length) continue; @@ -281,11 +343,13 @@ const fetchKlmangaChapterCount = async (requestHeaders, entry) => { return null; }; -const fetchNativeChapterCountUncached = async (requestHeaders, entry) => { +const fetchNativeChapterCountUncached = async (env, requestHeaders, entry) => { const providers = [fetchKlmangaChapterCount, fetchRawkumaChapterCount]; for (const provider of providers) { - const chapter = await provider(requestHeaders, entry).catch(() => null); + const chapter = await provider(env, requestHeaders, entry).catch( + () => null, + ); if (chapter !== null) return chapter; } @@ -306,7 +370,7 @@ const fetchNativeChapterCount = async (env, requestHeaders, entry) => { if (existing) return existing; - const promise = fetchNativeChapterCountUncached(requestHeaders, entry) + const promise = fetchNativeChapterCountUncached(env, requestHeaders, entry) .catch(() => null) .then((chapter) => { setCachedChapterCount(env, normalizedTitle, chapter); |