A decentralized music tracking and discovery platform built on AT Protocol 馃幍
at fix/spotify 115 lines 3.1 kB view raw
1import { TID } from "@atproto/common"; 2import type { BlobRef } from "@atproto/lexicon"; 3import chalk from "chalk"; 4import type { Context } from "context"; 5import { eq } from "drizzle-orm"; 6import * as Playlist from "lexicon/types/app/rocksky/playlist"; 7import { createAgent } from "lib/agent"; 8import downloadImage, { getContentType } from "lib/downloadImage"; 9import { StringCodec } from "nats"; 10import tables from "schema"; 11 12export function onNewPlaylist(ctx: Context) { 13 const sc = StringCodec(); 14 const sub = ctx.nc.subscribe("rocksky.playlist"); 15 (async () => { 16 for await (const m of sub) { 17 const payload: { 18 id: string; 19 did: string; 20 } = JSON.parse(sc.decode(m.data)); 21 console.log( 22 `New playlist: ${chalk.cyan(payload.did)} - ${chalk.greenBright(payload.id)}`, 23 ); 24 await putPlaylistRecord(ctx, payload); 25 } 26 })(); 27} 28 29async function putPlaylistRecord( 30 ctx: Context, 31 payload: { id: string; did: string }, 32) { 33 const agent = await createAgent(ctx.oauthClient, payload.did); 34 35 if (!agent) { 36 console.error( 37 `Failed to create agent, skipping playlist: ${chalk.cyan(payload.id)} for ${chalk.greenBright(payload.did)}`, 38 ); 39 return; 40 } 41 42 const [playlist] = await ctx.db 43 .select() 44 .from(tables.playlists) 45 .where(eq(tables.playlists.id, payload.id)) 46 .execute(); 47 48 let rkey = TID.nextStr(); 49 50 if (playlist.uri) { 51 rkey = playlist.uri.split("/").pop(); 52 } 53 54 const record: { 55 $type: string; 56 name: string; 57 description?: string; 58 createdAt: string; 59 picture?: BlobRef; 60 spotifyLink?: string; 61 tidalLink?: string; 62 appleMusicLink?: string; 63 youtubeLink?: string; 64 } = { 65 $type: "app.rocksky.playlist", 66 name: playlist.name, 67 description: playlist.description, 68 createdAt: new Date().toISOString(), 69 spotifyLink: playlist.spotifyLink, 70 }; 71 72 if (playlist.picture) { 73 const imageBuffer = await downloadImage(playlist.picture); 74 const encoding = await getContentType(playlist.picture); 75 const uploadResponse = await agent.uploadBlob(imageBuffer, { 76 encoding, 77 }); 78 record.picture = uploadResponse.data.blob; 79 } 80 81 if (!Playlist.validateRecord(record)) { 82 console.error(`Invalid record: ${chalk.redBright(JSON.stringify(record))}`); 83 return; 84 } 85 86 try { 87 const res = await agent.com.atproto.repo.putRecord({ 88 repo: agent.assertDid, 89 collection: "app.rocksky.playlist", 90 rkey, 91 record, 92 validate: false, 93 }); 94 const uri = res.data.uri; 95 console.log(`Playlist record created: ${chalk.greenBright(uri)}`); 96 await ctx.db 97 .update(tables.playlists) 98 .set({ uri }) 99 .where(eq(tables.playlists.id, payload.id)) 100 .execute(); 101 } catch (e) { 102 console.error(`Failed to put record: ${chalk.redBright(e.message)}`); 103 } 104 105 const [updatedPlaylist] = await ctx.db 106 .select() 107 .from(tables.playlists) 108 .where(eq(tables.playlists.id, payload.id)) 109 .execute(); 110 111 await ctx.meilisearch.post( 112 `indexes/playlists/documents?primaryKey=id`, 113 updatedPlaylist, 114 ); 115}