mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
d719c4ee5b
Carry ISBN-10 and ISBN-13 independently from accepted providers through Auto-fill previews, saves and background refreshes. Preserve existing identifiers and field locks, validate provider checksums and avoid combining different editions. Classify embedded ISBNs correctly and support ISBN-10 fallback in search, Book Dock confidence and file naming.
110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
import { type AudiobookChapter, type MetadataCandidate, MetadataProviderKey } from '@bookorbit/types';
|
|
|
|
import type { IdentifiableProvider } from '../../../src/modules/metadata-fetch/providers/metadata-provider';
|
|
|
|
const DEFAULT_TITLE = 'Metadata Lock Fixture';
|
|
|
|
export function createMetadataLockTestProviders(getCoverUrl: (providerKey: MetadataProviderKey) => string): IdentifiableProvider[] {
|
|
return [
|
|
{
|
|
key: MetadataProviderKey.GOODREADS,
|
|
label: 'Metadata Lock Goodreads',
|
|
identifiable: true,
|
|
search: (params) => Promise.resolve([buildMetadataLockCandidates(params.title, getCoverUrl).goodreads]),
|
|
lookupById: (providerId) => {
|
|
const title = humanizeProviderId(providerId) ?? DEFAULT_TITLE;
|
|
return Promise.resolve({ ...buildMetadataLockCandidates(title, getCoverUrl).goodreads, providerId });
|
|
},
|
|
},
|
|
{
|
|
key: MetadataProviderKey.OPEN_LIBRARY,
|
|
label: 'Metadata Lock Open Library',
|
|
identifiable: true,
|
|
search: (params) => Promise.resolve([buildMetadataLockCandidates(params.title, getCoverUrl).openLibrary]),
|
|
lookupById: (providerId) => {
|
|
const title = humanizeProviderId(providerId) ?? DEFAULT_TITLE;
|
|
return Promise.resolve({ ...buildMetadataLockCandidates(title, getCoverUrl).openLibrary, providerId });
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
export function buildMetadataLockCandidates(
|
|
queryTitle: string | undefined,
|
|
getCoverUrl: (providerKey: MetadataProviderKey) => string,
|
|
): { goodreads: MetadataCandidate; openLibrary: MetadataCandidate } {
|
|
const baseTitle = normalizeTitle(queryTitle);
|
|
const slug = toSlug(baseTitle);
|
|
|
|
const chapters: AudiobookChapter[] = [
|
|
{ title: 'Refreshed Chapter 1', startMs: 0, durationMs: 120_000 },
|
|
{ title: 'Refreshed Chapter 2', startMs: 120_000, durationMs: 150_000 },
|
|
];
|
|
|
|
return {
|
|
goodreads: {
|
|
provider: MetadataProviderKey.GOODREADS,
|
|
providerId: `gr-${slug}`,
|
|
title: `${baseTitle} - refreshed`,
|
|
subtitle: 'Refreshed subtitle',
|
|
authors: ['Refreshed Author'],
|
|
description: 'Refreshed description from fake Goodreads',
|
|
publisher: 'Refreshed Publisher',
|
|
isbn10: '0306406152',
|
|
isbn13: '9780306406157',
|
|
publishedYear: 2024,
|
|
language: 'fr',
|
|
pageCount: 321,
|
|
genres: ['Speculative Fiction', 'Adventure'],
|
|
coverUrl: getCoverUrl(MetadataProviderKey.GOODREADS),
|
|
narrators: ['Refreshed Narrator'],
|
|
durationSeconds: 5432,
|
|
abridged: true,
|
|
chapters,
|
|
comicMetadata: {
|
|
issueNumber: 'ISS-42',
|
|
volumeName: 'Volume Omega',
|
|
storyArcs: ['Arc Alpha'],
|
|
},
|
|
},
|
|
openLibrary: {
|
|
provider: MetadataProviderKey.OPEN_LIBRARY,
|
|
providerId: `ol-${slug}`,
|
|
// Same book as the Goodreads candidate above, as a second provider would report it:
|
|
// cross-provider agreement drops candidates that describe a different book.
|
|
title: `${baseTitle} - refreshed (Open Library edition)`,
|
|
subtitle: 'Open Library subtitle',
|
|
authors: ['Refreshed Author'],
|
|
description: 'Open Library fallback description',
|
|
publisher: 'Open Library Publisher',
|
|
publishedYear: 2023,
|
|
language: 'de',
|
|
pageCount: 222,
|
|
coverUrl: getCoverUrl(MetadataProviderKey.OPEN_LIBRARY),
|
|
},
|
|
};
|
|
}
|
|
|
|
function normalizeTitle(value: string | undefined): string {
|
|
const trimmed = value?.trim();
|
|
return trimmed && trimmed.length > 0 ? trimmed : DEFAULT_TITLE;
|
|
}
|
|
|
|
function toSlug(value: string): string {
|
|
const slug = value
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '');
|
|
return slug || 'fixture';
|
|
}
|
|
|
|
function humanizeProviderId(providerId: string): string | null {
|
|
const suffix = providerId.split('-').slice(1).join('-').trim();
|
|
if (!suffix) return null;
|
|
return suffix
|
|
.split('-')
|
|
.filter(Boolean)
|
|
.map((part) => part[0]?.toUpperCase() + part.slice(1))
|
|
.join(' ');
|
|
}
|