Spotify APIを使ってLINEBotであなたにおすすめプレイリストを全自動で作ってもらう

9月末にとうとう日本へ上陸したSpotify。
当初はAmazon Prime Musicがあるからいらないやと思っていたけれども、音楽系には珍しくAPIがあるというので弄ってみたいと思い、早速無料プランで申し込んでみた。
APIについていろいろ調べて見るものの日本に入ったばかりが原因なのかあまり情報がなかったのでここに記事として残す。

# Spotify API登録
資料が見つからなかったのでわからないが、Freeプランでも特に制限なくAPIは使えるよう。（違ったらごめんなさい）
[https://developer.spotify.com/](https://developer.spotify.com/) のメニューからMy Appsを選択し、CREATE AN APPを押下して、NameとDescriptionを適当に決めて登録する。
![1.PNG](https://qiita-image-store.s3.amazonaws.com/0/120983/751f445e-940e-c313-de4a-4ea9e30d231f.png)

今回はLINEBotと連携するのでRediret URIsは適当に入れていたのだが、あとでドはまりしたのでそこで説明する。
Client IDと Client Secretをひかえておいて、SAVEを押せば登録が完了だ。

# SDK取得
Spotify APIのPHPSDKがgithubで提供されているので利用させてもらう。
[spotify-web-api-php](https://github.com/jwilsson/spotify-web-api-php)
インストール方法はgithubに記載の方法で全く問題なく完了した。

# 認証
ここが一番苦労した。というか今まで使ってきたAPIはIDとSECRETさえあれば認証できていたのだが...
（実はきちんとAPI仕様書を読めば書いてあるのだ）

と言う事で、僕が認証までたどり着いた方法を書く。

### 失敗例
spotify-web-api-php のdocsは見ずに、海外サイトのサンプル例に従い以下のように認証をしていた。

```php:callback.php
require 'vendor/autoload.php';

$session = new SpotifyWebAPI\Session('CLIENT_ID', 'CLIENT_SECRET', 'REDIRECT_URI');
$api = new SpotifyWebAPI\SpotifyWebAPI();

// Request a access token with optional scopes
$scopes = array(
    'playlist-read-private',
    'playlist-modify-private', 
    'user-read-private',
    'playlist-modify'
);

$session->requestCredentialsToken($scopes);
$accessToken = $session->getAccessToken(); // We're good to go!

// Set the code on the API wrapper
$api->setAccessToken($accessToken);

getLog($api->me())
```
scopesは　[Web API: Using Scopes](https://developer.spotify.com/web-api/using-scopes/) を参考に自分で変更した。
でLINEBotと連携させて認証をさせようとしても、$api->me()で401:認証エラーで落ちる。
なぜなぜ？と何度やっても同じ結果だった。
一時間ほどたった後、spotify-web-api-phpの [Authorization](https://jwilsson.github.io/spotify-web-api-php/authorization.html)を見に行くと、

>Client Credentials Flow
>This method doesn’t require any user interaction and no access to user information are therefore granted.

と書いてある。**no access to user information are therefore granted.**　この方法ではユーザ情報にアクセスできない。Oh My God!
しかも、きちんとAPI仕様書の [Authorization](https://developer.spotify.com/web-api/authorization-guide/) を読めと書いてあるので読む。

何々、一度認証してaccess_tokenを取得して、それを利用してrefreshed access_tokenを取得するのか！
![Authorization-Code-Flow-Diagram.png](https://qiita-image-store.s3.amazonaws.com/0/120983/0aaabbdf-e5d9-ed76-e3a4-e14227dbc379.png)

### 成功例
ということでまずは index.php をWebサーバにおいて認証をしてみることに。
この際、My AppのRediret URIsにはこのindex.phpを指定すること。

```php:index.php
<?php

require 'vendor/autoload.php';

$session = new SpotifyWebAPI\Session('{YOUR_CLIENT_ID}', '{YOUR_CLIENT_SECRET}', '{YOUR_REDIRECt_URI}');
$api = new SpotifyWebAPI\SpotifyWebAPI();

if (isset($_GET['code'])) {
    $session->requestAccessToken($_GET['code']);
    $api->setAccessToken($session->getAccessToken());

    print_r($api->me());
    print_r($session->getAccessToken());
} else {
    header('Location: ' . $session->getAuthorizeUrl(array(
        'scope' => array(
          'playlist-read-private', // プレイリスト取得
          'playlist-modify-private', // プレイリスト変更
          'user-read-private',
          'playlist-modify'
        )
    )));
    die();
}
?>
```
おーー見事に自分の情報が出た。また無事にaccess_tokenも取得することができた。
と言う事でこちらを利用して、今度はrefresh access_token の発行とサーバからアクセスしてみる。

```php:callback.php
use SpotifyWebAPI\Session;
use SpotifyWebAPI\SpotifyWebAPI;

// spotify
$session = new Session('{YOUR_CLIENT_ID}', '{YOUR_CLIENT_SECRET}', '{YOUR_REDIRECt_URI}');
$api = new SpotifyWebAPI();

$refreshToken = '{YOUR_ACCESS_TOKEN}';

$session->refreshAccessToken($refreshToken);

$accessToken = $session->getAccessToken();

print_r($accessToken);
```
こちらも無事access_tokenが出た模様。と言う事でこの方法が正しいかはわからないが、現在も認証は出来ているのでこれで良しとしよう。

# Spotify APIを使って何を作るか考える
Spotify APIを利用したサンプルがあまり見つからなかった。
いろいろ見ていて、お！Spotifyっぽいと思ったのがあったのでそのアイデアを~~パクることに~~オマージュすることに。
ちなみに元ネタは [Spotibot](https://www.spotibot.com/) というページで、Bandやアーティスト名を入力すると、そのアーティストに似たアーティストのプレイリストを自動で作ってくれるものだ。
まずはこちらのページでイメージしてほしい。

あまりLINEBotとは親和性は良くなさそうではあるが（プレイリスト作成に時間がかかる）、LINEBotでもやってみたい事があったのでチャレンジしてみる。

# 全自動プレイリスト作成手順
### フロー
Spotibotには当たり前だがソースや設計書が公開されていないので、どうやったら作れるか考えてみる。
認証の失敗を教訓に、まずはAPI仕様書をじっくり眺めてみる。眺めてみて以下フローで出来そうな事を思いついた。

![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/120983/d5f4bd48-6a81-0fc4-8e72-8df11ae8f3d8.png)


### ソース
ぐわっと書いたのでかなり汚く、エラー処理なども無いが以下の通り。
これで無事思い描いた全自動プレイリストはできそうだ。

```php:callback.php
// Artist
function getArtist($text, $session, $api) {
  // get artist
  $results = $api->search($text, 'artist', array('limit' => 1));

  if(count($results->artists->items) == 0) return null;

  foreach($results->artists->items as $data) {

      $artist = array(
                      'id' => $data->id,
                      'name' => $data->name,
                      'image' => $data->images[0]->url,
                    );
  }


  return count($artist) == 0 ? 'Not Found.' : $artist;
}

function makePlayList($id, $session, $api) {

  // get related artist
  $results = $api->getArtistRelatedArtists($id)->artists;

  // get 15 artist
  $relatedArtists = array();

  $max = count($results) >= 20 ? 20 : count($results);

  for( $i = 0; $i < $max; $i++ ) {
    $related = array(
                   'id' => $results[$i]->id,
                   'name' => $results[$i]->name
    );
    array_push($relatedArtists, $related);
  }

  // get artist top tracks
  $top_tracks = array();

  foreach($relatedArtists as $related) {
    $top = $api->getArtistTopTracks($related['id'], array('country' => 'JP'))->tracks;

    if(count($top) != 0) array_push($top_tracks, $top[0]->id);
  }

  // create new playlist
  $user_id = $api->me()->id;

  $response =$api->createUserPlaylist($user_id, array(
    'name' => 'Your requested Related Artists'
  ));

  $playlist_id = $response->id;

  // add playlist
  $api->addUserPlaylistTracks($user_id, $playlist_id, $top_tracks);

  // get playlist
  $playlist = $api->getUserPlaylist($user_id, $playlist_id);

  $response = array(
    'url' => $playlist->external_urls->spotify,
    'image' => $playlist->images[0]->url,
  );


  return $response;
}

function getEtcArtist($name, $session, $api) {
  // get artist
  $results = $api->search($name, 'artist', array('offset' => 1, 'limit' => 5));

  //
  if(count($results->artists->items) == 0) return null;

  $artists = array();
  foreach($results->artists->items as $data) {

      array_push($artists, array(
                      'id' => $data->id,
                      'name' => $data->name,
                      'image' => count($data->images) != 0 ? $data->images[0]->url : "https://developer.spotify.com/wp-content/uploads/2016/07/icon3@2x.png",
                    )
      );
  }
  return $artists;

}
```

# LINEBot
今まで作ってきたLINEBotはテキストを返すばかりであったが、せっかくインタラクティブなものが用意されているので、Template Message機能で返信してみる。

流れ的には、第一にアーティスト名がリクエストされたら、Spotify APIを利用してアーティスト名と画像を表示してユーザに表示する。OKならばプレイリストを作る、NOならばカルーセルで名称が似ているアーティストをサジェストする。
言葉で説明しにくいのでソースと画像を晒す。

**getArtist**

```php:callback.php
  if($event instanceof TextMessage) {
      $reply_token = $event->getReplyToken();

      // getArtist
      $artist = getArtist($event->getText(), $session, $api);

      if(count($artist) == null) $bot->replyText($reply_token, "Not Found.");

      $actions = array(
        new PostbackTemplateActionBuilder("YES", "*****=1&***=". $artist['id']),
        new PostbackTemplateActionBuilder("NO", "****=-1&***=". $artist['name'])
      );

      $img_url = $artist['image'];

      $button = new ButtonTemplateBuilder($artist['name'], "Is your request ". $artist['name'] . "?", $img_url, $actions);

      $msg = new TemplateMessageBuilder("Your Request". $artist['name'], $button);

      $bot->replyMessage($reply_token, $msg);
    }
```
![Screenshot_2017-01-11-11-16-46-136_jp.naver.line.android.png](https://qiita-image-store.s3.amazonaws.com/0/120983/c027ce35-6520-6c4c-d75b-b1c4203a098b.png)

**makePlaylist**

```php:callback.php
      $query = $event->getPostbackData();

      if ($query) {
          parse_str($query, $data);

          foreach($data as $key => $value) {
            if ($key == '****' && isset($data['****'])) {
                $confirm = $value;
            }
            else if($key == '***' && isset($data['***'])) {
                $id = $value;
            }
            else if($key == '****' && isset($data['****'])) {
                $name = $value;
            }
          }
      }

      $reply_token = $event->getReplyToken();

      // YES
      if($confirm == 1) {
        $playlist = makePlayList($id, $session, $api);

        $actions = array(
          new UriTemplateActionBuilder("Open Spotify", $playlist['url']),
        );

        $img_url = $playlist['image'];

        $button = new ButtonTemplateBuilder('Playlist', 'Please click "Open Spotify".', $img_url, $actions);

        $msg = new TemplateMessageBuilder('Finish generate playlist', $button);

        $bot->replyMessage($reply_token, $msg);
      }
```
![Screenshot_2017-01-11-11-14-29-462_jp.naver.line.android.png](https://qiita-image-store.s3.amazonaws.com/0/120983/e161cd38-b7d8-ffc2-43d2-231a9055f163.png)

![Screenshot_2017-01-11-11-30-27-895_jp.naver.line.android.png](https://qiita-image-store.s3.amazonaws.com/0/120983/29be4081-4b3d-3b1c-b898-3ddb34ecc5e4.png)

![Screenshot_2017-01-11-11-30-35-925_jp.naver.line.android.png](https://qiita-image-store.s3.amazonaws.com/0/120983/132f3cc3-0d64-5db3-b016-4718f678ada2.png)

なかなか満足できるものだったので、公開したかったのだけれどもレスポンスに大きな課題があったのでできずorz

# 今後の課題
プレイリストを作る速度が遅いので以下を見て改良していくつもり。
<TABLE ALIGN=CENTER><TR><TD WIDTH=400px>
 <A HREF="http://qiita.com/yoichiro6642/items/6d4c7309210af20a5c8f" TARGET="_blank"><STRONG>大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ - Qiita</STRONG></A>
 <A HREF="http://b.hatena.ne.jp/entry/http://qiita.com/yoichiro6642/items/6d4c7309210af20a5c8f" TARGET="_blank">
 <IMG BORDER="0" SRC="http://b.hatena.ne.jp/entry/image/http://qiita.com/yoichiro6642/items/6d4c7309210af20a5c8f" BORDER="0" ALT="" /></a>
 <BR>
 大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ - Qiita... 
 </TD>
 <TD WIDTH=150px>
 <IMG STYLE="float:left;margin-right:.5em;" SRC="http://capture.heartrails.com/120x120/cool?http://qiita.com/yoichiro6642/items/6d4c7309210af20a5c8f" ALT="" WIDTH="120" HEIGHT="120" />
 </TD></TR>
</TABLE>

# 追記
非同期で処理するように対応した。


<TABLE ALIGN=CENTER><TR><TD WIDTH=400px>
 <A HREF="http://qiita.com/momochan/items/331e8db7dfc8a5dcdcb0" TARGET="_blank"><STRONG>【続編】Spotify APIを使ってLINEBotであなたにおすすめプレイリストを全自動で作ってもらう - Qiita</STRONG></A>
 <A HREF="http://b.hatena.ne.jp/entry/http://qiita.com/momochan/items/331e8db7dfc8a5dcdcb0" TARGET="_blank">
 <IMG BORDER="0" SRC="http://b.hatena.ne.jp/entry/image/http://qiita.com/momochan/items/331e8db7dfc8a5dcdcb0" BORDER="0" ALT="" /></a>
 <BR>
 【続編】Spotify APIを使ってLINEBotであなたにおすすめプレイリストを全自動で作ってもらう - Qiita... 
 </TD>
 <TD WIDTH=150px>
 <IMG STYLE="float:left;margin-right:.5em;" SRC="http://capture.heartrails.com/120x120/cool?http://qiita.com/momochan/items/331e8db7dfc8a5dcdcb0" ALT="" WIDTH="120" HEIGHT="120" />
 </TD></TR>
</TABLE>