@zivue/zuuid 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 - 2026-05-25
4
+
5
+ - Added live MusicBrainz lookup/search support for releases, release groups, recordings, artists, labels, and works.
6
+ - Added MusicBrainz category-first client facades under `listen.musicbrainz` and `people.musicbrainz`, plus a `providers/musicbrainz/client` subpath export.
7
+ - Added MusicBrainz fetch/search examples and README usage documentation.
8
+ - Added an IMDb suggestion-data fallback for challenge pages so known title IDs still resolve core metadata such as title, year, poster, type, rank, and cast summary.
9
+
3
10
  ## 0.2.2 - 2026-05-25
4
11
 
5
12
  - Added IMDb fetch-by-ID scraper support for movie and TV title pages.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Search, fetch, and normalize media metadata from external providers into a share
4
4
 
5
5
  This package is meant to be used by apps that need provider-backed lookup and transformation, but do not want provider-specific response shapes leaking through the app.
6
6
 
7
- TMDB, Open Library, and GamesDB include live search/fetch clients. IMDb includes a fetch-by-ID title-page scraper for movie and TV titles. Additional providers currently expose source-record transformers: you provide the raw provider payload, and this package normalizes it into `ZuuidData`.
7
+ TMDB, Open Library, GamesDB, and MusicBrainz include live search/fetch clients. IMDb includes a fetch-by-ID title-page scraper for movie and TV titles. Additional providers currently expose source-record transformers: you provide the raw provider payload, and this package normalizes it into `ZuuidData`.
8
8
 
9
9
  Storage, caching, indexing, review state, object-store keys, and persistence belong in a layer outside this package.
10
10
 
@@ -114,7 +114,7 @@ Ratings are normalized to a `0-5` scale when the provider exposes a compatible n
114
114
  | GamesDB | game | yes | yes | yes |
115
115
  | GamesDB | platform | no | yes | yes |
116
116
  | Jikan | anime, manga, producer, magazine, character, person | no | no | yes |
117
- | MusicBrainz | release, release-group, recording, artist, label, work | no | no | yes |
117
+ | MusicBrainz | release, release-group, recording, artist, label, work | yes | yes | yes |
118
118
  | OpenFoodFacts | product | no | no | yes |
119
119
  | OpenStreetMap | city, country, place, venue | no | no | yes |
120
120
  | Podcast / iTunes | podcast | no | no | yes |
@@ -154,9 +154,35 @@ const game = await zuuid.play.gamesdb?.game.fetch({ id: 17444 });
154
154
 
155
155
  GamesDB transforms accept both simple flat payloads and native TheGamesDB API envelopes with `data.games`, `data.platforms`, lookup maps, and `boxart` image metadata.
156
156
 
157
+
158
+ ## MusicBrainz
159
+
160
+ `MusicBrainzProvider` uses the public MusicBrainz JSON web service. No API key is required, but the provider sends a descriptive `User-Agent` by default and lets you override it.
161
+
162
+ ```ts
163
+ import { MusicBrainzProvider } from "@zivue/zuuid/providers/musicbrainz";
164
+
165
+ const musicbrainz = new MusicBrainzProvider({
166
+ userAgent: "your-app/1.0 (you@example.com)"
167
+ });
168
+
169
+ const release = await musicbrainz.fetchRelease({ id: "f5093c06-23e3-404f-aeaa-40f72885ee3a" });
170
+ const releaseGroups = await musicbrainz.searchReleaseGroups({ query: "Kind of Blue" });
171
+ const artist = await musicbrainz.fetchArtist({ id: "561d854a-6a28-4aa7-8c99-323e6ce46c2a" });
172
+ ```
173
+
174
+ The category-first client exposes MusicBrainz under `listen.musicbrainz` and `people.musicbrainz`:
175
+
176
+ ```ts
177
+ const zuuid = createZuuidClient({ providers: { musicbrainz: {} } });
178
+
179
+ const album = await zuuid.listen.musicbrainz?.releaseGroup.search({ query: "Kind of Blue" });
180
+ const artist = await zuuid.people.musicbrainz?.artist.search({ query: "Miles Davis" });
181
+ ```
182
+
157
183
  ## IMDb Scraper
158
184
 
159
- IMDb can be used as a credential-free alternative source when you already have an IMDb title ID. It scrapes the title page, preserves the fetched HTML and extracted JSON-LD in the raw `SourceRecord`, and transforms the JSON-LD into normalized `ZuuidData`.
185
+ IMDb can be used as a credential-free alternative source when you already have an IMDb title ID. It scrapes the title page, preserves the fetched HTML and extracted JSON-LD in the raw `SourceRecord`, and transforms the JSON-LD into normalized `ZuuidData`. If IMDb serves a challenge page instead of title HTML, the provider falls back to IMDb suggestion data for core fields such as title, year, poster, type, rank, and cast summary.
160
186
 
161
187
  ```ts
162
188
  import { ImdbProvider } from "@zivue/zuuid/providers/imdb";
@@ -374,6 +400,11 @@ zuuid.people.openlibrary?.fetch({ id: "OL23919A" });
374
400
 
375
401
  zuuid.read.openlibrary?.search({ query: "The Lord of the Rings" });
376
402
  zuuid.read.openlibrary?.fetch({ id: "OL82563W" });
403
+
404
+ zuuid.listen.musicbrainz?.release.search({ query: "Kind of Blue" });
405
+ zuuid.listen.musicbrainz?.releaseGroup.fetch({ id: "aaa50249-1e6b-3910-b830-7e2fb622a8c4" });
406
+ zuuid.listen.musicbrainz?.recording.search({ query: "So What" });
407
+ zuuid.people.musicbrainz?.artist.search({ query: "Miles Davis" });
377
408
  ```
378
409
 
379
410
  ## Example Scripts
@@ -396,6 +427,9 @@ npm run example:search -- tv "Game of Thrones"
396
427
  npm run example:search -- people "Brad Pitt"
397
428
  npm run example:search -- book "The Lord of the Rings"
398
429
  npm run example:search -- author "J. K. Rowling"
430
+ npm run example:search -- release-group "Kind of Blue"
431
+ npm run example:search -- recording "So What"
432
+ npm run example:search -- artist "Miles Davis"
399
433
  ```
400
434
 
401
435
  Fetch and transform a selected provider ID:
@@ -415,6 +449,9 @@ npm run example:fetch -- imdb:movie tt0137523
415
449
  npm run example:fetch -- imdb:tv tt0944947
416
450
  GAMESDB_API_KEY=... npm run example:fetch -- gamesdb:game 17444
417
451
  GAMESDB_API_KEY=... npm run example:fetch -- gamesdb:platform 6
452
+ npm run example:fetch -- musicbrainz:release f5093c06-23e3-404f-aeaa-40f72885ee3a
453
+ npm run example:fetch -- musicbrainz:release-group aaa50249-1e6b-3910-b830-7e2fb622a8c4
454
+ npm run example:fetch -- musicbrainz:artist 561d854a-6a28-4aa7-8c99-323e6ce46c2a
418
455
  ```
419
456
 
420
457
  Transformer-only providers do not have fetch examples. Wrap a real payload from your own provider client in a `SourceRecord` and call the transformer directly.
@@ -451,6 +488,7 @@ Provider exports:
451
488
  - `OpenLibraryProvider`
452
489
  - `ImdbProvider`
453
490
  - `GamesDbProvider`
491
+ - `MusicBrainzProvider`
454
492
  - `transformTmdbMovie(sourceRecord, options?)`
455
493
  - `transformTmdbTv(sourceRecord, options?)`
456
494
  - `transformTmdbPerson(sourceRecord, options?)`
package/dist/client.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { SearchResponse, ZuuidData, ZuuidSearchResult } from "./entity.js";
2
+ import { type FetchMusicBrainzInput, type MusicBrainzProviderOptions, type MusicBrainzSearchInput } from "./providers/musicbrainz/index.js";
2
3
  import { type FetchOpenLibraryAuthorInput, type FetchOpenLibraryBookInput, type OpenLibraryProviderOptions, type OpenLibrarySearchInput } from "./providers/openlibrary/index.js";
3
4
  import { type FetchTmdbMovieInput, type FetchTmdbPersonInput, type FetchTmdbTvInput, type TmdbProviderOptions, type TmdbSearchInput } from "./providers/tmdb/index.js";
4
5
  import { type FetchGamesDbGameInput, type FetchGamesDbPlatformInput, type GamesDbProviderOptions, type GamesDbSearchInput } from "./providers/gamesdb/index.js";
@@ -7,6 +8,7 @@ import type { SourceRecord } from "./source.js";
7
8
  export type ProviderConfigs = {
8
9
  gamesdb?: GamesDbProviderOptions;
9
10
  imdb?: ImdbProviderOptions;
11
+ musicbrainz?: MusicBrainzProviderOptions;
10
12
  openlibrary?: OpenLibraryProviderOptions;
11
13
  tmdb?: TmdbProviderOptions;
12
14
  };
@@ -44,10 +46,22 @@ export type ZuuidClient = {
44
46
  people: {
45
47
  tmdb?: ProviderClient<FetchTmdbPersonInput, TmdbSearchInput>;
46
48
  openlibrary?: ProviderClient<FetchOpenLibraryAuthorInput, OpenLibrarySearchInput>;
49
+ musicbrainz?: {
50
+ artist: ProviderClient<FetchMusicBrainzInput, MusicBrainzSearchInput>;
51
+ label: ProviderClient<FetchMusicBrainzInput, MusicBrainzSearchInput>;
52
+ };
47
53
  };
48
54
  read: {
49
55
  openlibrary?: ProviderClient<FetchOpenLibraryBookInput, OpenLibrarySearchInput>;
50
56
  };
57
+ listen: {
58
+ musicbrainz?: {
59
+ release: ProviderClient<FetchMusicBrainzInput, MusicBrainzSearchInput>;
60
+ releaseGroup: ProviderClient<FetchMusicBrainzInput, MusicBrainzSearchInput>;
61
+ recording: ProviderClient<FetchMusicBrainzInput, MusicBrainzSearchInput>;
62
+ work: ProviderClient<FetchMusicBrainzInput, MusicBrainzSearchInput>;
63
+ };
64
+ };
51
65
  play: {
52
66
  gamesdb?: {
53
67
  game: ProviderClient<FetchGamesDbGameInput, GamesDbSearchInput>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { MusicBrainzProvider, transformMusicBrainzArtist, transformMusicBrainzLabel, transformMusicBrainzRecording, transformMusicBrainzRelease, transformMusicBrainzReleaseGroup, transformMusicBrainzWork } from "./providers/musicbrainz/index.js";
1
2
  import { OpenLibraryProvider, transformOpenLibraryAuthor, transformOpenLibraryBook } from "./providers/openlibrary/index.js";
2
3
  import { TmdbProvider, transformTmdbMovie, transformTmdbPerson, transformTmdbTv } from "./providers/tmdb/index.js";
3
4
  import { GamesDbProvider, transformGamesDbGame, transformGamesDbPlatform } from "./providers/gamesdb/index.js";
@@ -5,6 +6,7 @@ import { ImdbProvider, transformImdbMovie, transformImdbTv } from "./providers/i
5
6
  export function createZuuidClient(config = {}) {
6
7
  const gamesdb = config.providers?.gamesdb ? new GamesDbProvider(config.providers.gamesdb) : undefined;
7
8
  const imdb = config.providers?.imdb ? new ImdbProvider(config.providers.imdb) : undefined;
9
+ const musicbrainz = config.providers?.musicbrainz ? new MusicBrainzProvider(config.providers.musicbrainz) : undefined;
8
10
  const openlibrary = config.providers?.openlibrary ? new OpenLibraryProvider(config.providers.openlibrary) : undefined;
9
11
  const tmdb = config.providers?.tmdb ? new TmdbProvider(config.providers.tmdb) : undefined;
10
12
  return Object.freeze({
@@ -62,6 +64,24 @@ export function createZuuidClient(config = {}) {
62
64
  searchSourceRecords: (input) => openlibrary.searchAuthorSourceRecords(input),
63
65
  transform: (source) => transformOpenLibraryAuthor(source, openlibrary.transformOptions())
64
66
  })
67
+ : undefined,
68
+ musicbrainz: musicbrainz
69
+ ? Object.freeze({
70
+ artist: Object.freeze({
71
+ fetch: (input) => musicbrainz.fetchArtist(input),
72
+ fetchSourceRecord: (input) => musicbrainz.fetchArtistSourceRecord(input),
73
+ search: (input) => musicbrainz.searchArtists(input),
74
+ searchSourceRecords: (input) => musicbrainz.searchArtistSourceRecords(input),
75
+ transform: (source) => transformMusicBrainzArtist(source)
76
+ }),
77
+ label: Object.freeze({
78
+ fetch: (input) => musicbrainz.fetchLabel(input),
79
+ fetchSourceRecord: (input) => musicbrainz.fetchLabelSourceRecord(input),
80
+ search: (input) => musicbrainz.searchLabels(input),
81
+ searchSourceRecords: (input) => musicbrainz.searchLabelSourceRecords(input),
82
+ transform: (source) => transformMusicBrainzLabel(source)
83
+ })
84
+ })
65
85
  : undefined
66
86
  }),
67
87
  read: Object.freeze({
@@ -75,6 +95,40 @@ export function createZuuidClient(config = {}) {
75
95
  })
76
96
  : undefined
77
97
  }),
98
+ listen: Object.freeze({
99
+ musicbrainz: musicbrainz
100
+ ? Object.freeze({
101
+ release: Object.freeze({
102
+ fetch: (input) => musicbrainz.fetchRelease(input),
103
+ fetchSourceRecord: (input) => musicbrainz.fetchReleaseSourceRecord(input),
104
+ search: (input) => musicbrainz.searchReleases(input),
105
+ searchSourceRecords: (input) => musicbrainz.searchReleaseSourceRecords(input),
106
+ transform: (source) => transformMusicBrainzRelease(source, { coverArtBaseUrl: musicbrainz.coverArtBaseUrl })
107
+ }),
108
+ releaseGroup: Object.freeze({
109
+ fetch: (input) => musicbrainz.fetchReleaseGroup(input),
110
+ fetchSourceRecord: (input) => musicbrainz.fetchReleaseGroupSourceRecord(input),
111
+ search: (input) => musicbrainz.searchReleaseGroups(input),
112
+ searchSourceRecords: (input) => musicbrainz.searchReleaseGroupSourceRecords(input),
113
+ transform: (source) => transformMusicBrainzReleaseGroup(source, { coverArtBaseUrl: musicbrainz.releaseGroupCoverArtBaseUrl })
114
+ }),
115
+ recording: Object.freeze({
116
+ fetch: (input) => musicbrainz.fetchRecording(input),
117
+ fetchSourceRecord: (input) => musicbrainz.fetchRecordingSourceRecord(input),
118
+ search: (input) => musicbrainz.searchRecordings(input),
119
+ searchSourceRecords: (input) => musicbrainz.searchRecordingSourceRecords(input),
120
+ transform: (source) => transformMusicBrainzRecording(source)
121
+ }),
122
+ work: Object.freeze({
123
+ fetch: (input) => musicbrainz.fetchWork(input),
124
+ fetchSourceRecord: (input) => musicbrainz.fetchWorkSourceRecord(input),
125
+ search: (input) => musicbrainz.searchWorks(input),
126
+ searchSourceRecords: (input) => musicbrainz.searchWorkSourceRecords(input),
127
+ transform: (source) => transformMusicBrainzWork(source)
128
+ })
129
+ })
130
+ : undefined
131
+ }),
78
132
  play: Object.freeze({
79
133
  gamesdb: gamesdb
80
134
  ? Object.freeze({
@@ -5,6 +5,7 @@ import type { ImdbFetchLike, ImdbProviderOptions, ImdbTransformOptions } from ".
5
5
  export declare class ImdbProvider {
6
6
  readonly fetcher: ImdbFetchLike;
7
7
  readonly titleBaseUrl: string | null;
8
+ readonly suggestionBaseUrl: string | null;
8
9
  readonly userAgent: string;
9
10
  constructor(options?: ImdbProviderOptions);
10
11
  transformOptions(): ImdbTransformOptions;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/imdb/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAA4F,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAChJ,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE3F,qBAAa,YAAY;IACvB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,GAAE,mBAAwB;IAM7C,gBAAgB,IAAI,oBAAoB;IAIlC,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAIrF,UAAU,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAKtE,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAIlF,OAAO,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAI1E"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/imdb/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAA4F,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAChJ,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE3F,qBAAa,YAAY;IACvB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,GAAE,mBAAwB;IAO7C,gBAAgB,IAAI,oBAAoB;IAIlC,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAIrF,UAAU,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAKtE,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAIlF,OAAO,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAI1E"}
@@ -1,16 +1,18 @@
1
- import { IMDB_DEFAULT_USER_AGENT, IMDB_TITLE_BASE_URL } from "./constants.js";
1
+ import { IMDB_DEFAULT_USER_AGENT, IMDB_SUGGESTION_BASE_URL, IMDB_TITLE_BASE_URL } from "./constants.js";
2
2
  import { fetchImdbMovieSourceRecord, fetchImdbTvSourceRecord, transformImdbMovie, transformImdbTv } from "./movie.js";
3
3
  export class ImdbProvider {
4
4
  fetcher;
5
5
  titleBaseUrl;
6
+ suggestionBaseUrl;
6
7
  userAgent;
7
8
  constructor(options = {}) {
8
9
  this.fetcher = options.fetch ?? fetch;
9
10
  this.titleBaseUrl = options.titleBaseUrl === undefined ? IMDB_TITLE_BASE_URL : options.titleBaseUrl;
11
+ this.suggestionBaseUrl = options.suggestionBaseUrl === undefined ? IMDB_SUGGESTION_BASE_URL : options.suggestionBaseUrl;
10
12
  this.userAgent = options.userAgent ?? IMDB_DEFAULT_USER_AGENT;
11
13
  }
12
14
  transformOptions() {
13
- return { titleBaseUrl: this.titleBaseUrl };
15
+ return { titleBaseUrl: this.titleBaseUrl, suggestionBaseUrl: this.suggestionBaseUrl };
14
16
  }
15
17
  async fetchMovieSourceRecord(input) {
16
18
  return fetchImdbMovieSourceRecord(this, input);
@@ -1,4 +1,5 @@
1
1
  export declare const IMDB_PROVIDER = "imdb";
2
2
  export declare const IMDB_TITLE_BASE_URL = "https://www.imdb.com/title";
3
3
  export declare const IMDB_DEFAULT_USER_AGENT = "@zivue/zuuid IMDb scraper (+https://github.com/zivue/zuuid)";
4
+ export declare const IMDB_SUGGESTION_BASE_URL = "https://v3.sg.media-imdb.com/suggestion";
4
5
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/providers/imdb/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,SAAS,CAAC;AACpC,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAChE,eAAO,MAAM,uBAAuB,gEAAgE,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/providers/imdb/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,SAAS,CAAC;AACpC,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAChE,eAAO,MAAM,uBAAuB,gEAAgE,CAAC;AACrG,eAAO,MAAM,wBAAwB,4CAA4C,CAAC"}
@@ -1,3 +1,4 @@
1
1
  export const IMDB_PROVIDER = "imdb";
2
2
  export const IMDB_TITLE_BASE_URL = "https://www.imdb.com/title";
3
3
  export const IMDB_DEFAULT_USER_AGENT = "@zivue/zuuid IMDb scraper (+https://github.com/zivue/zuuid)";
4
+ export const IMDB_SUGGESTION_BASE_URL = "https://v3.sg.media-imdb.com/suggestion";
@@ -37,22 +37,25 @@ async function fetchImdbTitleSourceRecord(provider, input, category) {
37
37
  throw new Error(`IMDb title request failed with status ${response.status}`);
38
38
  }
39
39
  const html = await response.text();
40
+ const pagePayload = extractImdbPayload(url, html);
41
+ const suggestion = await fetchSuggestionPayload(provider, id);
40
42
  return createSourceRecord({
41
43
  source: { provider: IMDB_PROVIDER, category, externalId: id },
42
- payload: extractImdbPayload(url, html)
44
+ payload: { ...objectPayload(pagePayload), suggestion: suggestion ?? null }
43
45
  });
44
46
  }
45
47
  async function transformImdbTitle(source, fallbackCategory, options = {}) {
46
48
  const payload = objectPayload(source.payload);
47
49
  const jsonLd = objectPayload(payload.jsonLd ?? {});
48
50
  const externalId = normalizeImdbTitleId(source.source.externalId);
49
- const category = source.source.category === "tv" || schemaType(jsonLd).includes("TVSeries") ? "tv" : fallbackCategory;
50
- const title = stringField(jsonLd, "name") ?? fallbackTitle(payload) ?? externalId;
51
+ const suggestion = suggestionItem(payload, externalId);
52
+ const category = source.source.category === "tv" || schemaType(jsonLd).includes("TVSeries") || stringField(suggestion, "qid")?.startsWith("tv") ? "tv" : fallbackCategory;
53
+ const title = stringField(jsonLd, "name") ?? stringField(suggestion, "l") ?? fallbackTitle(payload) ?? externalId;
51
54
  const data = await baseDataFromSource(source, IMDB_PROVIDER, category, category, externalId, title);
52
- data.primaryDate = datePrefix(stringField(jsonLd, "datePublished"));
55
+ data.primaryDate = datePrefix(stringField(jsonLd, "datePublished")) ?? suggestionYearDate(suggestion);
53
56
  addAlias(data, title, "primary", true, IMDB_PROVIDER);
54
57
  addDescription(data, IMDB_PROVIDER, stringField(jsonLd, "description"));
55
- addMedia(data, IMDB_PROVIDER, stringField(jsonLd, "image"), "poster");
58
+ addMedia(data, IMDB_PROVIDER, stringField(jsonLd, "image") ?? nestedSuggestionImage(suggestion), "poster");
56
59
  const aggregateRating = objectPayload(jsonLd.aggregateRating ?? {});
57
60
  const nativeRating = numericValue(aggregateRating.ratingValue);
58
61
  const normalizedRating = normalizeRating(nativeRating, 0, 10);
@@ -66,6 +69,12 @@ async function transformImdbTitle(source, fallbackCategory, options = {}) {
66
69
  addDetail(data, IMDB_PROVIDER, "duration", stringField(jsonLd, "duration"));
67
70
  addDetail(data, IMDB_PROVIDER, "schema_type", valueAsString(jsonLd["@type"]));
68
71
  addDetail(data, IMDB_PROVIDER, "url", stringField(payload, "url") ?? imdbTitleUrl(externalId, options.titleBaseUrl));
72
+ addDetail(data, IMDB_PROVIDER, "page_status", payload.challenge === true ? "challenge" : undefined);
73
+ addDetail(data, IMDB_PROVIDER, "imdb_type", stringField(suggestion, "q"));
74
+ addDetail(data, IMDB_PROVIDER, "imdb_type_id", stringField(suggestion, "qid"));
75
+ addDetail(data, IMDB_PROVIDER, "rank", numericValue(suggestion.rank));
76
+ addDetail(data, IMDB_PROVIDER, "year", numericValue(suggestion.y));
77
+ addDetail(data, IMDB_PROVIDER, "cast_summary", stringField(suggestion, "s"));
69
78
  for (const genre of stringArray(jsonLd.genre)) {
70
79
  addTag(data, genre);
71
80
  }
@@ -86,9 +95,31 @@ function extractImdbPayload(url, html) {
86
95
  html,
87
96
  title: cleanPageTitle(decodeHtml(textFromMatch(html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]))) ?? null,
88
97
  jsonLd: parseJsonScript(html, /<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/i) ?? null,
89
- nextData: parseJsonScript(html, /<script[^>]+id=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i) ?? null
98
+ nextData: parseJsonScript(html, /<script[^>]+id=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i) ?? null,
99
+ challenge: isChallengePage(html)
90
100
  };
91
101
  }
102
+ async function fetchSuggestionPayload(provider, id) {
103
+ const baseUrl = provider.suggestionBaseUrl?.replace(/\/$/, "");
104
+ if (!baseUrl) {
105
+ return undefined;
106
+ }
107
+ const bucket = id.slice(0, 1);
108
+ const response = await provider.fetcher(`${baseUrl}/${bucket}/${id}.json`, {
109
+ headers: {
110
+ Accept: "application/json",
111
+ "Accept-Language": "en-US,en;q=0.9",
112
+ "User-Agent": provider.userAgent
113
+ }
114
+ });
115
+ if (!response.ok) {
116
+ return undefined;
117
+ }
118
+ return response.json();
119
+ }
120
+ function isChallengePage(html) {
121
+ return html.includes("AwsWafIntegration") || html.includes("awsWafCookieDomainList") || html.includes("challenge-container");
122
+ }
92
123
  function parseJsonScript(html, pattern) {
93
124
  const raw = html.match(pattern)?.[1];
94
125
  const text = textFromMatch(raw);
@@ -124,6 +155,20 @@ function imdbTitleUrl(id, titleBaseUrl) {
124
155
  const base = titleBaseUrl?.replace(/\/$/, "");
125
156
  return base ? `${base}/${id}/` : undefined;
126
157
  }
158
+ function suggestionItem(payload, externalId) {
159
+ const suggestion = objectPayload(payload.suggestion ?? {});
160
+ const items = Array.isArray(suggestion.d) ? suggestion.d : [];
161
+ const matched = items.find((item) => item && typeof item === "object" && !Array.isArray(item) && stringField(item, "id") === externalId);
162
+ return matched && typeof matched === "object" && !Array.isArray(matched) ? matched : {};
163
+ }
164
+ function nestedSuggestionImage(suggestion) {
165
+ const image = objectPayload(suggestion.i ?? {});
166
+ return stringField(image, "imageUrl");
167
+ }
168
+ function suggestionYearDate(suggestion) {
169
+ const year = numericValue(suggestion.y);
170
+ return year ? `${Math.trunc(year)}-01-01` : undefined;
171
+ }
127
172
  function schemaType(jsonLd) {
128
173
  const value = jsonLd["@type"];
129
174
  return Array.isArray(value) ? value.map((item) => valueAsString(item)).filter(Boolean).join(",") : valueAsString(value) ?? "";
@@ -2,9 +2,11 @@ export type ImdbFetchLike = (input: string | URL, init?: RequestInit) => Promise
2
2
  export type ImdbProviderOptions = {
3
3
  fetch?: ImdbFetchLike;
4
4
  titleBaseUrl?: string | null;
5
+ suggestionBaseUrl?: string | null;
5
6
  userAgent?: string;
6
7
  };
7
8
  export type ImdbTransformOptions = {
8
9
  titleBaseUrl?: string | null;
10
+ suggestionBaseUrl?: string | null;
9
11
  };
10
12
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/imdb/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE3F,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/imdb/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE3F,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { SearchResponse, ZuuidData, ZuuidSearchResult } from "../../entity.js";
2
+ import { type SourceRecord } from "../../source.js";
3
+ import type { FetchMusicBrainzInput, MusicBrainzProviderOptions, MusicBrainzSearchInput, MusicBrainzProviderTransformOptions } from "./types.js";
4
+ export declare class MusicBrainzProvider {
5
+ readonly apiBase: string;
6
+ readonly coverArtBaseUrl: string | null;
7
+ readonly releaseGroupCoverArtBaseUrl: string | null;
8
+ readonly userAgent: string;
9
+ private readonly fetchImpl;
10
+ constructor(options?: MusicBrainzProviderOptions);
11
+ getJson<T>(path: string, params: Record<string, string>): Promise<T | undefined>;
12
+ transformOptions(): MusicBrainzProviderTransformOptions;
13
+ fetchReleaseSourceRecord(input: FetchMusicBrainzInput): Promise<SourceRecord | undefined>;
14
+ fetchRelease(input: FetchMusicBrainzInput): Promise<ZuuidData | undefined>;
15
+ searchReleaseSourceRecords(input: MusicBrainzSearchInput): Promise<SearchResponse<SourceRecord>>;
16
+ searchReleases(input: MusicBrainzSearchInput): Promise<SearchResponse<ZuuidSearchResult>>;
17
+ fetchReleaseGroupSourceRecord(input: FetchMusicBrainzInput): Promise<SourceRecord | undefined>;
18
+ fetchReleaseGroup(input: FetchMusicBrainzInput): Promise<ZuuidData | undefined>;
19
+ searchReleaseGroupSourceRecords(input: MusicBrainzSearchInput): Promise<SearchResponse<SourceRecord>>;
20
+ searchReleaseGroups(input: MusicBrainzSearchInput): Promise<SearchResponse<ZuuidSearchResult>>;
21
+ fetchRecordingSourceRecord(input: FetchMusicBrainzInput): Promise<SourceRecord | undefined>;
22
+ fetchRecording(input: FetchMusicBrainzInput): Promise<ZuuidData | undefined>;
23
+ searchRecordingSourceRecords(input: MusicBrainzSearchInput): Promise<SearchResponse<SourceRecord>>;
24
+ searchRecordings(input: MusicBrainzSearchInput): Promise<SearchResponse<ZuuidSearchResult>>;
25
+ fetchArtistSourceRecord(input: FetchMusicBrainzInput): Promise<SourceRecord | undefined>;
26
+ fetchArtist(input: FetchMusicBrainzInput): Promise<ZuuidData | undefined>;
27
+ searchArtistSourceRecords(input: MusicBrainzSearchInput): Promise<SearchResponse<SourceRecord>>;
28
+ searchArtists(input: MusicBrainzSearchInput): Promise<SearchResponse<ZuuidSearchResult>>;
29
+ fetchLabelSourceRecord(input: FetchMusicBrainzInput): Promise<SourceRecord | undefined>;
30
+ fetchLabel(input: FetchMusicBrainzInput): Promise<ZuuidData | undefined>;
31
+ searchLabelSourceRecords(input: MusicBrainzSearchInput): Promise<SearchResponse<SourceRecord>>;
32
+ searchLabels(input: MusicBrainzSearchInput): Promise<SearchResponse<ZuuidSearchResult>>;
33
+ fetchWorkSourceRecord(input: FetchMusicBrainzInput): Promise<SourceRecord | undefined>;
34
+ fetchWork(input: FetchMusicBrainzInput): Promise<ZuuidData | undefined>;
35
+ searchWorkSourceRecords(input: MusicBrainzSearchInput): Promise<SearchResponse<SourceRecord>>;
36
+ searchWorks(input: MusicBrainzSearchInput): Promise<SearchResponse<ZuuidSearchResult>>;
37
+ }
38
+ export declare function fetchMusicBrainzSourceRecord(provider: MusicBrainzProvider, category: string, input: FetchMusicBrainzInput): Promise<SourceRecord | undefined>;
39
+ export declare function searchMusicBrainzSourceRecords(provider: MusicBrainzProvider, category: string, input: MusicBrainzSearchInput): Promise<SearchResponse<SourceRecord>>;
40
+ export declare function searchMusicBrainz(provider: MusicBrainzProvider, category: string, input: MusicBrainzSearchInput, options?: MusicBrainzProviderTransformOptions): Promise<SearchResponse<ZuuidSearchResult>>;
41
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/musicbrainz/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ import { providerZuuid } from "../../identity.js";
2
+ import { createSourceRecord } from "../../source.js";
3
+ import { datePrefix, nestedString, objectPayload, stringField, valueAsString } from "../common.js";
4
+ import { MUSICBRAINZ_API_BASE, MUSICBRAINZ_ARTIST_CATEGORY, MUSICBRAINZ_COVER_ART_BASE_URL, MUSICBRAINZ_DEFAULT_USER_AGENT, MUSICBRAINZ_LABEL_CATEGORY, MUSICBRAINZ_PROVIDER, MUSICBRAINZ_RECORDING_CATEGORY, MUSICBRAINZ_RELEASE_CATEGORY, MUSICBRAINZ_RELEASE_GROUP_CATEGORY, MUSICBRAINZ_RELEASE_GROUP_COVER_ART_BASE_URL, MUSICBRAINZ_WORK_CATEGORY } from "./constants.js";
5
+ import { transformMusicBrainzArtist } from "./artist.js";
6
+ import { transformMusicBrainzLabel } from "./label.js";
7
+ import { transformMusicBrainzRecording } from "./recording.js";
8
+ import { transformMusicBrainzRelease } from "./release.js";
9
+ import { transformMusicBrainzReleaseGroup } from "./release-group.js";
10
+ import { transformMusicBrainzWork } from "./work.js";
11
+ export class MusicBrainzProvider {
12
+ apiBase;
13
+ coverArtBaseUrl;
14
+ releaseGroupCoverArtBaseUrl;
15
+ userAgent;
16
+ fetchImpl;
17
+ constructor(options = {}) {
18
+ this.apiBase = options.apiBase ?? MUSICBRAINZ_API_BASE;
19
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
20
+ this.coverArtBaseUrl = options.coverArtBaseUrl === undefined ? MUSICBRAINZ_COVER_ART_BASE_URL : options.coverArtBaseUrl;
21
+ this.releaseGroupCoverArtBaseUrl = options.releaseGroupCoverArtBaseUrl === undefined ? MUSICBRAINZ_RELEASE_GROUP_COVER_ART_BASE_URL : options.releaseGroupCoverArtBaseUrl;
22
+ this.userAgent = options.userAgent ?? MUSICBRAINZ_DEFAULT_USER_AGENT;
23
+ }
24
+ async getJson(path, params) {
25
+ const url = new URL(`${this.apiBase.replace(/\/$/, "")}/${path.replace(/^\//, "")}`);
26
+ url.searchParams.set("fmt", "json");
27
+ for (const [key, value] of Object.entries(params))
28
+ url.searchParams.set(key, value);
29
+ const response = await this.fetchImpl(url, { headers: { accept: "application/json", "user-agent": this.userAgent } });
30
+ if (response.status === 404)
31
+ return undefined;
32
+ if (!response.ok) {
33
+ const body = await response.text().catch(() => "");
34
+ throw new Error(`MusicBrainz API returned ${response.status}: ${body}`);
35
+ }
36
+ return response.json();
37
+ }
38
+ transformOptions() {
39
+ return { coverArtBaseUrl: this.coverArtBaseUrl, releaseGroupCoverArtBaseUrl: this.releaseGroupCoverArtBaseUrl };
40
+ }
41
+ async fetchReleaseSourceRecord(input) { return fetchMusicBrainzSourceRecord(this, MUSICBRAINZ_RELEASE_CATEGORY, input); }
42
+ async fetchRelease(input) { const source = await this.fetchReleaseSourceRecord(input); return source ? transformMusicBrainzRelease(source, this.transformOptions()) : undefined; }
43
+ async searchReleaseSourceRecords(input) { return searchMusicBrainzSourceRecords(this, MUSICBRAINZ_RELEASE_CATEGORY, input); }
44
+ async searchReleases(input) { return searchMusicBrainz(this, MUSICBRAINZ_RELEASE_CATEGORY, input, this.transformOptions()); }
45
+ async fetchReleaseGroupSourceRecord(input) { return fetchMusicBrainzSourceRecord(this, MUSICBRAINZ_RELEASE_GROUP_CATEGORY, input); }
46
+ async fetchReleaseGroup(input) { const source = await this.fetchReleaseGroupSourceRecord(input); return source ? transformMusicBrainzReleaseGroup(source, { coverArtBaseUrl: this.releaseGroupCoverArtBaseUrl }) : undefined; }
47
+ async searchReleaseGroupSourceRecords(input) { return searchMusicBrainzSourceRecords(this, MUSICBRAINZ_RELEASE_GROUP_CATEGORY, input); }
48
+ async searchReleaseGroups(input) { return searchMusicBrainz(this, MUSICBRAINZ_RELEASE_GROUP_CATEGORY, input, this.transformOptions()); }
49
+ async fetchRecordingSourceRecord(input) { return fetchMusicBrainzSourceRecord(this, MUSICBRAINZ_RECORDING_CATEGORY, input); }
50
+ async fetchRecording(input) { const source = await this.fetchRecordingSourceRecord(input); return source ? transformMusicBrainzRecording(source) : undefined; }
51
+ async searchRecordingSourceRecords(input) { return searchMusicBrainzSourceRecords(this, MUSICBRAINZ_RECORDING_CATEGORY, input); }
52
+ async searchRecordings(input) { return searchMusicBrainz(this, MUSICBRAINZ_RECORDING_CATEGORY, input, this.transformOptions()); }
53
+ async fetchArtistSourceRecord(input) { return fetchMusicBrainzSourceRecord(this, MUSICBRAINZ_ARTIST_CATEGORY, input); }
54
+ async fetchArtist(input) { const source = await this.fetchArtistSourceRecord(input); return source ? transformMusicBrainzArtist(source) : undefined; }
55
+ async searchArtistSourceRecords(input) { return searchMusicBrainzSourceRecords(this, MUSICBRAINZ_ARTIST_CATEGORY, input); }
56
+ async searchArtists(input) { return searchMusicBrainz(this, MUSICBRAINZ_ARTIST_CATEGORY, input, this.transformOptions()); }
57
+ async fetchLabelSourceRecord(input) { return fetchMusicBrainzSourceRecord(this, MUSICBRAINZ_LABEL_CATEGORY, input); }
58
+ async fetchLabel(input) { const source = await this.fetchLabelSourceRecord(input); return source ? transformMusicBrainzLabel(source) : undefined; }
59
+ async searchLabelSourceRecords(input) { return searchMusicBrainzSourceRecords(this, MUSICBRAINZ_LABEL_CATEGORY, input); }
60
+ async searchLabels(input) { return searchMusicBrainz(this, MUSICBRAINZ_LABEL_CATEGORY, input, this.transformOptions()); }
61
+ async fetchWorkSourceRecord(input) { return fetchMusicBrainzSourceRecord(this, MUSICBRAINZ_WORK_CATEGORY, input); }
62
+ async fetchWork(input) { const source = await this.fetchWorkSourceRecord(input); return source ? transformMusicBrainzWork(source) : undefined; }
63
+ async searchWorkSourceRecords(input) { return searchMusicBrainzSourceRecords(this, MUSICBRAINZ_WORK_CATEGORY, input); }
64
+ async searchWorks(input) { return searchMusicBrainz(this, MUSICBRAINZ_WORK_CATEGORY, input, this.transformOptions()); }
65
+ }
66
+ export async function fetchMusicBrainzSourceRecord(provider, category, input) {
67
+ const id = musicBrainzId(input.id);
68
+ const payload = await provider.getJson(`/${entityPath(category)}/${id}`, { inc: lookupIncludes(category) });
69
+ if (!payload)
70
+ return undefined;
71
+ return createSourceRecord({ source: { provider: MUSICBRAINZ_PROVIDER, category, externalId: id }, payload });
72
+ }
73
+ export async function searchMusicBrainzSourceRecords(provider, category, input) {
74
+ const payload = await searchPayload(provider, category, input);
75
+ const results = searchItems(payload, category);
76
+ return {
77
+ results: await Promise.all(results.map((item) => createSourceRecord({ source: { provider: MUSICBRAINZ_PROVIDER, category, externalId: stringField(item, "id") ?? "" }, payload: item }))),
78
+ pagination: pagination(payload)
79
+ };
80
+ }
81
+ export async function searchMusicBrainz(provider, category, input, options = {}) {
82
+ const payload = await searchPayload(provider, category, input);
83
+ const results = [];
84
+ for (const item of searchItems(payload, category)) {
85
+ const externalId = stringField(item, "id");
86
+ const title = titleFor(item, category);
87
+ if (!externalId || !title)
88
+ continue;
89
+ const zuuid = await providerZuuid({ provider: MUSICBRAINZ_PROVIDER, category, externalId });
90
+ results.push({
91
+ id: zuuid,
92
+ zuuid,
93
+ category: publicCategory(category),
94
+ title,
95
+ date: dateFor(item, category),
96
+ cover: coverFor(item, category, options),
97
+ rating: null,
98
+ weight: numberValue(item.score),
99
+ relationType: null,
100
+ attribute: attributeFor(item, category),
101
+ order: null,
102
+ source: { source: MUSICBRAINZ_PROVIDER, category, value: externalId }
103
+ });
104
+ }
105
+ return { results, pagination: pagination(payload) };
106
+ }
107
+ async function searchPayload(provider, category, input) {
108
+ const query = input.query.trim();
109
+ if (!query)
110
+ throw new Error("MusicBrainz search query must not be empty");
111
+ return objectPayload(await provider.getJson(`/${entityPath(category)}`, {
112
+ query,
113
+ limit: String(input.limit ?? 25),
114
+ offset: String(input.offset ?? 0)
115
+ }) ?? {});
116
+ }
117
+ function searchItems(payload, category) {
118
+ const value = payload[searchKey(category)];
119
+ return Array.isArray(value) ? value.filter(isObject) : [];
120
+ }
121
+ function pagination(payload) {
122
+ const count = numberValue(payload.count) ?? 0;
123
+ const offset = numberValue(payload.offset) ?? 0;
124
+ const limit = Array.isArray(payload[searchKeyFromPayload(payload)]) ? payload[searchKeyFromPayload(payload)].length : 0;
125
+ return { page: limit > 0 ? Math.floor(offset / limit) + 1 : 1, totalPages: limit > 0 ? Math.ceil(count / limit) : 0, totalResults: count };
126
+ }
127
+ function searchKeyFromPayload(payload) {
128
+ return ["releases", "release-groups", "recordings", "artists", "labels", "works"].find((key) => Array.isArray(payload[key])) ?? "";
129
+ }
130
+ function lookupIncludes(category) {
131
+ switch (category) {
132
+ case MUSICBRAINZ_RELEASE_CATEGORY:
133
+ return "artists+labels+release-groups+media+recordings+genres+tags+url-rels";
134
+ case MUSICBRAINZ_RELEASE_GROUP_CATEGORY:
135
+ return "artists+releases+genres+tags+url-rels";
136
+ case MUSICBRAINZ_RECORDING_CATEGORY:
137
+ return "artists+releases+isrcs+genres+tags+work-rels+url-rels";
138
+ case MUSICBRAINZ_ARTIST_CATEGORY:
139
+ return "aliases+annotation+genres+tags+area-rels+url-rels";
140
+ case MUSICBRAINZ_LABEL_CATEGORY:
141
+ return "aliases+annotation+genres+tags+area-rels+url-rels";
142
+ case MUSICBRAINZ_WORK_CATEGORY:
143
+ return "aliases+annotation+artists+iswcs+genres+tags+artist-rels+url-rels";
144
+ default:
145
+ return "genres+tags";
146
+ }
147
+ }
148
+ function entityPath(category) { return category === MUSICBRAINZ_RELEASE_GROUP_CATEGORY ? "release-group" : category; }
149
+ function searchKey(category) { return category === MUSICBRAINZ_RELEASE_GROUP_CATEGORY ? "release-groups" : `${category}s`; }
150
+ function publicCategory(category) { return category === MUSICBRAINZ_RELEASE_GROUP_CATEGORY ? "release_group" : category === MUSICBRAINZ_WORK_CATEGORY ? "musical_work" : category; }
151
+ function titleFor(item, category) { return category === MUSICBRAINZ_ARTIST_CATEGORY || category === MUSICBRAINZ_LABEL_CATEGORY ? stringField(item, "name") : stringField(item, "title"); }
152
+ function dateFor(item, category) { return (category === MUSICBRAINZ_RELEASE_GROUP_CATEGORY ? stringField(item, "first-release-date") : category === MUSICBRAINZ_RECORDING_CATEGORY ? stringField(item, "first-release-date") : category === MUSICBRAINZ_ARTIST_CATEGORY || category === MUSICBRAINZ_LABEL_CATEGORY ? nestedString(item, ["life-span", "begin"]) : stringField(item, "date")) ?? null; }
153
+ function attributeFor(item, category) { return category === MUSICBRAINZ_ARTIST_CATEGORY || category === MUSICBRAINZ_LABEL_CATEGORY || category === MUSICBRAINZ_WORK_CATEGORY ? stringField(item, "type") ?? null : stringField(item, "primary-type") ?? stringField(item, "status") ?? null; }
154
+ function coverFor(item, category, options) { const id = stringField(item, "id"); if (!id)
155
+ return null; if (category === MUSICBRAINZ_RELEASE_GROUP_CATEGORY && options.releaseGroupCoverArtBaseUrl)
156
+ return `${options.releaseGroupCoverArtBaseUrl.replace(/\/$/, "")}/${id}/front`; if (category === MUSICBRAINZ_RELEASE_CATEGORY && options.coverArtBaseUrl && objectPayload(item["cover-art-archive"] ?? {}).front === true)
157
+ return `${options.coverArtBaseUrl.replace(/\/$/, "")}/${id}/front`; return null; }
158
+ function numberValue(value) { if (typeof value === "number" && Number.isFinite(value))
159
+ return value; if (typeof value === "string") {
160
+ const parsed = Number(value);
161
+ return Number.isFinite(parsed) ? parsed : null;
162
+ } return null; }
163
+ function isObject(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
164
+ function musicBrainzId(value) {
165
+ const id = value.trim();
166
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id))
167
+ throw new Error(`Invalid MusicBrainz MBID: ${value}`);
168
+ return id.toLowerCase();
169
+ }
@@ -7,4 +7,6 @@ export declare const MUSICBRAINZ_LABEL_CATEGORY = "label";
7
7
  export declare const MUSICBRAINZ_WORK_CATEGORY = "work";
8
8
  export declare const MUSICBRAINZ_COVER_ART_BASE_URL = "https://coverartarchive.org/release";
9
9
  export declare const MUSICBRAINZ_RELEASE_GROUP_COVER_ART_BASE_URL = "https://coverartarchive.org/release-group";
10
+ export declare const MUSICBRAINZ_API_BASE = "https://musicbrainz.org/ws/2";
11
+ export declare const MUSICBRAINZ_DEFAULT_USER_AGENT = "@zivue/zuuid/0.2.3 (https://github.com/zivue/zuuid)";
10
12
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/providers/musicbrainz/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAClD,eAAO,MAAM,4BAA4B,YAAY,CAAC;AACtD,eAAO,MAAM,kCAAkC,kBAAkB,CAAC;AAClE,eAAO,MAAM,8BAA8B,cAAc,CAAC;AAC1D,eAAO,MAAM,2BAA2B,WAAW,CAAC;AACpD,eAAO,MAAM,0BAA0B,UAAU,CAAC;AAClD,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAChD,eAAO,MAAM,8BAA8B,wCAAwC,CAAC;AACpF,eAAO,MAAM,4CAA4C,8CAA8C,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/providers/musicbrainz/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAClD,eAAO,MAAM,4BAA4B,YAAY,CAAC;AACtD,eAAO,MAAM,kCAAkC,kBAAkB,CAAC;AAClE,eAAO,MAAM,8BAA8B,cAAc,CAAC;AAC1D,eAAO,MAAM,2BAA2B,WAAW,CAAC;AACpD,eAAO,MAAM,0BAA0B,UAAU,CAAC;AAClD,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAChD,eAAO,MAAM,8BAA8B,wCAAwC,CAAC;AACpF,eAAO,MAAM,4CAA4C,8CAA8C,CAAC;AACxG,eAAO,MAAM,oBAAoB,iCAAiC,CAAC;AACnE,eAAO,MAAM,8BAA8B,wDAAwD,CAAC"}
@@ -7,3 +7,5 @@ export const MUSICBRAINZ_LABEL_CATEGORY = "label";
7
7
  export const MUSICBRAINZ_WORK_CATEGORY = "work";
8
8
  export const MUSICBRAINZ_COVER_ART_BASE_URL = "https://coverartarchive.org/release";
9
9
  export const MUSICBRAINZ_RELEASE_GROUP_COVER_ART_BASE_URL = "https://coverartarchive.org/release-group";
10
+ export const MUSICBRAINZ_API_BASE = "https://musicbrainz.org/ws/2";
11
+ export const MUSICBRAINZ_DEFAULT_USER_AGENT = "@zivue/zuuid/0.2.3 (https://github.com/zivue/zuuid)";
@@ -1,3 +1,4 @@
1
+ export * from "./client.js";
1
2
  export * from "./artist.js";
2
3
  export * from "./constants.js";
3
4
  export * from "./label.js";
@@ -5,4 +6,5 @@ export * from "./recording.js";
5
6
  export * from "./release.js";
6
7
  export * from "./release-group.js";
7
8
  export * from "./work.js";
9
+ export * from "./types.js";
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/musicbrainz/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/musicbrainz/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
@@ -1,3 +1,4 @@
1
+ export * from "./client.js";
1
2
  export * from "./artist.js";
2
3
  export * from "./constants.js";
3
4
  export * from "./label.js";
@@ -5,3 +6,4 @@ export * from "./recording.js";
5
6
  export * from "./release.js";
6
7
  export * from "./release-group.js";
7
8
  export * from "./work.js";
9
+ export * from "./types.js";
@@ -0,0 +1,21 @@
1
+ export type MusicBrainzFetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
2
+ export type MusicBrainzProviderOptions = {
3
+ apiBase?: string;
4
+ fetch?: MusicBrainzFetchLike;
5
+ userAgent?: string;
6
+ coverArtBaseUrl?: string | null;
7
+ releaseGroupCoverArtBaseUrl?: string | null;
8
+ };
9
+ export type MusicBrainzProviderTransformOptions = {
10
+ coverArtBaseUrl?: string | null;
11
+ releaseGroupCoverArtBaseUrl?: string | null;
12
+ };
13
+ export type FetchMusicBrainzInput = {
14
+ id: string;
15
+ };
16
+ export type MusicBrainzSearchInput = {
17
+ query: string;
18
+ limit?: number;
19
+ offset?: number;
20
+ };
21
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/musicbrainz/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElG,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,2BAA2B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zivue/zuuid",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "TypeScript helpers for fetching, searching, and transforming Zuuid datasets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -255,6 +255,10 @@
255
255
  "./providers/gamesdb/client": {
256
256
  "types": "./dist/providers/gamesdb/client.d.ts",
257
257
  "import": "./dist/providers/gamesdb/client.js"
258
+ },
259
+ "./providers/musicbrainz/client": {
260
+ "types": "./dist/providers/musicbrainz/client.d.ts",
261
+ "import": "./dist/providers/musicbrainz/client.js"
258
262
  }
259
263
  },
260
264
  "types": "./dist/index.d.ts",