AWS Lambda

誰でも簡単!?AWSを使ったSpotifyプレイリスト自動更新システムのレシピ

はじめに

どうもどうも~ 駆け出しエンジニアの超川です!

いきなりですが皆さんは音楽のサブスクは利用していますか?
私はめちゃんこ使ってます。そこで思ったんですよ

「プレイリスト更新するのもめんどいしいつも同じ曲聞いてね??さすがに飽きたべ」

そんなこんなで今回のお話は「AWSでSpotifyのプレイリストの更新を自動化してみた」です。

Let's cooking~~!

本日の材料

・Spotify Developer  Spotifyのアカウントが必要!!(月額980円)
・AWS Lambda
・API Gateway
・AWS EventBridge
・コマンドラインインターフェース(私はGit Bashなるものを使用しました)

AWSに関しては無料範囲で構築可能

Spotifyを日常的に利用しているのなら実質0円!!
↑世界で一番信用できない言葉

1.Spotify APIのセットアップ

1-1.Spotify Developerアカウントの作成
Spotify DeveloperサイトへGO!!!!!
・右上のloginをクリックし、Spotifyのアカウント情報を入力
(Spotifyの有料会員じゃない人は先に入会しないと利用できませんT-T)
   
1-2.アプリケーションの作成
・右上のアカウント名の場所からダッシュボードにいき、「CREATE AN APP」をクリック
・「アプリケーション名」と「説明」を入力。
リダイレクトURLは仮に「https://example.com/callback」 とします。
(API Gatewayのエンドポイントが作成された後で、再度この設定を更新します。)

・その他はあけておいても大丈夫なので一番下の文のチェックだけ入れて「save」をクリックして作成。

1-3.作成したアプリケーションの詳細ページに移動する
・「Client ID」と「Client Secret」が表示されるので確認↓

※これらは後で使用するため、メモしておきます。

Client ID :ef7f5db1159d49b1b1bd16d5892cae2c

Client secret:3a6f053e94e64e07a5b28171341bc756
(※これはあくまで例としての架空のIDです。自分で作成したアプリのケーションのIDを使ってください!)

2.AWSコンソールにログイン

ここからログインできる

アカウント未作成の方はアカウントから作ろう!! 無料だよ~
(↓YoutubeにもAWSの公式がアカウント作成方法を上げてるので参考になりますよ!)
https://youtu.be/1VCIFN8B72o?si=pS14YogxwJ26UKcA

3.AWS Lambda関数の作成

Spotify APIと連携するためのAWS Lambda関数を作成します。
Lambdaはサーバーが無くてもコードを実行できるため
Spotifyのデータを取得し、プレイリストを更新する処理を行います。

3-1.「AWS Lambda」を検索し、AWS Lambdaのページに移動。

3-2.Lambda関数の作成
・サービスメニューから「Lambda」を選択
・「関数の作成」をクリック
・「一から作成」を選択し、関数名を入力(お好きなものでどうぞ~)
 →ここではtestにしてます
・ランタイムとして「Node.js」を選択し、「関数の作成」をクリックします。

▼Lambda関数コードの記述はコードの中には後々取得するものもあるので後回しにしてます

4.AWS API Gatewayの設定

Spotify APIとLambda関数を連携させるために、API Gatewayを設定します。

4-1.「API Gateway」を検索し、API Gatewayのページに移動します。

4-2.「APIの作成」をクリックしてREST APIの構築をクリック

4-3.新しいAPIを選択しAPI名に好きな名前を付ける、APIの作成をクリック
→例:Spotify

4-4.リソースのタブに移動し、「リソースの作成」をクリック


リソース名に好きなお名前を~(例:callback)
 (リソースパスはそのままでOK!)

4-5.作成したリソース「/callback」を選択し、「メソッドの作成」をクリック


メソッドタイプ :GET
統合タイプ   :Lambda関数
Lambda関数  :先ほど作成したLambda関数のリージョンと関数名を選択
↑これらの設定ができたら「メソッドの作成」をクリック

4-6.APIの設定画面に戻り、「APIのデプロイ」をクリック。
 ・新しいステージを選択(名前は適当にわかりやすいのでOK)
 ・「デプロイ」をクリックしてAPIをデプロイする

4-7.デプロイが完了すると、エンドポイントURLが表示されます。


このURLはSpotify DeveloperでリダイレクトURIとして使用するので、メモしておきましょう!

私の場合だとこれですね↓
https://lcfidlw1y1.execute-api.ap-northeast-1.amazonaws.com/stage

ですが今回は、この配下の「/callback」にてリソースを作成したのでSpotify DeveloperでリダイレクトURIとして使用するエンドポイントは後ろに「/callback」を付けた
https://lcfidlw1y1.execute-api.ap-northeast-1.amazonaws.com/stage/callback」 を使用します!

4-8.さっき作成したSpotifyAPIのリダイレクトURIに登録しましょう!
・Spotify Developer のダッシュボードに戻って作ったアプリをクリック
・右上のSettingsをクリックし、左下のEditで編集モードに

・Redirect URIsの欄に先ほどのAIP GateWayのエンドポイントを追加しましょう!
(例:https://lcfidlw1y1.execute-api.ap-northeast-1.amazonaws.com/stage/callback )

5.Spotify APIからトークンを取得

次に、Spotify APIを使用する際に認証情報として必要なアクセストークンやリフレッシュトークンを取得します!

5-1.認証用のURLを生成する
Spotify APIにアクセスしてトークンを取得するための認証用URLを作成します。以下のフォーマットを使用してください。

https://accounts.spotify.com/authorize
  ?client_id=YOUR_CLIENT_ID         
  &response_type=code
  &redirect_uri=YOUR_REDIRECT_URI
  &scope=user-top-read playlist-modify-public playlist-modify-private

YOUR_CLIENT_ID にはSpotify Developerで取得したClient ID
YOUR_REDIRECT_URI には先ほど設定したリダイレクトURIを指定してください。

↓こんな感じ

https://accounts.spotify.com/authorize?client_id=24c8a3b41141455ba9b256e4b5f484be&response_type=code&redirect_uri=https://lcfidlw1y1.execute-api.ap-northeast-1.amazonaws.com/stage/callback&scope=user-top-read playlist-modify-public playlist-modify-private

※Spotifyのスコープは、スペースで区切る必要があるので、scope=以降は
scope=user-top-read playlist-modify-public playlist-modify-private
このようにスペースを開けなければ失敗します!

5-2.作成した認証URLをWEB上で入力すると、Spotifyのログイン画面に切り替わる

5-3.ログインしたら↓こんな感じの画面になるので、URLの「code=」後を全部コピー


この「code=」以降のすべてが認証コードです!

AQCL61FW6vwXgDxr6AMbtBJUzPKhlj10C7FQYxIQcSY_Xhc-LSUZReoQGhSO_GwSW3Fqd-OU8NFj8FESrZmCfKLeDGhqL31pqK7AUQ4DANwaWA7R5J4D00yhAQFe1Pe5e4xuSdUEh4Qs0lIKaDlBwaQdRdUy8f4qe92jpAPm7KYkXuooIqSnzpOwDtKxD8dZMmS5xprfxAchQKrPMjogpsInFL1-5dU4kXO-6jNbeGhOIcoq4yUultXEDjig-Cc-MU2S3bstVzhdifxBVHmsaNjxrD8jOPMt1ikB0y_IloOYXADYAPxIjlkICWVnmw

※認証コードの有効期限は15分なのでここからは少し駆け足で!!

5-4.認証コードを使ってアクセストークンを取得しよう
「ターミナル」や「Git Bash」などのシェルが実行できるコマンドラインインターフェースを使用します

curl -X POST "https://accounts.spotify.com/api/token" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "grant_type=authorization_code&code=YOUR_AUTH_CODE&redirect_uri=YOUR_REDIRECT_URI&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"

YOUR_AUTH_CODE には認証用URLで取得した認証コード、YOUR_REDIRECT_URI には設定したリダイレクトURI、YOUR_CLIENT_ID とYOUR_CLIENT_SECRET にはSpotify Developerで取得したものを入力します。

↓こんな感じで入力して「Enter」

curl -X POST "https://accounts.spotify.com/api/token" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "grant_type=authorization_code" \
     -d "code=AQCL61FW6vwXgDxr6AMbtBJUzPKhlj10C7FQYxIQcSY_Xhc-LSUZReoQGhSO_GwSW3Fqd-OU8NFj8FESrZmCfKLeDGhqL31pqK7AUQ4DANwaWA7R5J4D00yhAQFe1Pe5e4xuSdUEh4Qs0lIKaDlBwaQdRdUy8f4qe92jpAPm7KYkXuooIqSnzpOwDtKxD8dZMmS5xprfxAchQKrPMjogpsInFL1-5dU4kXO-6jNbeGhOIcoq4yUultXEDjig-Cc-MU2S3bstVzhdifxBVHmsaNjxrD8jOPMt1ikB0y_IloOYXADYAPxIjlkICWVnmw" \
     -d "redirect_uri=https://lcfidlw1y1.execute-api.ap-northeast-1.amazonaws.com/stage/callback" \
     -d "client_id=24c8a3b41141455ba9b256e4b5f484be" \
     -d "client_secret=3a6f053e94e64e07a5b28171341bc756"

▼こう返ってきたら成功!

$ curl -X POST "https://accounts.spotify.com/api/token" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "grant_type=authorization_code" \
     -d "code=AQCL61FW6vwXgDxr6AMbtBJUzPKhlj10C7FQYxIQcSY_Xhc-LSUZReoQGhSO_GwSW3Fqd-OU8NFj8FESrZmCfKLeDGhqL31pqK7AUQ4DANwaWA7R5J4D00yhAQFe1Pe5e4xuSdUEh4Qs0lIKaDlBwaQdRdUy8f4qe92jpAPm7KYkXuooIqSnzpOwDtKxD8dZMmS5xprfxAchQKrPMjogpsInFL1-5dU4kXO-6jNbeGhOIcoq4yUultXEDjig-Cc-MU2S3bstVzhdifxBVHmsaNjxrD8jOPMt1ikB0y_IloOYXADYAPxIjlkICWVnmw" \
     -d "redirect_uri=https://lcfidlw1y1.execute-api.ap-northeast-1.amazonaws.com/stage/callback" \
     -d "client_id=24c8a3b41141455ba9b256e4b5f484be" \
     -d "client_secret=3a6f053e94e64e07a5b28171341bc756"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1083  100   552  100   531   4914   4727 --:--:-- --:--:-- --:--:--  9935{"access_token":"BQD6ROvdB7F5fiR10qNLNrTavvghJd69vcdNutBpZok8xlvBYdytN1qZDH4CMz_GfqUeu3TZHtSfK8Oon7xDX34aZOLNuI400oUS8M0fubtQhoPy_dOM4FKwuHH5uG-3lkth9i7OBiH1UK5TM89wv0KE1vVpsHwDgfdch5khCKn47JAbS8IjgDTsL56GbVoIJHz-OEEekHSQqpMwbSfYYte2uUv5ZrZJZRXf18nLGdDYUxIN699_qXRjFZ687r_EwBJ9su5QQFrOoGJA","token_type":"Bearer","expires_in":3600,"refresh_token":"AQBf-e6nRtpYKnW3nud6ahPLdMT33qjxok0sa7k1Y6BzLLZhCWIb5BSrVBo22-Zy71fVbGldQ4XN0ysEMVnIy-ym_GXSjsMoDBm6C1HxghxHbn3R9gTiVQRG7Sg3DGHFY-w","scope":"playlist-modify-private playlist-modify-public user-top-read"}

最後の"refresh_token"を探して抜き出してコピー!

AQBf-e6nRtpYKnW3nud6ahPLdMT33qjxok0sa7k1Y6BzLLZhCWIb5BSrVBo22-Zy71fVbGldQ4XN0ysEMVnIy-ym_GXSjsMoDBm6C1HxghxHbn3R9gTiVQRG7Sg3DGHFY-w

5-5.次に自動更新したいプレイリストのIDを入手
・プレイリストにいって、上の「・・・」から

シェア→プレイリストのコピーでURLをコピーできます     
https://open.spotify.com/playlist/7fbNQqIkgHWaTy9rr16g5y?si=0aa368a83d44425e
↑こんな感じのURLの「playlist/」の後に続く文字列で、「?」までの部分。このURLの場合だと

7fbNQqIkgHWaTy9rr16g5y

これも使うのでコピー

6.Lambda関数コードの記述

作成したLambda関数の「コードソース」セクションに移動し、以下のコードを貼り付けます。
今回はこちらにできたものをご用意しました。(ChatGPT大先生が)

↓一旦はまんま貼り付けちゃってください~~↓

const https = require('https');

// リフレッシュトークンを使ってアクセストークンを取得する関数
const refreshAccessToken = (refreshToken, clientId, clientSecret) => {
    return new Promise((resolve, reject) => {
        const data = new URLSearchParams({
            grant_type: 'refresh_token',
            refresh_token: refreshToken,
            client_id: clientId,
            client_secret: clientSecret,
        });

        const options = {
            hostname: 'accounts.spotify.com',
            path: '/api/token',
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': data.toString().length
            }
        };

        const req = https.request(options, (res) => {
            let responseData = '';
            res.on('data', (chunk) => { responseData += chunk; });
            res.on('end', () => {
                if (res.statusCode === 200) {
                    resolve(JSON.parse(responseData).access_token);
                } else {
                    reject(new Error(`Failed to refresh access token. Status code: ${res.statusCode}`));
                }
            });
        });

        req.on('error', (e) => { reject(e); });
        req.write(data.toString());
        req.end();
    });
};

// ユーザーの好みに基づいたおすすめ曲を取得する関数
const getRecommendations = (accessToken) => {
    return new Promise((resolve, reject) => {
        const options = {
            hostname: 'api.spotify.com',
            path: '/v1/recommendations?limit=5&seed_genres=j-pop',  // 好みのジャンルで設定
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        };

        const req = https.request(options, (res) => {
            let data = '';
            res.on('data', (chunk) => { data += chunk; });
            res.on('end', () => {
                if (res.statusCode === 200) {
                    resolve(JSON.parse(data));
                } else {
                    reject(new Error(`Failed to fetch recommendations. Status code: ${res.statusCode}`));
                }
            });
        });

        req.on('error', (e) => { reject(e); });
        req.end();
    });
};

// プレイリストの既存トラックを取得する関数
const getPlaylistTracks = (accessToken, playlistId) => {
    return new Promise((resolve, reject) => {
        const options = {
            hostname: 'api.spotify.com',
            path: `/v1/playlists/${playlistId}/tracks`,
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${accessToken}`
            }
        };

        const req = https.request(options, (res) => {
            let data = '';
            res.on('data', (chunk) => { data += chunk; });
            res.on('end', () => {
                if (res.statusCode === 200) {
                    resolve(JSON.parse(data));
                } else {
                    reject(new Error(`Failed to fetch playlist tracks. Status code: ${res.statusCode}`));
                }
            });
        });

        req.on('error', (e) => { reject(e); });
        req.end();
    });
};

// プレイリストにトラックを追加する関数
const addTracksToPlaylist = (accessToken, playlistId, trackUris) => {
    return new Promise((resolve, reject) => {
        const options = {
            hostname: 'api.spotify.com',
            path: `/v1/playlists/${playlistId}/tracks`,
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            }
        };

        const req = https.request(options, (res) => {
            let data = '';
            res.on('data', (chunk) => { data += chunk; });
            res.on('end', () => {
                if (res.statusCode === 201) {
                    resolve(JSON.parse(data));
                } else {
                    reject(new Error(`Failed to add tracks to playlist. Status code: ${res.statusCode}`));
                }
            });
        });

        req.on('error', (e) => { reject(e); });
        req.write(JSON.stringify({ uris: trackUris }));
        req.end();
    });
};

// Lambdaのメイン関数
exports.handler = async (event) => {
    const refreshToken = '入力してね';  // リフレッシュトークン
    const clientId = '入力してね';          // クライアントID
    const clientSecret = '入力してね';  // クライアントシークレット
    const playlistId = '入力してね';      // プレイリストID

    try {
        // 1. リフレッシュトークンを使ってアクセストークンを更新
        const accessToken = await refreshAccessToken(refreshToken, clientId, clientSecret);

        // 2. 既存のプレイリストのトラックを取得
        const existingTracks = await getPlaylistTracks(accessToken, playlistId);
        const existingTrackUris = existingTracks.items.map(item => item.track.uri);

        // 3. ユーザーの好みに基づくおすすめトラックを取得
        const recommendations = await getRecommendations(accessToken);
        const newTrackUris = recommendations.tracks.map(track => track.uri);

        // 4. 既に存在するトラックを除外
        const uniqueTrackUris = newTrackUris.filter(uri => !existingTrackUris.includes(uri));

        // 5. プレイリストに新しいトラックを追加
        if (uniqueTrackUris.length > 0) {
            const result = await addTracksToPlaylist(accessToken, playlistId, uniqueTrackUris);
            return {
                statusCode: 200,
                body: JSON.stringify({ message: 'プレイリストが更新されました!', result })
            };
        } else {
            return {
                statusCode: 200,
                body: JSON.stringify({ message: '新しいトラックはありませんでした。' })
            };
        }
    } catch (error) {
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message })
        };
    }
};

なげぇーーー
いくつかいじるところがあるのでいじっていきましょー!

// Lambdaのメイン関数
exports.handler = async (event) => {
    const refreshToken = '入力してね';  // リフレッシュトークン
    const clientId = '入力してね';          // クライアントID
    const clientSecret = '入力してね';  // クライアントシークレット
    const playlistId = '入力してね';      // プレイリストID

ここの「入力してね」は、今まで入手してきたIDたちを入力

path: '/v1/recommendations?limit=5&seed_genres=j-pop',  // 好みのジャンルで設定

今回はジャンルは「j-pop」にしていますが、この部分を変えれば好きなジャンルに変更できます!
(例:pop, rock, jazz, hip-hop, r-n-b, edm, classical, anime, j-pop, j-rock, techno, house, funk,, reggae, punk, metal)

また、今回は1回で最大5曲まで自動更新という制限をかけてます!
(他のコードについても日本語でちょくちょく解説入れてるのでよかったら見てね~)

入力出来たら「Deploy」を押して完成~~~~

7.毎日定期的に自動更新するようにLambdaを呼び出す仕組みをつくろう

9-1.AWSのコンソール画面に戻り、「EventBridge」で検索して移動

9-2.左の画面からルールを選び、「ルールの作成をクリック」

9-3.名前と説明を入力して、ルールタイプはスケジュールを選択

9-4.EventBridge Schedulerで続行をクリック

9-5.お好きな時間帯でスケジュール式を入力(ここでは毎日正午に自動更新にしています)

9-6.ターゲットとして、先ほど作成したLambda関数を指定し、進む。

9-7.「スケジュールの作成」をクリックで完成!

お疲れ様です~~ 

さいごに

少し長い内容になってしまいましたが、ここまで見てくれた方大好きです!
Lambda関数の内容を変更すると、もっと違った自動更新の仕組みなどを作れるので
よかったら調べて自分好みの味付けにしてみてください!

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA