C#
ifttt
iTunes
Webhook
discord

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

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

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

用語定義

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

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

基本的なイメージ

2018y11m14d_022626602.jpg

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

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

成果

2018y11m14d_014343048.jpg

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

実装

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

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

サーバーの設定画面に遷移するので、ここで[webhookを作成]ボタンを押下。
2018y11m14d_145553053.jpg

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

Discord_Webhook.jpg

!注意!
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に投稿は出来る。
ただし、別のサービスに送りたくなったときにソースを直す必要があるわけですよ。
あ、ハードコーディングする場合ね?
もちろんそこはいくらでもやりようがあるんだけど。

とにかく、IFTTTを使いたかったので、いいんですぅ!(ゴリ押し)

で、IFTTT。

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

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

New Appletを選択し、

2018y11m15d_010757519.jpg

をクリック。

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

2018y11m15d_011801112.jpg

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

2018y11m15d_012148157.jpg

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

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

2018y11m15d_012720295.jpg

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

2018y11m15d_014036002.jpg

この画面の

2018y11m15d_014055893.jpg

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

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

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

2018y11m15d_014914686.jpg

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

2018y11m15d_020506865.jpg

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

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

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



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

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

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

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

表. iTunesAppイベント一覧

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

表.IITTrack型プロパティ

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

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

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


・・・起きた!(19時)

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

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

ソース

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

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

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;が必要

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は秒単位での動作ポジションを取得する。MSはたぶんミリセク単位でとるやつ。 */
            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;を追加する必要あり。

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を叩いて曲情報を表示したりアートワークを埋め込んだりしてみる
_liTunesEventsInterfaceReference(英語)
IITTrack Interface Reference

お借りしたもの

IFTTT : https://ifttt.com/discover
IFTTTアイコン : https://icons8.jp/icon/new-icons/all