Something went wrong. Try again.
Experiment to rebuild Diffuse using web applets.
Something went wrong. Try again.
4.5 kB · 169 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170import { SubsonicAPI, type Child } from "subsonic-api";import * as URI from "uri-js";
import type { Consult, ConsultGrouping, GroupConsult, Track } from "@applets/core/types.d.ts";import { SCHEME } from "./constants.ts";import { autoTypeToTrackKind, buildURI, consultServer, createClient, groupTracksByServer, loadServers, parseURI, serverId, serversFromTracks,} from "./common.ts";import { provide, transfer } from "@scripts/common.ts";
////////////////////////////////////////////// TASKS////////////////////////////////////////////const actions = { consult, contextualize, groupConsult, list, resolve,};
const { tasks } = provide({ actions, tasks: actions });
export type Actions = typeof actions;export type Tasks = typeof tasks;
// Tasks
async function consult(fileUriOrScheme: string): Promise<Consult> { if (!fileUriOrScheme.includes(":")) return { supported: true, consult: "undetermined" };
const parsed = parseURI(fileUriOrScheme); if (!parsed) return { supported: true, consult: "undetermined" };
const consult = await consultServer(parsed.server); return { supported: true, consult };}
async function contextualize(tracks: Track[]) { return serversFromTracks(tracks);}
async function groupConsult(tracks: Track[]): Promise<GroupConsult> { const groups = groupTracksByServer(tracks);
const promises = Object.entries(groups).map(async ([serverId, { server, tracks }]) => { const available = await consultServer(server); const grouping: ConsultGrouping = available ? { available, tracks } : { available, reason: "Server ping failed", tracks };
return { key: `${SCHEME}:${serverId}`, grouping, }; });
const entries = (await Promise.all(promises)).map((entry) => [entry.key, entry.grouping]); const obj = Object.fromEntries(entries);
return transfer(obj);}
async function list(cachedTracks: Track[] = []) { const cache: Record<string, Record<string, Track>> = {};
cachedTracks.forEach((t: Track) => { const parsed = parseURI(t.uri); if (!parsed || !parsed.path) return;
const sid = serverId(parsed?.server);
cache[sid] ??= {}; cache[sid][URI.unescapeComponent(parsed.path)] = t; });
async function search(client: SubsonicAPI, offset = 0): Promise<Child[]> { const result = await client.search3({ query: "", artistCount: 0, albumCount: 0, songCount: 1000, songOffset: offset, });
const songs = result.searchResult3.song || [];
if (songs.length === 1000) { const moreSongs = await search(client, offset + 1000); return [...songs, ...moreSongs]; }
return songs; }
const servers = await loadServers(); const promises = Object.values(servers).map(async (server) => { const client = createClient(server); const sid = serverId(server); const list = await search(client, 0);
return list .filter((song) => !song.isVideo) .map((song) => { const path = song.path ? song.path.startsWith("/") ? song.path : `/${song.path}` : undefined; const fromCache = path ? cache[sid]?.[path] : undefined; if (fromCache) return fromCache;
const track: Track = { id: crypto.randomUUID(), kind: autoTypeToTrackKind(song.type), uri: buildURI(server, { songId: song.id, path }),
stats: { bitrate: song.bitRate, duration: song.duration, }, tags: { album: song.album, artist: song.artist, disc: { no: song.discNumber || 1 }, genre: song.genre, title: song.title, track: { no: song.track || 1 }, year: song.year, }, };
return track; }); });
const tracks = (await Promise.all(promises)).flat(1); return transfer(tracks);}
async function resolve({ uri }: { method: string; uri: string }) { const parsed = parseURI(uri); if (!parsed) return undefined;
const client = createClient(parsed.server); const songId = parsed.songId; if (!songId) return undefined;
// TODO: // const expiresInSeconds = 60 * 60 * 24 * 7; // 7 days // const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds;
const url = await client .download({ id: songId, format: "raw", }) .then((a) => a.url);
return { expiresAt: Infinity, url };}