Spotifyで現在再生中の曲をAWS IoTボタンでプレイリストに追加する

この記事は、[ケーシーエスキャロット Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kcscarrot) の16日目の記事です。

音楽を聴きながら仕事をしていると、ふと気になる曲が流れることがありますよね。
私はSpotifyを使用しているのですが、曲名を確認する程度なら[SpotMenu](https://kmikiy.github.io/SpotMenu/)が便利ですが、
プレイリストに登録する作業は、毎回Spotifyアプリを開いてポチポチしなければなりません。
仕事中には結構な手間ですし、集中が途切れる要因になります。

# 概要
そこで、AWS Lambdaを使って現在再生中の曲をプレイリストに追加するコードを書き、
IoTボタンと組み合わせて、1プッシュで実行出来るようにしたいと思います。
Spotifyのアカウントは取得済みを前提とします。
また、ローカルの開発端末はmacを想定としていますので、それ以外の方は適宜読み替えてください。

# SpotifyのAPIについて
SpotifyのAPIは、検索やトラック情報取得といったグローバルなAPIは
Client IDとClient Secretを使ってトークンを取得するだけで使用出来るのですが、
自分が今再生している曲を取得するようなプライベートな情報を扱うAPIは、OAuth認証が必要です。
つまり、インタラクティブな処理が必要ということになります。

# どう実現するか
Spotifyの[Authorization Code Flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow)を見ると、
`GET https://accounts.spotify.com/authorize`で返却されたログイン画面に、
ユーザー名とパスワードでログインし、リダイレクト先URLのパラメータの値を取得する必要があります。
ここの部分をLambdaで実現するために、Lambda上でseleniumを使い、headless chromeを動かすことにします。

# 手順
* Client IDとClient Secretの取得
* Redirect URIsの設定
* 登録先プレイリストの作成とIDの取得
* Lambdaの設定
  * serverless-chromiumのインストール
  * chromedriverのインストール
  * Redirect URIsの設定
  * 登録先プレイリストの作成とIDの取得
  * lambda_function.pyの作成

## Client IDとClient Secretの取得
[Spotify for Developers](https://developer.spotify.com/dashboard/)からログインし、Dashboardページにある`CREATE A CLIENT ID`から、必要な情報を入力して作成します。

* `App or Hardware Name`
    * 適当で大丈夫です
* `App or Hardware Description`
    * 適当で大丈夫です
* `What are you building?`
    * 適当で大丈夫です、`I don't know`にでもチェック付けておきましょう

次ページで3つのチェックボックスにチェックをつけ、SUBMITをクリックすると、
作成したアプリケーションのページに遷移します。

`SHOW CLIENT SECRET`をクリックし、Client IDとClient Secretを控えておきましょう。


## Redirect URIsの設定
先ほどのアプリケーション画面にある`EDIT SETTINGS`を開き、
`Redirect URIs`に`http://localhost`を記載し、ADDしてからSAVEしましょう。
ここに登録されたuriのみが、後ほど出てくる認証時のリダイレクト先のパラメータで指定出来ます。

## 登録先プレイリストの作成とIDの取得
Lambda実行時に登録するプレイリストをアプリなどから作成します。
IDの取得方法については、以下の通りです。
途中ログイン画面が入った場合は適宜ログインしてください。

1. Spotify for Developersのコンソールから[プレイリスト取得API](https://developer.spotify.com/console/get-current-user-playlists/)を開く
1. OAuth Tokenの`GET TOKEN`をクリック
1. `Required scopes for this endpoint:`に記載されている`playlist-read-private`にチェック
1. `REQUEST TOKEN`をクリック
1. `TRY IT`をクリック
1. 出力された結果から、対象プレイリストの"id"の値を取得

## Lambdaの設定
今回は最終的にデプロイパッケージのサイズが50MB程になるため、コンソールからはソースが編集出来ません。
なので、ローカルでソースを編集し、zipファイルにしてS3にアップロードしたファイルを使用します。
Lambdaでheadless chromeを使用する方法については、[AWS Lambda上のheadless chromeをPythonで動かす](https://qiita.com/nabehide/items/754eb7b7e9fff9a1047d)を参考にさせて頂きましたので、そちらをご参照ください。
こちらでは引用して記載します。

### serverless-chromiumのインストール
```sh
mkdir -p bin/
curl -SL https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-37/stable-headless-chromium-amazonlinux-2017-03.zip > headless-chromium.zip
unzip headless-chromium.zip -d bin/
rm headless-chromium.zip
```

### chromedriverのインストール
```sh
curl -SL https://chromedriver.storage.googleapis.com/2.37/chromedriver_linux64.zip > chromedriver.zip
unzip chromedriver.zip -d bin/
rm chromedriver.zip
```

### 必要なパッケージのインストール
現状、`pip install spotipy -t ./`でインストールすると、
current_user_playing_track()がないため以下のようにインストールします。

```sh
pip install --upgrade git+https://github.com/plamere/spotipy.git -t ./
pip install selenium -t ./
```

### lambda_function.pyの作成
いよいよ本題です。
seleniumを使ってログイン処理をし、`authorization_code`を取得した後、
それを使ってAPIからアクセストークンを取得し、現在再生中の曲をプレイリストに入れる処理になります。

```py
from spotipy import Spotify
from spotipy.oauth2 import SpotifyOAuth
from urllib.parse import urlencode, urlparse, parse_qs, quote
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

AUTHORIZE_URL = 'https://accounts.spotify.com/authorize'
CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxxxx'
CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxx'
LOGIN_USERNAME = 'xxxxxxxxxxxxxxxxxxxxxxx'
LOGIN_PASSWORD = 'xxxxxxxxxxxxxxxxxxxxxxx'
PLAYLIST_ID = 'xxxxxxxxxxxxxxxxxxxxxxx'
SCOPE = 'user-read-currently-playing playlist-modify-public playlist-modify-private'
LOCALHOST = 'http://localhost'

def lambda_handler(event, context):

    options = webdriver.ChromeOptions()
    options.binary_location = './bin/headless-chromium'

    # headlessで動かすために必要なオプション
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    options.add_argument('--window-size=1280x1696')
    options.add_argument('--disable-application-cache')
    options.add_argument('--disable-infobars')
    options.add_argument('--no-sandbox')
    options.add_argument('--hide-scrollbars')
    options.add_argument('--enable-logging')
    options.add_argument('--log-level=0')
    options.add_argument('--single-process')
    options.add_argument('--ignore-certificate-errors')
    options.add_argument('--homedir=/tmp')
    driver = webdriver.Chrome('./bin/chromedriver', chrome_options=options)

    params = {
        'client_id': CLIENT_ID,
        'response_type': 'code',
        'redirect_uri': LOCALHOST,
        'scope': SCOPE
    }

    # ヘッドレスブラウザでログイン処理をし、アクセストークン生成に必要なcodeの値を取得する
    driver.get('{}?{}'.format(AUTHORIZE_URL, urlencode(params, quote_via=quote)))
    WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.ID, 'login-username')))
    driver.find_element_by_id('login-username').send_keys(LOGIN_USERNAME)
    driver.find_element_by_id('login-password').send_keys(LOGIN_PASSWORD)
    driver.find_element_by_id('login-button').click()
    code = parse_qs(urlparse(driver.current_url).query).get('code')[0]

    # アクセストークンの取得
    so = SpotifyOAuth(client_id=CLIENT_ID, client_secret=CLIENT_SECRET, redirect_uri=LOCALHOST, scope=SCOPE)
    res_token = so.get_access_token(code=code)
    access_token = res_token['access_token']

    # 現在再生中の曲を取得してプレイリストに登録する
    sp = Spotify(auth=access_token)
    track_info = sp.current_user_playing_track()
    if track_info is None:
        return 'No music is being played.'
    response = sp.user_playlist_add_tracks(LOGIN_USERNAME, PLAYLIST_ID, [track_info['item']['uri']])

    return response
```
あとは作成したリソースをzipファイルにし、S3に配置したURLをLambdaで指定して実行するだけです。
なお、AWS IoTボタンとの連携は[こちら](https://dev.classmethod.jp/cloud/aws/aws-iot-enterprise-button/)を参考に実施しました。

# 実行してみる
処理時間を計ってみると、スムーズに終わって約20秒弱かかったので、Lambdaのタイムアウト値を1分程度に設定しておくのが良さそうです。
また、メモリも128MBだと少々心許ないので192MBに設定しておくことをオススメします。

# 最後に
曲のラストで「この曲いい！」と思ってポチッと実行しても、登録が間に合わない可能性があるのがネックですが、それは今後の課題として改善出来たらなと思っています。
少しざっくりとした説明になってしまったところもあるので、質問等があればコメントお願いします。
それでは皆さま職場での良き音楽ライフをお過ごしください！