「Integromatは便利だが不調で無料枠が厳しいが、IFTTTだとDBも無いし複雑なロジック組めない」問題をGASで一括解決

# 現状の問題

### Integromatを取り巻く現状の問題

Integromatの有料アカウントも持っているのですが、DBも一応使えて便利だったものの特に無料枠で使うには昨今結構厳しいです。

1. なんの前触れもアラートも無くWebHookが不調になったり（結構頻繁に、月0.5〜1くらいで）、先日などはなんと複数のシナリオで戦闘に設置してあるWebHook設定が吹っ飛ぶという大惨事が起きました。
WebHook設定はそれそのものの設定も消え去っており、再度WebHookを作成する必要がありましたし、そうするとURLも変わってしまうので関連するシステムや物によっては利用者に変更の通達を出さなくてはならない始末。
サポートに問合せをしても、更に驚くべき事にWebHook設定ごと消し飛んでいることに気づいていなかったようです。
2. 無料枠で出来る内容が厳しくなったようでアクティブなシナリオは3つまでのよう。
3. 以前は使えていたGMailのエイリアスでアカウントも取れなくなった（もともと想定外の利用方法でしたが）

### IFTTTを取り巻く現状の問題

IFTTTはサービスも多くて便利なのですが、DBや複雑なロジックを組もうとすると一気に厳しくなります。

1. 何しろA→Bとシンプルなアプレットしか作れない。
2. 複雑な物を作ろうとするとサービス化しなくてはならない。
3. 複雑化したところでIFTTTに閉じたDBが無いので不揮発なデータを扱いづらい。

# GASで解決しよう

まず、Integromatの無料枠では3つしかシナリオを持てないので論外となりました。
月1,000円でも使いたいとなれば有力候補になります。ただ、WebHookは非常に不安定なので私見ではありますが「プロダクションなどで使わないように要注意」です。
[こちらに簡単に各類似サービス（代替サービス）の2020年8月時点での主な制限や特徴について比較してまとめておきました。](https://qiita.com/mnr68/items/f7a2cc352c02af3882a5)https://qiita.com/mnr68/items/f7a2cc352c02af3882a5


IFTTTを何とか活かして「DBが使えない」不便と「複雑なロジックが組めない」不便をなんとかしたいところです。
そこで登場するのが言わずとしれたみんな大好きGAS（Google Apps Script）です。
ここではサンプルとしてSwitchBot Thermometer（SwitchBot温度計）により以下の機能を実装する具体的な例も挙げていきます。

| SwitchBot温度計の例 |
|:--|
| 自動運転「温度が下がりすぎたらクーラーを切って、温度が上がりすぎたらクーラーを入れる」これだけならIFTTTだけで出来るが、利用者が自動運転をオンにしているときだけこのような動作をするようにしたい。 |
| 自動運転のON/OFFは、IFTTTアプリでCONNECTIONをON/OFFする方法だと分かりづらいので、iPhoneのショートカットボタンなどでON/OFFできるようにする。そのためつまりはWebHookでON/OFFできる汎用的なものにしたい。 |

### 1. イベント/アクションにGoogle Spread Sheetのセルアップデートを使う

IFTTTにはたくさんのトリガーが用意されていますので、これをきっかけにDBを使いつつ複雑なロジックも組めたら便利です。
そこで、任意のイベント/アクションからまずはGoogle Spread Sheetのセルアップデートに繋ぎ込みます。
これで実はGASにも繋ぎ込めたことになります。
Google Spread Sheetのセルアップデート トリガーを使ってGASのメソッドを呼び出せば良いのです。
しかも、Google Spread Sheetなので簡易的なDBにもなっているというわけです。

| SwitchBot温度計の例（発動のIFTTTトリガーとアクション） |
|:--|
|<img width="473" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/a6e7b532-df43-fba5-84de-816d572d22d1.png">|
|<img width="477" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/58b82d5b-41a8-bcb6-10c5-50610e4f88fe.png">所定のワークシートのセル位置B1を指定しておきます。アップデートする値は何でも構いませんがここでは1としておきました|

### 2. GASであれこれ

当該のGoogle Spread Sheetを開いてスクリプトエディタにロジックを書きます。

```Javascript:コード.js
function decideAirConOn() {

  var CELL_SHOULD_CONTROLL_AIR_CON = 'A1';
  var CELL_IS_ON_REQUEST           = 'B1';
  var CELL_ON_COMMAND              = 'C1';
  var ON  = 1;
  var OFF = 0;

  var mySheet = SpreadsheetApp.getActiveSheet();
  var myCell = mySheet.getActiveCell();

  // 監視対象のセルがアップデートされた場合だけ反応する
  if (2 < myCell.getColumn() || myCell.getRow() != 1) return;

  var shouldControllAirCon = getVal(CELL_SHOULD_CONTROLL_AIR_CON);
  var isOnRequest          = getVal(CELL_IS_ON_REQUEST);
  // 自動運転中フラグが立っているときだけエアコンオンコマンド
  if (shouldControllAirCon == ON && isOnRequest != OFF) {
    setVal(CELL_ON_COMMAND, new Date());
  }
  setVal(CELL_IS_ON_REQUEST, '0');
  
  function getVal(target) {
    return mySheet.getRange(target).getValue();
  }

  function setVal(target, value) {
    return mySheet.getRange(target).setValue(value);
  }

}
```

####GASトリガー設定を忘れずに

Google Spread Sheetのトリガー（<img width="193" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/c65c11e8-97b7-6094-ff0b-5e15ee76df79.png">）にてセル変更時に所定の関数がキックされるようにしておきます。
GASには実行時間の制限がありますので、対象セル以外の変更には反応しないようにコード上で工夫しておきます。

### 3. IFTTTに返すにもやっぱりGoogle Spread Sheetのセルアップデートを使ってみる

元々IFTTTで完結したい物をロジック部分だけGASに出している訳なので、GASでロジックを実行して結果をIFTTTに返す（繋げる）必要があります。Google Spread Sheetのセルアップデートで受け取ったイベントなので、同じようにGoogle Spread Sheetのセルアップデートで返してみます。

####捕捉

この方法（セルアップデートをトリガーにする方法）だと「セルの操作」を通じて入りも出も全てを完結できる代わりに、セルをアップデートしてからIFTTT側がアップデートを検出するまでに数分のタイムラグが発生するようです。
IFTTT自体はトリガーによってチェック間隔が異なるようですが15分くらいではなかろうかと思われます。ただ、1時間待っても発動しないこともありIFTTTの問題なのか間隔の問題なのか良く分かりません。いずれにしてもこんなに反応が遅いとエアコンの自動ON/OFFの意味がありません。

そこでタイムラグが気になる場合はUrlFetchApp.fetchなどを使ったIFTTTのWebHook呼び出しを使った方が良さそうです。

セルのアップデートによりIFTTTに意志を伝えている部分は……

```Javascript:コード.js
setVal(CELL_ON_COMMAND, new Date());
...
  function setVal(target, value) {
    return mySheet.getRange(target).setValue(value);
  }
```
この部分です。
そのため、ここを以下のように書き換えることで直接、即座に（即座に近いタイミングで）IFTTTアクションを動作させることも出来ます。

```Javascript:コード.js
UrlFetchApp.fetch('https://maker.ifttt.com/trigger/YOUR_COMMAND/with/key/YOUR_ACCESS_CODE');
```
ここまで書いてきて何ですが、こちらの方がスマートかもしれません。


####IFTTTでの受け取り

IFTTT側でGoogle Spread Sheetのセルアップデートをトリガーにして、アクションをぶら下げるようにします。

| SwitchBot温度計の例（返しのIFTTTトリガーとアクション） |
|:--|
| <img width="472" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/fc7aed8b-25ca-b168-f805-d6088ef3c599.png">C1を監視するように指定します |
|<img width="476" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/0c99950b-f844-dd05-c81d-4792b7de1df9.png">アクションとしてはSwitchBot経由でエアコンを付けるだけです。ここでは、シーンを利用しています|

### 4. 自動運転のオン、オフについてもGoogle Spread Sheetのセルアップデートを使う

| SwitchBot温度計の例（自動運転オン/オフのIFTTTトリガーとアクション） |
|:--|
| <img width="482" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/e28c58d3-4f25-e163-319e-1c65f4b00d8d.png">ウェブフックにすることで、URLを叩けばいつでもどんな形でも自動運転オンオフが出来るようになります  |
| <img width="469" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/b19f0043-1f1e-62f9-ff43-16374b8ae40f.png">C1セルを1にすれば自動運転オン、0にすれば自動運転オフとなります  |

GASにてC1セルの値とANDを取って最終的な操作判定を行っていますので、こちらが大元の自動運転スイッチのように振る舞います。

同様にしてエアコンオフのフローも建て付ければ全体として完成となります。

### 5. 完成した初期リリースバージョン

サンプルなので簡単な物ですが、[GitHubリポジトリ](https://github.com/minola68000/AutoAirconGAS/blob/master/AutoAirconGAS.js)にも公開しておきました。
こちらにもスナップショットとして転載しておきます。
GASでは利用する機能によって初回実行時に認証が入ります。deploy()を手動実行して認証をする必要があります。
Google Spread Sheetですが結局、1行目を項目名、2行目を値の格納用、3行目は履歴保持用（デバッグ用）としました。

<img width="604" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206965/aa47c409-d8e9-92d3-2ade-448baa8b42e0.png">


```Javascript:コード.js
function decideAirConOn() {

  var CELL_SHOULD_CONTROLL_AIR_CON = 'A2';

  var CELL_IS_ON_REQUEST           = 'B2';
  var CELL_LAST_ON_REQUEST         = 'B3';
  var CELL_ON_COMMAND              = 'C2';

  var CELL_IS_OFF_REQUEST          = 'D2';
  var CELL_LAST_OFF_REQUES         = 'D3';
  var CELL_OFF_COMMAND             = 'E2';
  var ON  = '1';
  var OFF = '0';

  var URL_AIRCON_ON  = 'https://maker.ifttt.com/trigger/startAirCon/with/key/YOUR_IFTTT_USER_KEY';
  var URL_AIRCON_OFF = 'https://maker.ifttt.com/trigger/stopAirCon/with/key/YOUR_IFTTT_USER_KEY';
  
  var USE_FETCH = true;
  var USE_CELL = ! USE_FETCH;
  var IS_DEBUG = false;

  var mySheet = SpreadsheetApp.getActiveSheet();
  var myCell = mySheet.getActiveCell();

  // 監視対象のセルがアップデートされた場合だけ反応する
  if ( ! IS_DEBUG) {
    if (5 < myCell.getColumn() || myCell.getRow() != 2) return;
  }
  console.log('Start decision');

  var g_shouldControllAirCon = getVal(CELL_SHOULD_CONTROLL_AIR_CON);
  var g_isOnRequest          = getVal(CELL_IS_ON_REQUEST);
  var g_isOffRequest         = getVal(CELL_IS_OFF_REQUEST);
  console.log('isOnRequest:' + isOnRequest);
  console.log('isOffRequest:' + isOffRequest);

  // 自動運転中フラグが立っているときだけエアコンオン
  if (g_shouldControllAirCon == ON) {
    
    if (isOnRequest()) {
      console.log('Set command on');
      if (USE_FETCH) UrlFetchApp.fetch(URL_AIRCON_ON);
      if (USE_CELL) setVal(CELL_ON_COMMAND, new Date());
      setVal(CELL_LAST_ON_REQUEST, g_isOnRequest + ' ' + new Date());
      setVal(CELL_IS_ON_REQUEST, OFF);
    }
    
    // 自動運転中フラグが立っているときだけエアコンオフ
    if (isOffRequest()) {
      console.log('Set command off');
      if (USE_FETCH) UrlFetchApp.fetch(URL_AIRCON_OFF);
      if (USE_CELL) setVal(CELL_OFF_COMMAND, new Date());
      setVal(CELL_LAST_OFF_REQUES, g_isOffRequest + ' ' + new Date());
      setVal(CELL_IS_OFF_REQUEST, OFF);
    }

  }
  
  function isOnRequest() {
    return (g_isOnRequest != OFF);
  }

  function isOffRequest() {
    return (g_isOffRequest != OFF);
  }
  
  function getVal(target) {
    return mySheet.getRange(target).getValue();
  }

  function setVal(target, value) {
    return mySheet.getRange(target).setValue(value);
  }

}

function deploy() {

  SpreadsheetApp.getActiveSheet().mySheet.getActiveCell().getColumn();
  
  console.log(UrlFetchApp.fetch('https://maker.ifttt.com/trigger/deploy/with/key/YOUR_IFTTT_USER_KEY'));
}
```

### 6. GAS以外の方法

今回はGASを利用しましたが、他にも本格的な実装に使えるサービスが幾つかあります。GASが一番手っ取り早く（低工数で）やりたいことを実現できると思いますが、本格的な運用やサービス建て付けをする場合にはAWSかFirebaseを使うのが良いと思います。

| GASの代替サービス | AWS(Amazon) | Firebase(Google) |
|:--|:--|:--|
| Webhook受け | API Gatewayを使ってLambdaに引き込む | Functionsを使ってNode.js（Functions）に引き込む |
| Webhook呼び出し | Node.jsからPOST/GET |  Node.jsからPOST/GET  |
| DB | LambdaからDynamoを利用（完全サーバーレスで構築可能） | FunctionsからRealtime Database/Firestoneを利用（完全サーバーレスで構築可能） |
| 難易度/工数 | 結構面倒 | まぁまぁ面倒 |

# その他

ネット上にはSONYのMESHと組み合わせると最強といった記事も出てきたのですが、MESHはSONYの物理製品を買わないとバックグラウンド動作しないばかりか、組めるロジックは時計（時間）程度で全然最強に見えませんでした。フィッシングに近い悪意ある内容ではないかと感じました。
最後の方にだまし討ちで事の顛末が書かれてますので、試しにアプリなどをインストールしたり、アフィリエイトリンクを踏んでSONYのMESH製品を購入したりしてから気がつかないように気をつけましょう。