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

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

Jouw teksten, en de twee dingen die ze beloofden (shaer-mbz)

De titel is nu "MusicBrainz Koppeling" met jouw omschrijving eronder, in drie
talen. Maar die omschrijving belooft twee dingen die de code nog niet deed, en
een label dat liegt is erger dan een lelijk label. Dus allebei gebouwd.

"OF JE MUSICBRAINZ ID ALS JE DIE KENT". Plak je een UUID, dan wordt hij
rechtstreeks opgezocht in plaats van gezocht. Dat is geen luxe: een
zoekopdracht OP een UUID levert bij MusicBrainz niets op, dus zonder deze tak
gaf plakken juist het slechtste resultaat.

"MET TERUG-WEG VALIDATIE". Een koppeling van onze kant is een bewering --
iedereen kan een id in een veld typen. Pas als de MusicBrainz-pagina TERUGWIJST
naar dit domein is het een paar, en dan weet een lezer dat dezelfde persoon aan
allebei de kanten stond. Dezelfde gedachte als rel="me". Het paneel zegt
"wederzijds" of "nog eenzijdig", en bij dat laatste staat erbij WAT je moet doen:
het domein op je MusicBrainz-pagina zetten onder "social networking".

Wij zetten die terugwijzing niet zelf. Kan niet via hun API, en hoort ook niet.

ECHT GEMETEN, en dat gaf de test zijn vorm: robo-burr staat in MusicBrainz met
score 100 (24abe2be-c0bc-4c63-9642-d6f89ec6a00a, "AKA roboburr") en heeft
precies EEN url-relatie -- type "social network", naar sound-fabrics.com. Voor
dev.klonkt.com is de terug-weg dus false, en dat is precies de waarde van de
controle. Die vorm staat nu in een test.

Bij een storing komt er false EN een lege lijst uit: niet kunnen kijken is niet
hetzelfde als niet gevonden, en nooit stilletjes "wederzijds".

Een controle erbij die eist dat alle drie de talen dezelfde mb_-sleutels
hebben. Bij het herschrijven waren er zeven weggevallen die het scherm nog
gebruikt -- een gat in een vertaling geeft een leeg label en dat valt nooit
vanzelf op.

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

  • Property mode set to 100644
File size: 6.5 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// 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.
25import { isMbid, artiestUrl } from './ap-core.js';
26
27const 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 */
35function 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 */
48let laatste = 0;
49let beurt = Promise.resolve();
50function 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 */
67export 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/**
83 * Een MBID rechtstreeks opzoeken. Wie zijn id al kent hoeft niet te zoeken --
84 * en een zoekopdracht op een UUID levert bij MusicBrainz niets op, dus zonder
85 * deze tak zou plakken juist het slechtste resultaat geven.
86 */
87export async function haalArtiest(mbid) {
88 if (!isMbid(mbid)) return null;
89 const url = `${BASIS}/artist/${encodeURIComponent(mbid)}?inc=url-rels&fmt=json`;
90 try {
91 await opDeBeurt();
92 const r = await safeFetch(url, { headers: { Accept: 'application/json', 'User-Agent': userAgent() } });
93 if (!r || !r.ok) return null;
94 return kandidaat(await r.json());
95 } catch {
96 return null;
97 }
98}
99
100/**
101 * DE TERUG-WEG. Noemt de MusicBrainz-pagina van deze artiest ons domein?
102 *
103 * Een koppeling van onze kant is een bewering: iedereen kan een MBID in een
104 * veld typen. Pas als de artiestenpagina TERUGWIJST is het een paar, en dan
105 * weet een lezer dat dezelfde persoon aan allebei de kanten stond. Dat is
106 * dezelfde gedachte als rel="me" bij Mastodon.
107 *
108 * Wij zetten die terugwijzing NIET zelf: via hun API kan het niet, en het hoort
109 * ook niet -- de artiest doet dat op musicbrainz.org onder "social networking".
110 * Wij kijken alleen of hij er staat.
111 *
112 * Geeft { verified, urls } -- bij een storing verified:false en een lege lijst,
113 * want niet kunnen kijken is niet hetzelfde als niet gevonden.
114 */
115export async function controleerTerugweg(mbid, domein) {
116 const leeg = { verified: false, urls: [] };
117 if (!isMbid(mbid) || !domein) return leeg;
118 let host;
119 try { host = new URL(domein).host.toLowerCase(); } catch { return leeg; }
120 const url = `${BASIS}/artist/${encodeURIComponent(mbid)}?inc=url-rels&fmt=json`;
121 try {
122 await opDeBeurt();
123 const r = await safeFetch(url, { headers: { Accept: 'application/json', 'User-Agent': userAgent() } });
124 if (!r || !r.ok) return leeg;
125 const doc = await r.json();
126 const urls = (doc.relations || [])
127 .map((rel) => rel && rel.url && rel.url.resource)
128 .filter((u) => typeof u === 'string');
129 const wijst = urls.some((u) => { try { return new URL(u).host.toLowerCase() === host; } catch { return false; } });
130 return { verified: wijst, urls };
131 } catch {
132 return leeg;
133 }
134}
135
136/** Een kandidaat, teruggebracht tot wat een mens nodig heeft om te kiezen. */
137function kandidaat(a) {
138 if (!a || !a.id || !a.name) return null;
139 const jaren = [a['life-span']?.begin, a['life-span']?.ended ? a['life-span']?.end : null]
140 .filter(Boolean).join(' – ');
141 return {
142 mbid: a.id,
143 naam: a.name,
144 // "disambiguation" is het veld waarmee MusicBrainz zelf twee gelijknamige
145 // artiesten uit elkaar houdt. Precies wat de kiezer nodig heeft.
146 toelichting: a.disambiguation || '',
147 soort: a.type || '', // Person, Group, ...
148 land: a.country || '',
149 jaren,
150 url: `https://musicbrainz.org/artist/${a.id}`,
151 // Hun eigen zoekscore. Niet om op te sorteren -- dat doen zij al -- maar om
152 // een zwakke treffer te kunnen tonen als zwak.
153 score: Number(a.score) || 0,
154 };
155}
156
157// Her-geexporteerd zodat een aanroeper er niet over hoeft na te denken waar
158// ze precies wonen.
159export { isMbid, artiestUrl };
160export default { zoekArtiesten, haalArtiest, controleerTerugweg, isMbid, artiestUrl };
Note: See TracBrowser for help on using the repository browser.