aboutsummaryrefslogtreecommitdiff
path: root/apps/proxy/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/proxy/src')
-rw-r--r--apps/proxy/src/rawkuma.js90
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);