// Dev-only mock library data, for exercising the Library UI on a machine // without a populated music library. Inert unless explicitly enabled by // setting localStorage.mockLibrary = "1" in the webview (then reload); // remove the key to go back to real backend data. const ARTISTS = ["Aphex Twin","Boards of Canada","Caribou","Daft Punk","Eno Brian","Four Tet","Grimes","Holly Herndon","Jon Hopkins","Kraftwerk","LCD Soundsystem","Moderat","Nils Frahm","Oneohtrix","Portishead","Radiohead","Squarepusher","Tycho","Underworld","Vangelis","Wilco","Yello","Zero 7","Air","Bonobo","Burial","Clark","Deadmau5","Emancipator","Floating Points","Gold Panda","Helios","Kiasmos","Lorn","Moby","Nujabes","Ott","Plaid","Rival Consoles","Shpongle"]; // Titles vary by slot so the Library's classifier has something real to file: // a live set, demos, a remix record and a collection appear per artist cycle, // and a few rows have NO art_path so the generated artwork can be judged. const TITLE_SHAPES = [ (a, j) => `Album ${j + 1} of ${a}`, (a) => `Live at the Roundhouse`, (a) => `Four-Track Demos`, (a) => `Remixes 2001`, (a) => `Singles Collection`, ]; const albums = []; let aid = 1; for (let i = 0; i < ARTISTS.length; i++) { const n = 2 + (i % 4); // 2-5 albums per artist for (let j = 0; j < n; j++) { const shape = j < 2 ? TITLE_SHAPES[0] : TITLE_SHAPES[(i + j) % TITLE_SHAPES.length]; albums.push({ id: aid, artist: ARTISTS[i], title: shape(ARTISTS[i], j), year: 1995 + ((i + j) % 30), track_count: j === 2 && i % 5 === 0 ? 3 : 8 + ((i + j) % 7), art_path: (i + j) % 4 === 3 ? null : `mock://art/${aid}`, artist_art_path: `mock://artist/${i}`, added_at: 1700000000 + i * 86400 * 9 + j * 86400 * 40, path: `C:\\mock\\${i}\\${j}`, }); aid++; } } // Per-album file aggregates, the same shape library_album_stats returns. // Deterministic variety: a third FLAC 16/44.1, a few 24/96, the rest MP3. function statsFor(a) { const kind = a.id % 7; const flac = kind === 0 || kind === 3 || kind === 5; const hires = kind === 5; const perTrack = flac ? (hires ? 120e6 : 38e6) : 9e6; return { album_id: a.id, secs: a.track_count * (170 + (a.id % 90)), bytes: Math.round(a.track_count * perTrack), avg_kbps: flac ? (hires ? 2400 : 900) + (a.id % 120) : 192 + (a.id % 4) * 32, bit_depth: flac ? (hires ? 24 : 16) : null, sample_rate: flac ? (hires ? 96000 : 44100) : null, formats: [flac ? "flac" : "mp3"], last_played_at: a.id % 3 === 0 ? null : 1723000000 - (a.id % 40) * 86400, }; } function svgArt(hue, label) { const s = `` + `` + `${label}`; return "data:image/svg+xml;base64," + btoa(s); } function tracksFor(albumId) { const album = albums.find((a) => a.id === albumId); const artist = album?.artist ?? "?"; const s = album ? statsFor(album) : null; const ext = s?.formats[0] ?? "mp3"; const n = album?.track_count ?? 10; return Array.from({ length: n }, (_, k) => ({ id: albumId * 100 + k, album_id: albumId, title: `Track ${k + 1}`, artist, track_no: k + 1, duration_secs: 120 + ((albumId + k) % 9) * 23, bitrate_kbps: s?.avg_kbps ?? 320, bit_depth: s?.bit_depth ?? null, sample_rate: s?.sample_rate ?? null, size: Math.round((s?.bytes ?? 40e6) / n), path: `C:\\mock\\t${albumId}-${k}.${ext}`, })); } /* ── playlists: the desktop's shapes (library::Playlist rows with the command layer's duration_secs / unresolved_count, PlaylistItemPayload rows with `missing`). One pinned playlist holding every row kind, a setlist with coherent cue times, and an empty one. Mutations bump the revision and emit soulseek:playlists-changed, as the real commands do. */ let plSeq = 3; let plItemSeq = 100; const PL_T0 = 1_725_000_000; const PL = [ { id: 1, name: "Late night drive", track_count: 0, description: "Slow, wide, mostly synths.", kind: "manual", pinned: true, cover_path: "mock://cover1", created_at: PL_T0, updated_at: PL_T0, revision: 1, duration_secs: 0, unresolved_count: 0 }, { id: 2, name: "Boiler Room 2019", track_count: 0, description: null, kind: "setlist", pinned: false, cover_path: null, created_at: PL_T0 + 10, updated_at: PL_T0 + 10, revision: 1, duration_secs: 0, unresolved_count: 0 }, { id: 3, name: "Empty one", track_count: 0, description: null, kind: "manual", pinned: false, cover_path: null, created_at: PL_T0 + 20, updated_at: PL_T0 + 20, revision: 1, duration_secs: 0, unresolved_count: 0 }, ]; function plLocalRow(albumId, k, extra = {}) { const t = tracksFor(albumId)[k]; const a = albums.find((x) => x.id === albumId); return { title: t.title, artist: a.artist, album: a.title, duration_secs: t.duration_secs, path: t.path, art_path: a.art_path, kind: "local", source_line: null, start_ms: null, note: null, track_id: null, art_url: null, missing: false, ...extra }; } const PL_ITEMS = { 1: [ plLocalRow(1, 0), plLocalRow(2, 1), plLocalRow(5, 2, { missing: true }), plLocalRow(9, 3), { title: "One More Time", artist: "Daft Punk", album: "Discovery", duration_secs: 200, path: null, art_path: null, kind: "stream", source_line: "Daft Punk - One More Time", start_ms: null, note: null, track_id: "3135550", art_url: svgArt(200, "D"), missing: false }, { title: "ID", artist: "ID", album: null, duration_secs: null, path: null, art_path: null, kind: "unresolved", source_line: "07 ID - ID", start_ms: null, note: null, track_id: null, art_url: null, missing: false }, ], 2: [plLocalRow(3, 0, { start_ms: 0 }), plLocalRow(3, 1, { start_ms: 245_000 }), plLocalRow(3, 2, { start_ms: 1_130_000 })], 3: [], }; for (const id of Object.keys(PL_ITEMS)) PL_ITEMS[id].forEach((r, i) => { r.id = ++plItemSeq; r.position = i; }); function plSync(p) { const rows = PL_ITEMS[p.id] || []; p.track_count = rows.length; p.duration_secs = rows.reduce((n, r) => n + (r.duration_secs || 0), 0); p.unresolved_count = rows.filter((r) => r.kind === "unresolved").length; } PL.forEach(plSync); function plTouch(p) { if (p) { p.revision++; p.updated_at = Math.floor(Date.now() / 1000); plSync(p); } setTimeout(() => mockEmit("soulseek:playlists-changed", {}), 30); } const plOf = (args) => PL.find((p) => p.id === Number(args?.id ?? args?.playlistId)); /* the per-track auto-sync switch the lyrics sheet flips */ let autoSyncOff = false; /* every track has three timed lines; the sheet's tools have something to act on */ function mockLyrics(title) { return { status: "synced", source: "lrclib", synced: [{ time_ms: 0, text: "Here is " + (title || "a song") }, { time_ms: 4000, text: "Every word in time" }, { time_ms: 9000, text: "And the last one, late" }], plain: null, offset_ms: 0, auto_offset_ms: 0, auto_sync_off: autoSyncOff, }; } function mockPlaylists(cmd, args) { if (cmd === "list_playlists") return Promise.resolve(PL.map((p) => ({ ...p }))); if (cmd === "playlist_items") return new Promise((r) => setTimeout(() => r((PL_ITEMS[Number(args?.id)] || []).map((x) => ({ ...x }))), 80)); if (cmd === "playlist_cover") { const p = plOf(args); return Promise.resolve(p && p.cover_path ? svgArt(p.id === 1 ? 330 : 280, p.id === 1 ? "L" : "M") : null); } if (cmd === "playlist_cover_set") { const p = plOf(args); if (!p) return Promise.reject(new Error("no such playlist")); p.cover_path = String(args?.path || "mock://photo"); plTouch(p); return Promise.resolve(svgArt(300, "P")); } if (cmd === "playlist_cover_clear") { const p = plOf(args); if (p) p.cover_path = null; plTouch(p); return Promise.resolve(); } if (cmd === "create_playlist") { const p = { id: ++plSeq, name: args?.name || "New Playlist", track_count: 0, description: null, kind: args?.kind || "manual", pinned: false, cover_path: null, created_at: Math.floor(Date.now() / 1000), updated_at: 0, revision: 0, duration_secs: 0, unresolved_count: 0 }; PL.push(p); PL_ITEMS[p.id] = []; plTouch(p); return Promise.resolve(p.id); } if (cmd === "rename_playlist") { const p = plOf(args); if (p) p.name = args.name; plTouch(p); return Promise.resolve(); } if (cmd === "set_playlist_description") { const p = plOf(args); if (p) p.description = args.description || null; plTouch(p); return Promise.resolve(); } if (cmd === "set_playlist_pinned") { const p = plOf(args); if (p) p.pinned = !!args.pinned; plTouch(p); return Promise.resolve(); } if (cmd === "duplicate_playlist") { const p = plOf(args); if (!p) return Promise.reject(new Error("duplicate_playlist: no such playlist")); const c = { ...p, id: ++plSeq, name: args?.name || p.name + " copy", pinned: false, revision: 0 }; PL.push(c); PL_ITEMS[c.id] = (PL_ITEMS[p.id] || []).map((x, i) => ({ ...x, id: ++plItemSeq, position: i })); plTouch(c); return Promise.resolve(c.id); } if (cmd === "delete_playlist") { const i = PL.findIndex((p) => p.id === Number(args?.id)); if (i >= 0) { delete PL_ITEMS[PL[i].id]; PL.splice(i, 1); } plTouch(null); return Promise.resolve(); } if (cmd === "playlist_remove_items") { const p = plOf(args); const ids = new Set((args?.itemIds || []).map(Number)); if (p) { PL_ITEMS[p.id] = (PL_ITEMS[p.id] || []).filter((x) => !ids.has(x.id)); PL_ITEMS[p.id].forEach((x, i) => { x.position = i; }); } plTouch(p); return Promise.resolve(); } if (cmd === "playlist_add_items") { const p = plOf(args); if (!p) return Promise.reject(new Error("playlist_add_items: no such playlist")); const rows = (args?.items || []).map((it) => ({ title: it.title, artist: it.artist || "", album: it.album ?? null, duration_secs: it.duration_secs ?? null, path: it.path ?? null, art_path: it.art_path ?? null, kind: it.path ? "local" : (it.title && it.artist ? "stream" : "unresolved"), source_line: it.source_line ?? null, start_ms: it.start_ms ?? null, note: it.note ?? null, track_id: it.track_id ?? null, art_url: it.art_url ?? null, missing: false, id: ++plItemSeq, })); const list = PL_ITEMS[p.id]; const at = args?.at == null ? list.length : Math.min(Number(args.at), list.length); list.splice(at, 0, ...rows); list.forEach((x, i) => { x.position = i; }); plTouch(p); return Promise.resolve(rows.map((r) => r.id)); } if (cmd === "playlist_set_order") { const p = plOf(args); if (!p) return Promise.reject(new Error("playlist_set_order: no such playlist")); const by = new Map((PL_ITEMS[p.id] || []).map((x) => [x.id, x])); const next = (args?.orderedIds || []).map(Number).map((id) => by.get(id)).filter(Boolean); for (const x of PL_ITEMS[p.id] || []) if (!next.includes(x)) next.push(x); PL_ITEMS[p.id] = next; next.forEach((x, i) => { x.position = i; }); plTouch(p); return Promise.resolve(); } if (cmd === "playlist_relink_item") { const p = plOf(args); const it = p ? (PL_ITEMS[p.id] || []).find((x) => x.id === Number(args?.itemId)) : null; if (!it) return Promise.reject(new Error("playlist_relink_item: no such row")); if (args.path != null) { it.path = args.path; it.kind = "local"; it.missing = false; } if (args.artPath != null) it.art_path = args.artPath; if (args.durationSecs != null) it.duration_secs = args.durationSecs; if (args.title != null) it.title = args.title; if (args.artist != null) it.artist = args.artist; if (args.album != null) it.album = args.album; plTouch(p); return Promise.resolve(); } return undefined; } /* ── the import doors: a pasted tracklist, a link, a YouTube set — a draft in the backend's shape (ImportDraft / DraftEntry, resolved against the mock library), then commit makes the playlist ── */ function impEntry(ordinal, artist, title, raw, start_ms = null) { const own = albums.find((a) => a.artist.toLowerCase() === artist.toLowerCase()); const tr = own ? tracksFor(own.id).find((t) => t.title.toLowerCase() === title.toLowerCase()) : null; const weak = !artist || !title || title.toLowerCase() === "id"; return { ordinal, title, artist, album: tr ? own.title : null, duration_secs: tr ? tr.duration_secs : (weak ? null : 200 + ((ordinal * 37) % 180)), start_ms, raw, resolution: tr ? "local" : weak ? "unresolved" : "stream", matched_by: tr ? "tags" : null, path: tr ? tr.path : null, art_path: tr ? own.art_path : null, track_id: null, art_url: null, isrc: null, }; } function impParse(text) { const out = []; for (const raw of String(text || "").split(/\r?\n/)) { const line = raw.trim(); if (!line) continue; const m = /^(?:(\d+):(\d\d)(?::(\d\d))?\s+)?(.*?)\s+[-–]\s+(.*)$/.exec(line); const start_ms = m && m[1] != null ? ((m[3] != null ? (+m[1] * 3600 + +m[2] * 60 + +m[3]) : (+m[1] * 60 + +m[2])) * 1000) : null; out.push(impEntry(out.length, m ? m[4].trim() : "", m ? m[5].trim() : line, raw, start_ms)); } return out; } function impDraft(entries, source_name, format, name = null, extra = {}) { return { source_name, format, name, description: null, kind: entries.some((e) => e.start_ms != null) ? "setlist" : "manual", entries, warnings: [], resolved_count: entries.filter((e) => e.resolution === "local").length, stream_count: entries.filter((e) => e.resolution === "stream").length, unresolved_count: entries.filter((e) => e.resolution === "unresolved").length, cover_url: null, ...extra, }; } function mockImport(cmd, args) { if (cmd === "playlist_import_text") { const entries = impParse(args?.text); if (!entries.length) return Promise.reject(new Error("Nothing in that text reads as a track.")); return new Promise((r) => setTimeout(() => r(impDraft(entries, "Pasted tracklist", "text")), 400)); } if (cmd === "playlist_import_link" || cmd === "playlist_import_youtube") { const url = String(args?.url || ""); if (!/^https?:\/\//.test(url)) return Promise.reject(new Error("That is not a link.")); const set = cmd === "playlist_import_youtube"; const format = set ? "youtube" : /spotify/.test(url) ? "spotify" : /apple/.test(url) ? "apple-music" : /tidal/.test(url) ? "tidal" : /deezer/.test(url) ? "deezer" : /youtu/.test(url) ? "youtube-playlist" : null; if (!format) return Promise.reject(new Error("That link is not a playlist SoulStream can read.")); const lines = set ? ["00:00 Daft Punk - One More Time", "05:20 Daft Punk - Aerodynamic", "09:00 ID - ID", "12:10 Aphex Twin - Track 1"] : ["Daft Punk - One More Time", "Daft Punk - Aerodynamic", "Daft Punk - Digital Love", "Aphex Twin - Track 1", "ID - ID"]; const entries = impParse(lines.join("\n")); return new Promise((r) => { let done = 0; const total = entries.length; const tick = setInterval(() => { done++; mockEmit("playlist:link-progress", { done, total, waiting: false }); if (done >= total) { clearInterval(tick); setTimeout(() => r(impDraft(entries, set ? "Mock Channel — Boiler Room 2019" : "Mock Curator — Late Night Mix", format, set ? "Boiler Room 2019 (live)" : "Late Night Mix", { cover_url: svgArt(280, "M"), warnings: set ? ["Tracklist from the video's chapters."] : [] })), 200); } }, 180); }); } if (cmd === "playlist_import_track_links") { const links = String(args?.text || "").split(/\r?\n/).map((l) => l.trim()) .filter((l) => /^https?:\/\//.test(l) || /^spotify:track:/.test(l)); if (!links.length) return Promise.reject(new Error("Nothing to import in that paste.")); /* one row per link, in the paste's order — the lines a real lookup would have read off each track's own public page. A page each is slow, and the pace here is slow on purpose: the walk's still takes long enough to miss a wait card that closes inside a second. */ const named = ["Aphex Twin - Track 1", "Daft Punk - One More Time", "Bicep - Glue", "Four Tet - Track 2"]; const entries = impParse(links.map((_, i) => named[i % named.length]).join("\n")); return new Promise((r) => { let done = 0; const tick = setInterval(() => { done++; mockEmit("playlist:link-progress", { done, total: links.length, waiting: false }); if (done >= links.length) { clearInterval(tick); setTimeout(() => r(impDraft(entries, args?.name || "Pasted track links", "links", args?.name ?? null, { warnings: [entries.length + " track" + (entries.length === 1 ? "" : "s") + " read from " + links.length + " pasted link" + (links.length === 1 ? "" : "s") + "."], })), 250); } }, 600); }); } if (cmd === "playlist_import_folder") { const root = String(args?.path || ""); if (!root) return Promise.resolve(null); /* the desktop's dialog, dismissed */ const name = root.split(/[\\/]/).filter(Boolean).pop() || "Folder"; /* four files off a record the library already holds, as the backend's walk hands them over: every row local, matched by path */ const own = albums[0]; const entries = tracksFor(own.id).slice(0, 4).map((t, i) => ({ ordinal: i, title: t.title, artist: own.artist, album: own.title, duration_secs: t.duration_secs, start_ms: null, raw: t.path, resolution: "local", matched_by: "path", path: t.path, art_path: own.art_path, track_id: null, art_url: null, isrc: null, })); return new Promise((r) => setTimeout(() => r(impDraft(entries, name, "folder", name)), 500)); } if (cmd === "playlist_import_commit") { const name = String(args?.name || "").trim(); if (!name) return Promise.reject(new Error("The playlist needs a name")); const p = { id: ++plSeq, name, track_count: 0, description: args?.description ?? null, kind: args?.kind === "setlist" ? "setlist" : "manual", pinned: false, cover_path: args?.coverUrl ? "mock://cover" : null, created_at: Math.floor(Date.now() / 1000), updated_at: 0, revision: 0, duration_secs: 0, unresolved_count: 0 }; PL.push(p); PL_ITEMS[p.id] = (args?.entries || []).map((e, i) => ({ id: ++plItemSeq, position: i, title: e.title, artist: e.artist, album: e.album ?? null, duration_secs: e.duration_secs ?? null, path: e.path ?? null, art_path: e.art_path ?? null, kind: e.path ? "local" : e.resolution === "unresolved" ? "unresolved" : "stream", source_line: e.raw ?? null, start_ms: e.start_ms ?? null, note: null, track_id: e.track_id != null ? String(e.track_id) : null, art_url: e.art_url ?? null, missing: false, })); plTouch(p); return Promise.resolve(p.id); } if (cmd === "catalog_search_tracks") { const words = String(args?.query || "").toLowerCase().replace(/\b(artist|track|album):/g, " ").replace(/["']/g, " ").split(/\s+/).filter(Boolean); const hits = CAT_TRACKS.filter((t) => { const hay = (t.artist + " " + t.title + " " + t.album).toLowerCase(); return words.every((w) => hay.includes(w)); }); return new Promise((r) => setTimeout(() => r(hits.slice(0, args?.limit || 10)), 90)); } if (cmd === "catalog_track_by_isrc") return Promise.resolve(null); if (cmd === "catalog_artist_track_candidates") return Promise.resolve([]); return undefined; } const enabled = (() => { try { return localStorage.getItem("mockLibrary") === "1"; } catch { return false; } })(); /** Returns a Promise for mocked commands, or undefined to pass through. */ export function mockInvoke(cmd, args) { if (!enabled) return undefined; if (cmd === "app_status") return Promise.resolve({ version: "mock", connected_as: null }); if (cmd === "library_albums") return Promise.resolve(albums); if (cmd === "library_album_stats") return Promise.resolve(albums.map(statsFor)); if (cmd === "recent_albums") return Promise.resolve(albums.slice(0, 3).map((a, i) => ({ kind: "library", album_id: a.id, title: a.title, artist: a.artist, art_path: a.art_path, last_played_at: Math.floor(Date.now() / 1000) - 3600 * (i + 1) }))); if (cmd === "album_tracks") return new Promise((r) => setTimeout(() => r(tracksFor(args.albumId)), 60)); if (cmd === "album_art") { /* not a path: the real one fails to open it, which is why the art gate has to answer a data: URL itself */ if (String(args.path || "").startsWith("data:")) return Promise.reject(new Error("album_art: not a path")); const m = /(\d+)$/.exec(args.path || ""); const num = m ? +m[1] : 0; return Promise.resolve(svgArt((num * 47) % 360, String.fromCharCode(65 + (num % 26)))); } { const pl = mockPlaylists(cmd, args) ?? mockImport(cmd, args); if (pl) return pl; } // The track index, as the library crate shapes it (TrackIndexRow) — the // phone's Tracks tab and its library search read these. if (cmd === "library_track_index" || cmd === "library_track_search") { const rows = albums.flatMap((a) => tracksFor(a.id).map((t) => ({ title: t.title, artist: a.artist, album: a.title, duration_secs: t.duration_secs, path: t.path, art_path: a.art_path, }))); if (cmd === "library_track_index") return Promise.resolve(rows); const words = String(args?.query || "").toLowerCase().split(/\s+/).filter(Boolean); return Promise.resolve(words.length ? rows.filter((r) => { const hay = (r.artist + r.title + r.album).toLowerCase(); return words.every((w) => hay.includes(w)); }).slice(0, 40) : []); } const r = mockCatalog(cmd, args) ?? mockNet(cmd, args); if (r === undefined) console.warn("[mock] no mock for " + cmd + " — the real IPC is not here"); return r; } /* ── the fake network ─────────────────────────────────────────────────── A Soulseek behind the phone's screens: a stored account that signs in at boot, a room list, rooms with a few lines in them, a search answered by five peers, and a stream that arrives over twelve seconds with its waveform growing — enough to walk Search, Community and the rising lane in a plain browser. Events go out over the bus below; the harness can fire its own through window.__ssMock.emit. */ const bus = new Map(); /** Returns a Promise for a mocked page, or undefined to pass through. */ export function mockListen(event, cb) { if (!enabled) return undefined; let set = bus.get(event); if (!set) { set = new Set(); bus.set(event, set); } set.add(cb); return Promise.resolve(() => { set.delete(cb); }); } export function mockEmit(event, payload) { for (const cb of bus.get(event) || []) { try { cb({ event, payload }); } catch (e) { console.error(e); } } } const later = (ms, fn) => setTimeout(fn, ms); if (enabled && typeof window !== "undefined") window.__ssMock = { emit: mockEmit }; const MOCK_USER = "mockuser"; const ROOMS = [["Lounge", 412], ["Electronic", 98], ["Vinyl Rips", 37], ["Jazz", 61], ["Lossless Only", 23], ...Array.from({ length: 90 }, (_, i) => ["Room " + (i + 1), 90 - i])]; const PEOPLE = ["ada", "bo", "cy", "dee"]; const LINES = ["anyone got the 2024 remaster?", "the vinyl rip is warmer imo", "that folder is complete btw", "brb"]; /* one person already named, so a cold read of the list has something in it */ const CONTACTS = [{ username: "dusty_crates", petname: "Dusty the digger", group: "Traders", note: "always has the vinyl rips", trusted: true, favorite: false }]; const PEERS = [ { username: "hifi_hank", slot: true, speed: 2400, queue: 0, ext: "flac", depth: 24, rate: 96000 }, { username: "dusty_crates", slot: true, speed: 900, queue: 0, ext: "flac", depth: 16, rate: 44100 }, { username: "bo", slot: false, speed: 1500, queue: 3, ext: "flac", depth: 16, rate: 44100 }, { username: "mp3_marge", slot: true, speed: 500, queue: 0, ext: "mp3", bitrate: 320 }, { username: "slowpoke", slot: false, speed: 60, queue: 12, ext: "mp3", bitrate: 192 }, ]; /* a peer's own card, and the comparison overlap.rs keeps per username */ const MOCK_PIC = "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR42mOQz+7HihiGlgQAV11GQceAU4sAAAAASUVORK5CYII="; const MOCK_ABOUT = "Ripping my own vinyl since 2004. FLAC only, folders named Artist - Year - Album. Ask before you queue a whole discography."; const MOCK_LIKES = ["french house", "vinyl rips", "detroit techno", "boards of canada"]; const MOCK_HATES = ["transcodes", "128 kbps"]; const OVERLAP_COMMON = [["Daft Punk", "Discovery"], ["Daft Punk", "Homework"], ["Caribou", "Swim"]]; const OVERLAP_GAPS = [ ["Boards of Canada", "Geogaddi"], ["Aphex Twin", "Drukqs"], ["Daft Punk", "Alive 2007"], ["Burial", "Untrue"], ["Four Tet", "Rounds"], ["Autechre", "Amber"], ]; const overlaps = new Map(); const pairsOf = (list) => list.map(([artist, album]) => ({ artist, album })); const TITLES = ["One More Time", "Aerodynamic", "Digital Love", "Harder, Better, Faster, Stronger", "Crescendolls", "Nightvision", "Superheroes", "High Life", "Something About Us", "Voyager", "Veridis Quo", "Short Circuit", "Face to Face", "Too Long"]; function peerResponse(token, peer, query) { const artist = /daft/i.test(query) ? "Daft Punk" : (query.split(/\s+/)[0] || "Various"); const album = /discovery/i.test(query) ? "Discovery" : "Homework"; const folder = "@@shared\\Music\\" + artist + "\\" + artist + " - 2001 - " + album + (peer.ext === "flac" ? " [FLAC]" : " [MP3 " + peer.bitrate + "]"); const files = TITLES.map((t, i) => { const name = String(i + 1).padStart(2, "0") + " - " + t + "." + peer.ext; const duration = 200 + ((i * 37) % 180); const size = peer.ext === "flac" ? Math.round(duration * (peer.depth === 24 ? 320000 : 110000)) : Math.round(duration * peer.bitrate * 125); return { path: folder + "\\" + name, name, folder, size, extension: peer.ext, bitrate: peer.bitrate ?? null, duration, sample_rate: peer.rate ?? 44100, bit_depth: peer.depth ?? null, vbr: false }; }); return { token, username: peer.username, has_free_upload_slot: peer.slot, upload_speed: peer.speed, queue_length: peer.queue, files }; } /* the catalog: one artist, two records, the songs of one of them */ const CAT_ARTIST = { id: 27, name: "Daft Punk", picture: svgArt(210, "DP"), album_count: 4, fans: 4200000 }; const CAT_NAMESAKE = { id: 28, name: "Daft Punk", picture: "https://cdn-images.dzcdn.net/images/artist/d41d8cd98f00b204e9800998ecf8427e/1000x1000-000000-80-0-0.jpg", album_count: 1, fans: 120 }; const CAT_ALBUMS = [ { id: 302127, title: "Discovery", artist: "Daft Punk", cover: null, release_date: "2001-03-12", record_type: "album", track_count: 14, edition_group: 302127, is_soundtrack: false }, { id: 302128, title: "Homework", artist: "Daft Punk", cover: null, release_date: "1997-01-20", record_type: "album", track_count: 16, edition_group: null, is_soundtrack: false }, ]; const CAT_TRACKS = TITLES.map((t, i) => ({ id: 3135550 + i, title: t, artist: "Daft Punk", artist_id: 27, rank: 900000 - i * 1000, album: "Discovery", album_id: 302127, cover: null, duration_secs: 200 + ((i * 37) % 180), preview: null, track_no: i + 1, })); const CAT_DISCO = [ ...CAT_ALBUMS, { id: 302129, title: "Discovery (Deluxe Edition)", artist: "Daft Punk", cover: null, release_date: "2001-03-12", record_type: "album", track_count: 16, edition_group: 302127, is_soundtrack: false }, { id: 302130, title: "Alive 1997", artist: "Daft Punk", cover: null, release_date: "2001-10-01", record_type: "album", track_count: 1, edition_group: null, is_soundtrack: false }, { id: 302131, title: "One More Time", artist: "Daft Punk", cover: null, release_date: "2000-11-13", record_type: "single", track_count: 2, edition_group: null, is_soundtrack: false }, { id: 302132, title: "Musique Vol. 1 1993–2005", artist: "Daft Punk", cover: null, release_date: "2006-04-03", record_type: "compile", track_count: 15, edition_group: null, is_soundtrack: false }, { id: 302133, title: "TRON: Legacy (Original Motion Picture Soundtrack)", artist: "Daft Punk", cover: null, release_date: "2010-12-03", record_type: "album", track_count: 22, edition_group: null, is_soundtrack: true }, { id: 302134, title: "Human After All", artist: "Daft Punk", cover: null, release_date: "2005-03-14", record_type: "album", track_count: 10, edition_group: null, is_soundtrack: false }, { id: 302135, title: "Random Access Memories", artist: "Daft Punk", cover: null, release_date: "2013-05-17", record_type: "album", track_count: 13, edition_group: null, is_soundtrack: false }, ]; /* the follows feed needs covers and dates near today */ const dayIso = (offset) => new Date(Date.now() + offset * 86400000).toISOString().slice(0, 10); for (const a of CAT_DISCO) a.cover = svgArt((a.id * 31) % 360, a.title[0]); CAT_DISCO.push( { id: 302136, title: "Infinity Repeating", artist: "Daft Punk", cover: svgArt(300, "I"), release_date: dayIso(-9), record_type: "single", track_count: 2, edition_group: null, is_soundtrack: false }, { id: 302137, title: "Alive 2027", artist: "Daft Punk", cover: svgArt(120, "A"), release_date: dayIso(21), record_type: "album", track_count: 12, edition_group: null, is_soundtrack: false }, ); const NEWS = [ { title: "Daft Punk announce Alive 2027 — a live record from the reunion shows", url: "https://example.com/news/1", source: "Pitchfork", date: Math.floor(Date.now() / 1000) - 3600 * 5 }, { title: "Infinity Repeating gets a video", url: "https://example.com/news/2", source: "NME", date: Math.floor(Date.now() / 1000) - 86400 * 2 }, ]; const RELATED = [["Justice", 411], ["Air", 412], ["Kavinsky", 413], ["Chemical Brothers", 414]].map(([name, id], i) => ({ id, name, picture: i % 2 ? null : svgArt(40 + id, name[0]), album_count: 3, fans: 900000 - id * 100 })); /* the heroes chosen this session — pick_image → set_artist_hero */ const heroes = {}; let follows = []; function mockCatalog(cmd, args) { if (cmd === "catalog_artist") { const id = Number(args?.artistId); const a = id === CAT_ARTIST.id ? CAT_ARTIST : RELATED.find((r) => r.id === id); if (!a) return Promise.reject(new Error("catalog_artist: unknown artist")); const own = a === CAT_ARTIST; return new Promise((r) => setTimeout(() => r({ artist: a, banner: heroes[a.id] ?? null, banners: heroes[a.id] ? [heroes[a.id]] : [], banner_custom: !!heroes[a.id], logos: [], popular_tracks: own ? CAT_TRACKS.slice(0, 7).map((t, i) => ({ ...t, listeners: 2_400_000 - i * 310_000 })) : [], albums: own ? CAT_DISCO : [{ id: 900 + id, title: a.name + " Live", artist: a.name, cover: null, release_date: "2015-01-01", record_type: "album", track_count: 9, edition_group: null, is_soundtrack: false }], top_genre: "Electronic", socials: [], }), 220)); } if (cmd === "catalog_artist_socials") { const id = Number(args?.artistId); return new Promise((r) => setTimeout(() => r(id === CAT_ARTIST.id ? [{ kind: "website", url: "https://example.com" }, { kind: "wikipedia", url: "https://en.wikipedia.org/wiki/Daft_Punk" }] : []), 300)); } if (cmd === "home_warm") return Promise.resolve(); if (cmd === "home_artist_news") return new Promise((r) => setTimeout(() => r(Object.fromEntries((args?.artists || []).map((n) => [n, /daft/i.test(n) ? NEWS : []]))), 150)); if (cmd === "catalog_artist_identity") return Promise.resolve({ artist_id: Number(args?.artistId), albums: [], socials: [], type_hints: [], owned_album_ids: [] }); if (cmd === "catalog_artist_profile") return Promise.resolve({ listeners: 3_100_000, plays: 210_000_000, tags: ["electronic", "house", "french house", "dance"] }); if (cmd === "catalog_artist_bio") return Promise.resolve("Daft Punk were a French electronic music duo formed in Paris in 1993 by Guy-Manuel de Homem-Christo and Thomas Bangalter. They achieved popularity in the late 1990s as part of the French house movement."); if (cmd === "catalog_related") return Promise.resolve(RELATED); /* Popular's own lane: ranked, with counts — and for the artists whose page gives none (the related ones), the only Popular there is */ if (cmd === "catalog_artist_popular") { const id = Number(args?.artistId); const a = id === CAT_ARTIST.id ? CAT_ARTIST : RELATED.find((r) => r.id === id); if (!a) return Promise.resolve([]); const rows = id === CAT_ARTIST.id ? CAT_TRACKS.slice(0, 7) : TITLES.slice(0, 4).map((t, i) => ({ id: 4200000 + id * 10 + i, title: t, artist: a.name, artist_id: id, rank: 700000 - i * 1000, album: a.name + " Live", album_id: 900 + id, cover: null, duration_secs: 210 + i * 13, preview: null, track_no: i + 1, })); return new Promise((r) => setTimeout(() => r(rows.map((t, i) => ({ ...t, listeners: 1_900_000 - i * 240_000 }))), 260)); } /* the deferred pass: a count per row, by that row's own artist id */ if (cmd === "catalog_track_listeners") { const rows = args?.tracks || []; return new Promise((r) => setTimeout(() => r(rows.map((t, i) => (t.artistId ? 640_000 - i * 20_000 : null))), 260)); } if (cmd === "set_artist_hero") { const id = Number(args?.artistId); heroes[id] = svgArt(180, "H"); return Promise.resolve({ data_url: heroes[id], width: 3840, height: 2160 }); } if (cmd === "clear_artist_hero") { const id = Number(args?.artistId); const had = id in heroes; delete heroes[id]; return Promise.resolve(had); } if (cmd === "artist_play_stats") return Promise.resolve({ seconds: 7380, last_played_at: Math.floor(Date.now() / 1000) - 86400, top_title: "Track 1", top_count: 12 }); if (cmd === "followed_artists") return Promise.resolve(follows.slice()); if (cmd === "follow_artist") { follows.push({ artist_id: args?.artistId, name: args?.name, portrait: args?.portrait ?? null, followed_at: Date.now() / 1000 }); later(30, () => mockEmit("soulseek:follows-changed", {})); return Promise.resolve(); } if (cmd === "unfollow_artist") { follows = follows.filter((f) => f.artist_id !== args?.artistId); later(30, () => mockEmit("soulseek:follows-changed", {})); return Promise.resolve(); } if (cmd === "catalog_search") { const q = String(args?.query || "").toLowerCase(); const hit = /daft|discovery|one more|punk/.test(q); return new Promise((r) => setTimeout(() => r(hit ? { artists: [CAT_ARTIST, CAT_NAMESAKE], albums: CAT_ALBUMS, tracks: CAT_TRACKS.slice(0, 8) } : { artists: [], albums: [], tracks: [] }), 120)); } if (cmd === "catalog_album") { const a = CAT_ALBUMS.find((x) => x.id === Number(args?.albumId)); if (!a) return Promise.reject(new Error("catalog_album: unknown album")); /* the last two rows are a guest's: the album's own artist anchor has no count for them, which is what the deferred pass is for */ return Promise.resolve({ album: a, tracks: a.id === 302127 ? CAT_TRACKS.map((t, i) => (i >= CAT_TRACKS.length - 2 ? { ...t, artist: "Daft Punk feat. Todd Edwards", artist_id: 411 } : { ...t, listeners: 2_400_000 - i * 150_000 })) : [], album_plays: a.id === 302127 ? 310_000_000 : null, album_listeners: a.id === 302127 ? 4_100_000 : null, totals_summed: false }); } if (cmd === "catalog_album_info") { const t = String(args?.title || ""); return new Promise((r) => setTimeout(() => r(/discovery/i.test(t) ? { description: "Discovery is the second studio album by the French electronic music duo Daft Punk, released on 12 March 2001. It marks a shift from the Chicago house of their debut to a house style more heavily inspired by disco, post-disco, garage house and synth-pop. The album's singles included the worldwide hit One More Time.", genre: "Electronic", label: "Virgin", source: "wikipedia" } : null), 150)); } if (cmd === "download_file") { const id = ++downloadSeq; const total = args?.size || 20e6; const name = String(args?.path || "").split("\\").pop(); const base = { id, username: args?.username, path: args?.path, name, queue_place: null, local_path: null, error: null }; later(200, () => mockEmit("soulseek:download", { ...base, state: "connecting", received: 0, total })); let got = 0; const step = setInterval(() => { got = Math.min(total, got + total / 8); mockEmit("soulseek:download", { ...base, state: got >= total ? "complete" : "transferring", received: Math.round(got), total, local_path: got >= total ? "C:\\mock\\library\\" + name : null }); if (got >= total) { clearInterval(step); mockEmit("soulseek:library-changed", {}); } }, 400 + (id % 3) * 120); return Promise.resolve(id); } if (cmd === "cancel_download") return Promise.resolve(); if (cmd === "rescan_library") return Promise.resolve(); if (cmd === "library_art_prewarm") return Promise.resolve(); if (cmd === "refresh_all_album_art") return new Promise((r) => setTimeout(() => r(140), 300)); if (cmd === "refresh_artist_art") return new Promise((r) => setTimeout(() => r(26), 300)); if (cmd === "catalog_art") return Promise.resolve(svgArt(200, "D")); if (cmd === "stream_cache_ready") return Promise.resolve(cacheReady.has(args?.path) || (xfer && xfer.path === args?.path && xfer.received >= xfer.total)); if (cmd === "preload_stream") { const path = args?.path; cacheSizes.set(path, args?.size || 0); if (!cacheReady.has(path) && !preloading.has(path)) { preloading.add(path); later(4000, () => { preloading.delete(path); cacheReady.add(path); }); } return Promise.resolve(); } if (cmd === "preload_cached_stream") return Promise.resolve(cacheReady.has(args?.path)); if (cmd === "excluded_search_phrases") return Promise.resolve([]); if (cmd === "copy_stream_to_folder") { /* the real command copies only what the stream cache already holds */ let saved = 0, missing = 0; for (const f of args?.files || []) { if (cacheReady.has(f.path)) saved++; else missing++; } return Promise.resolve([saved, missing]); } if (cmd === "play_row_count") return Promise.resolve(4210); if (cmd === "debug_stats") { /* the overlay's shape, camelCase as serde renames it; the numbers wander a little so the screen is seen to be live */ const t = Date.now() / 1000; return Promise.resolve({ cpuProcess: 7.5 + Math.sin(t), cpuSystem: 22 + Math.cos(t) * 3, gpuPercent: null, gpuAppPercent: null, memWorkingSet: 431_000_000, memPrivate: 0, memSystemUsed: 5_400_000_000, memSystemTotal: 7_900_000_000, netRxBps: 1_250_000, netTxBps: 96_000, processCount: 3, uptimeSecs: 742, dlReceived: xfer ? Math.round(xfer.received) : null, dlTotal: xfer ? xfer.total : null, dlDone: xfer ? xfer.received >= xfer.total : null, normalizationEnabled: mockSettings.volume_normalization !== false, targetLufs: -14, loudness: { lufs: -9.8, peak: 0.98, gainDb: -4.2, cached: true, analyzing: false, source: "cache", headStartDb: 0, prefixGainDb: null }, mainFadeGain: 1, normNowDb: -4.2, normTargetDb: -4.2, previewVolume: null, previewNormDb: null, preloadDl: [{ username: "dusty_crates", path: "Music\\Daft Punk\\Discovery\\02 Aerodynamic.flac", state: "transferring", place: null, received: 5_000_000, total: 12_000_000, speedBps: 850_000 }], }); } if (cmd === "get_settings") return Promise.resolve({ ...mockSettings, stream_source_prefs: { ...mockSettings.stream_source_prefs }, download_source_prefs: { ...mockSettings.download_source_prefs } }); if (cmd === "set_settings") { /* the command's arguments are camelCase; the config's fields are snake_case */ for (const [k, v] of Object.entries(args || {})) mockSettings[k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase())] = v; return Promise.resolve(); } if (cmd === "plugin:storage|pick_folder") return Promise.resolve({ uri: "content://mock/tree/primary%3AMusic", path: "C:\\mock\\Music" }); if (cmd === "plugin:storage|all_files_access") return Promise.resolve({ granted: true }); if (cmd === "plugin:storage|request_all_files_access") return Promise.resolve(); if (cmd === "plugin:storage|pick_image") return Promise.resolve({ path: "C:\\mock\\photo.jpg" }); if (cmd === "plugin:share|pending_share") { /* the harness shares by setting window.__ssMock.share and waking the page */ const t = (typeof window !== "undefined" && window.__ssMock?.share) || null; if (t && window.__ssMock) window.__ssMock.share = null; return Promise.resolve({ text: t }); } return undefined; } let token = 0; let downloadSeq = 0; const mockSettings = { download_dir: "C:\\mock\\Library", volume_normalization: true, library_layout: "{artist}\\{album}", shared_folders: [], upload_slots: 2, listen_port: null, share_files: 1234, word_timing: false, replace_embedded_art: true, embed_art_max_edge: 1500, write_folder_cover: true, stream_keep_forever: false, stream_retention_days: 30, stream_cache_max_mb: 2048, stream_source_prefs: { max: "any", min: "any", priority: "quality" }, download_source_prefs: { max: "any", min: "any", priority: "quality" }, }; const cacheReady = new Set(); // paths a preload or a finished stream has fully fetched const cacheSizes = new Map(); // path → the file's size, for stream_cache_len const preloading = new Set(); let xfer = null; // the one fake transfer: { received, total, timer } let posTimer = null; // the fake engine's position ticks /* the interests this fake account has declared, and the server's match on them — a declaration moves both, the way the real server does */ const MY_INTERESTS = { likes: ["Daft Punk", "french house", "Warp Records"], hates: ["loudness war"] }; function mockNet(cmd, args) { if (cmd === "get_credentials") return Promise.resolve({ username: MOCK_USER, has_password: true }); if (cmd === "connect_stored" || cmd === "connect_and_login") { later(400, () => mockEmit("soulseek:state", { kind: "connected", username: MOCK_USER })); return Promise.resolve(); } if (cmd === "disconnect") { later(50, () => mockEmit("soulseek:state", { kind: "disconnected" })); return Promise.resolve(); } if (cmd === "forget_credentials") return Promise.resolve(); if (cmd === "buddy_list") return Promise.resolve(["ada", "cy"]); if (cmd === "buddy_add") return Promise.resolve(["ada", "cy", args?.username].filter(Boolean)); if (cmd === "buddy_remove") return Promise.resolve(["ada", "cy"].filter((u) => u !== args?.username)); if (cmd === "contacts_meta_list") return Promise.resolve(CONTACTS.slice()); if (cmd === "contact_meta_set") { /* the backend's own rule: an entry with nothing set is dropped */ const meta = args?.meta || {}; const txt = (v) => (typeof v === "string" && v.trim() ? v.trim() : null); const i = CONTACTS.findIndex((c) => c.username === meta.username); if (i >= 0) CONTACTS.splice(i, 1); const kept = { username: meta.username, petname: txt(meta.petname), group: txt(meta.group), note: txt(meta.note), trusted: !!meta.trusted, favorite: !!meta.favorite }; if (kept.petname || kept.group || kept.note || kept.trusted || kept.favorite) CONTACTS.push(kept); return Promise.resolve(CONTACTS.slice()); } if (cmd === "slsk_room_list") { later(200, () => mockEmit("soulseek:room-list", ROOMS.map(([name, users]) => ({ name, users })))); return Promise.resolve(); } if (cmd === "slsk_join_room") { const room = args?.room; later(250, () => mockEmit("soulseek:room-joined", { room, users: [...PEOPLE, MOCK_USER].map((username) => ({ username })) })); LINES.forEach((message, i) => later(400 + i * 350, () => mockEmit("soulseek:room-message", { room, username: PEOPLE[i % PEOPLE.length], message, ts: Math.floor(Date.now() / 1000) }))); return Promise.resolve(); } if (cmd === "slsk_leave_room") { later(50, () => mockEmit("soulseek:room-left", { room: args?.room })); return Promise.resolve(); } if (cmd === "slsk_say_room") { later(120, () => mockEmit("soulseek:room-message", { room: args?.room, username: MOCK_USER, message: args?.message, ts: Math.floor(Date.now() / 1000) })); return Promise.resolve(); } if (cmd === "slsk_message_user") { later(900, () => mockEmit("soulseek:private-message", { username: args?.username, message: "sure, sending", timestamp: Math.floor(Date.now() / 1000) })); return Promise.resolve(); } if (cmd === "slsk_browse_user") { /* a share: two records and a folder of scans, in the browse DTO's shape */ const dir = (album, ext, bitrate) => { const folder = "@@shared\\Music\\Daft Punk\\Daft Punk - 2001 - " + album + (ext === "flac" ? " [FLAC]" : " [MP3 " + bitrate + "]"); return { name: folder, files: TITLES.slice(0, album === "Discovery" ? 14 : 8).map((t, i) => { const name = String(i + 1).padStart(2, "0") + " - " + t + "." + ext; const duration = 200 + ((i * 37) % 180); return { name, path: folder + "\\" + name, size: ext === "flac" ? duration * 110000 : duration * bitrate * 125, ext, bitrate: ext === "flac" ? null : bitrate, duration }; }), }; }; return new Promise((r) => setTimeout(() => r([ dir("Discovery", "flac"), dir("Homework", "mp3", 320), { name: "@@shared\\Music\\Scans", files: [{ name: "front.jpg", path: "@@shared\\Music\\Scans\\front.jpg", size: 1200000, ext: "jpg", bitrate: null, duration: null }] }, ]), 600)); } if (cmd === "slsk_user_info") { const u = String(args?.username || ""); const peer = PEERS.find((x) => x.username === u); return new Promise((r) => setTimeout(() => r({ username: u, description: MOCK_ABOUT, picture_png_base64: MOCK_PIC, picture_note: null, total_uploads: 18422, queue_size: peer ? peer.queue : 0, has_free_slot: peer ? peer.slot : true, }), 450)); } if (cmd === "slsk_user_interests") { return new Promise((r) => setTimeout(() => r({ likes: MOCK_LIKES.slice(), hates: MOCK_HATES.slice() }), 400)); } if (cmd === "overlap_list") return Promise.resolve([...overlaps.values()]); if (cmd === "overlap_compute") { /* the real one browses their whole share before it compares: the wait is the point */ const u = String(args?.username || ""); const o = { username: u, computed_at: Math.floor(Date.now() / 1000), folders: 812, audio_files: 9134, their_albums: 74, albums_in_common: pairsOf(OVERLAP_COMMON), albums_in_common_count: OVERLAP_COMMON.length, artists_in_common: ["Daft Punk", "Caribou"], artists_in_common_count: 2, gaps: pairsOf(OVERLAP_GAPS), gaps_count: OVERLAP_GAPS.length, score: OVERLAP_COMMON.length / 74, }; return new Promise((r) => setTimeout(() => { overlaps.set(u.toLowerCase(), o); r(o); }, 1100)); } if (cmd === "overlap_forget") { overlaps.delete(String(args?.username || "").toLowerCase()); return Promise.resolve(); } if (cmd === "start_search") { const t = ++token; const q = String(args?.query || ""); later(300, () => mockEmit("soulseek:search-results", [peerResponse(t, PEERS[1], q)])); later(700, () => mockEmit("soulseek:search-results", [peerResponse(t, PEERS[0], q), peerResponse(t, PEERS[3], q)])); later(1300, () => mockEmit("soulseek:search-results", [peerResponse(t, PEERS[2], q), peerResponse(t, PEERS[4], q)])); return Promise.resolve(t); } if (cmd === "stop_search") return Promise.resolve(); if (cmd === "slsk_interests_mine") return Promise.resolve({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() }); if (cmd === "slsk_interest_set") { const thing = String(args?.thing || "").trim(); if (!thing) return Promise.reject(new Error("nothing to declare")); const list = args?.like ? MY_INTERESTS.likes : MY_INTERESTS.hates; const at = list.findIndex((t) => t.toLowerCase() === thing.toLowerCase()); if (args?.on && at < 0) list.push(thing); if (!args?.on && at >= 0) list.splice(at, 1); return new Promise((r) => setTimeout(() => r({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() }), 200)); } if (cmd === "slsk_similar_users") { /* the server ranks on the likes we declared: one more name per like */ const pool = ["hifi_hank", "dusty_crates", "mp3_marge", "ada", "cy", "bo"]; const n = MY_INTERESTS.likes.length; return new Promise((r) => setTimeout(() => r(pool.slice(0, Math.min(pool.length, n + 1)).map((username, i) => ({ username, rating: Math.max(1, n - i) }))), 300)); } if (cmd === "player_state") return Promise.resolve({ status: "idle", position: 0 }); if (cmd === "preload_local") return Promise.resolve(); if (cmd === "waveform") { const n = args?.buckets || 900; return new Promise((r) => setTimeout(() => r(Array.from({ length: n }, (_, i) => 0.2 + 0.75 * Math.abs(Math.sin(i * 0.23) * Math.cos(i * 0.017)))), 120)); } if (cmd === "lyrics_get") return new Promise((r) => setTimeout(() => r(mockLyrics(args?.title)), 150)); if (cmd === "lyrics_search") return new Promise((r) => setTimeout(() => r([ { id: 901, title: args?.title || "Track", artist: args?.artist || "?", album: "The Mock Sessions", synced: true, instrumental: false, durationSecs: 214 }, { id: 902, title: (args?.title || "Track") + " (Live)", artist: args?.artist || "?", album: null, synced: false, instrumental: false, durationSecs: 233 }, ]), 200)); if (cmd === "lyrics_select") return Promise.resolve({ ...mockLyrics(args?.title), source: "lrclib #" + args?.candidateId }); if (cmd === "lyrics_set_offset") return Promise.resolve(); if (cmd === "lyrics_save_text") { const lines = String(args?.text || "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean); const timed = lines.map((l) => { const m = /^\[(\d+):(\d\d(?:\.\d+)?)\]\s*(.*)$/.exec(l); return m ? { time_ms: Math.round((+m[1] * 60 + +m[2]) * 1000), text: m[3] } : null; }); const synced = timed.every(Boolean); return Promise.resolve({ status: synced ? "synced" : "plain", source: "pasted", synced: synced ? timed : [], plain: synced ? null : lines.join("\n"), offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false }); } if (cmd === "lyrics_set_auto_sync") { autoSyncOff = !args?.enabled; return Promise.resolve(); } if (cmd === "lyrics_save_lines") { /* the Rust drops the empty lines and sorts by time — a half-tapped set is stored as the lines that HAVE one */ const lines = (args?.lines || []).filter((l) => String(l?.text || "").trim()) .map((l) => ({ time_ms: l.time_ms, text: l.text })).sort((a, b) => a.time_ms - b.time_ms); if (!lines.length) return Promise.reject(new Error("Nothing to save")); /* the Rust's payload_from does NOT carry the entry's auto-sync flag back from a write — false here, faithfully, so the walk notices if the store ever applies a save over the switch */ return Promise.resolve({ status: "synced", source: "user", synced: lines, plain: null, offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false }); } /* the vocal-reduced copy: a render takes a beat, and it is a new file */ if (cmd === "lyrics_prepare_instrumental") return new Promise((r) => setTimeout(() => r({ path: String(args?.path || "") + ".instrumental.wav", cached: false, durationSecs: 214 }), 400)); if (cmd === "lyrics_clear") return Promise.resolve(); if (cmd === "clear_play_history") return Promise.resolve(); if (cmd === "refresh_album_art") return new Promise((r) => setTimeout(() => r("C:\\mock\\cover.jpg"), 200)); if (cmd === "play_local") { /* a file: the engine reports playing after a beat, then the position from where it was asked to start */ if (xfer) { clearInterval(xfer.timer); xfer = null; } clearInterval(posTimer); const duration = args?.duration || 240; let secs = Number(args?.startSecs) || 0; later(300, () => { mockEmit("soulseek:player", { kind: "playing" }); posTimer = setInterval(() => { secs += 0.5; mockEmit("soulseek:player", { kind: "position", secs, duration }); }, 500); }); return Promise.resolve(); } if (cmd === "play_stream") { if (xfer) clearInterval(xfer.timer); clearInterval(posTimer); const total = args?.size || 30e6; const duration = args?.duration || 240; xfer = { received: 0, total, timer: null, path: args?.path }; cacheSizes.set(args?.path, total); xfer.timer = setInterval(() => { xfer.received = Math.min(total, xfer.received + total / 30); const done = xfer.received >= total; mockEmit("soulseek:stream", { received: Math.round(xfer.received), total, done, failed: null }); if (done) { clearInterval(xfer.timer); cacheReady.add(xfer.path); } }, 400); let secs = 0; later(1200, () => { mockEmit("soulseek:player", { kind: "playing" }); posTimer = setInterval(() => { secs += 0.5; mockEmit("soulseek:player", { kind: "position", secs, duration }); }, 500); }); return Promise.resolve(); } if (cmd === "stream_waveform") { const coverage = xfer ? xfer.received / xfer.total : 0; const n = Math.round((args?.buckets || 900) * coverage); const peaks = Array.from({ length: n }, (_, i) => 0.25 + 0.7 * Math.abs(Math.sin(i * 0.37) * Math.cos(i * 0.011))); return Promise.resolve({ coverage, peaks }); } if (cmd === "stream_cache_len") return Promise.resolve(cacheReady.has(args?.path) ? (cacheSizes.get(args.path) || 0) : (xfer && xfer.path === args?.path ? Math.round(xfer.received) : 0)); if (cmd === "save_to_library") { /* a byte-complete cache files at once; anything else goes to the download pipeline */ let organized = 0, queued = 0; for (const f of args?.files || []) { if (cacheReady.has(f.path)) { organized++; continue; } queued++; mockInvoke("download_file", { username: f.username, path: f.path, size: f.size }); } if (organized) later(30, () => mockEmit("soulseek:library-changed", {})); return Promise.resolve([organized, queued]); } if (cmd === "stream_embedded_art") return Promise.resolve(xfer && xfer.received > 200000 ? svgArt(200, "D") : null); if (cmd === "record_play") return Promise.resolve(1); if (cmd === "add_play_seconds") return Promise.resolve(); return undefined; }