QtアプリからIFTTT Webhooksのトリガを実行する


1. 初めに

Qtアプリ(Qt5/C++)からIFTTTのWebhooksを叩いて、スマホにプッシュ通知するサンプルを紹介します。

IFTTTのWebhooksに関して、参考にしたサイトはこちらです。
クライアントであるQtアプリからHTTP POSTを実行するサンプルを作ってみたくて作成しました。
ソースコードはgithubに置いてあります。
IFTTTに関しては、元ネタ記事をほぼ同じですが、1つの記事に完結していたほうが読みやすいと思ったので、キャプチャをペタペタ貼って説明しています。

<-- 2018/05/07 追記 start -->
Qtのサンプルに関して
投稿して1日経ち、やはりvalue1()などにデフォルト実装がないのはおかしい(value1だけ設定してvalue2やvalue3を使用しない場合を考慮できていない)為、デフォルト実装を入れるよう実装を変えました。
混乱させてしまい申し訳ありません。
<-- 2018/05/07 追記 end -->

1.1. 動作確認環境

2. 動作

IFTTTやプログラムの説明をする前に、動作の説明をします。
Qtアプリを実行すると、

という画面が立ち上がります。
このアプリの「POST Request」ボタンを押下すると、

が表示され、その後、POSTリクエストが正常に終了すると、

が表示されます。
スマホ側では、Notificationで、

のように通知されます。
「hello」というイベントが発生し、Value1、Value2、Value3にそれぞれ、test1、test2、test3というパラメータが設定されているのが確認できます。

3. IFTTTの設定

ブラウザを開きます。(Internet ExplorerはNGのようです。本記事の操作はGoogle Chromeを使用しています)
IFTTTにSign Inして、アプレットの作成を行います。

3.1. アプレット作成

メニューの「My Applets」から、「New Applet」(スマホアプリだと「+」ボタン)を押下します。
すると、下記のような画面が表示されます。

3.1.1. トリガ(this)

まず、thisを選択し、検索バーで「web」を入力して、

を選択します。
次に、

を選択します。

すると、イベント名を入力する画面になりますので、入力します。
ここでは、元ネタ記事と同じように「hello」とします。

入力後、「Create trigger」を押下します。

3.1.2. アクション(that)

thatを選択後、検索バーで「noti」を入力し、

を選択します。
次に、

を選択します。
次に、

の画面で、Messageを下記のように入力します。

使用できるパラメータは、「Add ingredient」ボタンを押下して入力するのが確実です。
入力後「Create action」ボタンを押下します。
その後、「Finish」ボタンを押下すればアプレット作成完了です。

3.2. IFTTT webhooksの動作確認

作成したアプレットを選択し、左上のマークを押下します。

次に、右上の「Documentation」ボタンを押下します。

すると、次の表示されます。

この画面は、どうやってWeb APIを叩いてよいのかの全てが説明され、試しに動作確認までできる、かなりわかりやすいページです。

Webhooks APIのURLに設定する要素は下記の5つです。

  • イベント名
  • アクセスKey
  • value1
  • value2
  • value3

※IFTTTのアプレット作成画面だとvalue1はValue1と表示されていますが、URLのパラメータとしては全部小文字の名前になります。

Your Key isのところにアクセスキーが記述されていますので、これをメモっておきます。(Qtのプログラムで使用します。)
ついでに、アプレットの動作確認をします。
赤字の部分に各パラメータに設定したい値を入力し、「Test It」ボタンを押下します。
すると、Qtアプリの代わりに、トリガが発生します。
アクションが実行されたことをスマホで確認したら、ブラウザ上での操作は終了です。

4. Qtアプリの実装

IFTTTにアクセスするクラスをして作成したのは、

  • IftttAccessManagerクラス
  • IftttTestクラス(IftttAccessManagerクラスの子クラス)

の2つです。

4.1. IftttAccessManagerクラス

IFTTT Webhooksにアクセスする仕組みを定義したクラスです。
IftttAccessManagerクラス自身では、下記コードのように、QNetworkAccessManagerを使用してPOSTしています。

プロジェクトファイルに

IftttSample.pro
QT += network

の設定をすれば使用できるようになります。

ifttttest.cpp
IftttAccessManager::IftttAccessManager(QObject *parent) : QObject(parent)
{
    m_networkManager = new QNetworkAccessManager(this);
}

void IftttAccessManager::requestPost()
{
    QUrl url("https://maker.ifttt.com/trigger/" + eventName()
             + "/with/key/" + webhooksKey());
    QUrlQuery postData;
    QString value1Str = value1();
    if (!value1Str.isEmpty()) {
        postData.addQueryItem("value1", value1Str);
    }
    QString value2Str = value2();
    if (!value2Str.isEmpty()) {
        postData.addQueryItem("value2", value2Str);
    }
    QString value3Str = value3();
    if (!value3Str.isEmpty()) {
        postData.addQueryItem("value3", value3Str);
    }
    url.setQuery(postData);
    QNetworkRequest request(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader,
        "application/x-www-form-urlencoded");
    m_networkReply = m_networkManager->post(request, postData.toString(QUrl::FullyEncoded).toUtf8());
    connect(m_networkReply, &QNetworkReply::finished, this,
            [=](){
        if (m_networkReply->error()) {
            emit postFinished((m_networkReply->errorString()));
        } else {
            emit postFinished("POST finished");
        }
        m_networkReply->deleteLater();
    });

カスタマイズできる5つのパラメータについては、デザインパターンのTemplate Methodパターンで、子クラスに設定させます。

ifttttest.h
protected:
    virtual QString value1();
    virtual QString value2();
    virtual QString value3();
    virtual QString webhooksKey() = 0;
    virtual QString eventName() = 0;

※Template Methodパターンについて補足
Template Methodパターンでは子クラスに設定させる要素を必ずしもpure virtualにしなければならないわけではありません。virtualだけ付けて( =0 を付けずに)、親クラスでデフォルト設定を記述しておく場合もあります。今回の例ではvalue1()関数などがそれに当たります。(value1Str.isEmpty()で判定する為にデフォルト実装で空文字を返します)
Template Methodパターンは、必ずpure virtualにしなければならないと勘違いしている人(というかソースコード)を見かけたことがあるので、もしそういう人が近くにいたら教えてあげてほしいです。
pure virtual関数をOverrideした子クラスの関数の中身が全て同じだったという悲しいコードはあまり見たくないです。。。

4.2. IftttTestクラス

今回動作確認用のパラメータを記述したクラスです。
webhooksKey()は、3.2.項でメモっておいたKeyを設定してください。

ifttttest.cpp
QString IftttTest::value1()
{
    return "test1";
}

QString IftttTest::value2()
{
    return "test2";
}

QString IftttTest::value3()
{
    return "test3";
}

QString IftttTest::webhooksKey()
{
    return "Your Key";
}


QString IftttTest::eventName()
{
    return "hello";
}

4.3. コードを記述している時に気づいたこと

QNetworkAccessManager周りはQt4→Qt5に移行している内に仕様が変わったようです。
2点気づいたことがあります。

1つ目は、パラメータを設定する為のQUrl::addQueryItem()がDuplicatedになっており、Qt5.10.1ではビルドすら通らないことです。
代わりに、QUrlQuery::addQueryItem()を使用するようです。
詳しくは、Stack Overflowの記事を参照してください。

2つ目は、エラーハンドリングについてです。
最初、QNetworkAccessManager::finish()シグナルをキャッチし、引数のQUrlQuery *replyから
reply->error()でエラーかどうかを判断するつもりだったのですが、判断できませんでした。
「https」を「htps」に変更してエラー処理の動作確認を行いましたが、No Errorが返ってきます。
ここを見る限りできそうだったのですが、仕様が変わったのでしょうか?

そこで、本サンプルのようにpost()関数の返り値であるQNetworkReplyから判断します。
このpost()関数の返り値で返ってきたQNetworkReplyは自分でdeleteする必要がある為、エラーかどうかを確認後、QNetworkReply::deleteLater()を呼んでいます。

5. 最後に

いかがでしたでしょうか。
QNetworkAccessManagerを使用すれば、わりと簡単にクライアント側のPOSTのコードを書け、IFTTTを使用すれば、サーバ側の動作も簡単に実装することができることがわかりました。
ネットワークプログラムというと、どうしても(特にクライアントサイドのプログラムを書いている人には)ハードルが高いというイメージがありますが、作ってみればそう難しいことではないと思います。
Gitに登録してから、

  • 可変パラメータは全て画面で設定できるようにしておけば、もっとかっこいいサンプルになったとか
  • よく考えれば、value1、value2、value3はpure virtualにしなくてよくね?ということに気づいたりとか・・・

まあ、この辺りで勘弁してくださいm(_ _"m)

読者様の参考になれば幸いです。