SpotifyのNowPlayingを投稿しステータスに設定もしてくれるSlackbotを作ってみた

この記事は[ニフクラ](https://www.nifcloud.com/)等を提供している、[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct)の14日目の記事です。

前日は@George22eさんの[ヴァイオリンのヴィブラートをtartiniを使って可視化し、改善に役立てた話](https://rallyho.hatenablog.com/entry/2022/12/13/093553)でした。 
ヴァイオリンのヴィブラートの改善にソフトを使うという発想は今までにない気がして、とても素敵だなと思いました。
自分も学生の頃に合唱をしていたので、歌声のヴィブラートも可視化できるのか気になるのでtartiniを使ってみたいです。

# はじめに
みなさんは普段音楽を聴くときにサブスクリプションサービスは利用しますか？自分は[Spotify](https://www.spotify.com)をよく活用しています。
Spotifyでは毎年年末に[Spotifyまとめ](https://www.spotify.com/jp/wrapped/)というその年の利用状況を振り返る内容を見ることができ、その情報によると2022年は`32,186分 = 536.433333時間 = 22.351388875日`もの時間Spotifyで音楽を聴いていたようです😯

https://twitter.com/earth429/status/1602151420886716416

そんなSpotifyですが、Web APIが公開されています。
https://developer.spotify.com/documentation/web-api/

(このSpotify APIを知ったのは、出身校の後輩が使っていたからでした。一度どこかで使ってみたいなとネタを考えていました。)
https://github.com/NNCT18J/over-sound-lightup

また、自分は新入社員なのですが、新人研修内の開発演習でSlackbotを作成しました。その経験を活かし、Spotify APIをSlackbotから使ってみよう！と思いました。

そこで、`spotify slackbot`でGoogle検索してみると・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/31f93ebf-c81b-4b57-1eb4-d36aa04abd29.png)

ふむふむ、よさそうな記事があるな
ﾘﾝｸﾎﾟﾁｰ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/d9c94ce3-0081-7cb0-7015-2924184e7b1d.png)
**あれ？弊社のアドベントカレンダーの記事じゃん！！！**

https://qiita.com/sasachi1231/items/ca61b1deafd8e5baeca3

という経緯で運命を感じたので、上記の@sasachi1231さんの記事を参考にしながら、自分が現在聴いている曲をSlackに投稿するSlackbotを作ってみようと思いました。
同じものを作るのではつまらないので
* Boltを利用する
* Slackのステータスに現在再生している曲を表示する

この2つを目標にやってみることにしました。

# Bolt
> Bolt is the swiftest way to start programming with the Slack Platform in JavaScript, Python, or Java.
> https://api.slack.com/tools/bolt

> Bolt は Slack アプリ開発のための公式フレームワークです。JavaScript (Node.js)、Python 、Java で利用することができます。
> https://api.slack.com/lang/ja-jp


BoltはSlack公式のフレームワークのため、現在Slackbotを新しく作ろうとする方は主にBoltを利用されるかと思います。
また、Slack APIのページにもBoltからAPIを利用するメソッドが記述していたりします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/3f32a97c-3744-add2-c66f-1f99631aa887.png)
(BoltでAPIにアクセスするメソッドが記述している。)
https://api.slack.com/methods/users.profile.set

# Slackのステータスに表示
Spotify APIとSlackの活用方法について調べていたとき、Slackのステータスに現在聴いている曲をリンクするという記事を見つけました。
https://medium.com/do-you-write-a-blog/slackのstatusにspotifyの曲-アーティスト情報をリンクする-9b94b17fb05c

Slackのステータスに表示しておけば、いつでも自分の聴いている曲をアピールできるな！と思ったので、こちらも実装したいと思いました。

# botの動作フローと構成
こちらは参考にした[元記事](https://qiita.com/sasachi1231/items/ca61b1deafd8e5baeca3)の内容をもとに書くと
`Slash Commandsでbotを呼び出す → Spotifyで現在再生している曲をAPI経由で取得してくる → 取得結果から曲名とアーティスト名をPost → Slackのステータスに設定する`

という形になります。botにリプライ(メンション)を飛ばす形式からスラッシュコマンド(Slash Commands)でbotを呼び出す形式にしました。
これは、Boltでスラッシュコマンドを利用するサンプルやスラッシュコマンドに関するドキュメントがしっかりあったり、利用しやすそうなのでスラッシュコマンドで呼び出してみることにしました。
~~botにメンションをして呼び出す方法がわからなくて変えたわけではないです。~~

スラッシュコマンドについて詳しくは、以下を確認してください。
https://slack.dev/bolt-python/ja-jp/concepts#commands
https://api.slack.com/interactivity/slash-commands

botはPythonで動かし、以下のパッケージを使用しています。

* slack_bolt
    * 前述したBoltフレームワーク。
        * https://github.com/slackapi/bolt-python
* spotipy
    * PythonでSpotify APIを利用するためのライブラリ。
        * https://github.com/spotipy-dev/spotipy
    * PythonでSpotify APIを利用する記事を検索するとだいたいこれを使っている。
    * Spotify APIの[公式ドキュメント内](https://developer.spotify.com/documentation/web-api/libraries/)にも記載がある

# 作り方
:::note warn
Slackbotの追加方法とSpotify APIの利用開始方法の詳細説明は省略します
:::

* Slackbotの追加方法について参考
    * Bolt入門ガイドの「アプリを作成する」
        * https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#create-an-app

* Spotify APIの利用開始方法について参考
    * https://developer.spotify.com/dashboard/ 
        * ここから利用開始
    * 公式ドキュメント Web API Tutorialの「Set Up Your Account」と「Register Your Application」
        * https://developer.spotify.com/documentation/web-api/quick-start/
    * 元記事にも書いてあります
        * https://qiita.com/sasachi1231/items/ca61b1deafd8e5baeca3#spotify%E5%81%B4%E3%81%AE%E8%A8%AD%E5%AE%9A

## Slackbotの事前設定
botを動かすにあたって必要な設定について記述します。

### botに必要なスコープ
アプリのOAuth & Permissionsから設定します。
詳しくはBolt入門ガイドの「トークンとアプリのインストール」を参照してください。
https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#tokens-and-installing-apps

ここで、User OAuth TokenとBot User OAuth Tokenを取得できるようになります。
また、Socket Modeも有効にしてください。

**Bot Token Scopes**
* [commands](https://api.slack.com/scopes/commands)
    * スラッシュコマンドを利用するために必要

**User Token Scopes**
* [users.profile:write](https://api.slack.com/scopes/users.profile:write)
    * Slackのステータスを変えるのに必要

### スラッシュコマンドの作成
アプリのSlash Commandsから新しいコマンドを作成できます。コマンドをアプリを実行する前に作成しておきましょう。
詳しくはSlack APIのスラッシュコマンドに関するページの「Creating a Slash Command」を参照してください。
https://api.slack.com/interactivity/slash-commands#creating_commands

* Command
    * /np
        * コマンド名はお好みで大丈夫です。(今回は元記事で`@botの名前 np`で実行しているためこちらにしました)    

## Spotify APIの事前設定
クライアントIDとクライアントシークレットを入手するためにアプリ登録をしたと思います。
https://developer.spotify.com/documentation/general/guides/authorization/app-settings/

登録したアプリのEDIT SETTINGSからRedirect URIsを次のように設定してください。
`https://example.com/callback/`

(このURLはユーザー認証時に利用するのですが、EDIT SETTINGSの例として挙げられていたexample.comでも問題なく動作したのでこの設定にしています。)

## サーバの準備

### 実行環境
実行環境はPythonが動けば基本問題ないですが、Dockerだと問題が生じます(後述)
自分はニフクラコンピューティングのサーバー上のUbuntuで実行を行いました。

```:実行環境
(.venv) student@ubuntu:~/working/spotipy$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04 LTS
Release:        20.04
Codename:       focal
(.venv) student@ubuntu:~/working/spotipy$ pip3 --version
pip 20.0.2 from /home/student/working/spotipy/.venv/lib/python3.8/site-packages/pip (python 3.8)
(.venv) student@ubuntu:~/working/spotipy$ pip3 list
Package            Version  
------------------ ---------  
slack-bolt         1.16.0   
slack-sdk          3.19.5   
spotipy            2.21.0   
(一部省略)
```

### Python仮想環境の準備
Bolt入門ガイドの「プロジェクトをセットアップする」を参考にしながら行います。
https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#setting-up-your-project

まず、作業用のディレクトリを未作成であれば作成し、ディレクトリ内に移動します。(ディレクトリ名は任意です。)
```:作業用ディレクトリ作成
student@ubuntu:~/working$ mkdir spotipy
student@ubuntu:~/working$ cd spotipy
```

次に、[Python仮想環境](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment)を用いて仮想環境を作成し、アクティブにします。
```:Python仮想環境を作成
student@ubuntu:~/working/spotipy$ python3 -m venv .venv
student@ubuntu:~/working/spotipy$ source .venv/bin/activate
```

こちらを実行すると、shellに(.venv)と表示されるようになります。
`which python3`を実行すると、仮想環境がアクティブになっていることが確認できます。
```:Python仮想環境がアクティブであるか確認
(.venv) student@ubuntu:~/working/spotipy$ which python3
/home/student/working/spotipy/.venv/bin/python3
```

### トークンの環境変数への保存
こちらもBolt入門ガイドの「プロジェクトをセットアップする」を参考にしながら行います。
https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#setting-up-your-project

SlackアプリやSpotify APIの各ページから以下のトークンをコピーします

**Slackアプリ**
* User OAuth Token (xoxpから始まる, OAuth & Permissionsページから確認可能)
* Bot User OAuth Token (xoxbから始まる, OAuth & Permissionsページから確認可能)
* App-Level Tokens（xappから始まる, Basic Informationページから確認可能）
* User ID
    * 取得方法参考: https://scheduling.help.receptionist.jp/slack-id
    * 自分のユーザーIDをコピーしてください。

**Spotify API**
* ユーザー名(https://www.spotify.com/jp/account/overview/ から確認可能)
* Client ID(https://developer.spotify.com/dashboard/ で作成したAPP内から確認可能)
* Client Secret(Client IDと同様のページからSHOW CLIENT SECRETを押すと確認可能)

これらを環境変数に保存します。環境変数に保存することで、プログラムから安全に利用することができます。
```:トークンを環境変数に保存
(.venv) student@ubuntu:~/working/spotipy$ export SLACK_USER_TOKEN=(User OAuth Token)
(.venv) student@ubuntu:~/working/spotipy$ export SLACK_BOT_TOKEN=(Bot User OAuth Token)
(.venv) student@ubuntu:~/working/spotipy$ export SLACK_APP_TOKEN=(App-Level Tokens)
(.venv) student@ubuntu:~/working/spotipy$ export SLACK_USER_ID=(User ID)
(.venv) student@ubuntu:~/working/spotipy$ export SPOTIFY_USER_NAME=(ユーザー名)
(.venv) student@ubuntu:~/working/spotipy$ export SPOTIFY_CLIENT_ID=(Client ID)
(.venv) student@ubuntu:~/working/spotipy$ export SPOTIFY_CLIENT_SECRET=(Client Secret)
```

### Pythonパッケージのインストール
こちらもBolt入門ガイドの「プロジェクトをセットアップする」を参考にしながら行います。
https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#setting-up-your-project

pipを用いてBoltとspotipyのインストールを行います。
```:必要なパッケージのインストール
(.venv) student@ubuntu:~/working/spotipy$ pip3 install slack_bolt
(.venv) student@ubuntu:~/working/spotipy$ pip3 install spotipy
```

これでプログラムを動かす事前準備は完了です。

## プログラム
以下がSlackbotを動かすためのPythonプログラムのソースコードです。

```python:app.py
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import spotipy

# ボットトークンとソケットモードハンドラーを使ってアプリを初期化します
# https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#setting-up-your-projectを参照
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

@app.command("/np")
def np_command(ack, respond, command, body, client, say):
    ack()

    # Slackアプリ関連
    my_user_id = os.environ.get("SLACK_USER_ID")

    # Spotify API関連
    user_name = os.environ.get("SPOTIFY_USER_NAME")
    scope = "user-read-currently-playing"
    client_id = os.environ.get("SPOTIFY_CLIENT_ID")
    client_secret = os.environ.get("SPOTIFY_CLIENT_SECRET")
    redirect_uri ="https://example.com/callback/"

    token = spotipy.oauth2.SpotifyOAuth(client_id=client_id, 
                                        client_secret=client_secret, 
                                        redirect_uri=redirect_uri, 
                                        scope=scope, 
                                        username=user_name, 
                                        open_browser=False)

    spotify = spotipy.Spotify(auth_manager=token)
    current_track = spotify.current_user_playing_track()

    if body["user_id"] == my_user_id:
        if type(current_track) == dict:
            # NowPlayingの内容をbotがチャンネルに投稿
            say("NowPlaying: " + current_track["item"]["name"] + " - " + 
            current_track["item"]["artists"][0]["name"] + "\n" +
            current_track["item"]["external_urls"]["spotify"] + "\n" +
            current_track["item"]["album"]["images"][1]["url"]
            )

            # Slackのステータスを変更
            client.users_profile_set(token=os.environ.get("SLACK_USER_TOKEN"), profile={
                "status_text": current_track["item"]["name"] + " - " + current_track["item"]["artists"][0]["name"],
                "status_emoji": ":headphones:",
                "status_expiration": "0"
            })
            respond("ステータスに設定しました！")
        else:
            respond("何も再生してないよ")
    else:
        respond("きみ誰？")

# アプリを起動します
# https://slack.dev/bolt-python/ja-jp/tutorial/getting-started#setting-up-your-projectを参照
if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
```



# 実行
では、作成したプログラムを実行してみましょう。shellから以下のコマンドで実行します。
```:プログラムの実行コマンド
(.venv) student@ubuntu:~/working/spotipy$ python3 app.py
```

プログラムを実行したら、botをチャンネルに招待し、チャンネルに`/np`と送信してみましょう。
初回実行時はbotからすぐレスポンスは返されず、実行したshell上にこんなメッセージが出ると思います。

```
(.venv) student@ubuntu:~/working/spotipy$ python3 app.py 
⚡️ Bolt app is running!
Go to the following URL: https://accounts.spotify.com/authorize?・・・(以下省略)
Enter the URL you were redirected to:
```
「Go to the following URL」と表示されたURLにアクセスすると、以下のようなSpotifyのログイン画面が表示されると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/48001674-d693-cc9a-c0ab-c1ab6741be5c.png)

こちらにログインすると、アプリケーションの認証を行ってよいか確認されるので、自分の作成したアプリで間違いなければ同意しましょう。
すると、以下のような https://example.com/ にアクセスした時と同じ画面が表示されると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/aa0be4be-0c04-a815-e90b-47a9a7285776.png)

このときブラウザのアドレスバーのURLが重要で、以下のような内容になっているはずです。こちらをコピーします。
```:ユーザー認証用のURL
https://example.com/callback/?code=・・・(以下省略)
```
このURLをshellにペーストし、Enterキーを押すと、spotipyのユーザー認証が成功します。
すると、app.pyと同じディレクトリ内にキャッシュが生成されます。
```:キャッシュの確認
(.venv) student@ubuntu:~/working/spotipy$ ls -la
total 20
drwxrwxr-x 3 student student 4096 Dec  8 16:25 .
drwxrwxr-x 4 student student 4096 Dec  8 15:20 ..
-rw-rw-r-- 1 student student 2962 Dec  8 16:21 app.py
-rw-rw-r-- 1 student student  485 Dec  8 16:25 .cache-(Spotifyのユーザー名)
drwxrwxr-x 6 student student 4096 Dec  8 15:28 .venv
```
このキャッシュがあれば、次回からは認証は不要です。再度プログラムを実行し直し、`/np`と送信してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/e2ba4e8b-49b2-e0bc-a597-0ca7a8c8fdbf.png)

すると、botからレスポンスが返され・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/cc6b351c-7c61-9db4-768b-7eefe3927929.png)
**自分が今Spotifyで聴いている曲が表示されました！**
SlackはSpotifyのリンクを自動的にプレビューしてくれるので、それっぽく見えます。また、アルバムアートワーク(いわゆるCDジャケット)の画像URLもSpotify APIで取得して、プレビューとして表示されるのもいい感じ。
(プレビュー部分はアルバムアートワークの画像がQiitaに載せると著作権等の問題がある可能性があるためSlack上で削除しています。)

また、「ステータスに設定しました！」というメッセージがbotから返されているので、Slackの自分のステータスを確認すると・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/f898a576-d236-38a2-7a2b-03fe94f437aa.png)
**ちゃんと設定できている！**
これで自分の聴いている曲をいつでもアピールできますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/06787fde-7717-b1b7-2e15-1fb56f32aec3.png)
(自分のSlackの表示名右横のemojiにマウスカーソルを合わせても見れます。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537400/64cdf8be-bf18-512d-dcb6-2f185c8ed9aa.png)
(他のメンバーがダイレクトメッセージを送ろうとするときにも見れます。)

# つまったところ
何箇所か開発中につまったところがあるので、参考になればと思い記載しておきます

## spotipyでのユーザー認証
今回、Spotifyで今聴いている曲を取得するため、Spotify APIのユーザー認証を行う必要があります。
このユーザー認証が少し厄介で、spotipyであれば一度ブラウザに飛んでSpotifyにログインを行い、アプリを認証する必要があります。
Pythonを実機のローカル環境などで実行した場合は(WSLなど)自動的にブラウザが開くのですが、Dockerやクラウド上のサーバーなどだと自動では開きません。(これが前述したDockerだと問題が生じますと言った理由です)
もっとうまいやり方があり、Docker内でも実行できるのかも知れませんが、今回は諦めてPythonの仮想環境を用意しました。(これは今後の課題でもあります。)
解決方法についてですが、ユーザー認証の際に、`spotipy.oauth2.SpotifyOAuth`というメソッドを用いています。
https://spotipy.readthedocs.io/en/2.21.0/#spotipy.oauth2.SpotifyOAuth
こちらのパラメータで、`open_browser`というものがあります。ドキュメントによると
>open_browser: Optional, whether or not the web browser should be opened to
>authorize a user

とのことだったので、こちらの値はデフォルトでTrueですがFalseにすることで、初回実行時にshell上にSpotifyのログイン画面が表示されるURLが表示されます。
(Trueだと`Enter the URL you were redirected to:`のみしか表示されない。)

## users.profile.setでemojiの指定の仕方
Slackのステータス変更にSlack APIの`users.profile.set`メソッドを使用しました。
https://api.slack.com/methods/users.profile.set#status

こちらのメソッドでemojiを指定するときに、最初エラーが返されました。
```:users.profile.set API methodを使用したときのエラー
slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/users.profile.set)
The server responded with: {'ok': False, 'error': 'profile_status_set_failed_not_emoji_syntax'}
```

これはstatus_emojiの値を絵文字の名前のみにしていたことで起こりました。
```:間違ったstatus_emojiの指定方法
"status_emoji": "headphones",
```

ですが、実際には
```:正しいstatus_emojiの指定方法
"status_emoji": ":headphones:",
```
emojiの名前の前後に`:`が必要でした。地味にひっかかりやすい気がするので注意。
(ちなみに、記事を作成しているときにドキュメントのexampleをよく見返すと
`"status_emoji": ":train:",`と指定していたので、ドキュメントをちゃんと読む重要性を再度実感しました・・・)

# おわりに
Slack上にSpotifyで現在聴いている曲を簡単に共有できるようになりました。
また、Slackのステータスに現在聴いている曲を設定することで、botが参加しているチャンネルにいないメンバーに向けてもアピールできるようになりました。
現在は手動でステータスに設定していますが、定期的にSpotify APIを呼び出し、現在聴いている曲を自動で設定する機能や他のメンバーも使えるようにエンハンスしていきたいと考えています。

この記事は[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct) の 14日目の記事でした。

明日は @toyo_muraさんの「ブラウザからワンクリックでサーバ上にdockerコンテナを立ち上げる」です。
自分も業務でDockerをよく使うため、ブラウザからワンクリックでコンテナを立ち上げる方法があれば便利だなと思うのでとても楽しみです。