/**
* Spotify Custom Component - Spotify Remote Control (Questscript V2)
*
* @UseApp {SPOTIFY}
*
* License:
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the “Software”), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Changelog:
* v0.1 Initial version
*
*/
enableDebugMode();
// Spotify Fetch Helper
async function fetchWebApi(endpoint, method, body) {
const res = await fetch(`https://api.spotify.com${endpoint}`, {
method,
...(body && { body: JSON.stringify(body) }),
});
return await res.json();
}
return defineCustomItem(
(Questmate) => {
Questmate.registerView("ITEM_CONFIG_VIEW", async ({ useConfigData }) => {
const [playlistId, setPlaylistId] = useConfigData("playlistId", null);
const [deviceId, setDeviceId] = useConfigData("deviceId", null);
return {
components: [
{
id: "playlist",
type: "dropdown",
title: "Playlist",
value: playlistId,
optionNoun: "Playlist",
optionPluralNoun: "Playlists",
onSelect: setPlaylistId,
getOptions: async () =>
(await playlistsDataSource.retrieve()).map((playlist) => ({
label: playlist.name || "Untitled Playlist",
value: playlist.id,
})),
},
{
id: "device",
type: "dropdown",
title: "Device",
value: deviceId,
optionNoun: "Device",
optionPluralNoun: "Devices",
onSelect: setDeviceId,
getOptions: async () =>
(await devicesDataSource.retrieve()).map((device) => ({
label: device.name || "Untitled Device",
value: device.id,
})),
},
],
};
});
Questmate.registerView("ITEM_RUN_VIEW", async ({ useConfigData }) => {
const [playlistId] = useConfigData("playlistId", null);
const [deviceId] = useConfigData("deviceId", null);
const components = [];
const playlistData = await fetchWebApi(
`/v1/playlists/${playlistId}`,
"GET"
);
components.push({
id: "play",
type: "button",
title: `🎶 ${playlistData.name}`,
buttonLabel: "Play",
onPress: async () => {
const playResponse = await fetchWebApi(
`/v1/me/player/play?device_id=${deviceId}`,
"PUT",
{
context_uri: `spotify:playlist:${playlistId}`,
}
);
console.log(JSON.stringify(playResponse));
},
});
return { components };
});
const playlistsDataSource = Questmate.registerDataSource({
id: "playlists",
initialData: [],
refreshInterval: 120,
fetcher: () => async () => {
const playlistsData = await fetchWebApi("/v1/me/playlists", "GET");
const { items } = playlistsData;
if (!items) {
return {
error: {
message: "Failed to retrieve playlists. No items key found.",
details: { playlistsData },
},
};
}
return { data: items };
},
});
const devicesDataSource = Questmate.registerDataSource({
id: "devices",
initialData: [],
refreshInterval: 120,
fetcher: () => async () => {
const devicesData = await fetchWebApi("/v1/me/player/devices", "GET");
const { devices } = devicesData;
if (!devices) {
return {
error: {
message: "Failed to retrieve devices. No devices key found.",
details: { devicesData },
},
};
}
return { data: devices };
},
});
}
);