source: Klonkt/src/services/MusicBrainzService.js@ a117862

main
Last change on this file since a117862 was a117862, checked in by Robin <roboburr@…>, 4 weeks ago

Stap 1: een artiest kan zichzelf opzoeken in MusicBrainz (shaer-mbz)

Waarom dit geen dialect is en Funkwhale's Track/ArtistCredit wel: een MBID is
geen vocabulaire maar een REGISTER. Ernaar verwijzen is als een ISBN noemen --
je neemt niemands model over en je wijst naar iets dat al bestaat. En het is de
brug die Funkwhale zelf al kent, want hun Track draagt musicbrainzId.

WAT ER STAAT

  • MusicBrainzService: zoeken op artiestennaam, kandidaten met hun disambiguatie, soort, land en jaren erbij. De naam alleen is niet genoeg om te kiezen -- er zijn drie bands die Nirvana heten, en dat is precies waar hun disambiguation-veld voor is.
  • mb_artist_id en mb_artist_name op sites. De naam erbij zodat het scherm kan tonen WAT er gekoppeld is zonder ervoor te netwerken, en zodat een verkeerde koppeling opvalt.
  • GET /admin/audio/api/musicbrainz, dat standaard zoekt op de artiestennaam die al in de site staat.

HUN TWEE HARDE REGELS, INGEBAKKEN EN GETEST. Overtreden leidt tot een BLOKKADE
en niet tot een foutmelding, dus "het werkte toen ik het probeerde" is er geen
bewijs voor:

  • hoogstens EEN verzoek per seconde, over de hele applicatie. Daarom draait de zoekopdracht server-side: die regel geldt per applicatie en niet per bezoeker, dus twee tabbladen zouden hem samen overtreden. Een test doet twee zoekopdrachten tegelijk en eist een gat van een seconde.
  • een echte User-Agent met contactgegevens. Een test leest hem uit het verzoek.

DE KEUZE BLIJFT VAN DE ARTIEST. We tonen kandidaten; we kiezen er niet zelf een,
ook niet als er maar een treffer is. Een verkeerd geraden MBID koppelt iemand aan
het werk van een ander, en dat is erger dan geen koppeling.

WAT ER NIET IN ZIT: schrijven naar MusicBrainz. Kan niet via hun API voor
artiesten -- alleen tags, ratings, ISRC's en barcodes -- en zou ook niet moeten.

Zonder netwerk getest: globalThis.fetch vervangen en musicbrainz.org in
AP_ALLOW_HOSTS, zodat er geen DNS aan te pas komt. Anders test het of deze
machine internet heeft.

Co-Authored-By: Claude Opus 5 <noreply@…>

  • Property mode set to 100644
File size: 4.4 KB
Line 
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 */
21import { safeFetch } from './ActivityPubService.js';
22
23const BASIS = 'https://musicbrainz.org/ws/2';
24
25/**
26 * De User-Agent die MusicBrainz eist. Hun regel: naam, versie en een manier om
27 * contact op te nemen. Een lege of generieke string is precies waarop ze
28 * blokkeren, dus als er geen contact is ingesteld zeggen we dat met zoveel
29 * woorden in plaats van iets aardigs te verzinnen.
30 */
31function userAgent() {
32 const contact = (process.env.MUSICBRAINZ_CONTACT || '').trim()
33 || (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '')
34 || 'geen-contact-ingesteld';
35 return `Klonkt/1.0 ( ${contact} )`;
36}
37
38/**
39 * Hun tempo aanhouden: ten hoogste een verzoek per seconde, over de HELE
40 * applicatie. Geen bibliotheek en geen wachtrij -- een belofte die de volgende
41 * aanroeper laat wachten tot het weer mag. Zonder dit is de eerste drukke dag
42 * meteen een blokkade, en dan werkt het bij iedereen niet meer.
43 */
44let laatste = 0;
45let beurt = Promise.resolve();
46function opDeBeurt() {
47 beurt = beurt.then(async () => {
48 const wachten = 1000 - (Date.now() - laatste);
49 if (wachten > 0) await new Promise((r) => setTimeout(r, wachten));
50 laatste = Date.now();
51 });
52 return beurt;
53}
54
55/**
56 * Zoek artiesten op naam. Geeft de kandidaten met alles wat nodig is om er EEN
57 * uit te kiezen -- de naam alleen is niet genoeg, want er zijn drie bands die
58 * Nirvana heten. Vandaar disambiguation, land en de jaren erbij.
59 *
60 * Geeft een LEGE lijst bij een storing, geen exceptie: niet kunnen zoeken is
61 * vervelend, maar het mag het beheerscherm niet omvergooien.
62 */
63export async function zoekArtiesten(naam, { limit = 8 } = {}) {
64 const q = String(naam || '').trim();
65 if (!q) return [];
66 const url = `${BASIS}/artist?query=${encodeURIComponent(q)}&fmt=json&limit=${Math.min(25, Math.max(1, limit))}`;
67 try {
68 await opDeBeurt();
69 const r = await safeFetch(url, { headers: { Accept: 'application/json', 'User-Agent': userAgent() } });
70 if (!r || !r.ok) return [];
71 const doc = await r.json();
72 return (doc.artists || []).map(kandidaat).filter(Boolean);
73 } catch {
74 return [];
75 }
76}
77
78/** Een kandidaat, teruggebracht tot wat een mens nodig heeft om te kiezen. */
79function kandidaat(a) {
80 if (!a || !a.id || !a.name) return null;
81 const jaren = [a['life-span']?.begin, a['life-span']?.ended ? a['life-span']?.end : null]
82 .filter(Boolean).join(' – ');
83 return {
84 mbid: a.id,
85 naam: a.name,
86 // "disambiguation" is het veld waarmee MusicBrainz zelf twee gelijknamige
87 // artiesten uit elkaar houdt. Precies wat de kiezer nodig heeft.
88 toelichting: a.disambiguation || '',
89 soort: a.type || '', // Person, Group, ...
90 land: a.country || '',
91 jaren,
92 url: `https://musicbrainz.org/artist/${a.id}`,
93 // Hun eigen zoekscore. Niet om op te sorteren -- dat doen zij al -- maar om
94 // een zwakke treffer te kunnen tonen als zwak.
95 score: Number(a.score) || 0,
96 };
97}
98
99/** Is dit een MBID? Een UUID, en niets anders. */
100export function isMbid(s) {
101 return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(String(s || '').trim());
102}
103
104/** De publieke pagina van een artiest, of null als het geen MBID is. */
105export function artiestUrl(mbid) {
106 return isMbid(mbid) ? `https://musicbrainz.org/artist/${String(mbid).trim().toLowerCase()}` : null;
107}
108
109export default { zoekArtiesten, isMbid, artiestUrl };
Note: See TracBrowser for help on using the repository browser.