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 () => {
|
test('updateAnilistPostWatchProgress skips when progress already reached', async () => {
|
||||||
const originalFetch = globalThis.fetch;
|
const originalFetch = globalThis.fetch;
|
||||||
let call = 0;
|
let call = 0;
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ function pickBestSearchResult(
|
|||||||
native?: string | null;
|
native?: string | null;
|
||||||
};
|
};
|
||||||
}>,
|
}>,
|
||||||
): { id: number; title: string } | null {
|
): { id: number; title: string; episodes: number | null } | null {
|
||||||
const filtered = media.filter((item) => {
|
const filtered = media.filter((item) => {
|
||||||
const totalEpisodes = item.episodes;
|
const totalEpisodes = item.episodes;
|
||||||
return totalEpisodes === null || totalEpisodes >= episode;
|
return totalEpisodes === null || totalEpisodes >= episode;
|
||||||
@@ -247,7 +247,7 @@ function pickBestSearchResult(
|
|||||||
const selected = exact ?? candidates[0]!;
|
const selected = exact ?? candidates[0]!;
|
||||||
const selectedTitle =
|
const selectedTitle =
|
||||||
selected.title?.english || selected.title?.romaji || selected.title?.native || title;
|
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 {
|
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`;
|
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(
|
export async function guessAnilistMediaInfo(
|
||||||
mediaPath: string | null,
|
mediaPath: string | null,
|
||||||
mediaTitle: string | null,
|
mediaTitle: string | null,
|
||||||
@@ -394,7 +403,8 @@ export async function updateAnilistPostWatchProgress(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentProgress = entry.progress ?? 0;
|
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 {
|
return {
|
||||||
status: 'skipped',
|
status: 'skipped',
|
||||||
message: `AniList already at episode ${currentProgress} (${picked.title}).`,
|
message: `AniList already at episode ${currentProgress} (${picked.title}).`,
|
||||||
@@ -404,14 +414,18 @@ export async function updateAnilistPostWatchProgress(
|
|||||||
const saveResponse = await anilistGraphQl<AnilistSaveEntryData>(
|
const saveResponse = await anilistGraphQl<AnilistSaveEntryData>(
|
||||||
accessToken,
|
accessToken,
|
||||||
`
|
`
|
||||||
mutation ($mediaId: Int!, $progress: Int!) {
|
mutation ($mediaId: Int!, $progress: Int!, $status: MediaListStatus!) {
|
||||||
SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: CURRENT) {
|
SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status) {
|
||||||
progress
|
progress
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{ mediaId: picked.id, progress: episode },
|
{
|
||||||
|
mediaId: picked.id,
|
||||||
|
progress: episode,
|
||||||
|
status: shouldMarkCompleted ? 'COMPLETED' : 'CURRENT',
|
||||||
|
},
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
const saveError = firstErrorMessage(saveResponse);
|
const saveError = firstErrorMessage(saveResponse);
|
||||||
@@ -421,6 +435,8 @@ export async function updateAnilistPostWatchProgress(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
status: 'updated',
|
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