-
Notifications
You must be signed in to change notification settings - Fork 24
/
playNext.js
165 lines (147 loc) · 5.54 KB
/
playNext.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// @ts-check
// NAME: Play Next
// AUTHOR: daksh2k
// DESCRIPTION: Add the current track to the top of the queue
/// <reference path="../shared/types/spicetify.d.ts" />
(function playNext() {
if (!(Spicetify.CosmosAsync && Spicetify.Queue && Spicetify.ContextMenu && Spicetify.URI)) {
setTimeout(playNext, 200);
return;
}
// Add context menu option to tracks, playlist and albums
function uriTrack(uris) {
if (uris.length > 1) {
return true;
}
const uriObj = Spicetify.URI.fromString(uris[0]);
switch (uriObj.type) {
case Spicetify.URI.Type.TRACK:
case Spicetify.URI.Type.PLAYLIST:
case Spicetify.URI.Type.PLAYLIST_V2:
case Spicetify.URI.Type.ALBUM:
case Spicetify.URI.Type.LOCAL:
return true;
}
return false;
}
function getToken() {
return Spicetify.Platform.AuthorizationAPI._tokenProvider._token.accessToken;
}
const fetchAlbum = async (uri) => {
const arg = uri.split(":")[2];
const res = await Spicetify.CosmosAsync.get(`wg://album/v1/album-app/album/${arg}/desktop`);
const items = [];
for (const disc of res.discs) {
const availables = disc.tracks.filter((track) => track.playable);
items.push(...availables.map((track) => track.uri));
}
return items;
};
const fetchAlbumFromWebApi = async (url) => {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
const albumDetails = await res.json();
return [
...albumDetails.items.map((item) => item.uri),
...(!!albumDetails.next ? await fetchAlbumFromWebApi(albumDetails.next) : []),
];
};
const fetchPlaylist = async (uri) => {
const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}/rows`, {
policy: { link: true },
});
return res.rows.map((item) => item.link);
};
function shuffle(array) {
let counter = array.length;
if (counter <= 1) return array;
const first = array[0];
// While there are elements in the array
while (counter > 0) {
// Pick a random index
let index = Math.floor(Math.random() * counter);
// Decrease counter by 1
counter--;
// And swap the last element with it
let temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
// Re-shuffle if first item is the same as pre-shuffled first item
while (array[0] === first) {
array = shuffle(array);
}
return array;
}
/**
* Main entry point when clicked from context menu.
* @param uris List of uris for uniquesly identifying tracks/playlist/album etc.
* */
async function fetchAndAdd(uris) {
const uri = uris[0];
const uriObj = Spicetify.URI.fromString(uri);
if (
uris.length > 1 ||
uriObj.type === Spicetify.URI.Type.TRACK ||
uriObj.type === Spicetify.URI.Type.LOCAL
) {
addToNext(uris);
return;
}
let tracks = [];
switch (uriObj.type) {
case Spicetify.URI.Type.PLAYLIST:
case Spicetify.URI.Type.PLAYLIST_V2:
tracks = await fetchPlaylist(uri);
break;
case Spicetify.URI.Type.ALBUM:
tracks = await fetchAlbumFromWebApi(
`https://api.spotify.com/v1/albums/${uri.split(":")[2]}/tracks?limit=50`,
);
break;
}
if (Spicetify.Player.getShuffle()) tracks = shuffle(tracks);
addToNext(tracks);
}
/**
* Add the selected track to the top of the queue and update the queue
* @param uris List of uris/tracks to add.
*/
async function addToNext(uris) {
const uriObjects = uris.map((uri) => ({ uri }));
const queue = await Spicetify.Platform.PlayerAPI.getQueue();
if (queue.queued.length > 0) {
//Not empty, add all the tracks before first track
const beforeTrack = {
uri: queue.queued[0].uri,
uid: queue.queued[0].uid,
};
await Spicetify.Platform.PlayerAPI.insertIntoQueue(uriObjects, {
before: beforeTrack,
})
.then(() => Spicetify.showNotification("Added to Play Next"))
.catch((err) => {
console.error("Failed to add to queue", err);
Spicetify.showNotification("Unable to Add! Check Console.", true);
});
} else {
//if queue is empty, simply add to queue
await Spicetify.addToQueue(uriObjects)
.then(() => Spicetify.showNotification("Added to Play Next"))
.catch((err) => {
console.error("Failed to add to queue", err);
Spicetify.showNotification("Unable to Add! Check Console.", true);
});
}
}
// Add option to Context Menu
new Spicetify.ContextMenu.Item(
"Play Next",
fetchAndAdd,
uriTrack,
`<svg role="img" height="16" width="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3.67 8.67h14V11h-14V8.67zm0-4.67h14v2.33h-14V4zm0 9.33H13v2.34H3.67v-2.34zm11.66 0v7l5.84-3.5-5.84-3.5z"></path></svg>`,
).register();
})();