Spotify Play Button

/**
 * 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 };
        },
      });
  }
);