fix(watchlist): resolve 429 backoff + unique-exact-match acceptance

This commit is contained in:
afiqzudinhadi 2026-07-30 13:05:31 +08:00
parent 22a409ce9b
commit b77a189848
5 changed files with 70 additions and 16 deletions

View file

@ -30,6 +30,18 @@ export class TmdbRateLimitError extends Error {
retryAfterMs: number = 2000;
}
export async function withRateLimitRetry<T>(fn: () => Promise<T>, sleep: (ms: number) => Promise<void>): Promise<T> {
try {
return await fn();
} catch (e) {
if (e instanceof TmdbRateLimitError) {
await sleep(e.retryAfterMs);
return await fn();
}
throw e;
}
}
const ACTIVE_STATUSES = new Set(['Returning Series', 'In Production', 'Planned', 'Pilot']);
function strip(s: string | undefined): string {
@ -63,17 +75,7 @@ export async function syncFolder(deps: SyncDeps, opts: SyncOptions = {}): Promis
continue;
}
try {
let detail: any;
try {
detail = await deps.fetchDetail(ref.tmdbId, ref.isMovie);
} catch (e) {
if (e instanceof TmdbRateLimitError) {
await deps.sleep(e.retryAfterMs);
detail = await deps.fetchDetail(ref.tmdbId, ref.isMovie);
} else {
throw e;
}
}
const detail: any = await withRateLimitRetry(() => deps.fetchDetail(ref.tmdbId, ref.isMovie), deps.sleep);
const record = buildRecord(detail, ref.isMovie, frontmatter);
const rendered = renderNote(record, extractMyNotes(body));
report.synced++;

View file

@ -1,6 +1,6 @@
import { Notice, TFile, TFolder } from 'obsidian';
import type MediaDbPlugin from 'packages/obsidian/src/main';
import { syncFolder, TmdbRateLimitError, type SyncDeps, type SyncReport } from 'packages/obsidian/src/watchlist/SyncEngine';
import { syncFolder, withRateLimitRetry, TmdbRateLimitError, type SyncDeps, type SyncReport } from 'packages/obsidian/src/watchlist/SyncEngine';
import { fetchDetail, searchTitle, type HttpJsonFn } from 'packages/obsidian/src/watchlist/tmdb';
import { obsidianFetch } from 'packages/obsidian/src/utils/Utils';
import { parseNote, noteTmdbRef, stripQuotes } from 'packages/obsidian/src/watchlist/parse';
@ -111,7 +111,7 @@ export class WatchlistController {
report.scanned++;
try {
const filename = note.path.split('/').pop() ?? note.path;
const result = await resolveNote(frontmatter, filename, search);
const result = await withRateLimitRetry(() => resolveNote(frontmatter, filename, search), deps.sleep);
if (!result) {
report.ambiguous++;
deps.log(`ambiguous/no match: ${note.path}`);

View file

@ -18,8 +18,8 @@ async function tryOne(query: string, isMovie: boolean, year: string | undefined,
const results = await search(query, isMovie, year);
if (results.length === 0) return null;
const q = query.toLowerCase();
const exact = results.find(r => resultTitles(r).includes(q));
const pick = exact ?? (results.length === 1 ? results[0] : null);
const exacts = results.filter(r => resultTitles(r).includes(q));
const pick = exacts.length === 1 ? exacts[0] : exacts.length === 0 && results.length === 1 ? results[0] : null;
if (!pick) return null;
return { tmdbId: String(pick.id), isMovie, matchedTitle: resultTitle(pick) };
}