1import {
2 Client,
3 CredentialManager,
4 ok,
5 simpleFetchHandler,
6} from "@atcute/client";
7
8import type {} from "@atcute/bluesky";
9import type { ComAtprotoRepoApplyWrites } from "@atcute/atproto";
10import {
11 ShCometV0FeedPlaylist,
12 ShCometV0FeedPlaylistTrack,
13 ShCometV0FeedTrack,
14} from "@comet/lexicons";
15import type { ResourceUri } from "@atcute/lexicons";
16import { splitEvery } from "rambdax";
17
18// const manager = new CredentialManager({ service: "https://pds.ovy.sh" });
19const manager = new CredentialManager({ service: "https://bsky.social" });
20const rpc = new Client({ handler: manager });
21
22interface Type {
23 $type: `${string}.${string}.${string}`;
24 [key: string]: any;
25}
26
27const createRecord = <T extends Type>(record: T) =>
28 ok(
29 rpc.post("com.atproto.repo.createRecord", {
30 input: { collection: record.$type, repo: manager.session!.did, record },
31 }),
32 );
33
34await manager.login({
35 identifier: Bun.env.COMET_TEST_IDENT!,
36 password: Bun.env.COMET_TEST_PASSWORD!,
37});
38
39/** Upload a test audio blob. */
40const uploadAudio = async () => {
41 const inputAudio = Bun.file("./test-track.opus");
42 const { blob: audio } = await ok(
43 rpc.post("com.atproto.repo.uploadBlob", { input: inputAudio }),
44 );
45 console.log(audio);
46};
47
48/** Create a test track record. */
49const createTrack = async () => {
50 const audio = {
51 $type: "blob",
52 ref: {
53 $link: "bafkreifiu63dr52dxzrurnspha5xvzlzqkho3hdzdhu6zvthrrvdpd6yve",
54 },
55 mimeType: "audio/opus",
56 size: 3349806,
57 } as const;
58
59 const track: ShCometV0FeedTrack.Main = {
60 $type: "sh.comet.v0.feed.track",
61 audio,
62 title: "Testing Track 6",
63 createdAt: new Date().toJSON(),
64 };
65
66 const response = await createRecord(track);
67 console.log(response);
68};
69
70/** Create a test playlist */
71const createPlaylist = async () => {
72 const playlistRecord: ShCometV0FeedPlaylist.Main = {
73 $type: "sh.comet.v0.feed.playlist",
74 title: "Testing Playlist",
75 type: "sh.comet.v0.feed.playlist#playlist",
76 createdAt: new Date().toJSON(),
77 tags: ["testing", "music"],
78 };
79
80 const { uri: playlist } = await createRecord(playlistRecord);
81 console.log("created playlist", playlist);
82
83 const collection = "sh.comet.v0.feed.playlistTrack";
84 const tracks = [
85 "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2gsib2s2e",
86 "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2muqtnu2w",
87 "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2njjm6p2y",
88 "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2nrehj52o",
89 "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2nnacyg23",
90 ] as ResourceUri[];
91
92 const created = await ok(
93 rpc.post("com.atproto.repo.applyWrites", {
94 input: {
95 repo: manager.session!.did,
96 writes: tracks.map(
97 (track, position) =>
98 ({
99 $type: "com.atproto.repo.applyWrites#create",
100 collection,
101 value: {
102 $type: collection,
103 playlist,
104 track,
105 position,
106 } satisfies ShCometV0FeedPlaylistTrack.Main,
107 }) satisfies ComAtprotoRepoApplyWrites.Create,
108 ),
109 },
110 }),
111 );
112
113 console.log(created);
114 console.log("created playlist tracks");
115};
116
117/** Create a veeeeery large test playlist. */
118const createLargePlaylist = async () => {
119 const playlistRecord: ShCometV0FeedPlaylist.Main = {
120 $type: "sh.comet.v0.feed.playlist",
121 title: "Very lorge playlist",
122 type: "sh.comet.v0.feed.playlist#compilation",
123 createdAt: new Date().toJSON(),
124 };
125
126 const { uri: playlist } = await createRecord(playlistRecord);
127 console.log("created playlist", playlist);
128
129 const collection = "sh.comet.v0.feed.playlistTrack";
130 const tracks = new Array(2500)
131 .fill(
132 "at://did:plc:jrrhosrfzgjf6v4oydav6ftb/sh.comet.v0.feed.track/3lpq2gsib2s2e" as ResourceUri,
133 )
134 .map(
135 (track, position) =>
136 ({
137 $type: "com.atproto.repo.applyWrites#create",
138 collection,
139 value: {
140 $type: collection,
141 playlist,
142 track,
143 position,
144 } satisfies ShCometV0FeedPlaylistTrack.Main,
145 }) satisfies ComAtprotoRepoApplyWrites.Create,
146 );
147
148 for (const chunk of splitEvery(100, tracks)) {
149 // TODO: don't hit ratelimit
150 await ok(
151 rpc.post("com.atproto.repo.applyWrites", {
152 input: {
153 repo: manager.session!.did,
154 writes: chunk,
155 },
156 }),
157 );
158 console.log("wrote chunk");
159 }
160
161 console.log("created playlist tracks");
162};
163
164// const testQuery = async () => {
165// const x = await ok(rpc.get("sh.comet.v0.actor.getProfile", {}));
166// };
167
168// await uploadAudio();
169// await createTrack();
170// await createPlaylist();
171await createLargePlaylist();