AppleMusicで聞いている曲をSlackに連携

## 概要
ZOZO AdventCalendar2023、9日目です。

アドベントカレンダーは2回目の参加になります。
だいぶQiita自体からも離れていて久しぶりの投稿で拙い記事ですが、宜しくお願いいたします。

入社からまだ日が浅く、アドベントカレンダーの準備を全くしていなかったので今回は過去遊びで作ったものについて書くラフなものにしたいと思います


### やりたいこと

1. SlackのアイコンをAppleMusicで聞いている曲のジャケットにする
2. Slackのステータスを ♫Nowplaying '{曲名} / {アーティスト名}'にする

### なぜやるか

1. コロナによるフルリモート
    - アフターコロナでリモート勤務がメインになった時に、 新卒や中途の方が入ってくるも対面で会うことがないまま数ヶ月が過ぎ...みたいなことが多々見られ、それによって現場でのメンバーでのコミュニケーションが圧倒的に枯渇した。Slackのプロフィールだけでも何か会話するきっかけにつなげたいと考えました。
1. 会社のSlackのアイコンをいつも決め兼ねていた
    - 「自分の顔写真もなんかなぁ。。」、「奇をてらうのもなんかなぁ。。」、「かといって何も設定しないのもなぁ。。」という優柔不断と自意識過剰な性格を発揮
1. どうせなら技術を使いたい
    - これはエンジニアの性ですかね, 何かを変えようと思った時に、自分の中に何か実りのあるものにしたいと考えました。

上記三点からこんな結論に至りました。


。
。
。
。
。
。

フルリモートになったことによって(といっても前からだけど)基本作業とか集中してやるときにMacでAppleMusicで音楽聞いていることもあるので、そのジャケットの画像をプロフィール画像にできたら面白そう！
さらにステータスでNowPlaying {曲名}/{アーティスト名}とかできたら
会社の人に
「この人この曲聞くんだ！」とか
「こんなアーティスト聞くんだ！」
ってなって話のネタになりそう。

## 大まかなシステム概要
1.　iTunesが起動していて曲が再生中でなければ終了
2.　再生している曲の情報とジャケット写真を取得する
3.　Slackのプロフィールを変更する
4.　次回曲が変わるタイミングを算出してsleep
5.　ループ 

## 手順
### Slackの設定
※Slackのアカウントがない場合はワークスペース及びアカウントを用意してください

#### Slack App作成
##### 1. 作成方法
[Slack App](https://api.slack.com/apps)にアクセスして**Creat an App**ボタンを押下

![スクリーンショット 2023-12-08 17.02.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133737/0fecc05c-d298-87c1-01db-b56bca50b0fd.png)


その後アプリの名前とどのワークスペースで利用するものなのかを入力するフォームが表示されるので任意の名前と使いたいワークスペースを選択してCreate Appボタンをクリック

##### 2. Appに権限を付与する

自分のプロフィールの変更ができるように作成したAppに権限を付与します
Create Appを押して遷移した画面の以下の画像の赤枠の**Permissions**を押下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133737/9fa83741-f9ac-fad9-bd7d-9efa3b5e80c3.png)

さらに, 遷移先で少しスクロールしてScopesの**User Token Scopes**という項目で**users.profile.write**という権限を付与します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133737/0130ca9f-a201-ad0a-05f5-2098017e9d81.png)

上記, 画像のようにSuccess!が表示されたらページトップにスクロールして
**Install App to Workspace**というボタンをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133737/5237af4f-7ae1-b086-7ab5-281e63b5400c.png)

以上でSlack側でのセットアップは完了です

Install App to Workspaceが完了した後の遷移先の以下のが画像の赤枠の**OAuth Access Token**は後で使用するのでコピっておいてください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133737/f8b2a040-184e-e6c8-0a59-5932ae84b9e9.png)




### 環境構築
ということで早速環境構築していきます

```bash
-- まずはpythonを動かすための環境をvenvで構築をする
$ python3 -m venv venv

-- venvを起動
$ source venv/bin/activate

-- venv内にてpip3でappscriptというモジュールをインストール
(venv)$ pip3 install appscript
-- 後々使うので今のうちにインストールしておく
(venv)$ pip3 install requests
```

このappscriptはmacOSのAppleScriptというものpythonで使えるようにしたものくらいに捉えてもらえれば良いと思います。
これに関しての記事が本当に皆無(日本語記事なんて本当に1個か2個くらいしか見つからなかった)なのでAppleScriptをappscriptに力技(想像でこうかな？のトライアンドエラーを繰り返して)Pythonの記法に落とし込みました。
※正直かなり疲れた

### 試しに動かしてみよう
#### 1. ファイル作成

```bash
-- まずはスクリプトを書き込むファイルを生成しましょう
(venv)$ touch applemusic_to_slack.py
```

#### 2. AppleMusicを起動するスクリプトを書き込む

```python
# 以下二行は魔法の言葉みたいなものと捉えてください
from aeosa import appscript
it = appscript.app('Music')

# applemusicを起動するスクリプト
it.activate()
```

#### 3. 実行
ファイルを保存したら実行してみましょう

```python
-- 先ほど保存したファイルを実行する
(venv)$ python3 applemusic_to_slack.py
```

これでapplemusicが起動するようになります

### 再生中の曲のジャケット画像を取得してみる
#### 1. ジャケット画像を格納するディレクトリを作成する

```bash
-- ジャケット画像を格納するディレクトリを作成する
(venv)$ mkdir downloads
```

#### 2. ファイルの中身を書き換える

```python
from aeosa import appscript
import time
import datetime

it = appscript.app('Music')

#iTunesが起動してるかどうか
#起動していなかったら終了
if it.isrunning():
  #曲が再生中かどうか取得する
  state = it.player_state.get()
  #曲が再生中なら情報を取りに行く
  if state.name == 'playing':
    #アートワークのバイナリーデータの取得
    artworkRaw = it.current_track.artworks[1].raw_data.get()
    #保存するディレクトリとファイル名及び拡張子の指定
    fileName = f'./downloads/{it.current_track.name.get()}.jpg'
    #画像を保存する
    with open(fileName, mode='wb') as f:
      f.write(artworkRaw)

#プログラムが終了したことを標準出力する
print("end")
```

#### 3. 実行

ファイルを保存したら好きな音楽を再生して実行してみましょう

```bash
-- 先ほど保存したファイルを実行する
(venv)$ python3 applemusic_to_slack.py
```

これでdownloadsディレクトリに画像が保存されます。

### 完成版

```python
from aeosa import appscript
import requests
import time
import datetime
import json
import os

it = appscript.app('Music')

# Slackのプロフィール画像変更用のAPI
slackSetPhotoApi = ※1
# Slackのステータス変更用のAPI
slackProfileSetAPI = ※2
# APIのトーク
apiToken =  ※3

#iTunesが起動してるかどうか
#起動していなかったら終了

if it.isrunning():
  playingFlg = True
  # playingFlgがTrueの間はループを続ける
  while playingFlg:
    #曲が再生中かどうか取得する
    state = it.player_state.get()
    #曲が再生中なら情報を取りに行く
    if state.name == 'playing':
      #アーティスト名取得
      artist = it.current_track.artist.get()
      #曲名取得
      title = it.current_track.name.get()

      artworkRaw = it.current_track.artworks[1].raw_data.get()
      #保存するディレクトリとファイル名及び拡張子の指定
      fileName = f'./downloads/{it.current_track.name.get()}.jpg'
      #画像を保存する
      with open(fileName, mode='wb') as f:
        f.write(artworkRaw)

      #プロフィール画像変更
      ##プロフィール画像変更用のファイルの生成
      profImgFile = {
          'token': (None, apiToken),
          'image': (open(fileName, 'rb'))
      }


      ##APIリクエスト
      profImgRes = requests.post(slackSetPhotoApi, files=profImgFile)

      ##例外処理を入れないので一応結果を標準出力しておく
      print(profImgRes.text)

      #ステータス変更
      ##ステータス変更用のパラメーターの作成
      statusParam = {
        'token': apiToken,
        'profile': json.dumps({
          'status_text': f"NowPlaying '{title} / {artist}'",
          'status_emoji': ':musical_note:'
        })
      }

      ##APIリクエスト
      statusRes = requests.post(slackProfileSetAPI, params=statusParam)

      ##例外処理を入れないので一応結果を標準出力しておく
      print(statusRes.text)

      #どんどんファイルが蓄積されていってしまうので削除
      os.remove(fileName)
      #曲の時間を取得して秒に直す
      #曲の時間だけはmm:ssの形式でしか取得できないので
      min, sec = map(int, it.current_track.time.get().split(":"))
      total_seconds = datetime.timedelta(minutes=min, seconds=sec).total_seconds()
      #経過時間を取得する
      elapsed_time = it.player_position.get()
      #残り時間を計算する(sleepの時間を取得するため)
      remaining_time = total_seconds - elapsed_time

      #残り時間にバッファで1秒プラスした時間sleepする
      time.sleep(remaining_time + 1)

    #再生中でなければplayingFlgをFlaseにする
    else:
      playingFlg = False
      #一応ステータスを標準出力する
      print("Itunes is currently:", state.name)

#プログラムが終了したことを標準出力する
print("end")
```

## デモ

ファイルを保存したら好きな曲を再生して以下を実行してください。

```bash
-- 先ほど保存したファイルを実行する
(venv)$ python3 applemusic_to_slack.py
```

![トリム.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133737/b4811fb5-ff31-38de-b8bf-2efc4e4aef27.gif)


スクリプト自体iTunesが起動中で再生している限りはwhileで実行されているのであとはこのまま放置で大丈夫です
↓こんな感じで途中で曲を飛ばしたり,止めたりしなければ次の曲が再生されたら今度はその曲の情報に変更されます

![トム2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133737/b2a5fd16-6d80-04c5-66c7-1b2df3a33145.gif)


## 振り返り
### イケてないところ
1. あくまで曲の長さをとって次の曲に切り替わるだろうポイントでループして
　いるので途中で曲を変えたりすると正しく連携されないことがある
2. ライブラリに追加してある曲じゃないとエラーになる
3. ずっと起動しているわけではないの最低1日に一回は起動する必要がある
4. また, 途中でなんらかのエラーが起きた場合に再度コマンド入力して起動す　　
　る必要がある