aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/proxy/src/index.js86
-rw-r--r--apps/proxy/src/mangadex.js29
-rw-r--r--apps/proxy/src/supabase.js1
3 files changed, 100 insertions, 16 deletions
diff --git a/apps/proxy/src/index.js b/apps/proxy/src/index.js
index 4f18ba44..26121f10 100644
--- a/apps/proxy/src/index.js
+++ b/apps/proxy/src/index.js
@@ -135,6 +135,7 @@ const parseMangaPayload = async (request) => {
return manga
.map((entry) => ({
anilistId: Number(entry?.anilistId),
+ progress: entry?.progress ? Number(entry.progress) : 0,
status: String(entry?.status || ""),
startYear: entry?.startYear ? Number(entry.startYear) : null,
nativeTitle: entry?.nativeTitle || null,
@@ -203,6 +204,43 @@ const queueBootstrap = (env, manga) =>
})
.filter(Boolean);
+const parseOptionalNumber = (value) => {
+ if (value === null || value === undefined || value === "") return null;
+
+ const number = Number.parseFloat(String(value));
+
+ return Number.isFinite(number) ? number : null;
+};
+
+const recommendedVolumeText = (volumeChapterBoundaries, progress) => {
+ if (!volumeChapterBoundaries || !Number.isFinite(progress) || progress <= 0)
+ return null;
+
+ let recommended = null;
+ let recommendedNumber = null;
+
+ for (const [volumeText, chapterBoundary] of Object.entries(
+ volumeChapterBoundaries,
+ )) {
+ const volumeNumber = parseOptionalNumber(volumeText);
+ const boundaryNumber = parseOptionalNumber(chapterBoundary);
+
+ if (
+ volumeNumber === null ||
+ boundaryNumber === null ||
+ boundaryNumber > progress
+ )
+ continue;
+
+ if (recommendedNumber === null || volumeNumber > recommendedNumber) {
+ recommended = String(volumeNumber);
+ recommendedNumber = volumeNumber;
+ }
+ }
+
+ return recommended;
+};
+
const handleMangaChapterCounts = async (request, env, ctx) => {
if (!hasSupabaseConfig(env))
return jsonResponse(
@@ -215,25 +253,35 @@ const handleMangaChapterCounts = async (request, env, ctx) => {
if (!manga.length) return jsonResponse(request, { data: {} });
+ const mangaById = new Map(manga.map((entry) => [entry.anilistId, entry]));
const anilistIds = manga.map((entry) => entry.anilistId);
const [existingRows, failureRows] = await Promise.all([
getMangadexRowsByAniListIds(env, anilistIds),
getMangadexFailureRowsByAniListIds(env, anilistIds),
]);
- const existingIds = new Set(existingRows.map((row) => row.anilist_id));
+ const existingRowsById = new Map(
+ existingRows.map((row) => [row.anilist_id, row]),
+ );
const recentFailures = new Set(
failureRows
.filter((row) => isRecentFailure(row, bootstrapRetryMinutes(env)))
.map((row) => row.anilist_id),
);
- const missingRows = manga.filter(
- (entry) =>
- !existingIds.has(entry.anilistId) && !recentFailures.has(entry.anilistId),
- );
- const pendingRows = missingRows.filter((entry) =>
+ const rowsNeedingBackfill = manga.filter((entry) => {
+ const row = existingRowsById.get(entry.anilistId);
+
+ if (!row) return !recentFailures.has(entry.anilistId);
+
+ return (
+ entry.progress > 0 &&
+ row.volume_chapter_boundaries === null &&
+ !recentFailures.has(entry.anilistId)
+ );
+ });
+ const pendingRows = rowsNeedingBackfill.filter((entry) =>
bootstrapInFlight.has(entry.anilistId),
);
- const queueableRows = missingRows.filter(
+ const queueableRows = rowsNeedingBackfill.filter(
(entry) => !bootstrapInFlight.has(entry.anilistId),
);
@@ -245,15 +293,21 @@ const handleMangaChapterCounts = async (request, env, ctx) => {
);
const data = Object.fromEntries(
- existingRows.map((row) => [
- String(row.anilist_id),
- {
- chapter: row.latest_en_chapter_number,
- ...(row.latest_en_volume_text === null
- ? {}
- : { volumeText: row.latest_en_volume_text }),
- },
- ]),
+ existingRows.map((row) => {
+ const entry = mangaById.get(row.anilist_id);
+ const volumeText = recommendedVolumeText(
+ row.volume_chapter_boundaries,
+ entry?.progress || 0,
+ );
+
+ return [
+ String(row.anilist_id),
+ {
+ chapter: row.latest_en_chapter_number,
+ ...(volumeText === null ? {} : { volumeText }),
+ },
+ ];
+ }),
);
const pending = [
...new Set(
diff --git a/apps/proxy/src/mangadex.js b/apps/proxy/src/mangadex.js
index d2a42154..dea33e70 100644
--- a/apps/proxy/src/mangadex.js
+++ b/apps/proxy/src/mangadex.js
@@ -223,9 +223,37 @@ const chooseLatestAggregateChapter = (volumes) => {
return latest;
};
+const buildVolumeChapterBoundaries = (volumes) => {
+ const boundaries = {};
+
+ for (const [volumeKey, volumeEntry] of Object.entries(volumes || {})) {
+ const chapters = volumeEntry?.chapters || {};
+
+ for (const chapterEntry of Object.values(chapters)) {
+ const volumeText =
+ chapterEntry?.volume || volumeEntry?.volume || volumeKey || null;
+ const volumeNumber = parseOptionalNumber(volumeText);
+ const chapterNumber = parseOptionalNumber(chapterEntry?.chapter || null);
+
+ if (volumeNumber === null || chapterNumber === null) continue;
+
+ const key = String(volumeNumber);
+ const current = boundaries[key];
+
+ if (current === undefined || chapterNumber > current)
+ boundaries[key] = chapterNumber;
+ }
+ }
+
+ return Object.keys(boundaries).length ? boundaries : null;
+};
+
const buildIndexedRow = (manga, mangadexId, aggregateResponse) => {
const now = new Date().toISOString();
const latest = chooseLatestAggregateChapter(aggregateResponse?.volumes);
+ const volumeChapterBoundaries = buildVolumeChapterBoundaries(
+ aggregateResponse?.volumes,
+ );
return {
anilist_id: manga.anilistId,
@@ -233,6 +261,7 @@ const buildIndexedRow = (manga, mangadexId, aggregateResponse) => {
latest_en_chapter_text: latest?.chapterText || null,
latest_en_chapter_number: latest?.chapterNumber || null,
latest_en_volume_text: latest?.volumeText || null,
+ volume_chapter_boundaries: volumeChapterBoundaries,
latest_en_chapter_id: latest?.chapterId || null,
latest_chapter_updated_at: now,
last_seen_at: now,
diff --git a/apps/proxy/src/supabase.js b/apps/proxy/src/supabase.js
index 314caf59..b0038844 100644
--- a/apps/proxy/src/supabase.js
+++ b/apps/proxy/src/supabase.js
@@ -4,6 +4,7 @@ const INDEX_COLUMNS = [
"latest_en_chapter_text",
"latest_en_chapter_number",
"latest_en_volume_text",
+ "volume_chapter_boundaries",
"latest_en_chapter_id",
"latest_chapter_updated_at",
"last_seen_at",