Experiment to rebuild Diffuse using web applets.
0

Configure Feed

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

1import * as URI from "uri-js"; 2 3import type { Consult, ConsultGrouping, GroupConsult, Track } from "@applets/core/types.d.ts"; 4import { isAudioFile } from "@scripts/input/common"; 5import { 6 bucketId, 7 bucketsFromTracks, 8 buildURI, 9 consultBucket, 10 createClient, 11 groupTracksByBucket, 12 loadBuckets, 13 parseURI, 14} from "./common"; 15import { expose } from "@scripts/common"; 16import { SCHEME } from "./constants"; 17 18//////////////////////////////////////////// 19// ACTIONS 20//////////////////////////////////////////// 21const actions = expose({ 22 consult, 23 contextualize, 24 groupConsult, 25 list, 26 resolve, 27}); 28 29export type Actions = typeof actions; 30 31// Actions 32 33async function consult(fileUriOrScheme: string): Promise<Consult> { 34 if (!fileUriOrScheme.includes(":")) return { supported: true, consult: "undetermined" }; 35 36 const parsed = parseURI(fileUriOrScheme); 37 if (!parsed) return { supported: true, consult: "undetermined" }; 38 39 const consult = await consultBucket(parsed.bucket); 40 return { supported: true, consult }; 41} 42 43async function contextualize(tracks: Track[]) { 44 return bucketsFromTracks(tracks); 45} 46 47async function groupConsult(tracks: Track[]): Promise<GroupConsult> { 48 const groups = groupTracksByBucket(tracks); 49 50 const promises = Object.entries(groups).map(async ([bucketId, { bucket, tracks }]) => { 51 const available = await consultBucket(bucket); 52 const grouping: ConsultGrouping = available 53 ? { available, tracks } 54 : { available, reason: "Bucket unavailable" }; 55 56 return { 57 key: `${SCHEME}:${bucketId}`, 58 grouping, 59 }; 60 }); 61 62 const entries = (await Promise.all(promises)).map((entry) => [entry.key, entry.grouping]); 63 return Object.fromEntries(entries); 64} 65 66async function list(cachedTracks: Track[] = []) { 67 const cache = cachedTracks.reduce((acc: Record<string, Record<string, Track>>, t: Track) => { 68 const parsed = parseURI(t.uri); 69 if (!parsed) return acc; 70 71 const bid = bucketId(parsed?.bucket); 72 const trk = { [parsed.path]: t }; 73 74 return { ...acc, [bid]: acc[bid] ? { ...acc[bid], ...trk } : trk }; 75 }, {}); 76 77 const buckets = await loadBuckets(); 78 const promises = Object.values(buckets).map(async (bucket) => { 79 const client = createClient(bucket); 80 const bid = bucketId(bucket); 81 82 const list = await Array.fromAsync( 83 client.listObjects({ 84 prefix: bucket.path.replace(/^\//, ""), 85 }), 86 ); 87 88 return list 89 .filter((l) => isAudioFile(l.key)) 90 .map((l) => { 91 const cachedTrack = cache[bid][l.key]; 92 93 const id = cachedTrack?.id || crypto.randomUUID(); 94 const stats = cachedTrack?.stats; 95 const tags = cachedTrack?.tags; 96 97 const track: Track = { 98 id, 99 stats, 100 tags, 101 uri: buildURI(bucket, l.key), 102 }; 103 104 return track; 105 }); 106 }); 107 108 return (await Promise.all(promises)).flat(1); 109} 110 111async function resolve({ method, uri }: { method: string; uri: string }) { 112 const parsed = parseURI(uri); 113 if (!parsed) return undefined; 114 115 const expiresInSeconds = 60 * 60 * 24 * 7; // 7 days 116 const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds; 117 118 const client = createClient(parsed.bucket); 119 const url = await client.getPresignedUrl(method.toUpperCase() as any, parsed.path); 120 121 return { expiresAt: expiresAtSeconds, url }; 122}