mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-09 15:13:32 -07:00
fix(anilist): mark entry completed when final episode is reached (#115)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: anilist
|
||||
|
||||
- Marked AniList entries completed when a post-watch update reaches the final known episode of the season.
|
||||
@@ -235,6 +235,86 @@ test('updateAnilistPostWatchProgress uses the configured AniList rate limiter',
|
||||
}
|
||||
});
|
||||
|
||||
test('updateAnilistPostWatchProgress marks the final season episode completed', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let call = 0;
|
||||
globalThis.fetch = (async (_input, init) => {
|
||||
call += 1;
|
||||
const body = JSON.parse(String(init?.body)) as { variables?: Record<string, unknown> };
|
||||
if (call === 1) {
|
||||
return createJsonResponse({
|
||||
data: {
|
||||
Page: {
|
||||
media: [{ id: 12, episodes: 12, title: { english: 'Final Show' } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (call === 2) {
|
||||
return createJsonResponse({
|
||||
data: {
|
||||
Media: { id: 12, mediaListEntry: { progress: 11, status: 'CURRENT' } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
assert.equal(body.variables?.progress, 12);
|
||||
assert.equal(body.variables?.status, 'COMPLETED');
|
||||
return createJsonResponse({
|
||||
data: { SaveMediaListEntry: { progress: 12, status: 'COMPLETED' } },
|
||||
});
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
const result = await updateAnilistPostWatchProgress('token', 'Final Show', 12);
|
||||
assert.equal(result.status, 'updated');
|
||||
assert.match(result.message, /completed/i);
|
||||
assert.equal(call, 3);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('updateAnilistPostWatchProgress marks an already watched final season episode completed', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let call = 0;
|
||||
globalThis.fetch = (async (_input, init) => {
|
||||
call += 1;
|
||||
const body = JSON.parse(String(init?.body)) as { variables?: Record<string, unknown> };
|
||||
if (call === 1) {
|
||||
return createJsonResponse({
|
||||
data: {
|
||||
Page: {
|
||||
media: [{ id: 12, episodes: 12, title: { english: 'Final Show' } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (call === 2) {
|
||||
return createJsonResponse({
|
||||
data: {
|
||||
Media: { id: 12, mediaListEntry: { progress: 12, status: 'CURRENT' } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
assert.equal(body.variables?.progress, 12);
|
||||
assert.equal(body.variables?.status, 'COMPLETED');
|
||||
return createJsonResponse({
|
||||
data: { SaveMediaListEntry: { progress: 12, status: 'COMPLETED' } },
|
||||
});
|
||||
}) as typeof fetch;
|
||||
|
||||
try {
|
||||
const result = await updateAnilistPostWatchProgress('token', 'Final Show', 12);
|
||||
assert.equal(result.status, 'updated');
|
||||
assert.match(result.message, /completed/i);
|
||||
assert.equal(call, 3);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test('updateAnilistPostWatchProgress skips when progress already reached', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let call = 0;
|
||||
|
||||
@@ -228,7 +228,7 @@ function pickBestSearchResult(
|
||||
native?: string | null;
|
||||
};
|
||||
}>,
|
||||
): { id: number; title: string } | null {
|
||||
): { id: number; title: string; episodes: number | null } | null {
|
||||
const filtered = media.filter((item) => {
|
||||
const totalEpisodes = item.episodes;
|
||||
return totalEpisodes === null || totalEpisodes >= episode;
|
||||
@@ -247,7 +247,7 @@ function pickBestSearchResult(
|
||||
const selected = exact ?? candidates[0]!;
|
||||
const selectedTitle =
|
||||
selected.title?.english || selected.title?.romaji || selected.title?.native || title;
|
||||
return { id: selected.id, title: selectedTitle };
|
||||
return { id: selected.id, title: selectedTitle, episodes: selected.episodes };
|
||||
}
|
||||
|
||||
function isUpdateableListStatus(status: string | null | undefined): boolean {
|
||||
@@ -259,6 +259,15 @@ function formatListStatus(status: string | null | undefined): string {
|
||||
return `marked ${status.toLowerCase().replace(/_/g, ' ')} on AniList`;
|
||||
}
|
||||
|
||||
function isKnownFinalEpisode(totalEpisodes: number | null, episode: number): boolean {
|
||||
return (
|
||||
typeof totalEpisodes === 'number' &&
|
||||
Number.isInteger(totalEpisodes) &&
|
||||
totalEpisodes > 0 &&
|
||||
episode === totalEpisodes
|
||||
);
|
||||
}
|
||||
|
||||
export async function guessAnilistMediaInfo(
|
||||
mediaPath: string | null,
|
||||
mediaTitle: string | null,
|
||||
@@ -394,7 +403,8 @@ export async function updateAnilistPostWatchProgress(
|
||||
}
|
||||
|
||||
const currentProgress = entry.progress ?? 0;
|
||||
if (typeof currentProgress === 'number' && currentProgress >= episode) {
|
||||
const shouldMarkCompleted = isKnownFinalEpisode(picked.episodes, episode);
|
||||
if (typeof currentProgress === 'number' && currentProgress >= episode && !shouldMarkCompleted) {
|
||||
return {
|
||||
status: 'skipped',
|
||||
message: `AniList already at episode ${currentProgress} (${picked.title}).`,
|
||||
@@ -404,14 +414,18 @@ export async function updateAnilistPostWatchProgress(
|
||||
const saveResponse = await anilistGraphQl<AnilistSaveEntryData>(
|
||||
accessToken,
|
||||
`
|
||||
mutation ($mediaId: Int!, $progress: Int!) {
|
||||
SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: CURRENT) {
|
||||
mutation ($mediaId: Int!, $progress: Int!, $status: MediaListStatus!) {
|
||||
SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status) {
|
||||
progress
|
||||
status
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ mediaId: picked.id, progress: episode },
|
||||
{
|
||||
mediaId: picked.id,
|
||||
progress: episode,
|
||||
status: shouldMarkCompleted ? 'COMPLETED' : 'CURRENT',
|
||||
},
|
||||
options,
|
||||
);
|
||||
const saveError = firstErrorMessage(saveResponse);
|
||||
@@ -421,6 +435,8 @@ export async function updateAnilistPostWatchProgress(
|
||||
|
||||
return {
|
||||
status: 'updated',
|
||||
message: `AniList updated "${picked.title}" to episode ${episode}.`,
|
||||
message: shouldMarkCompleted
|
||||
? `AniList updated "${picked.title}" to episode ${episode} and marked it completed.`
|
||||
: `AniList updated "${picked.title}" to episode ${episode}.`,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user