A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4 171 lines 4.8 kB view raw
1import * as TID from "@atcute/tid"; 2import foundation from "~/common/foundation.js"; 3 4foundation.setup({ title: "V3.x Import | Diffuse" }); 5 6const main = /** @type {HTMLElement} */ (document.querySelector("main")); 7 8/** 9 * @import {PlaylistItem, Track} from "~/definitions/types.d.ts" 10 */ 11 12// Setup 13const favourites = await foundation.orchestrator.favourites(); 14const output = await foundation.orchestrator.output(); 15 16// Elements 17const fileInput = 18 /** @type {HTMLInputElement} */ (document.querySelector("#file")); 19const importFavouritesBtn = 20 /** @type {HTMLButtonElement} */ (document.querySelector( 21 "#import-favourites", 22 )); 23const importPlaylistItemsBtn = 24 /** @type {HTMLButtonElement} */ (document.querySelector( 25 "#import-playlist-items", 26 )); 27const statusEl = /** @type {HTMLElement} */ (document.querySelector("#status")); 28 29/** @type {Record<string, any> | null} */ 30let json = null; 31 32/** 33 * Show a status message. 34 * @param {string} message 35 * @param {"success" | "error"} type 36 */ 37function showStatus(message, type) { 38 statusEl.textContent = message; 39 statusEl.className = `status status--${type}`; 40 statusEl.hidden = false; 41} 42 43// Parse file on selection 44fileInput.onchange = async () => { 45 const file = fileInput.files?.[0]; 46 47 json = null; 48 statusEl.hidden = true; 49 importFavouritesBtn.disabled = true; 50 importPlaylistItemsBtn.disabled = true; 51 52 if (!file) return; 53 54 try { 55 json = JSON.parse(await file.text()); 56 } catch (err) { 57 console.error("Failed to parse JSON:", err); 58 showStatus( 59 `Failed to parse JSON: ${/** @type {Error} */ (err).message}`, 60 "error", 61 ); 62 return; 63 } 64 65 if (json?.favourites?.data?.length > 0) { 66 importFavouritesBtn.disabled = false; 67 } 68 69 if (json?.playlists?.data?.length > 0) { 70 importPlaylistItemsBtn.disabled = false; 71 } 72}; 73 74// Import favourites on button click 75importFavouritesBtn.onclick = async () => { 76 /** @type {any[]} */ 77 const items = json?.favourites?.data; 78 if (!items || items.length === 0) return; 79 80 try { 81 /** @type {Track[]} */ 82 const tracks = items.map((item) => ({ 83 $type: "sh.diffuse.output.track", 84 id: "", 85 uri: "", 86 tags: { 87 artist: item.artist ?? "", 88 title: item.title ?? "", 89 }, 90 })); 91 92 await favourites.include(tracks); 93 showStatus(`Imported ${tracks.length} favourite(s).`, "success"); 94 } catch (err) { 95 console.error("Import failed:", err); 96 showStatus(`Import failed: ${/** @type {Error} */ (err).message}`, "error"); 97 } 98}; 99 100// Import playlist items on button click 101importPlaylistItemsBtn.onclick = async () => { 102 /** @type {any[]} */ 103 const items = json?.playlists?.data; 104 if (!items || items.length === 0) return; 105 106 try { 107 const now = new Date().toISOString(); 108 109 const existingCol = output.playlistItems.collection(); 110 /** @type {any[]} */ 111 const existing = existingCol.state === "loaded" ? existingCol.data : []; 112 const existingPlaylistNames = new Set(existing.map((p) => p.playlist)); 113 114 const newPlaylistItems = items 115 .filter((item) => !existingPlaylistNames.has(item.name ?? "Untitled")) 116 .flatMap((item) => { 117 const playlistName = item.name ?? "Untitled"; 118 const isUnordered = !!item.collection; 119 120 /** @type {PlaylistItem[]} */ 121 const playlistItems = []; 122 123 /** @type {any[]} */ (item.tracks ?? []).forEach((track, index) => { 124 playlistItems.push({ 125 $type: "sh.diffuse.output.playlistItem", 126 id: TID.now(), 127 playlist: playlistName, 128 positionedAfter: isUnordered 129 ? undefined 130 : index > 0 131 ? playlistItems[index - 1].id 132 : undefined, 133 criteria: [ 134 { 135 field: "tags.album", 136 value: track.album ?? "", 137 transformations: ["toLowerCase"], 138 }, 139 { 140 field: "tags.artist", 141 value: track.artist ?? "", 142 transformations: ["toLowerCase"], 143 }, 144 { 145 field: "tags.title", 146 value: track.title ?? "", 147 transformations: ["toLowerCase"], 148 }, 149 ], 150 createdAt: now, 151 updatedAt: now, 152 }); 153 }); 154 155 return playlistItems; 156 }); 157 158 await output.playlistItems.save([...existing, ...newPlaylistItems]); 159 const playlistCount = new Set(newPlaylistItems.map((p) => p.playlist)).size; 160 showStatus(`Imported ${playlistCount} playlist(s).`, "success"); 161 } catch (err) { 162 console.error("Import failed:", err); 163 showStatus(`Import failed: ${/** @type {Error} */ (err).message}`, "error"); 164 } 165}; 166 167//////////////////////////////////////////// 168// 🚀 169//////////////////////////////////////////// 170 171foundation.ready();