forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
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}