[備忘録] lambda(Node.js) から spotify api を呼んでみた


# はじめに
[[備忘録] AWS CDKでRDS,RDS Proxy,Lambda を構築](https://qiita.com/m-chan/items/f56e9300351f68fdda08) までで、AWS CDKの始め方、lambda, apigateway, RDS の設定と使い方を学んできた。  
次にこれらを使って独自のwebサービスを開発したい！  
そこで、lambda　から spotify api を呼んでプレイリストを作成してみることにした。


# 実施したこと
1. spotify の設定(app の作成および Client ID / Client secret の取得)
1. リフレッシュトークンの取得
1. アクセストークンの取得
1. プレイリスト の作成

### 1. spotifyの設定
spotify のアカウントは既に持っていたので、そのアカウントでログインした状態で [dashboard](https://developer.spotify.com/dashboard) にアクセス、有効化した。  
すると下記画面に遷移するので、 create app からアプリを作成していく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280374/87db3315-629f-a898-db4c-5cda45f726b2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280374/8b78905e-5a29-aba2-2fbd-5570aa7acc41.png)
app の作成完了。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280374/c9c1c8a7-9389-6131-1961-a98dbbdee3f6.png)
Settings　に移動すると、Client ID と Client secret を確認できたのでひかえておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280374/f2c44e69-8d9d-bbe0-7f60-59715d2fbc7c.png)

### 2. リフレッシュトークンの取得

spotify api を呼ぶためにはアクセストークンが必要だが、60分の有効期限があるため、リフレッシュトークンからアクセストークンを取得してapiを呼ぶようにする。  

リフレッシュトークンを取得するために先に認可コードが必要になる。  
下記をブラウザで実行すると、リダイレクト先でURLからcodeを取得できる。

```
https://accounts.spotify.com/authorize?client_id=${client_id}&response_type=code&redirect_uri=${redirect_uri}&scope=playlist-modify-private%&state=state
```
ただし、[scope](https://developer.spotify.com/documentation/web-api/concepts/scopes) は利用する api によって異なる。今回はプレイリストの作成をしたいので、playlist-modify-private とした。

次に、```https://accounts.spotify.com/api/token```　にPOSTリクエストを送信する。  
ここで、ヘッダーには、
- Content-type 
    - application/x-www-form-urlencoded
- Authorization
    - Basic ```${client_id}:${client_secret}```をbase64変換したもの

リクエストボディには、
- redirect_uri
    - 1. spotifyの設定で指定したもの
- grant_type
    - authorization_code
- code
    - 先ほど取得したもの

を指定する。
レスポンスボディに resfresh_token が含まれているので、それをこの先利用する。

### 3. アクセストークンの作成

下記でAPIを呼ぶために必要なアクセストークンを取得する。  
```app-lambda.ts
    const requestBody = JSON.parse(event.body);
    const authorizationCode = requestBody.code;

    // encode
    const client_id = '*****'
    const client_secret = '*****'
    const client = Buffer.from(`${client_id}:${client_secret}`).toString('base64');

    // get access token
    const requestBodyForAccessToken = new URLSearchParams();
    requestBodyForAccessToken.append('grant_type', 'refresh_token');
    if(secureParam['refresh-token']) {
      requestBodyForAccessToken.append('refresh_token', *****);
    }
    
    const responstForAccessToken = await axios.post('https://accounts.spotify.com/api/token', requestBodyForAccessToken, { headers: { 
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: `Basic ${client}` 
    }});
    const accessToken = responstForAccessToken.data.access_token;
```

### 4. プレイリストの作成

ここから本題の spotify api を呼んでみたいと思う。
プレイリスト名とそのプレイリストに追加する曲名、アーティスト名は、lambdaのリクエストボディから取得する。

```app-lambda.ts
export const handler = async (
    event: APIGatewayProxyEvent
    ): Promise<APIGatewayProxyResult> => {
    
    const RESPONSE_HEADERS = {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type,Authorization,access-token",
    };
    const response = {
        statusCode: 200,
        headers: RESPONSE_HEADERS,
        body: JSON.stringify({
          message: 'SUCCESS'
        })
    };
    var tracks: any[] = [];
    
    if(event.body){
        const requestBody = JSON.parse(event.body);
        tracks = requestBody.tracks;
        playlistName = requestBody.playlistName;
    } else {
        response.statusCode = 400;
        response.body = JSON.stringify({
            message: 'INVALID REQUEST'
        });
        return response;
    }
    try{
        // search for item
        const trackIds: string[] = [];
        
        for await (const [index, track] of tracks.entries()) {
          // create url
          const searchItemUrl = encodeURI(`${spotifyBaseUrl}/search?q=remaster%20track:${track.song}%20artist:${track.artistName}&type=track`);
          const item = await axios.get(searchItemUrl, { headers: {
            'Authorization': `Bearer ${accessToken}`
          }});
          var skip: boolean = false;
          item.data.tracks.items.forEach((item: any) => {
            if(!skip){
              if(item.name.includes(track.song)) {
                trackIds.push(item.id);
                skip = true;
              }
            }
          })
        }
        if(trackIds.length === 0) {
          response.statusCode = 403;
          response.body = JSON.stringify({
            message: 'NOT FOUND'
          });
          return response;
        }

        // create playlist
        const playlistData = {
          "name": playlistName,
          "public": false,
        };
        const playlist = await axios.post(`${spotifyBaseUrl}/users/${userId}/playlists`, playlistData, { headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type':  'application/json',
        }});
        const playlistId = playlist.data.id;
        
        // add items to playlist
        const uris: string[] = [];
        trackIds.forEach((track) => {
          uris.push(`spotify:track:${track}`);
        });
    
        await axios.post(`${spotifyBaseUrl}/playlists/${playlistId}/tracks`,  { uris }, { headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type':  'application/json',
        }});
    } catch(error) {
        console.log(error);
        response.statusCode = 500;
        response.body = JSON.stringify({
          message: 'INTERVAL SERVER ERROR'
        });
        return response;
      }
    return response;
};
    
```


# 最後に
参考にした文献を記す。
- https://qiita.com/t_furu/items/5dc277a06ac3fb2c1f47
- https://www.npmjs.com/package/@types/spotify-web-api-node
- https://qiita.com/okakam/items/2ad69ea7a84e75cc9c05
- https://developer.spotify.com/documentation/web-api/tutorials/getting-started
- https://zenn.dev/noriyu/articles/484570e025c8f9