/** * AudioEmbedService — Parse URLs and return embed HTML * Supports: Spotify, Bandcamp, SoundCloud, Apple Music, YouTube, Vimeo * * Usage in post content: *

https://open.spotify.com/track/123abc

* → *
* *
*/ // Dezelfde lijst soorten als de server en de editor gebruiken. Zie // assets/js/shared/post-music-type.js: die module is puur, dus hij mag hier. import { SOORTEN } from '../assets/js/shared/post-music-type.js'; // "Open in" icons (brand-colored via CSS .pat-link--). const OPEN_IN_SVG = { spotify: '', youtube: '', soundcloud: '', }; class AudioEmbedService { // Small "open in" links for a track (Spotify/YouTube/SoundCloud). The hrefs // are already validated server-side (https + correct host only). Returns '' // when no links exist. Placed next to the play button (outside the button → // no conflict with playback). static openInLinks(t) { if (!t) return ''; const out = []; const add = (url, key, label) => { if (!url) return; out.push(`${OPEN_IN_SVG[key]}`); }; add(t.link_spotify, 'spotify', 'Spotify'); add(t.link_youtube, 'youtube', 'YouTube'); add(t.link_soundcloud, 'soundcloud', 'SoundCloud'); return out.length ? `${out.join('')}` : ''; } static detectProvider(url) { if (!url || typeof url !== 'string') return null; url = url.trim(); // Only embed http(s) URLs. The provider regexes below are NOT anchored, // so without this check e.g. `javascript:alert(1)//youtu.be/x` would match // and land as an embed URL (stored XSS via an [[embed:...]] shortcode — // that text never passes through the HTML sanitizer because it lives in a // text node). The scheme guard excludes javascript:/data:/vbscript: etc. if (!/^https?:\/\//i.test(url)) return null; // Spotify if (/open\.spotify\.com\/(track|album|playlist|episode|show)\/([A-Za-z0-9]+)/i.test(url)) { const match = url.match(/\/(track|album|playlist|episode|show)\/([A-Za-z0-9]+)/i); return { provider: 'spotify', type: match[1], id: match[2], url }; } // Bandcamp if (/bandcamp\.com\/(track|album)/i.test(url)) { return { provider: 'bandcamp', url }; } // SoundCloud if (/soundcloud\.com/i.test(url)) { return { provider: 'soundcloud', url }; } // Apple Music if (/music\.apple\.com\/([a-z]{2})\/(?:album|playlist|song)\//i.test(url)) { return { provider: 'applemusic', url }; } // YouTube — a video id is always exactly 11 characters (aligns with the // client-side ytId() in embed-player.js, which also expects {11}). // // A link may carry a video, a playlist, or both, and until now we kept only // the video and threw `list=` away -- so a link to an album played its first // song and stopped. The ref now keeps whichever is there, in the same three // shapes the Klonkt hub uses, so one ref travels between the two unchanged: // // "