iTunesで再生した楽曲をIFTTT経由でDiscordに投げてみた話

恥ずかしながら、最近までWeb系のプロダクト（最近主流のキラキラしてるやつ）にはほとほと疎く
この間ようやくIFTTTなるものを知った。

で、いろいろ弄っているうちになんか面白そう！と思って表題のアプリを作ってみようと思い立ったのが昨日。
大変雑な感じの組み方をしてしまったのだけれど、せっかくなので記事にしてしまおうと思ってみたり。


# 用語定義

一応わかりやすさのために用語を簡単にまとめておきたい。

| 用語 | ザックリ説明 |
|:-:|:--|
| IFTTT  | web経由でいろんなサービス間の橋渡しをしてくれるサービス。<br>webhookからwebhookをつなげてくれる子。  |
| iTunes  | appleが提供してる音楽再生アプリ。<br>いや、音楽再生のみじゃないんだけど、windowsだと基本こんな認識  |
| Discord  | チャットアプリ。ゲーム配信に特化してるイメージがある。<br>会議通話も配信もいける。アプリの説明文大好き。  |


# 基本的なイメージ

![2018y11m14d_022626602.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/e44cfc7f-b08c-b856-806f-d4129eddeda2.jpeg)

うーん、シンプル。
ちなみにwebhookを利用してるので、IFTTT経由しなくてもDiscordにメッセージ投げるのは出来ちゃいます。
IFTTT弄らない分手間もかからないかも。
ただ、IFTTTを経由することで、IFTTTから出力先に指定できる400超のサービスに拡張できるってのは間違いなく魅力かと。


さて･･･しっかり書くのは明日にして、とりあえず成果だけ挙げてお休み。
気がついたら2時回ってたので･･･
ちゃんと計測してないけど、4時間くらいで出来る簡単なアプリ。


# 成果
![2018y11m14d_014343048.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/fe222fd6-3818-74fe-39df-4b6e3dd3329e.jpeg)

{再生したトラックのタイトル} - {アルバム名}/{アーティスト名}
の書式。この書式はWindowsアプリで整える。

# 実装
## DiscordのWebhook用アドレスを取得する

まずはDiscord。
Discordのサーバーアイコンを右クリック。
コンテキストメニューから[サーバー設定]-[webhooks]を選択。

サーバーの設定画面に遷移するので、ここで[webhookを作成]ボタンを押下。
![2018y11m14d_145553053.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/3b030dd9-27d5-ac84-e6b6-5d6b5f80a791.jpeg)

webhook契機でメッセージを送る際のデフォルト名と、投稿先のチャンネル、アイコンなどを設定し、
WEBHOOK URLをコピーする。

![Discord_Webhook.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/57dfb39f-4203-4531-e734-7b8794e0e67c.jpeg)


>！注意！
Webhook URLを公開すると、匿名の誰かが好き勝手メッセージ投げられるようになる。控えよう。

後はコピーしたURLにjson形式のデータをPOSTしてやればDiscordへの投稿はOK。
jsonのKeyは色々あるけど、とりあえず使うならcontent(本文)さえ入れておけばOK。
お手軽なキーと内容の表はこんな感じ

| Key | 説明 |
|:-:|:--|
| content  | Discordの本文。メッセージ部分とも。  |
| username  | 投稿者の名前。指定しない場合、DiscordのWebhoook設定で指定したデフォルト名が使われる  |
| avatar_url  | 投稿者のアイコンを指すURL。指定しない場合、DiscordのWebhook設定で指定したデフォルトアイコンが使われる。  |

なお、Content-Typeはapplication/jsonをちゃんと指定しないとおむずがる模様。


参考にさせていただいたもの：DiscordにWebhookでいろいろ投稿する
https://qiita.com/Eai/items/1165d08dce9f183eac74

---
---

## IFTTTでAppletを作る。

今回一番使いたかったところ。
先にも述べたとおり、IFTTTを介さなくてもjson形式のbodyを書き込んだHTTPリクエストを送ればDiscordに投稿は出来る。
ただし、別のサービスに送りたくなったときにソースを直す必要があるわけですよ。
あ、ハードコーディングする場合ね？
もちろんそこはいくらでもやりようがあるんだけど。

<b>とにかく、IFTTTを使いたかったので、いいんですぅ！（ゴリ押し）</b>

で、IFTTT。

もうこれ説明割愛してもいいんじゃないの？ってくらいサンプルにあふれてるんだけど、
一応webhook入力の例は見当たらなかった気がするから書いてみよう。

まずはIFTTTにアクセス。
※アカウントの登録はしてある前提。

New Appletを選択し、

![2018y11m15d_010757519.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/6a81e3ac-0036-c908-63c1-33fa03f607f8.jpeg)

をクリック。

[Choose a service]画面の検索窓に"webhook"と打ち込み、出てきた[webhook]をクリック。

![2018y11m15d_011801112.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/da34363f-6627-fd3f-a397-0141e6d6ce64.jpeg)

Choose trigger画面に遷移するので、[Receive a web request]を選択。

![2018y11m15d_012148157.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/f7a01e11-06b8-51f5-e6c5-23910486d939.jpeg)

すると、triggerに必要な情報を埋めてね！画面に遷移するので、
必要なEvent Nameテキストボックスに任意の文字列を設定する。

ちなみに、気になったので試してみたら、日本語も指定できました。
エンコードによる差異とか出そうで怖いからascii文字のみの方がよさそうだけども。

![2018y11m15d_012720295.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/f5958b37-ac9e-b527-d15d-952c91bd6b02.jpeg)

ここまででtriggerの設定はOK。
次に

![2018y11m15d_014036002.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/c2eeeb77-f7a6-a5bc-4634-059ac526d6e0.jpeg)

この画面の

![2018y11m15d_014055893.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/d424fe15-036a-6786-91c9-6e5c1c22e783.jpeg)

をクリックしてDiscordに投げるwebhookの設定をしていきます。

[Choose action service]画面に遷移したら、triggerの設定時と同じように、検索窓に"webhook"と入力し、
出てきた[webhook]をクリック。

[Choose action]画面で[Make a web request]をクリックし、次の画面へ

![2018y11m15d_014914686.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/c0195ac6-cd90-1b20-8762-115628f4cf4e.jpeg)

[Make a web request]をクリックし、遷移した[Complete action fields]画面で送信するHTTPリクエストの内容を設定。
設定項目と対応は以下の通り

![2018y11m15d_020506865.jpg](https://qiita-image-store.s3.amazonaws.com/0/259679/efd6d889-eb56-feb7-06ea-4faee9de6585.jpeg)


| 項目 | 説明 |
|:-:|:--|
| URL |  送信先のURLをここに指定する。<br>Discordにメッセージを投げる場合、<br>ここにDiscordのwebhook先URLを貼り付ける。 |
| Method  | HTTPリクエストにおけるメソッドを指定する。<br>データを投げる場合、基本的にはPOSTでいいはず。  |
| Content Type  | HTTPリクエストのBody部分の内容を指定する。<br>今回はjson形式なので、"application/json"を指定  |
| Body | HTTPリクエストのBody部。<br>今回はjson形式でデータを送る。  |

今回のBodyはシンプルにこんな感じ
>{ 
   "username" : "IFTTT music", 
   "avatar_url": "https://img.icons8.com/color/1600/ifttt.png", 
   "content" : " {{Value1}}" 
 }

{{Value1}}の部分にはtrigger側のwebhookで投げられてきたHTTPリクエストのvalue1キーの値がそのまま入るイメージ。

入力が終わったら画面下部の[Create action]をクリック。
[Review and finish]画面で[Finish]ボタンを押下してappletの作成を完了しましょう。

これでIFTTTの設定は終わり。
ちなみにIFTTTのwebhookによるトリガは、[webhook service]の[Documentation]ページから動作を確認することが出来ます。

Service一覧から[webhooks]を選択し、Webhooks画面へ。
または、[ここ](https://ifttt.com/maker_webhooks)から移動。

画面右上にあるDocumentationボタンを押下してDocumentation画面へ移動。
Documentation画面の[To trigger an Event]以下の値を書き換えて[Test It]ボタンを押下することで
作成したHTTPリクエストを送ることが出来ます。

ちなみに、この画面にはアカウント固有のキーが表示されてるので
例によって公開しないほうがいいです。
悪戯し放題！

---
---

## iTunesで再生した楽曲を取得し、HTTPリクエストを投げるアプリを作る

さて、ここまできてようやく大詰め。
iTunesで再生した楽曲を取得するお時間です。

参考にさせていただいたサイトさんの内容とかなり被るので要点を

1. iTunesLib(COMオブジェクト)を参照に加える。
1. using iTunesLib;をコードの先頭に足しておく。
1. iTunesAppインスタンスを取得し、イベントハンドラを登録する。
<br>※イベントは以下の表.iTunesAppイベント一覧を参照のこと
1. 引数として渡されるObjectをIITTrack型にキャストし、必要なデータを取得
1. データを整形してHTTPリクエストにまとめる
1. IFTTTにポイっ！って
1. ※使わなくなったらiTunesAppオブジェクトを解放する。登録したイベントハンドラの削除も忘れずに


表. iTunesAppイベント一覧

| イベント名 | 型 | 引数 | 概要 |
|:-:|:-:|:-:|:--|
|  OnDatabaseChangedEvent | void  | object deletedObjectIDs,<br> object changedObjectIDs  | iTunesデータベースに変更が起きたときに発生。|
|  OnPlayerPlayEvent | void  | object iTrack | iTunesでトラックを再生するときに発生。<br>引数としてトラック情報が渡される。<br>コード内で利用する場合、IITTrack型にキャストして利用する。<br>IITTrack型については表.IITTrack型プロパティを参照のこと  |
|  OnPlayerStopEvent | void  | object iTrack  | iTunesでトラックの再生を停止するときに発生。<br>引数としてトラック情報が渡される。<br>コード内で利用する場合、IITTrack型にキャストして利用する。<br>IITTrack型については表.IITTrack型プロパティを参照のこと  |
|  OnPlayerPlayingTrackChangedEvent | void | object iTrack  | iTunesで再生中のトラック情報が変更されるときに発生。<br>次楽曲の再生や、前楽曲の再生を行った場合にも発生する。<br>引数としてトラック情報が渡される。<br>コード内で利用する場合、IITTrack型にキャストして利用する。<br>IITTrack型については表.IITTrack型プロパティを参照のこと  |
|  OnCOMCallsDisabledEvent | void  | [ComAliasName("iTunesLib.ITCOMDisabledReason")] ITCOMDisabledReason reason | COM(iTuneslib)へのアクセスが繰延(differ)される場合に発生。<br>説明文読む限りモーダルダイアログが出たときとかじゃないかな？ |
|  OnCOMCallsEnabledEvent | void  | - | COM(iTuneslib)への繰延(differ)状態が解除された場合に発生？<br>なんかモーダルダイアログが出てると遅延が起きるらしい。<br>その状態が解除されると発生するっぽい  |
|  OnQuittingEvent | void  | - | iTunesが終了されるときに発生。  |
|  OnAboutToPromptUserToQuitEvent | void | - | iTunesが”ユーザーに終了するか問い合わせるプロンプトを表示する”時に発生<br>iTunesに絡めたアプリ立ち上げた状態でiTunes終了しようとすると"連携アプリあるけど本当にiTunes閉じる？"って聞いてくるアレ<br>  |
|  OnSoundVolumeChangedEvent | void  | int newVolume | 音量変更時に発生。  |

表.IITTrack型プロパティ

| プロパティ | 型 | 説明 |
|:-:|:-:|:--|
| Album  | string  | アルバム名  |
| Artist  | string  | 読んで字のごとく、アーティスト名。  |
| Artwork | IITArtworkCollection |アートワークを格納してるとかなんとか。そうなると画像データなんだろうなぁ。<br>今回はノータッチ|
| Bitrate | int |ビットレート。これ説明要らないのでは？|
| BPM | int | BPM。Beats Per Minuitの略なんだとか。テンポの単位なんですって！ |
| Comment | string | コメント。基本的にiTunesで楽曲のコンテキストメニューからプロパティ開かない限り見る機会ないやつ。 |
| Compilation | bool | Compilation albumからのトラックかどうかを返却。<br> ･･･これ要るん･･･？|
| Composer | string | コンポーザー。作曲家のことだそうな。 |
| DateAdded | Datetime | プレイリストに追加された日。 |
| DiskCount | int | 元アルバムの総枚数が格納されてるそうな。 |
| DiskNumber | int | えー、元アルバムの何枚目に収録されてるかを格納･･･これ使ってるのかな？ |
| Duration | int | トラックの長さを秒単位で返してくれるらしい。処理に使うならこっちかな？ |
| Enabled | bool | 再生可否を示すプロパティ。 |
| EQ | string | EQ。心の知能指数･･･ではなく、イコライザのことっぽい。<br>EQプリセットの名前を示すプロパティだそうな |
| Finish | int | トラックの停止時間を秒で格納しているとかなんとか。<br>レジューム再生用？<br>ノータッチでした |
| Genre | string | ジャンル。これ難しいよね。特にメタルとロックとパンク。 |
| Grouping | string | グルーピングタグ。なんかタグでまとめておけるっぽいよ、トラック。そのためのタグ？ |
| KindAsString | string | ”AAC audio file”みたいなファイルの種類を文字列で返してくれるそうな。 |
| Kind | ITTrackKind | 音声ファイルの種別情報みたい。enum。ファイル、CD、URL、デバイス、ライブラリとかを判別してる？ |
| ModificationDate | DateTime | 更新日。 |
| Name  | string  | トラックの名前。大抵の場合楽曲名。  |
| PlayedCount | int | 再生回数。 |
| PlayedDate | DateTime | 最後に再生した日付を記録してるみたい。<br>古い順に並べると懐メロがいろいろ聞けるかも？ |
| Playlist | IITPlaylist | トラックが登録されてるプレイリストを返却してくれるっぽい。 |
| PlaylistID | int | 多分playListの中の番号･･･かな？<br> |
| PlayOrderIndex | int | オーナープレイリストにおける再生順序、だそうな。 |
| Rating | int | トラックのレーティング。一応0から100の値で設定できるみたい。<br>0は未設定値として扱ってて、アルバムのレーティングが適用されるんだって |
| SampleRate | int | サンプリングレート。Hz単位だそうな |
| Size | int | トラックの情報量(byte)。 |
| Start | int | トラックの開始時間を秒で返却･･･もしかして使ってない領域があるってこと？ |
| Time | string| プレイ時間。mm:ssみたいな書式でくる。 |
| TrackCount  |  int | アルバムの総トラック数  |
| TrackNumber  | int  | アルバムのトラック番号  |
| VolumeAdjustment | int | トラックごとのボリューム調整情報。-100~100の値で設定してるそうな。<br>加減値？ |
| Year | int | 録音、リリース年を格納してるそうな |

えー、つらつら書いたんですが
上の成果を見てもらってわかるとおり、今回触ったのは
Album、Name、Artistくらいなので、ほかは一応調べたけど使う場合はちょっと疑ってくださいな

ソースは･･･どうしよう？
勢いで作ったから雑なんだよなぁ･･･
まあ、部分部分上げます。起きたらね！

---
・・・起きた！（19時）


ってことでちょろちょろソース挙げていきます。

参照の加え方とかは参考にさせていただいたページをご覧あれ。
iTunesをインストールしてるPCなら問題なくiTunesLibは見つかるハズ。

#### ソース

今回はなんとなくFormアプリケーションで組みましたが･･･ぶっちゃけコンソールアプリで十分だった気がする。

コンストラクタでiTunesAppオブジェクトを取得し、イベントを登録する。
iTunes.(Event名) += ... って部分ね。
･･･そういえばDesigner.csの読み方に関する記事書こうとしてガッツリ忘れてたなぁ。
再生中のトラックが有る場合、トラック情報が更新されたか判定するためのメソッドに処理を投げるようにしてます。
わざわざObject型にキャストしてるのは、iTunesAppのイベントと共通の処理にしたかったから。

``` C#:Form1.cs_iTunesAppオブジェクト設定部分
    public partial class Form1 : Form {

        delegate void SetTextCallback(string text);

        private iTunesApp iTunes;
        private IITTrack cTrack;
        
        public Form1() {
            InitializeComponent();

            /* iTunesCOMオブジェクトの取得とイベントハンドラの登録 */
            iTunes = new iTunesApp();
            iTunes.OnPlayerPlayEvent += ITunes_OnPlayerPlayEvent;
            iTunes.OnPlayerPlayingTrackChangedEvent += ITunes_OnPlayerPlayingTrackChangedEvent;

            if ( iTunes.CurrentTrack != null ) {
                /* 再生中のトラックが存在する場合 */
                isNewTrack( (Object)iTunes.CurrentTrack );

            }else {
                /* 再生中のトラックが存在しない場合 */
                /* nop */

            }

            return;
        }
```

イベントハンドラ内の処理として

1. 保存してるトラックと同一のものか判定
2. 新しいトラックである場合、クラス内に保存しているトラック情報を更新
3. 必要なトラック情報を取得してHTTPリクエストを作成してポイッ！ってする

HTTPリクエスト投げるにあたって、using System.Net;が必要

``` C#:Form1.cs_イベントハンドラ内の処理

        /// <summary>
        /// Track再生開始時にコール
        /// </summary>
        /// <param name="iTrack"></param>
        private void ITunes_OnPlayerPlayEvent(object iTrack) {

            isNewTrack(iTrack);
            return;
        }

        /// <summary>
        /// Trackが変更された場合にコール
        /// </summary>
        /// <param name="iTrack"></param>
        private void ITunes_OnPlayerPlayingTrackChangedEvent(object iTrack) {

            isNewTrack(iTrack);
            return;
        }

        /// <summary>
        /// 引数に指定されたトラックが新規トラックであるか判定する
        /// </summary>
        /// <param name="iTrack"></param>
        /// <returns></returns>
        private bool isNewTrack( object iTrack) {
            bool ret = false;
            IITTrack paramTrack = (IITTrack)iTrack;

            if ( (cTrack == null) || ( cTrack.Name != paramTrack.Name ) ) {
                /* 保存しているトラックが引数のトラックと異なる場合 */
                cTrack = paramTrack;
                refreshInfomation();
                ret = true;

            }else {
                /* 保存しているトラックが引数のトラックと一致する場合 */
                /* nop */
            }

            return ret;
        }

        /// <summary>
        /// フォームの情報を更新する。
        /// </summary>
        private void refreshInfomation() {

            string trackInfo;
            string album;
            string trackNo;
            string trackName;
            string artist;
            string tracktime;
            string sendMsg;


            album = cTrack.Album;
            trackNo = cTrack.TrackNumber + "/" + cTrack.TrackCount;
            trackName = cTrack.Name;
            artist = cTrack.Artist;
            tracktime = cTrack.Time;

            /* iTunes.PlayerPositionは秒単位での動作ポジションを取得する。ＭＳはたぶんミリセク単位でとるやつ。 */
            trackInfo = album + "\r" + trackNo + "\r" + trackName + "\r" + artist + "\r" + iTunes.PlayerPosition + "\r" + tracktime;

            updateText(trackInfo);

            /* webhook処理 */
            sendMsg = trackName + "- " + album + "/" + artist;
            webhook2IFTTT(sendMsg);

            return;
        }

        /// <summary>
        /// IFTTTへのwebhook処理
        /// </summary>
        /// <param name=""></param>
        private void webhook2IFTTT(string pStr) {

            updateHTTPreq("Request ready");

            /* POST ペイロードの調整 */
            string payload = " { \"value1\" : \"" + pStr + " \" } ";
            byte[] byteArray = Encoding.UTF8.GetBytes(payload);

            /* HTTPリクエストの送信処理 */
            /**
             * Method : POST
             * ContentType : application/json
             * IFTTTのwebhookに投げるHTTPリクエストの調整
             */ 
            WebRequest wReq = WebRequest.Create("https://maker.ifttt.com/trigger/playMusic/with/key/！ここはアカウント別のキー！");
            wReq.Method = "POST";
            wReq.ContentType = "application/json";
            wReq.ContentLength = byteArray.Length;

            Stream dataStream = wReq.GetRequestStream();
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();

            WebResponse wRes = wReq.GetResponse();

            updateHTTPreq( "Request send" );

            /* リソースの解放 */
            wRes.Close();

            return;
        }

```





で、終了時はCOMオブジェクトを解放しないといけませんよ、と
登録したイベントハンドラはiTunes.(イベント名) -= ...で削除してます。
COMを解放するにあたってusing System.Runtime.InteropServices;を追加する必要あり。

``` C#:Form1.cs_終了時の処理

        /// <summary>
        /// Formクローズ時の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
            if (iTunes != null) {
                iTunes.OnPlayerPlayEvent -= ITunes_OnPlayerPlayEvent;
                iTunes.OnPlayerPlayingTrackChangedEvent -= ITunes_OnPlayerPlayingTrackChangedEvent;

                Marshal.ReleaseComObject(iTunes);
                iTunes = null;
            } else {
                /* nop */
            }

        }

```

ちょっとその、きったないソースだけど勢いで作ったから多少はね？
しっかり作る場合はちゃんと例外処理とかログとかいい塩梅で入れなきゃだけど。



参考にさせていただいたもの : 
[from Agonymous coward - C#でiTunes COM SDKを叩いて曲情報を表示したりアートワークを埋め込んだりしてみる](https://hsy.me/2015/07/05/itunes-com-sdk-with-csharp/)
[_liTunesEventsInterfaceReference(英語)](http://www.joshkunz.com/iTunesControl/interface__IiTunesEvents.html)
[IITTrack Interface Reference](http://www.joshkunz.com/iTunesControl/interfaceIITTrack.html)

# お借りしたもの
IFTTT : https://ifttt.com/discover
IFTTTアイコン : https://icons8.jp/icon/new-icons/all