| [a117862] | 1 | /**
|
|---|
| 2 | * Een artiest zoekt zichzelf op in MusicBrainz (shaer-mbz).
|
|---|
| 3 | *
|
|---|
| 4 | * WAAROM DIT GEEN DIALECT IS. Funkwhale's Track/Artist/ArtistCredit zijn hun
|
|---|
| 5 | * eigen vocabulaire -- hun docs noemen ze letterlijk "Custom Funkwhale object"
|
|---|
| 6 | * -- en wij kunnen ze niet eerlijk vullen: artiest en album zijn bij ons
|
|---|
| 7 | * tekstkolommen, geen entiteiten. Een MBID is iets anders: geen vocabulaire
|
|---|
| 8 | * maar een REGISTER. Ernaar verwijzen is als een ISBN noemen. Je neemt niemands
|
|---|
| 9 | * model over en je wijst naar iets dat al bestaat.
|
|---|
| 10 | *
|
|---|
| 11 | * WAT HIER NIET GEBEURT: schrijven. Via hun API zijn alleen tags, ratings,
|
|---|
| 12 | * ISRC's en barcodes in te dienen -- artiesten, releases en recordings niet,
|
|---|
| 13 | * dat gaat via hun website. Wij lezen dus alleen, en dat is meteen de
|
|---|
| 14 | * geruststelling: we kunnen hun register niet vervuilen.
|
|---|
| 15 | *
|
|---|
| 16 | * TWEE HARDE REGELS VAN HUN KANT, allebei hieronder ingebakken omdat ze bij
|
|---|
| 17 | * overtreding tot blokkade leiden en niet tot een foutmelding:
|
|---|
| 18 | * - hoogstens EEN verzoek per seconde, per applicatie (niet per bezoeker)
|
|---|
| 19 | * - een echte User-Agent, met contactgegevens
|
|---|
| 20 | */
|
|---|
| 21 | import { safeFetch } from './ActivityPubService.js';
|
|---|
| [f59a46c] | 22 | // De twee pure vormcontroles wonen in ap-core: ActivityPubService heeft ze ook
|
|---|
| 23 | // nodig voor de actor, en zonder die verhuizing zou dat een KRINGLOOP zijn --
|
|---|
| 24 | // deze module leent immers safeFetch dáár.
|
|---|
| 25 | import { isMbid, artiestUrl } from './ap-core.js';
|
|---|
| [a117862] | 26 |
|
|---|
| 27 | const BASIS = 'https://musicbrainz.org/ws/2';
|
|---|
| 28 |
|
|---|
| 29 | /**
|
|---|
| 30 | * De User-Agent die MusicBrainz eist. Hun regel: naam, versie en een manier om
|
|---|
| 31 | * contact op te nemen. Een lege of generieke string is precies waarop ze
|
|---|
| 32 | * blokkeren, dus als er geen contact is ingesteld zeggen we dat met zoveel
|
|---|
| 33 | * woorden in plaats van iets aardigs te verzinnen.
|
|---|
| 34 | */
|
|---|
| 35 | function userAgent() {
|
|---|
| 36 | const contact = (process.env.MUSICBRAINZ_CONTACT || '').trim()
|
|---|
| 37 | || (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '')
|
|---|
| 38 | || 'geen-contact-ingesteld';
|
|---|
| 39 | return `Klonkt/1.0 ( ${contact} )`;
|
|---|
| 40 | }
|
|---|
| 41 |
|
|---|
| 42 | /**
|
|---|
| 43 | * Hun tempo aanhouden: ten hoogste een verzoek per seconde, over de HELE
|
|---|
| 44 | * applicatie. Geen bibliotheek en geen wachtrij -- een belofte die de volgende
|
|---|
| 45 | * aanroeper laat wachten tot het weer mag. Zonder dit is de eerste drukke dag
|
|---|
| 46 | * meteen een blokkade, en dan werkt het bij iedereen niet meer.
|
|---|
| 47 | */
|
|---|
| 48 | let laatste = 0;
|
|---|
| 49 | let beurt = Promise.resolve();
|
|---|
| 50 | function opDeBeurt() {
|
|---|
| 51 | beurt = beurt.then(async () => {
|
|---|
| 52 | const wachten = 1000 - (Date.now() - laatste);
|
|---|
| 53 | if (wachten > 0) await new Promise((r) => setTimeout(r, wachten));
|
|---|
| 54 | laatste = Date.now();
|
|---|
| 55 | });
|
|---|
| 56 | return beurt;
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| 59 | /**
|
|---|
| 60 | * Zoek artiesten op naam. Geeft de kandidaten met alles wat nodig is om er EEN
|
|---|
| 61 | * uit te kiezen -- de naam alleen is niet genoeg, want er zijn drie bands die
|
|---|
| 62 | * Nirvana heten. Vandaar disambiguation, land en de jaren erbij.
|
|---|
| 63 | *
|
|---|
| 64 | * Geeft een LEGE lijst bij een storing, geen exceptie: niet kunnen zoeken is
|
|---|
| 65 | * vervelend, maar het mag het beheerscherm niet omvergooien.
|
|---|
| 66 | */
|
|---|
| 67 | export async function zoekArtiesten(naam, { limit = 8 } = {}) {
|
|---|
| 68 | const q = String(naam || '').trim();
|
|---|
| 69 | if (!q) return [];
|
|---|
| 70 | const url = `${BASIS}/artist?query=${encodeURIComponent(q)}&fmt=json&limit=${Math.min(25, Math.max(1, limit))}`;
|
|---|
| 71 | try {
|
|---|
| 72 | await opDeBeurt();
|
|---|
| 73 | const r = await safeFetch(url, { headers: { Accept: 'application/json', 'User-Agent': userAgent() } });
|
|---|
| 74 | if (!r || !r.ok) return [];
|
|---|
| 75 | const doc = await r.json();
|
|---|
| 76 | return (doc.artists || []).map(kandidaat).filter(Boolean);
|
|---|
| 77 | } catch {
|
|---|
| 78 | return [];
|
|---|
| 79 | }
|
|---|
| 80 | }
|
|---|
| 81 |
|
|---|
| 82 | /** Een kandidaat, teruggebracht tot wat een mens nodig heeft om te kiezen. */
|
|---|
| 83 | function kandidaat(a) {
|
|---|
| 84 | if (!a || !a.id || !a.name) return null;
|
|---|
| 85 | const jaren = [a['life-span']?.begin, a['life-span']?.ended ? a['life-span']?.end : null]
|
|---|
| 86 | .filter(Boolean).join(' – ');
|
|---|
| 87 | return {
|
|---|
| 88 | mbid: a.id,
|
|---|
| 89 | naam: a.name,
|
|---|
| 90 | // "disambiguation" is het veld waarmee MusicBrainz zelf twee gelijknamige
|
|---|
| 91 | // artiesten uit elkaar houdt. Precies wat de kiezer nodig heeft.
|
|---|
| 92 | toelichting: a.disambiguation || '',
|
|---|
| 93 | soort: a.type || '', // Person, Group, ...
|
|---|
| 94 | land: a.country || '',
|
|---|
| 95 | jaren,
|
|---|
| 96 | url: `https://musicbrainz.org/artist/${a.id}`,
|
|---|
| 97 | // Hun eigen zoekscore. Niet om op te sorteren -- dat doen zij al -- maar om
|
|---|
| 98 | // een zwakke treffer te kunnen tonen als zwak.
|
|---|
| 99 | score: Number(a.score) || 0,
|
|---|
| 100 | };
|
|---|
| 101 | }
|
|---|
| 102 |
|
|---|
| [f59a46c] | 103 | // Her-geexporteerd zodat een aanroeper er niet over hoeft na te denken waar
|
|---|
| 104 | // ze precies wonen.
|
|---|
| 105 | export { isMbid, artiestUrl };
|
|---|
| [a117862] | 106 | export default { zoekArtiesten, isMbid, artiestUrl };
|
|---|