tangled
alpha
login
or
join now
t1c.dev
/
rocksky
forked from
rocksky.app/rocksky
2
fork
atom
A decentralized music tracking and discovery platform built on AT Protocol 🎵
2
fork
atom
overview
issues
pulls
pipelines
[web] migrate into rocksky xrpc api
tsiry-sandratraina.com
8 months ago
4f9cc9f5
25fad9fb
+197
-229
18 changed files
expand all
collapse all
unified
split
apps
api
src
context.ts
xrpc
app
rocksky
actor
getProfile.ts
web
src
api
feed.ts
library.ts
playlists.ts
components
Handle
Handle.tsx
hooks
useLibrary.tsx
useProfile.tsx
pages
album
Album.tsx
artist
Albums
Albums.tsx
Artist.tsx
profile
Profile.tsx
library
albums
Albums.tsx
lovedtracks
LovedTracks.tsx
overview
topalbums
TopAlbums.tsx
toptracks
TopTracks.tsx
song
PopularAlbums
PopularAlbums.tsx
Song.tsx
+1
apps/api/src/context.ts
···
26
export const ctx = {
27
oauthClient: await createClient(db),
28
resolver: createBidirectionalResolver(baseIdResolver),
0
29
kv: new Map<string, string>(),
30
client,
31
db: drizzle.db,
···
26
export const ctx = {
27
oauthClient: await createClient(db),
28
resolver: createBidirectionalResolver(baseIdResolver),
29
+
baseIdResolver,
30
kv: new Map<string, string>(),
31
client,
32
db: drizzle.db,
+5
-2
apps/api/src/xrpc/app/rocksky/actor/getProfile.ts
···
62
return Effect.tryPromise({
63
try: async () => {
64
if (!params.did?.startsWith("did:plc:") && !!params.did) {
0
65
return fetch(
66
`https://dns.google/resolve?name=_atproto.${params.did}&type=TXT`
67
)
68
.then((res) => res.json())
69
.then(
70
(data) =>
71
-
_.get(data, "Answer.0.data", "").replace(/"/g, "").split("=")[1]
0
0
72
)
73
.then((did) => ({
74
did,
···
98
return Effect.tryPromise({
99
try: async () => {
100
if (params.did) {
101
-
return fetch(`https://plc.directory/${did}`)
102
.then((res) => res.json())
103
.then((data) => ({
104
did,
···
62
return Effect.tryPromise({
63
try: async () => {
64
if (!params.did?.startsWith("did:plc:") && !!params.did) {
65
+
const handle = await ctx.baseIdResolver.handle.resolve(params.did);
66
return fetch(
67
`https://dns.google/resolve?name=_atproto.${params.did}&type=TXT`
68
)
69
.then((res) => res.json())
70
.then(
71
(data) =>
72
+
_.get(data, "Answer.0.data", handle)
73
+
.replace(/"/g, "")
74
+
.split("=")[1]
75
)
76
.then((did) => ({
77
did,
···
101
return Effect.tryPromise({
102
try: async () => {
103
if (params.did) {
104
+
return fetch(`https://plc.directory/${params.did}`)
105
.then((res) => res.json())
106
.then((data) => ({
107
did,
+21
-17
apps/web/src/api/feed.ts
···
1
-
import axios from "axios";
2
-
import { API_URL } from "../consts";
3
4
export const getFeed = () => {
5
return [];
6
};
7
8
export const getFeedByUri = async (uri: string) => {
9
-
const response = await axios.get(`${API_URL}/users/${uri}`);
0
0
0
0
0
10
11
if (response.status !== 200) {
12
return null;
13
}
14
15
return {
16
-
id: response.data.track_id?.xata_id,
17
-
title: response.data.track_id?.title,
18
-
artist: response.data.track_id?.artist,
19
-
albumArtist: response.data.track_id?.album_artist,
20
-
album: response.data.track_id?.album,
21
-
cover: response.data.track_id?.album_art,
22
tags: [],
23
-
artistUri: response.data.track_id?.artist_uri,
24
-
albumUri: response.data.track_id?.album_uri,
25
-
listeners: response.data.listeners || 1,
26
-
scrobbles: response.data.scrobbles || 1,
27
-
lyrics: response.data.track_id?.lyrics,
28
-
spotifyLink: response.data.track_id?.spotify_link,
29
-
composer: response.data.track_id?.composer,
30
-
uri: response.data.track_id?.uri,
31
};
32
};
···
1
+
import { client } from ".";
0
2
3
export const getFeed = () => {
4
return [];
5
};
6
7
export const getFeedByUri = async (uri: string) => {
8
+
if (uri.includes("app.rocksky.song")) {
9
+
return null;
10
+
}
11
+
const response = await client.get("/xrpc/app.rocksky.scrobble.getScrobble", {
12
+
params: { uri },
13
+
});
14
15
if (response.status !== 200) {
16
return null;
17
}
18
19
return {
20
+
id: response.data?.id,
21
+
title: response.data?.title,
22
+
artist: response.data?.artist,
23
+
albumArtist: response.data?.albumArtist,
24
+
album: response.data?.album,
25
+
cover: response.data?.cover,
26
tags: [],
27
+
artistUri: response.data?.artistUri,
28
+
albumUri: response.data?.albumUri,
29
+
listeners: response.data?.listeners || 1,
30
+
scrobbles: response.data?.scrobbles || 1,
31
+
lyrics: response.data?.lyrics,
32
+
spotifyLink: response.data?.spotifyLink,
33
+
composer: response.data?.composer,
34
+
uri: response.data?.uri,
35
};
36
};
+50
-50
apps/web/src/api/library.ts
···
1
-
import axios from "axios";
2
-
import { API_URL } from "../consts";
3
4
export const getSongByUri = async (uri: string) => {
5
-
const response = await axios.get(`${API_URL}/users/${uri}`);
0
0
6
return {
7
id: response.data?.id,
8
title: response.data?.title,
9
artist: response.data?.artist,
10
-
albumArtist: response.data?.album_artist,
11
album: response.data?.album,
12
-
cover: response.data?.album_art,
13
tags: [],
14
-
artistUri: response.data?.artist_uri,
15
-
albumUri: response.data?.album_uri,
16
-
listeners: response.data?.listeners || 1,
17
-
scrobbles: response.data?.scrobbles || 1,
18
lyrics: response.data?.lyrics,
19
-
spotifyLink: response.data?.spotify_link,
20
composer: response.data?.composer,
21
uri: response.data?.uri,
22
};
···
30
id: string;
31
title: string;
32
artist: string;
33
-
album_artist: string;
34
-
album_art: string;
35
uri: string;
36
-
play_count: number;
37
-
album_uri?: string;
38
-
artist_uri?: string;
39
}[]
40
> => {
41
-
const response = await axios.get(
42
-
`${API_URL}/users/${uri}/tracks?size=${limit}`
0
43
);
44
-
return response.data;
45
};
46
47
export const getArtistAlbums = async (
···
52
id: string;
53
title: string;
54
artist: string;
55
-
album_art: string;
56
-
artist_uri: string;
57
uri: string;
58
}[]
59
> => {
60
-
const response = await axios.get(
61
-
`${API_URL}/users/${uri}/albums?size=${limit}`
0
62
);
63
-
return response.data;
64
};
65
66
export const getArtists = async (did: string, offset = 0, limit = 30) => {
67
-
const response = await axios.get(
68
-
`${API_URL}/users/${did}/artists?size=${limit}&offset=${offset}`
69
-
);
70
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
71
-
return response.data.map((x: any) => ({ ...x, scrobbles: x.play_count }));
72
};
73
74
export const getAlbums = async (did: string, offset = 0, limit = 12) => {
75
-
const response = await axios.get(
76
-
`${API_URL}/users/${did}/albums?size=${limit}&offset=${offset}`
77
-
);
78
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
79
-
return response.data.map((x: any) => ({
80
-
...x,
81
-
scrobbles: x.play_count,
82
-
}));
83
};
84
85
export const getTracks = async (did: string, offset = 0, limit = 20) => {
86
-
const response = await axios.get(
87
-
`${API_URL}/users/${did}/tracks?size=${limit}&offset=${offset}`
88
-
);
89
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
90
-
return response.data.map((x: any) => ({ ...x, scrobbles: x.play_count }));
91
};
92
93
export const getLovedTracks = async (did: string, offset = 0, limit = 20) => {
94
-
const response = await axios.get(
95
-
`${API_URL}/users/${did}/likes?size=${limit}&offset=${offset}`
0
0
0
96
);
97
-
return response.data;
98
};
99
100
export const getAlbum = async (did: string, rkey: string) => {
101
-
const response = await axios.get(
102
-
`${API_URL}/users/${did}/app.rocksky.album/${rkey}`
103
-
);
104
return response.data;
105
};
106
107
export const getArtist = async (did: string, rkey: string) => {
108
-
const response = await axios.get(
109
-
`${API_URL}/users/${did}/app.rocksky.artist/${rkey}`
110
-
);
111
return response.data;
112
};
···
1
+
import { client } from ".";
0
2
3
export const getSongByUri = async (uri: string) => {
4
+
const response = await client.get("/xrpc/app.rocksky.song.getSong", {
5
+
params: { uri },
6
+
});
7
return {
8
id: response.data?.id,
9
title: response.data?.title,
10
artist: response.data?.artist,
11
+
albumArtist: response.data?.albumArtist,
12
album: response.data?.album,
13
+
cover: response.data?.albumArt,
14
tags: [],
15
+
artistUri: response.data?.artistUri,
16
+
albumUri: response.data?.albumUri,
17
+
listeners: response.data?.uniqueListeners || 1,
18
+
scrobbles: response.data?.playCount || 1,
19
lyrics: response.data?.lyrics,
20
+
spotifyLink: response.data?.spotifyLink,
21
composer: response.data?.composer,
22
uri: response.data?.uri,
23
};
···
31
id: string;
32
title: string;
33
artist: string;
34
+
albumArtist: string;
35
+
albumArt: string;
36
uri: string;
37
+
playCount: number;
38
+
albumUri?: string;
39
+
artistUri?: string;
40
}[]
41
> => {
42
+
const response = await client.get(
43
+
"/xrpc/app.rocksky.artist.getArtistTracks",
44
+
{ params: { uri, limit } }
45
);
46
+
return response.data.tracks;
47
};
48
49
export const getArtistAlbums = async (
···
54
id: string;
55
title: string;
56
artist: string;
57
+
albumArt: string;
58
+
artistUri: string;
59
uri: string;
60
}[]
61
> => {
62
+
const response = await client.get(
63
+
"/xrpc/app.rocksky.artist.getArtistAlbums",
64
+
{ params: { uri, limit } }
65
);
66
+
return response.data.albums;
67
};
68
69
export const getArtists = async (did: string, offset = 0, limit = 30) => {
70
+
const response = await client.get("/xrpc/app.rocksky.actor.getActorArtists", {
71
+
params: { did, limit, offset },
72
+
});
73
+
return response.data;
0
74
};
75
76
export const getAlbums = async (did: string, offset = 0, limit = 12) => {
77
+
const response = await client.get("/xrpc/app.rocksky.actor.getActorAlbums", {
78
+
params: { did, limit, offset },
79
+
});
80
+
return response.data;
0
0
0
0
81
};
82
83
export const getTracks = async (did: string, offset = 0, limit = 20) => {
84
+
const response = await client.get("/xrpc/app.rocksky.actor.getActorSongs", {
85
+
params: { did, limit, offset },
86
+
});
87
+
return response.data;
0
88
};
89
90
export const getLovedTracks = async (did: string, offset = 0, limit = 20) => {
91
+
const response = await client.get(
92
+
"/xrpc/app.rocksky.actor.getActorLovedSongs",
93
+
{
94
+
params: { did, limit, offset },
95
+
}
96
);
97
+
return response.data.tracks;
98
};
99
100
export const getAlbum = async (did: string, rkey: string) => {
101
+
const response = await client.get("/xrpc/app.rocksky.album.getAlbum", {
102
+
params: { uri: `at://${did}/app.rocksky.album/${rkey}` },
103
+
});
104
return response.data;
105
};
106
107
export const getArtist = async (did: string, rkey: string) => {
108
+
const response = await client.get("/xrpc/app.rocksky.artist.getArtist", {
109
+
params: { uri: `at://${did}/app.rocksky.artist/${rkey}` },
110
+
});
111
return response.data;
112
};
+13
-7
apps/web/src/api/playlists.ts
···
1
-
import axios from "axios";
2
-
import { API_URL } from "../consts";
3
4
export const getPlaylists = async (
5
did: string
···
16
trackCount: number;
17
}[]
18
> => {
19
-
const response = await axios.get(`${API_URL}/users/${did}/playlists`);
20
-
return response.data;
0
0
0
0
0
21
};
22
23
export const getPlaylist = async (
···
56
discNumber: number;
57
}[];
58
}> => {
59
-
const response = await axios.get(
60
-
`${API_URL}/users/${did}/app.rocksky.playlist/${rkey}`
61
-
);
0
0
62
return response.data;
63
};
···
1
+
import { client } from ".";
0
2
3
export const getPlaylists = async (
4
did: string
···
15
trackCount: number;
16
}[]
17
> => {
18
+
const response = await client.get(
19
+
"/xrpc/app.rocksky.actor.getActorPlaylists",
20
+
{
21
+
params: { did },
22
+
}
23
+
);
24
+
return response.data.playlists;
25
};
26
27
export const getPlaylist = async (
···
60
discNumber: number;
61
}[];
62
}> => {
63
+
const response = await client.get("/xrpc/app.rocksky.playlist.getPlaylist", {
64
+
params: {
65
+
uri: `at://${did}/app.rocksky.playlist/${rkey}`,
66
+
},
67
+
});
68
return response.data;
69
};
+2
-2
apps/web/src/components/Handle/Handle.tsx
···
39
...profiles,
40
[did]: {
41
avatar: profile.data.avatar,
42
-
displayName: profile.data.display_name,
43
handle: profile.data.handle,
44
spotifyConnected: profile.data.spotifyConnected,
45
-
createdAt: profile.data.xata_createdat,
46
did,
47
},
48
}));
···
39
...profiles,
40
[did]: {
41
avatar: profile.data.avatar,
42
+
displayName: profile.data.displayName,
43
handle: profile.data.handle,
44
spotifyConnected: profile.data.spotifyConnected,
45
+
createdAt: profile.data.createdAt,
46
did,
47
},
48
}));
+18
apps/web/src/hooks/useLibrary.tsx
···
37
queryKey: ["artists", did, offset, limit],
38
queryFn: () => getArtists(did, offset, limit),
39
enabled: !!did,
0
0
0
0
0
0
40
});
41
42
export const useAlbumsQuery = (did: string, offset = 0, limit = 12) =>
···
44
queryKey: ["albums", did, offset, limit],
45
queryFn: () => getAlbums(did, offset, limit),
46
enabled: !!did,
0
0
0
0
0
0
47
});
48
49
export const useTracksQuery = (did: string, offset = 0, limit = 20) =>
···
51
queryKey: ["tracks", did, offset, limit],
52
queryFn: () => getTracks(did, offset, limit),
53
enabled: !!did,
0
0
0
0
0
0
54
});
55
56
export const useLovedTracksQuery = (did: string, offset = 0, limit = 20) =>
···
37
queryKey: ["artists", did, offset, limit],
38
queryFn: () => getArtists(did, offset, limit),
39
enabled: !!did,
40
+
select: (data) =>
41
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+
data?.artists.map((x: any) => ({
43
+
...x,
44
+
scrobbles: x.playCount,
45
+
})),
46
});
47
48
export const useAlbumsQuery = (did: string, offset = 0, limit = 12) =>
···
50
queryKey: ["albums", did, offset, limit],
51
queryFn: () => getAlbums(did, offset, limit),
52
enabled: !!did,
53
+
select: (data) =>
54
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+
data?.albums.map((x: any) => ({
56
+
...x,
57
+
scrobbles: x.playCount,
58
+
})),
59
});
60
61
export const useTracksQuery = (did: string, offset = 0, limit = 20) =>
···
63
queryKey: ["tracks", did, offset, limit],
64
queryFn: () => getTracks(did, offset, limit),
65
enabled: !!did,
66
+
select: (data) =>
67
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+
data?.tracks.map((x: any) => ({
69
+
...x,
70
+
scrobbles: x.playCount,
71
+
})),
72
});
73
74
export const useLovedTracksQuery = (did: string, offset = 0, limit = 20) =>
+13
-11
apps/web/src/hooks/useProfile.tsx
···
43
44
const fetchProfile = async () => {
45
try {
46
-
const response = await fetch(`${API_URL}/profile`, {
47
-
headers: {
48
-
Authorization: `Bearer ${token}`,
49
-
},
50
-
}).then((res) => res.text());
0
0
0
51
setData(response);
52
setError(null);
53
} catch (e) {
···
62
if (data !== "Unauthorized" && data !== "Internal Server Error" && data) {
63
const profile = JSON.parse(data);
64
setProfile({
65
-
avatar: `https://cdn.bsky.app/img/avatar/plain/${localStorage.getItem(
66
-
"did"
67
-
)}/${profile.avatar.ref["$link"]}@jpeg`,
68
displayName: profile.displayName,
69
handle: profile.handle,
70
spotifyUser: {
71
-
isBeta: profile.spotifyUser?.is_beta_user,
72
},
73
spotifyConnected: profile.spotifyConnected,
74
did: profile.did,
75
googledriveUser: {
76
-
isBeta: profile.googledrive?.is_beta_user,
77
},
78
dropboxUser: {
79
-
isBeta: profile.dropbox?.is_beta_user,
80
},
81
});
82
}
83
84
if (
85
!data ||
0
86
data === "Unauthorized" ||
87
data === "Internal Server Error" ||
88
(error && localStorage.getItem("token"))
···
43
44
const fetchProfile = async () => {
45
try {
46
+
const response = await fetch(
47
+
`${API_URL}/xrpc/app.rocksky.actor.getProfile`,
48
+
{
49
+
headers: {
50
+
Authorization: `Bearer ${token}`,
51
+
},
52
+
}
53
+
).then((res) => res.text());
54
setData(response);
55
setError(null);
56
} catch (e) {
···
65
if (data !== "Unauthorized" && data !== "Internal Server Error" && data) {
66
const profile = JSON.parse(data);
67
setProfile({
68
+
avatar: profile.avatar,
0
0
69
displayName: profile.displayName,
70
handle: profile.handle,
71
spotifyUser: {
72
+
isBeta: profile.spotifyUser?.isBetaUser,
73
},
74
spotifyConnected: profile.spotifyConnected,
75
did: profile.did,
76
googledriveUser: {
77
+
isBeta: profile.googledrive?.isBetaUser,
78
},
79
dropboxUser: {
80
+
isBeta: profile.dropbox?.isBetaUser,
81
},
82
});
83
}
84
85
if (
86
!data ||
87
+
Object.keys(data).length === 0 ||
88
data === "Unauthorized" ||
89
data === "Internal Server Error" ||
90
(error && localStorage.getItem("token"))
+18
-44
apps/web/src/pages/album/Album.tsx
···
66
artistUri?: string;
67
label?: string;
68
tracks: {
69
-
xata_id: string;
70
-
track_number: number;
71
album: string;
72
-
album_art: string;
73
-
album_artist: string;
74
title: string;
75
artist: string;
76
-
xata_created: string;
77
uri: string;
78
-
album_uri: string;
79
-
artist_uri: string;
80
duration: number;
81
-
disc_number: number;
82
}[];
83
} | null>(null);
84
const uri = `${did}/app.rocksky.album/${rkey}`;
···
87
if (!isLoading && !isError) {
88
setAlbum({
89
id: data.id,
90
-
albumArt: data.album_art,
91
-
artistUri: data.artist_uri,
92
artist: data.artist,
93
title: data.title,
94
year: data.year,
95
uri: data.uri,
96
-
listeners: data.listeners,
97
-
scrobbles: data.scrobbles,
98
tracks: data.tracks,
99
-
releaseDate: data.release_date
100
-
? dayjs(data.release_date).format("MMMM D, YYYY")
101
: data.year?.toString(),
102
-
label: data.tracks[0].copyright_message || data.tracks[0].label,
103
});
104
// eslint-disable-next-line @typescript-eslint/no-explicit-any
105
-
setDisc(Math.max(...data.tracks.map((track: any) => track.disc_number)));
106
}
107
}, [data, isLoading, isError]);
108
···
199
<div className="mt-[20px]">
200
{disc < 2 && (
201
<TableBuilder
202
-
data={album?.tracks.map((x) => ({
203
-
id: x.xata_id,
204
-
trackNumber: x.track_number,
205
-
albumArt: x.album_art,
206
-
title: x.title,
207
-
artist: x.artist,
208
-
uri: x.uri,
209
-
albumUri: album?.uri,
210
-
artistUri: x.artist_uri,
211
-
albumArtist: x.album_artist,
212
-
duration: x.duration,
213
-
discNumber: x.disc_number,
214
-
}))}
215
emptyMessage="You haven't listened to any music yet."
216
divider="clean"
217
overrides={{
···
317
Volume {i + 1}
318
</LabelLarge>
319
<TableBuilder
320
-
data={album?.tracks
321
-
.filter((x) => x.disc_number == i + 1)
322
-
.map((x) => ({
323
-
id: x.xata_id,
324
-
trackNumber: x.track_number,
325
-
albumArt: x.album_art,
326
-
title: x.title,
327
-
artist: x.artist,
328
-
uri: x.uri,
329
-
albumUri: album?.uri,
330
-
artistUri: x.artist_uri,
331
-
albumArtist: x.album_artist,
332
-
duration: x.duration,
333
-
discNumber: x.disc_number,
334
-
}))}
335
emptyMessage="You haven't listened to any music yet."
336
divider="clean"
337
overrides={{
···
66
artistUri?: string;
67
label?: string;
68
tracks: {
69
+
id: string;
70
+
trackNumber: number;
71
album: string;
72
+
albumArt: string;
73
+
albumArtist: string;
74
title: string;
75
artist: string;
76
+
createdAt: string;
77
uri: string;
78
+
albumUri: string;
79
+
artistUri: string;
80
duration: number;
81
+
discNumber: number;
82
}[];
83
} | null>(null);
84
const uri = `${did}/app.rocksky.album/${rkey}`;
···
87
if (!isLoading && !isError) {
88
setAlbum({
89
id: data.id,
90
+
albumArt: data.albumArt,
91
+
artistUri: data.artistUri,
92
artist: data.artist,
93
title: data.title,
94
year: data.year,
95
uri: data.uri,
96
+
listeners: data.uniqueListeners,
97
+
scrobbles: data.playCount,
98
tracks: data.tracks,
99
+
releaseDate: data.releaseDate
100
+
? dayjs(data.releaseDate).format("MMMM D, YYYY")
101
: data.year?.toString(),
102
+
label: data.tracks[0].copyrightMessage || data.tracks[0].label,
103
});
104
// eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+
setDisc(Math.max(...data.tracks.map((track: any) => track.discNumber)));
106
}
107
}, [data, isLoading, isError]);
108
···
199
<div className="mt-[20px]">
200
{disc < 2 && (
201
<TableBuilder
202
+
data={album?.tracks}
0
0
0
0
0
0
0
0
0
0
0
0
203
emptyMessage="You haven't listened to any music yet."
204
divider="clean"
205
overrides={{
···
305
Volume {i + 1}
306
</LabelLarge>
307
<TableBuilder
308
+
data={album?.tracks.filter((x) => x.discNumber == i + 1)}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
309
emptyMessage="You haven't listened to any music yet."
310
divider="clean"
311
overrides={{
+7
-7
apps/web/src/pages/artist/Albums/Albums.tsx
···
24
id: string;
25
title: string;
26
artist: string;
27
-
album_art: string;
28
-
artist_uri: string;
29
uri: string;
30
}[];
31
}
···
47
<FlexGridItem {...itemProps} key={album.id}>
48
{album.uri && (
49
<Link to={`/${album.uri.split("at://")[1]}`}>
50
-
<SongCover cover={album.album_art} size={230} />
51
</Link>
52
)}
53
-
{!album.uri && <SongCover cover={album.album_art} size={230} />}
54
{album.uri && (
55
<Link to={`/${album.uri.split("at://")[1]}`}>
56
<LabelMedium className="!text-[var(--color-text)]">
···
59
</Link>
60
)}
61
{!album.uri && <LabelMedium>{album.title}</LabelMedium>}
62
-
{album.artist_uri && (
63
-
<Link to={`/${album.artist_uri.split("at://")[1]}`}>
64
<LabelSmall className="!text-[var(--color-text-muted)]">
65
{album.artist}
66
</LabelSmall>
67
</Link>
68
)}
69
-
{!album.artist_uri && (
70
<LabelSmall className="!text-[var(--color-text-muted)]">
71
{album.artist}
72
</LabelSmall>
···
24
id: string;
25
title: string;
26
artist: string;
27
+
albumArt: string;
28
+
artistUri: string;
29
uri: string;
30
}[];
31
}
···
47
<FlexGridItem {...itemProps} key={album.id}>
48
{album.uri && (
49
<Link to={`/${album.uri.split("at://")[1]}`}>
50
+
<SongCover cover={album.albumArt} size={230} />
51
</Link>
52
)}
53
+
{!album.uri && <SongCover cover={album.albumArt} size={230} />}
54
{album.uri && (
55
<Link to={`/${album.uri.split("at://")[1]}`}>
56
<LabelMedium className="!text-[var(--color-text)]">
···
59
</Link>
60
)}
61
{!album.uri && <LabelMedium>{album.title}</LabelMedium>}
62
+
{album.artistUri && (
63
+
<Link to={`/${album.artistUri.split("at://")[1]}`}>
64
<LabelSmall className="!text-[var(--color-text-muted)]">
65
{album.artist}
66
</LabelSmall>
67
</Link>
68
)}
69
+
{!album.artistUri && (
70
<LabelSmall className="!text-[var(--color-text-muted)]">
71
{album.artist}
72
</LabelSmall>
+10
-23
apps/web/src/pages/artist/Artist.tsx
···
28
const Artist = () => {
29
const { did, rkey } = useParams<{ did: string; rkey: string }>();
30
31
-
const uri = `${did}/app.rocksky.artist/${rkey}`;
32
const artistResult = useArtistQuery(did!, rkey!);
33
const artistTracksResult = useArtistTracksQuery(uri);
34
const artistAlbumsResult = useArtistAlbumsQuery(uri);
···
53
id: string;
54
title: string;
55
artist: string;
56
-
album_art: string;
57
-
artist_uri: string;
58
uri: string;
59
}[]
60
>([]);
···
72
id: artistResult.data.id,
73
name: artistResult.data.name,
74
born: artistResult.data.born,
75
-
bornIn: artistResult.data.born_in,
76
died: artistResult.data.died,
77
-
listeners: artistResult.data.listeners,
78
-
scrobbles: artistResult.data.scrobbles,
79
picture: artistResult.data.picture,
80
tags: artistResult.data.tags,
81
uri: artistResult.data.uri,
82
-
spotifyLink: artistResult.data.spotify_link,
83
});
84
// eslint-disable-next-line react-hooks/exhaustive-deps
85
}, [artistResult.data, artistResult.isLoading, artistResult.isError, did]);
···
94
}
95
96
setTopTracks(
97
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
98
-
artistTracksResult.data.map((track: any) => ({
99
...track,
100
-
albumArt: track.album_art,
101
-
albumArtist: track.album_artist,
102
-
albumUri: track.album_uri,
103
-
artistUri: track.artist_uri,
104
}))
105
);
106
}, [
···
119
return;
120
}
121
122
-
setTopAlbums(
123
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
124
-
artistAlbumsResult.data.map((album: any) => ({
125
-
...album,
126
-
albumArt: album.album_art,
127
-
albumArtist: album.album_artist,
128
-
albumUri: album.album_uri,
129
-
artistUri: album.artist_uri,
130
-
}))
131
-
);
132
}, [
133
artistAlbumsResult.data,
134
artistAlbumsResult.isLoading,
···
28
const Artist = () => {
29
const { did, rkey } = useParams<{ did: string; rkey: string }>();
30
31
+
const uri = `at://${did}/app.rocksky.artist/${rkey}`;
32
const artistResult = useArtistQuery(did!, rkey!);
33
const artistTracksResult = useArtistTracksQuery(uri);
34
const artistAlbumsResult = useArtistAlbumsQuery(uri);
···
53
id: string;
54
title: string;
55
artist: string;
56
+
albumArt: string;
57
+
artistUri: string;
58
uri: string;
59
}[]
60
>([]);
···
72
id: artistResult.data.id,
73
name: artistResult.data.name,
74
born: artistResult.data.born,
75
+
bornIn: artistResult.data.bornIn,
76
died: artistResult.data.died,
77
+
listeners: artistResult.data.uniqueListeners,
78
+
scrobbles: artistResult.data.playCount,
79
picture: artistResult.data.picture,
80
tags: artistResult.data.tags,
81
uri: artistResult.data.uri,
82
+
spotifyLink: artistResult.data.spotifyLink,
83
});
84
// eslint-disable-next-line react-hooks/exhaustive-deps
85
}, [artistResult.data, artistResult.isLoading, artistResult.isError, did]);
···
94
}
95
96
setTopTracks(
97
+
artistTracksResult.data.map((track) => ({
0
98
...track,
99
+
scrobbles: track.playCount || 1,
0
0
0
100
}))
101
);
102
}, [
···
115
return;
116
}
117
118
+
setTopAlbums(artistAlbumsResult.data);
0
0
0
0
0
0
0
0
0
119
}, [
120
artistAlbumsResult.data,
121
artistAlbumsResult.isLoading,
+4
-4
apps/web/src/pages/profile/Profile.tsx
···
53
54
setUser({
55
avatar: profile.data.avatar,
56
-
displayName: profile.data.display_name,
57
handle: profile.data.handle,
58
spotifyUser: {
59
-
isBeta: profile.data.spotifyUser?.is_beta_user,
60
},
61
spotifyConnected: profile.data.spotifyConnected,
62
did: profile.data.did,
···
66
...profiles,
67
[did]: {
68
avatar: profile.data.avatar,
69
-
displayName: profile.data.display_name,
70
handle: profile.data.handle,
71
spotifyConnected: profile.data.spotifyConnected,
72
-
createdAt: profile.data.xata_createdat,
73
did,
74
},
75
}));
···
53
54
setUser({
55
avatar: profile.data.avatar,
56
+
displayName: profile.data.displayName,
57
handle: profile.data.handle,
58
spotifyUser: {
59
+
isBeta: profile.data.spotifyUser?.isBetaUser,
60
},
61
spotifyConnected: profile.data.spotifyConnected,
62
did: profile.data.did,
···
66
...profiles,
67
[did]: {
68
avatar: profile.data.avatar,
69
+
displayName: profile.data.displayName,
70
handle: profile.data.handle,
71
spotifyConnected: profile.data.spotifyConnected,
72
+
createdAt: profile.data.createdat,
73
did,
74
},
75
}));
+2
-2
apps/web/src/pages/profile/library/albums/Albums.tsx
···
82
id: x.id,
83
title: x.title,
84
artist: x.artist,
85
-
albumArt: x.album_art,
86
-
artistUri: x.artist_uri,
87
uri: x.uri,
88
scrobbles: x.scrobbles,
89
}))
···
82
id: x.id,
83
title: x.title,
84
artist: x.artist,
85
+
albumArt: x.albumArt,
86
+
artistUri: x.artistUri,
87
uri: x.uri,
88
scrobbles: x.scrobbles,
89
}))
+3
-7
apps/web/src/pages/profile/lovedtracks/LovedTracks.tsx
···
67
68
setLovedTracks(
69
// eslint-disable-next-line @typescript-eslint/no-explicit-any
70
-
lovedTracksResult.data.records.map((item: any) => ({
71
-
...item.track_id,
72
-
albumArt: item.track_id.album_art,
73
-
albumArtist: item.track_id.album_artist,
74
-
albumUri: item.track_id.album_uri,
75
-
artistUri: item.track_id.artist_uri,
76
-
date: item.xata_createdat,
77
}))
78
);
79
// eslint-disable-next-line react-hooks/exhaustive-deps
···
67
68
setLovedTracks(
69
// eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+
lovedTracksResult.data.map((item: any) => ({
71
+
...item,
72
+
date: item.createdAt,
0
0
0
0
73
}))
74
);
75
// eslint-disable-next-line react-hooks/exhaustive-deps
+4
-4
apps/web/src/pages/profile/overview/topalbums/TopAlbums.tsx
···
76
topAlbums.map((album: any) => (
77
<FlexGridItem {...itemProps} key={album.id}>
78
<Link to={`/${album.uri?.split("at://")[1]}`}>
79
-
<SongCover cover={album.album_art} size={230} />
80
</Link>
81
<Link to={`/${album.uri?.split("at://")[1]}`}>
82
<LabelMedium className="!text-[var(--color-text)]">
83
{album.title}
84
</LabelMedium>
85
</Link>
86
-
{album.artist_uri && (
87
-
<Link to={`/${album.artist_uri.split("at://")[1]}`}>
88
<LabelSmall className="!text-[var(--color-text-muted)]">
89
{album.artist}
90
</LabelSmall>
91
</Link>
92
)}
93
-
{!album.artist_uri && (
94
<LabelSmall className="!text-[var(--color-text-muted)]">
95
{album.artist}
96
</LabelSmall>
···
76
topAlbums.map((album: any) => (
77
<FlexGridItem {...itemProps} key={album.id}>
78
<Link to={`/${album.uri?.split("at://")[1]}`}>
79
+
<SongCover cover={album.albumArt} size={230} />
80
</Link>
81
<Link to={`/${album.uri?.split("at://")[1]}`}>
82
<LabelMedium className="!text-[var(--color-text)]">
83
{album.title}
84
</LabelMedium>
85
</Link>
86
+
{album.artistUri && (
87
+
<Link to={`/${album.artistUri.split("at://")[1]}`}>
88
<LabelSmall className="!text-[var(--color-text-muted)]">
89
{album.artist}
90
</LabelSmall>
91
</Link>
92
)}
93
+
{!album.artistUri && (
94
<LabelSmall className="!text-[var(--color-text-muted)]">
95
{album.artist}
96
</LabelSmall>
+1
-10
apps/web/src/pages/profile/overview/toptracks/TopTracks.tsx
···
88
return;
89
}
90
91
-
setTopTracks(
92
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
93
-
tracksResult.data.map((track: any) => ({
94
-
...track,
95
-
albumArt: track.album_art,
96
-
albumArtist: track.album_artist,
97
-
albumUri: track.album_uri,
98
-
artistUri: track.artist_uri,
99
-
}))
100
-
);
101
// eslint-disable-next-line react-hooks/exhaustive-deps
102
}, [tracksResult.data, tracksResult.isLoading, tracksResult.isError, did]);
103
···
88
return;
89
}
90
91
+
setTopTracks(tracksResult.data);
0
0
0
0
0
0
0
0
0
92
// eslint-disable-next-line react-hooks/exhaustive-deps
93
}, [tracksResult.data, tracksResult.isLoading, tracksResult.isError, did]);
94
+7
-7
apps/web/src/pages/song/PopularAlbums/PopularAlbums.tsx
···
24
id: string;
25
title: string;
26
artist: string;
27
-
album_art: string;
28
-
artist_uri: string;
29
uri: string;
30
}[];
31
artist: string;
···
51
<FlexGridItem {...itemProps} key={album.id}>
52
{album.uri && (
53
<Link to={`/${album.uri.split("at://")[1]}`}>
54
-
<SongCover cover={album.album_art} size={230} />
55
</Link>
56
)}
57
-
{!album.uri && <SongCover cover={album.album_art} size={230} />}
58
{album.uri && (
59
<Link to={`/${album.uri.split("at://")[1]}`}>
60
<LabelMedium className="!text-[var(--color-text)]">
···
63
</Link>
64
)}
65
{!album.uri && <LabelMedium>{album.title}</LabelMedium>}
66
-
{album.artist_uri && (
67
-
<Link to={`/${album.artist_uri.split("at://")[1]}`}>
68
<LabelSmall className="!text-[var(--color-text-muted)]">
69
{album.artist}
70
</LabelSmall>
71
</Link>
72
)}
73
-
{!album.artist_uri && (
74
<LabelSmall className="!text-[var(--color-text-muted)]">
75
{album.artist}
76
</LabelSmall>
···
24
id: string;
25
title: string;
26
artist: string;
27
+
albumArt: string;
28
+
artistUri: string;
29
uri: string;
30
}[];
31
artist: string;
···
51
<FlexGridItem {...itemProps} key={album.id}>
52
{album.uri && (
53
<Link to={`/${album.uri.split("at://")[1]}`}>
54
+
<SongCover cover={album.albumArt} size={230} />
55
</Link>
56
)}
57
+
{!album.uri && <SongCover cover={album.albumArt} size={230} />}
58
{album.uri && (
59
<Link to={`/${album.uri.split("at://")[1]}`}>
60
<LabelMedium className="!text-[var(--color-text)]">
···
63
</Link>
64
)}
65
{!album.uri && <LabelMedium>{album.title}</LabelMedium>}
66
+
{album.artistUri && (
67
+
<Link to={`/${album.artistUri.split("at://")[1]}`}>
68
<LabelSmall className="!text-[var(--color-text-muted)]">
69
{album.artist}
70
</LabelSmall>
71
</Link>
72
)}
73
+
{!album.artistUri && (
74
<LabelSmall className="!text-[var(--color-text-muted)]">
75
{album.artist}
76
</LabelSmall>
+18
-32
apps/web/src/pages/song/Song.tsx
···
56
const Song = () => {
57
const { did, rkey } = useParams<{ did: string; rkey: string }>();
58
59
-
let uri = `${did}/app.rocksky.scrobble/${rkey}`;
60
61
if (window.location.pathname.includes("app.rocksky.song")) {
62
-
uri = `${did}/app.rocksky.song/${rkey}`;
63
}
64
if (window.location.pathname.includes("app.rocksky.scrobble")) {
65
-
uri = `${did}/app.rocksky.scrobble/${rkey}`;
66
}
67
68
const scrobbleResult = useFeedByUriQuery(uri);
69
const songResult = useSongByUriQuery(uri);
70
71
const artistTracksResult = useArtistTracksQuery(
72
-
songResult.data?.artistUri?.split("at://")[1] ||
73
-
scrobbleResult.data?.artistUri?.split("at://")[1],
74
5
75
);
76
const artistAlbumResult = useArtistAlbumsQuery(
77
-
songResult.data?.artistUri?.split("at://")[1] ||
78
-
scrobbleResult.data?.artistUri?.split("at://")[1],
79
10
80
);
81
···
100
id: string;
101
title: string;
102
artist: string;
103
-
album_art: string;
104
-
artist_uri: string;
105
uri: string;
106
}[]
107
>([]);
···
162
id: x.id,
163
title: x.title,
164
artist: x.artist,
165
-
albumArtist: x.album_artist,
166
-
albumArt: x.album_art,
167
uri: x.uri,
168
-
scrobbles: x.play_count,
169
-
albumUri: x.album_uri,
170
-
artistUri: x.artist_uri,
171
}))
172
);
173
// eslint-disable-next-line react-hooks/exhaustive-deps
···
182
return;
183
}
184
185
-
setTopAlbums(
186
-
artistAlbumResult.data.map((x) => ({
187
-
id: x.id,
188
-
title: x.title,
189
-
artist: x.artist,
190
-
album_art: x.album_art,
191
-
artist_uri: x.artist_uri!,
192
-
uri: x.uri,
193
-
}))
194
-
);
195
// eslint-disable-next-line react-hooks/exhaustive-deps
196
}, [artistAlbumResult.data, artistAlbumResult.isLoading]);
197
···
315
</div>
316
</Group>
317
318
-
{
319
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
320
-
song?.tags.map((tag: any) => (
321
-
<Tag closeable={false} kind={KIND.purple}>
322
-
{tag}
323
-
</Tag>
324
-
))
325
-
}
326
327
{song?.lyrics && (
328
<>
···
56
const Song = () => {
57
const { did, rkey } = useParams<{ did: string; rkey: string }>();
58
59
+
let uri = `at://${did}/app.rocksky.scrobble/${rkey}`;
60
61
if (window.location.pathname.includes("app.rocksky.song")) {
62
+
uri = `at://${did}/app.rocksky.song/${rkey}`;
63
}
64
if (window.location.pathname.includes("app.rocksky.scrobble")) {
65
+
uri = `at://${did}/app.rocksky.scrobble/${rkey}`;
66
}
67
68
const scrobbleResult = useFeedByUriQuery(uri);
69
const songResult = useSongByUriQuery(uri);
70
71
const artistTracksResult = useArtistTracksQuery(
72
+
songResult.data?.artistUri || scrobbleResult.data?.artistUri,
0
73
5
74
);
75
const artistAlbumResult = useArtistAlbumsQuery(
76
+
songResult.data?.artistUri || scrobbleResult.data?.artistUri,
0
77
10
78
);
79
···
98
id: string;
99
title: string;
100
artist: string;
101
+
albumArt: string;
102
+
artistUri: string;
103
uri: string;
104
}[]
105
>([]);
···
160
id: x.id,
161
title: x.title,
162
artist: x.artist,
163
+
albumArtist: x.albumArtist,
164
+
albumArt: x.albumArt,
165
uri: x.uri,
166
+
artistUri: x.artistUri,
167
+
scrobbles: x.playCount,
0
168
}))
169
);
170
// eslint-disable-next-line react-hooks/exhaustive-deps
···
179
return;
180
}
181
182
+
setTopAlbums(artistAlbumResult.data);
0
0
0
0
0
0
0
0
0
183
// eslint-disable-next-line react-hooks/exhaustive-deps
184
}, [artistAlbumResult.data, artistAlbumResult.isLoading]);
185
···
303
</div>
304
</Group>
305
306
+
{// eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+
song?.tags.map((tag: any) => (
308
+
<Tag closeable={false} kind={KIND.purple}>
309
+
{tag}
310
+
</Tag>
311
+
))}
0
0
312
313
{song?.lyrics && (
314
<>