Skip to content

Commit 7bd45f3

Browse files
committed
tidal/core: implement adding to / removing from playlists
1 parent 3a61323 commit 7bd45f3

File tree

10 files changed

+335
-50
lines changed

10 files changed

+335
-50
lines changed

src/main/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,23 @@ app.whenReady().then(async () => {
210210
}
211211
});
212212

213+
ipcMain.on('removeFromPlaylist', (ev, data) => {
214+
// data is {song, playlist}
215+
player.removeFromPlaylist(data.song, data.playlist).then(() => {
216+
console.log("e");
217+
player.uncachePlaylist(data.playlist)
218+
mainWindow.webContents.send('reloadPlaylist', data.playlist);
219+
});
220+
});
221+
222+
ipcMain.on('addToPlaylist', (ev, data) => {
223+
// data is {song, playlist}
224+
player.addToPlaylist(data.song, data.playlist).then(() => {
225+
player.uncachePlaylist(data.playlist)
226+
});
227+
});
228+
229+
213230
ipcMain.on('mpdGetSongs', (ev, data) => {
214231
mpd.listSongs(data).then((res) => {
215232
mainWindow.webContents.send('updateMpdSongList', res);

src/main/player/player.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,23 +232,75 @@ let tidalGotPlaylists = false;
232232
let spotifyGotPlaylists = false;
233233

234234
let playlists: Array<PlaylistDataShort> = [];
235+
let tidalPlaylists: Array<PlaylistDataShort> = [];
236+
let spotifyPlaylists: Array<PlaylistDataShort> = [];
235237

236238
async function updatePlaylists() {
239+
console.log("updating playlists")
240+
237241
if (!tidalGotPlaylists) {
238242
tidal.getPlaylists().then((res) => {
239-
playlists = playlists.concat(res);
243+
tidalPlaylists = res;
244+
playlists = tidalPlaylists.concat(spotifyPlaylists);
240245
mainWindow.webContents.send('updatePlaylists', playlists);
241246
tidalGotPlaylists = true;
242247
}).catch(() => { });
243248
}
244249

245250
if (!spotifyGotPlaylists) {
246251
spotify.getPlaylists().then((res) => {
247-
playlists = playlists.concat(res);
252+
spotifyPlaylists = res;
253+
playlists = tidalPlaylists.concat(spotifyPlaylists);
248254
mainWindow.webContents.send('updatePlaylists', playlists);
249255
spotifyGotPlaylists = true;
250256
}).catch(() => { });
251257
}
258+
259+
mainWindow.webContents.send('updatePlaylists', playlists);
260+
}
261+
262+
function uncachePlaylist(playlist: PlaylistDataShort) {
263+
if (playlistDatas.has(playlist.source + "_" + playlist.identifier))
264+
playlistDatas.delete(playlist.source + "_" + playlist.identifier);
265+
}
266+
267+
async function removeFromPlaylist(song: SongDataShort, playlist: PlaylistDataShort): Promise<boolean> {
268+
return new Promise<boolean>(
269+
async (res) => {
270+
if (playlist.source == "mpd")
271+
res(false); // TODO:
272+
else if (playlist.source == "tidal")
273+
tidal.removeFromPlaylist(song, playlist).then((e) => {
274+
res(true);
275+
}).catch((e) => { res(false); });
276+
else if (playlist.source == "spotify")
277+
res(false);
278+
else
279+
res(false);
280+
}
281+
);
282+
}
283+
284+
async function addToPlaylist(song: SongDataShort, playlist: PlaylistDataShort): Promise<boolean> {
285+
return new Promise<boolean>(
286+
async (res) => {
287+
if (song.source != playlist.source) {
288+
res(false);
289+
return;
290+
}
291+
292+
if (playlist.source == "mpd")
293+
res(false); // TODO:
294+
else if (playlist.source == "tidal")
295+
tidal.addToPlaylist(song, playlist).then((e) => {
296+
res(true);
297+
}).catch((e) => { res(false); });
298+
else if (playlist.source == "spotify")
299+
res(false);
300+
else
301+
res(false);
302+
}
303+
);
252304
}
253305

254306
async function getPlaylistData(playlist: PlaylistDataShort): Promise<PlaylistData> {
@@ -295,4 +347,7 @@ export default {
295347
initPlayer,
296348
updatePlaylists,
297349
getPlaylistData,
350+
removeFromPlaylist,
351+
addToPlaylist,
352+
uncachePlaylist,
298353
};

src/main/player/tidal/tidal.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,10 @@ async function getPlaylistData(data: PlaylistDataShort): Promise<PlaylistData> {
530530
);
531531

532532
response2.then((x) => {
533+
534+
if (x.headers.has("etag"))
535+
pd.etag = x.headers.get("etag");
536+
533537
x.json().then((itemJson) => {
534538
if (!itemJson.items)
535539
return;
@@ -941,6 +945,89 @@ async function getLyrics(identifier: string): Promise<LyricData> {
941945
});
942946
}
943947

948+
async function removeFromPlaylist(song: SongDataShort, playlist: PlaylistDataShort): Promise<boolean> {
949+
return new Promise<boolean>(
950+
async (res) => {
951+
if (!(song.index >= 0)) {
952+
console.log("tidal: removeFromPlaylist: invalid idx");
953+
res(false);
954+
}
955+
956+
getPlaylistData(playlist).then(async (x) => {
957+
if (x.etag == null) {
958+
console.log("playlist: no etag");
959+
res(false);
960+
return;
961+
}
962+
963+
const response =
964+
await fetch(
965+
TIDAL_URL + TIDAL_PLAYLISTS_API_ENDPOINT + "/" + playlist.identifier + "/items/" + song.index + "?order=INDEX&orderDirection=ASC&countryCode=" + TIDAL_COUNTRY_CODE + "&locale=en_US&deviceType=BROWSER",
966+
{
967+
method: 'DELETE',
968+
headers: {
969+
"Accept": "*/*",
970+
"authorization": "Bearer " + TIDAL_SESSION_TOKEN,
971+
"Host": "listen.tidal.com",
972+
"if-none-match": "" + x.etag,
973+
}
974+
}
975+
);
976+
977+
res(true);
978+
}).catch((e) => {
979+
console.log(e);
980+
res(false);
981+
});
982+
}
983+
)
984+
}
985+
986+
async function addToPlaylist(song: SongDataShort, playlist: PlaylistDataShort): Promise<boolean> {
987+
return new Promise<boolean>(
988+
async (res) => {
989+
getPlaylistData(playlist).then(async (x) => {
990+
if (x.etag == null) {
991+
console.log("playlist: no etag");
992+
res(false);
993+
return;
994+
}
995+
996+
const bodey = new URLSearchParams();
997+
bodey.append("onArtifactNotFound", "FAIL");
998+
bodey.append("onDupes", "FAIL");
999+
bodey.append("trackIds", "" + song.identifier);
1000+
1001+
const response =
1002+
await fetch(
1003+
TIDAL_URL + TIDAL_PLAYLISTS_API_ENDPOINT + "/" + playlist.identifier + "/items?countryCode=" + TIDAL_COUNTRY_CODE + "&locale=en_US&deviceType=BROWSER",
1004+
{
1005+
method: 'POST',
1006+
headers: {
1007+
"Accept": "*/*",
1008+
"authorization": "Bearer " + TIDAL_SESSION_TOKEN,
1009+
"Host": "listen.tidal.com",
1010+
"if-none-match": x.etag,
1011+
},
1012+
body: bodey,
1013+
}
1014+
);
1015+
1016+
response.json().then((data) => {
1017+
console.log(data);
1018+
res(true);
1019+
}).catch((e) => {
1020+
console.log(e);
1021+
res(false);
1022+
});
1023+
}).catch(e => {
1024+
console.log(e);
1025+
res(false);
1026+
});
1027+
}
1028+
)
1029+
}
1030+
9441031
export default {
9451032
performSearch,
9461033
play,
@@ -956,4 +1043,6 @@ export default {
9561043
getArtistData,
9571044
getAlbumData,
9581045
getLyrics,
1046+
removeFromPlaylist,
1047+
addToPlaylist,
9591048
}

src/main/types/playlistData.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export interface PlaylistData {
88
identifier: string;
99
albumUrl?: string;
1010
gatheredAt?: number;
11+
etag?: string;
1112
};

src/preload/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ if (process.contextIsolated) {
7676
loginState: (callback) => ipcRenderer.on('loginState',
7777
(_event, value) => callback(value)
7878
),
79+
reloadPlaylist: (callback) => ipcRenderer.on('reloadPlaylist',
80+
(_event, value) => callback(value)
81+
),
7982
})
8083
} catch (error) {
8184
console.error(error)

src/renderer/src/components/view/pages/Playlists.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import PageTitle from "./shared/PageTitle.svelte";
33
import { type PlaylistDataShort } from "../../../../../main/types/playlistDataShort";
44
import PlaylistList from "./shared/PlaylistList.svelte";
5-
import { currentPage } from "../../state/sharedState.svelte";
5+
import { changePageTo, currentPage } from "../../state/sharedState.svelte";
66
import SongList from "./shared/SongList.svelte";
77
88
let playlists: Array<PlaylistDataShort> = $state([]);

src/renderer/src/components/view/pages/shared/PlaylistEntry.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<script lang="ts">
22
import { type PlaylistDataShort } from "../../../../../../main/types/playlistDataShort";
3-
import { changePageTo } from "../../../state/sharedState.svelte";
3+
import {
4+
changePageTo,
5+
currentPage,
6+
} from "../../../state/sharedState.svelte";
47
58
let { playlist /* PlaylistDataShort */, alternate, index } = $props();
69

src/renderer/src/components/view/pages/shared/SongEntry.svelte

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import type { PlaylistDataShort } from "../../../../../../main/types/playlistDataShort";
23
import type { SongDataShort } from "../../../../../../main/types/songData";
34
import { changePageTo } from "../../../state/sharedState.svelte";
45
import ArtistEntry from "./songEntry/ArtistEntry.svelte";
@@ -15,6 +16,7 @@
1516
identifier = "",
1617
source = "",
1718
index = 0,
19+
playlist /* PlaylistDataShort */ = null,
1820
queue = false,
1921
songs = [],
2022
} = $props();
@@ -125,6 +127,39 @@
125127
closeMenu();
126128
}
127129
130+
function removeFromPlaylist() {
131+
const songInfo: SongDataShort = {
132+
title: $state.snapshot(title),
133+
artistString: $state.snapshot(artistString),
134+
artists: $state.snapshot(artistList),
135+
album: $state.snapshot(album),
136+
identifier: $state.snapshot(identifier),
137+
source: $state.snapshot(source),
138+
duration: $state.snapshot(duration),
139+
index:
140+
$state.snapshot(index) -
141+
1 /* index for playlist is 0 based, while here it's 1 based for the user. */,
142+
};
143+
console.log("removing index " + ($state.snapshot(index) - 1) + " from playlist");
144+
window.electron.ipcRenderer.send("removeFromPlaylist", {song: songInfo, playlist: $state.snapshot(playlist)});
145+
closeMenu();
146+
}
147+
148+
function addToPlaylist(playlist: PlaylistDataShort) {
149+
const songInfo: SongDataShort = {
150+
title: $state.snapshot(title),
151+
artistString: $state.snapshot(artistString),
152+
artists: $state.snapshot(artistList),
153+
album: $state.snapshot(album),
154+
identifier: $state.snapshot(identifier),
155+
source: $state.snapshot(source),
156+
duration: $state.snapshot(duration),
157+
};
158+
console.log("adding to playlist")
159+
window.electron.ipcRenderer.send("addToPlaylist", {song: songInfo, playlist: playlist});
160+
closeMenu();
161+
}
162+
128163
function clickedAlbum() {
129164
if (!albumId || albumId == "") return;
130165
@@ -201,7 +236,11 @@
201236
opacity={contextMenuOpacity}
202237
removeFromQueueCallback={removeFromQueue}
203238
addAsNextCallback={addAsNext}
239+
addToPlaylistCallback={addToPlaylist}
240+
removeFromPlaylistCallback={removeFromPlaylist}
204241
{queue}
242+
playlist={playlist == null ? false : true}
243+
source={source}
205244
/>
206245
{/if}
207246
</div>

0 commit comments

Comments
 (0)