SpyteFitエンドポイントとSpotify Web APIを使用して、現在再生中のコンポーネントを作成します.


オリジナルポストhttps://www.koenraijer.io/blog/spotify-api
以来ずっとthis implementation of the Spotify API in Next.js (フッターをチェックしてください)、私はsveltekitで同じことをしたいです.その瞬間に誰かが聞いている歌を知っていることは、それ以外のほとんど静的なウェブサイトがかなりダイナミックであると感じさせます.
それで、私はAPIの要求、終点の世界に私の探求を再開しました.Fetching from a public API with SvelteKit Endpoints .
私はHTTPメソッドについて多くを学びましたPostman APIリクエストをテストする簡単な方法として.このポストを通して散らばっているのは、私が物事を理解するのを助けてくれたリソースへの多くのリンクです.彼らは、再学習のための、またはさらなる学習の出発点としてあなたに興味があります.

0.1私たちは何をするつもりですか?


我々は、使用するつもりですAuthorization Code Flow Spotify Web APIへのアクセスを取得します.我々は、我々がこの瞬間に聞いているトラックについてのデータを得るために我々のアクセスを使用します、そして、過去半年の我々の20のトップトラックと同様に.SvelteKit終点を使用して、それを実行しますserverless functions . データは、それらのエンドポイントを使用してフロントエンドにアクセスできます.我々はそれを取得し、きれいにsvelteコンポーネントで表示されます.
これは次のようになります.

0.2 spotifyアプリを作成する


フォローthis tutorial あなた自身のspotifyアプリを作成します.
  • edit settings 追加http://localhost:3000 URIをリダイレクトする.「追加」をクリックし、ポップアップの下部に保存してください.
  • クライアントのIDとクライアントの秘密(最初に表示クライアントの秘密を押す必要があります)、アプリケーションの概要ページに通知します.

    0.3変数の格納.env


    クリエイトsrc > .env . クライアントIDとクライアントの秘密を追加します.名前をプレフィックスするVITE_ ( otherwise you can't import them locally ). 私たちは後でVERCELにこれらの環境変数としてVercelに渡します.
    // .env
    VITE_SPOTIFY_CLIENT_ID=<clientid>
    VITE_SPOTIFY_CLIENT_SECRET=<clientsecret>
    
    我々は、後でこれに戻って来ます.まず、認証コードフローを見てみましょう.

    0.4承認コードフロー


    このイメージは、Spotify Web APIからデータを取得するために必要な手順のかなり明確な概要を提供します.
    必要なことを示します.
  • コードを得るために認可する
  • コードを使用して、リフレッシュトークンを取得します
  • アクセストークンを使用してアクセストークンを取得します
  • アクセストークンを使用してデータを取得します
  • 1.0ユーザ認証を要求する


    GETリクエストを送るべきですhttps://accounts.spotify.com/authorize 次のパラメータを指定します.
  • client_id (私たちはこれを知っています)
  • response_type : これをcode .
  • redirect_uri : http://localhost:3000 (アプリケーションの設定と同じ)
  • scope : あなたが要求する許可を得ることができるものを決定します.チュートリアルでは、使用しますuser-read-currently-playing and user-top-read . スコープはスペースで区切られます.Check out other authorization scopes .
  • 我々は一度だけログインして取得する必要があるのでcode , 我々はちょうど手でリクエストを構築し、我々のブラウザに貼り付けます.次のURLとペーストをブラウザでペーストします(他のすべてのパラメータはすでに埋められています).
    // Browser
    https://accounts.spotify.com/authorize?client_id=<clientid>&response_type=code&redirect_uri=http%3A%2F%2Flocalhost:3000&scope=user-read-currently-playing%20user-top-read
    
    これは以下のようにリダイレクトします.
    http://localhost:3000/?code=<code>
    
    今のコードを保存します.我々は、リフレッシュトークンを得るためにそれを使用します.

    2.0リフレッシュトークンの取得


    次に、コマンドラインツールを使用しますcURL Spotify APIトークンエンドポイントにPOSTリクエストを送信します.我々はそれを使用してリクエストヘッダを渡す-H , と我々のリクエスト本文パラメータを使用して-d チェックアウトthis list of curl options あなたがinteresedされるならば.
    // Terminal
    curl -H "Authorization: Basic <base64 encoded client_id:client_secret>"
    -d grant_type=authorization_code -d code=<code> -d redirect_uri=http%3A
    %2F%2Flocalhost:3000 https://accounts.spotify.com/api/token
    
    以下を追加する必要があります.
  • 我々が以前に得たコード.
  • エーBase64 あなたのクライアントIDと秘密の形式でエンコードされた文字列clientid:clientsecret . あなたは1つを作成することができますhere .
  • コマンドを端末/cmdプロンプトでペーストし、Enterキーを押します.
    あなたが戻って何がJSON形式でリフレッシュトークンを保持する必要があります.
    リフレッシュトークンを追加します.env ファイル名VITE_SPOTIFY_REFRESH_TOKEN . The .env ファイルは次のようになります.
    // .env
    VITE_SPOTIFY_CLIENT_ID=<clientid>
    VITE_SPOTIFY_CLIENT_SECRET=<clientsecret>
    VITE_SPOTIFY_REFRESH_TOKEN=<refreshtoken>
    

    3.0アクセストークンを得る


    私たちのリフレッシュトークンは無期限に有効ですが、私たちはspotify APIからデータを要求するのにそれを使用することができません.そのためには、アクセストークンが必要です.各アクセストークンは1時間有効であるので、プログラムを要求する必要があります.
    だから、作成src > routes > api > access_token.json.js . これはendpoint その輸出はrequest handler function これはPOSTリクエストを正しいSpotifyエンドポイントに送信し、アクセストークンを返します.
    注意:このエンドポイントはPOSTリクエストを送信しますが、結果はGETエンドポイントを通じてアクセスできます.これは最初は混乱した.
    // access_token.json.js
    const client_id = import.meta.env.VITE_SPOTIFY_CLIENT_ID;
    const client_secret = import.meta.env.VITE_SPOTIFY_CLIENT_SECRET;
    const refresh_token = import.meta.env.VITE_SPOTIFY_REFRESH_TOKEN;
    const redirect_uri = "http://localhost:3000/"
    const token_endpoint = `https://accounts.spotify.com/api/token`;
    
    export const get = async () => {
        const { access_token } = await fetch(token_endpoint, {
            method: 'POST',
            headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams({
                grant_type: 'refresh_token',
                refresh_token,
                redirect_uri,
                client_id,
                client_secret,
            })
        }).then(res => res.json());
    
        return {
            body: access_token
        }
    };
    
    次のコードを実行します.
  • 我々が必要とする変数を宣言またはインポートする.env per Vite documentation .
  • async関数のエクスポートpost , エンドポイントのURLは、POSTメソッドで呼び出されます.アクセストークンが必要なときには、あとで正確に行います.
  • 使用するfetch API (バニラJS)はポストリクエストを送信するtoken_endpoint , 必要に応じてheaders リクエストパラメータbody ). あなたは、彼らが正確に「要求アクセストークン」の下にあるものを見つけることができますAuthorization Code Flow .

  • 注:我々はすでに我々のリフレッシュトークンを持っているので、我々は使用するgrant_type: 'refresh_token' の代わりにgrant_type: 'authorization_code' .
  • 用途.then() メソッドdocs ) レスポンスをJSONに変換するには
  • 結果を定数として保存するconst { access_token } . この構文はobject destructuring .
  • 4.0私たちのアクセストークンを使用してSpotify Web APIに要求を送信します


    現在再生中の曲とトップトラックのデータは、おそらく別のエンドポイントでより良いオフです.我々は、我々は現在再生中の曲を更新するたびにトップトラックを必要としない、その逆.
    そこで、2つのエンドポイントを作成します.now_playing.json.js , top_tracks.json.js .

    4.1 NowCount再生エンドポイント


    クリエイトsrc > routes > api > now_playing.json.js , そしてそれは我々が現在遊んでいるトラックにGET要求とリターンデータを送るでしょう.
    
    const now_playing_endpoint = `https://api.spotify.com/v1/me/player/currently-playing`;
    
    export async function get() {
        const {access_token} = await fetch('http://localhost:3000/api/access_token.json').then(res => res.json())
    
        const res = await fetch(now_playing_endpoint, {
            headers: {
                Authorization: `Bearer ${access_token}`
            }
        })
    
        if (res.status === 204 || res.status > 400) {
            return {body: { isPlaying: false }}
        }
    
        const song = await res.json();
        const isPlaying = song.is_playing;
        const title = song.item.name;
        const artist = song.item.artists.map((_artist) => _artist.name).join(', ');
        const album = song.item.album.name;
        const albumImageUrl = song.item.album.images[0].url;
        const songUrl = song.item.external_urls.spotify;
    
        return {
        body: {title, artist, album, isPlaying, albumImageUrl, songUrl},
        }
    }
    
  • からのアクセスaccess_token.json .
  • GETリクエストを送信するnow_playing_endpoint 承認としての我々のAccessResトークンで.
  • 応答が204か400のステータスかどうかを調べます.これは、Spotifyを使用していない場合はAPIが返されます.私たちは、あなたが現在聞いていないと言うことを望むので、我々はエラーメッセージを望みません.エラーメッセージとリターンを傍受します{isPlaying: false} 代わりに.
  • 応答オブジェクトに入り、いくつかの定数を作成します.

  • 注:アーティストのために、我々は複数のアーティスト(複数であることができるように)の上に行き、コンマで区切られたストリングで一緒にそれらに加わる.
  • 作成したすべての変数を返します.
  • これで、新しく作成されたエンドポイントをhttp://localhost:3000/api/now_playing.json .

    4.2 toptrackトラック終点


    クリエイトsrc > routes > api > top_tracks.json.js . このコードは、NowClose再生エンドポイントに非常に似ています.
    
    const top_tracks_endpoint = `https://api.spotify.com/v1/me/top/tracks`;
    
    export async function get() {
        const {access_token} = await fetch('http://localhost:3000/api/access_token.json').then(res => res.json())
    
        const data = await fetch(top_tracks_endpoint, {
            headers: {
                Authorization: `Bearer ${access_token}`
            }
        }).then(res => res.json());
    
        return {
            status: 200,
            body: {top_tracks: data.items},
        }
    }
    
    ライブで見るhttp://localhost:3000/api/top_tracks.json !

    4.3 APIレスポンスに対処することについて


    上のコードではtop_tracks ASdata.items . 私は必要data.items 私が要求から得たものを見たので、次のコードを使用します
    <pre>{JSON.stringify(data, null, 2)}</pre>
    
    チェックアウトJSON.stringify() on MDN を参照してください.

    5.0私たちのnowplayコンポーネントを作成する


    クリエイトsrc > lib > components > NowPlaying.svelte . これはnow_playing.json エンドポイントと我々は現在再生している曲が表示されます.それはスタイルのための風変わりなCSSを使用します.
    <script>
        import {onMount} from 'svelte';
        let song = {}
    
        async function getNowPlaying() {
            song = await fetch('/api/now_playing.json').then(res => res.json())
        }
    
        onMount(async () => {
            getNowPlaying();
        })
    
        setInterval(() => {getNowPlaying()}, 5000);
    </script>
    
    <div class="text-sm w-fit text-gray-500 bg-gray-100 border-2 p-4 pl-5 py-2 rounded-lg">
    
        {#if song.isPlaying}
            <div class="audio relative m-0 p-0 mr-1 top-0 inline bg-gray-500">
                <span></span>
                <span></span>
                <span></span>
            </div>
    
            <span class="leading-0"><span class="font-semibold ml-4"><a href={song.songUrl} rel="noopener noreferrer" target="_blank" class="hover:underline">{song.title}</a></span> - {song.artist}</span>
    
        {:else}
            <svg class="inline h-4 w-4 top-0 -translate-y-[1.5px]" fill="currentColor" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M16 0c-8.803 0-16 7.197-16 16s7.197 16 16 16c8.803 0 16-7.197 16-16s-7.12-16-16-16zM23.36 23.12c-0.319 0.479-0.881 0.64-1.36 0.317-3.76-2.317-8.479-2.797-14.083-1.52-0.557 0.165-1.037-0.235-1.199-0.72-0.156-0.557 0.24-1.036 0.719-1.197 6.084-1.36 11.365-0.803 15.521 1.76 0.563 0.24 0.64 0.88 0.401 1.36zM25.281 18.719c-0.401 0.563-1.12 0.803-1.683 0.401-4.317-2.641-10.88-3.437-15.916-1.839-0.641 0.156-1.365-0.161-1.521-0.803-0.161-0.64 0.156-1.359 0.797-1.52 5.844-1.761 13.041-0.876 18 2.161 0.484 0.24 0.724 1.041 0.323 1.599zM25.443 14.24c-5.125-3.043-13.683-3.36-18.563-1.839-0.801 0.239-1.599-0.24-1.839-0.964-0.239-0.797 0.24-1.599 0.959-1.839 5.683-1.681 15.041-1.359 20.964 2.161 0.719 0.401 0.957 1.36 0.557 2.079-0.401 0.563-1.36 0.801-2.079 0.401z"/></svg>
            <strong>Not playing</strong> - <span class="text-gray-500">Spotify</span>
        {/if}
    
        <br>
    
        <a href="/dashboard" sveltekit:prefetch class="text-xs text-primary hover:underline flex justify-end text-right"><svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 inline-block bottom-0 translate-y-[0.45rem]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
        <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
        </svg>Top tracks</a>
    
    </div>
    
    <style>
    
    .audio span {
        width: 4px;
        height: 100%;
        border-radius: 2px;
        position: absolute;
        bottom: .25rem;
        @apply bg-gray-500;
    }
    
    .audio span:first-of-type {
    
    margin-top: 0;
    
    }
    
    .audio span:nth-child(1) {
    
    animation: animationTest 1.5s infinite ease-in-out;
    
    left: 0;
    
    }
    
    .audio span:nth-child(2) {
    
    animation: animationTest 1.75s infinite ease-in-out;
    
    left: 5px;
    
    }
    
    .audio span:nth-child(3) {
    
    animation: animationTest 2s infinite ease-in-out;
    
    left: 10px;
    
    }
    
    @keyframes animationTest {
    
    0% { height: 2px; }
    
    50% { height: 14px; }
    
    100% { height: 2px; }
    
    }
    
    </style>
    
    私はほとんどこれを投稿したので、あなた自身のカスタムコンポーネントを作成することに触発することができます.あなたはデータを持っているので、何かクールにそれを作る!
  • 用途setInterval 新しいリクエストを5秒ごとにする.私は歌の長さと歌の進行(両方の利用可能)を使用して考えられていたが、それは過度に複雑で、エラーがちだった.
  • スベルトを使う{#if} 構文は、現在再生中の曲を表示するか、“再生しない- spotify”私は曲を演奏しているかどうかに基づいて.
  • 現在再生中の曲は、私が2009年から適応して盗んだ音の波を模倣するCSSアニメーションを持っていますthis codepen . それは、<style タグ.
  • 6.0ダッシュボードを作成する


    クリエイトsrc > routes > dashboard.svelte . このページは私たちのトップトラックからtop_tracks.json エンドポイント.
    <script context="module">
        export const load = async ({fetch}) => {
            const top_tracks = await fetch('./api/top_tracks.json').then(res => res.json())
    
            return {
                props: top_tracks
            }
        }
    </script>
    
    <script>
        export let top_tracks;
    </script>
    
    <h1 class="text-3xl mb-2 text-base">Top tracks</h1>
    <p class="mb-8">Curious what I'm currently jamming to? Here's my top tracks on Spotify updated daily.</p>
    <div>
        {#each top_tracks.top_tracks as track, index}
            <div class="grid grid-cols-4 content-start">
                <div class="col-span-3">    
                    <a class="font-semibold text-lg" href={track.external_urls.spotify} rel="noopener noreferrer" target="_blank">{index + 1}. {track.name}</a>
                    <span>{track.artists[0].name}</span>
                </div>
                <img class="mb-4 mt-2 h-20 w-20" alt="{track.name}'s album cover" height={track.album.images[1].height} width={track.album.images[1].width} src={track.album.images[1].url}>
            </div>
            <hr class="mt-2 mb-8">
        {/each}
    </div>
    

    7.0改善の余地


    私は、このコードが改善されることができるいくつかの方法があると確信しています.これらは私が既に考えたものの一部です.

  • Shadow endpoints あなたがダッシュボードにより多くのものを加えることを計画しないならば、使われることができました.それらを作るload Boilerplateは多くの場合、不要ですが、これまでのドキュメントに追加されていません.
  • 私はおそらくエラー処理部門に欠けている.それは私が詳細について学習する計画です.
  • APIリクエストの数は、expires_in 新しいアクセストークンを要求するときに変数.古いアクセストークンが動作しているかどうかをチェックするほうが良いでしょう.
  • おそらく5秒ごとにAPIリクエストの代わりに、今再生中の曲を得るためのよりエレガントなソリューションがありますか?
  • Svelteの使用{#await} 構文表示"読み込み中"演奏しない代わりに.
  • あなたが私のコードを改善する提案があるならば、誤りを見つけてください、さもなければ、他のコメントをしてください😉.