こんにちは。ドワーフのそーすい(@ffp_sousui)です。

先月は仕事が落ち着いていたので久しぶりに家でプログラミングしようかなーと思い、ツイッターのbotをサーバーレスで作ってみました。

botの概要

天獄bot(@dqx_tengoku_bot

ドラクエXに「邪神の宮殿 天獄」というコンテンツ(以下天獄)があり、ゲーム内で不定期に開催されています。いつ開くか分からないのと、開いたら3日間続くのが特徴です。
  1. 天獄が開始されているかhttps://hiroba.dqx.jp/sc/game/tengokuを確認
  2. 開始されていたらTwitterにつぶやく
  3. 終了まで1日切った時もつぶやく
  4. サーバーレスで無料で使える

作った理由

天獄は報酬が美味しいのですが、毎日ゲームにログインしている人ばかりではないため、自動でお知らせしてくれるbotがあれば開いたタイミングを逃さず便利だと思いました。

ユーザーに役に立ち、運営に迷惑をかけないbotがコンセプトです。

botの仕組み

次の3つのサービスを利用しています。
情報の取得はGoogle スプレッドシートとGoogle Apps Script(以下GAS)で行い、ハブとなるIFTTTに連携します。IFTTTはTwitterに連携し、Twitterがユーザーに見える形でつぶやきます。

次からどのように各サービスを利用しているか技術的な話に入ります。

GoogleスプレッドシートとGAS

まずGoogleスプレッドシートとGASの役割分担は次のようにしました。
  • 情報の取得、ツイートの作成 - Googleスプレッドシート
  • 情報の更新、ツイートの連携 - GAS
Googleスプレッドシートでは次のように関数を組み合わせて情報を取得しています。

Googleスプレッドシート

C列に数式を入力しておき、GASでB列に貼り付けて実行するようにしました。

情報の取得ではIMPORTXML関数がポイントです。 この関数は任意のWebページの任意の項目を取得することが出来ます。複数取得した場合は下のセルに値が連続して入ります。

今回のケースでは「tengoku is-open」クラスのdivが存在すれば天獄が開いていると判定することにしました。取得できなければISNA関数がtrueになるので、NOT関数で反転させてisOpenがB4セルで分かるようにしています。

IMPORTXML関数の使い方は公式ドキュメントにありますが、第2引数のXPathの仕様をぐぐった方が参考になるかと思います。

IMPORTXMLのヘルプ


GASには先程の画面の「ツール」→「スクリプト エディタ」から移動します。こうするとブックとGASが紐づいた状態でプログラムが組めるので便利です。

GAS

ソース
// GASのトリガーから実行する関数
function myFunction() {
  // 開いているシートを取得
  var sheet = SpreadsheetApp.getActiveSheet();
  
  // 数式を取得
  var rangeFrom = sheet.getRange(4, 3, 9);
  var valuesFrom = rangeFrom.getValues();
  
  // 数式を設定して最新情報を取得
  var rangeTo = sheet.getRange(4, 2, 9);
  rangeTo.setFormulas(valuesFrom);
  
  // 作成したツイートを取得
  var tweetNew = sheet.getRange("B11").getValue();
  var tweetLast = sheet.getRange("B1").getValue();
  
  // エラーでなければ
  if(tweetNew != "#N/A" && tweetNew != "#ERROR!"){
    // 前回のツイートから更新されたらつぶやく
    if(tweetLast != tweetNew){
      // ITFFFに連携
      callIFTTT(tweetNew);
      // 前回のツイートを更新
      sheet.getRange("B1").setValue(tweetNew);
    }
  }
  
  // 次回再読込されない事が無いようにクリア
  rangeTo.clear();
}

// IFTTTのWebhooksを呼び出す
function callIFTTT(value1) {
  // トリガーのURL
  var url = 'https://maker.ifttt.com/trigger/イベント名/with/key/キー';
  // ヘッダー設定
  var headers = {
    "Content-Type": "application/json"
  };
  // post内容
  var data = {
    "value1": value1
  };
  
  // postの設定
  var options = {
    "method" : "post",
    "headers": headers,
    // JavaScriptオブジェクトをJSON文字列に変換
    "payload" : JSON.stringify(data)
  };
  
  // 呼び出し
  UrlFetchApp.fetch(url, options);
}


IMPORTXML関数がシート側でいつ再読込されるか分からないため、GASで明示的に再計算とクリアしています。本当はVBAのCalculateのように再計算させたかったのですが、いい方法が分からず苦肉の策です。

Googleスプレッドシート側のエラーはISERROR関数で判定したかったのですが、GASの実行が不安定になった時(IMPORTXMLがLoading...で時間がかかっている時)は判定できなかったので、GAS側に持ってきました。これも本当は文字列で比較する以外の方法が知りたい所です。

IFTTTのWebhooksについては後述しますが、UrlFetchApp.fetch()でリクエストを投げています。Postの設定が少し大変でしたが、公式ドキュメントに詳しく書いてあるのでGoogle翻訳でいけました。

Class UrlFetchApp(英語)

GASができたら手動で動かしてみましょう。関数が複数ある時は画面上の「関数を選択」で実行したい関数を選んで、左の三角マークのボタンで実行できます。権限が求められた場合は許可してあげます。

動作が確認できたらトリガーを設定します。実行ボタンの1つ左の時計ボタンを押すとトリガーの設定ができます。

GASのトリガー

右下の「トリガーを追加」をクリックすると設定できます。


時間ベースのトリガー

作成した関数を選択してから時間主導型を選択します。分ベースのタイマーは最短で1分が設定できます。今回は1分の精度は求めていないので負荷が少ない5分にしました。これで5分に1回GASが実行できます。

なお5分間のうちいつ実行されるのかは指定することが出来ないので、実行履歴を見ながら何回かタイマーを設定しなおすと0分に近い所で実行できるかもしれません。

IFTTT

IFTTTのアプレットではトリガーのthisにWebhooks、アクションのthatにTwitterのPost a tweetを設定しました。

最初はトリガーにGoogleスプレッドシートのCell updatedを指定していましたが、タイムラグが大きく天獄が開いてから45分後にツイートされてしまう事があったので、最終的に即時性と汎用性の高いWebhooksにしました。B1セルに色を付けていたのはこの時の名残です。

IFTTTでは多少の英単語とアイコンを見れば簡単にアプレットが作れるので、基本的な説明は割愛しますが、Webhooksの設定がポイントなのでここを見ていきます。右上の自分のアカウントをクリックして、services→Webhooksと進み、設定画面を開きます。

https://ifttt.com/maker_webhooks

IFTTTのWebhooks
ここで右上のDocumentationをクリックするとWebhooksの使い方が表示されます。

Webhooksの使い方
リクエストを送るURLが表示されます。eventにはアプレットのトリガーを作った時のイベント名を入れます。keyは自分のアカウントのkeyが表示されています。先程のGASから呼び出していたのがこのWebhooksになります。

JSONで引数を3つまで設定できますが、今回はツイート内容を1つにまとめて連携するのでValue1だけ使っています。

Test Itボタンをクリックするとcurlコマンドが実行されてテストできます。curlコマンドは今回覚える必要はありませんが、気になる方はぐぐってみてください。

アプレットのアクション側は受け取ったValue1をつぶやくだけです。

IFTTTのTwitter設定

Twitter

Twitter側で特別な設定はありません。IFTTTに連携を許可するぐらいです。

普通にログインしてプロフィールの設定などをしておきましょう。

しいて言えば2重投稿や空ツイートはTwitter側が弾いてくれます。

結果


天獄が1時に開いてから1分以内にお知らせツイートができました。やったー!

GASがいいタイミングで動作してくれて、IFTTTがすぐに反応してくれました。

感想

プロトタイプは1日で作れましたが、上手く連携できるまでには試行錯誤がありました。

最初はAWS LambdaのPythonでTwitterAPIを呼ぶ想定でしたが、途中でTwitterAPIは開発者申請が必要になっていた事に気づき、しかも申請が承認されるまで1ヶ月ぐらいかかるらしいので急遽別のプランを練り直しました。

Twitterにつぶやく方法を検討して最終的にIFTTTにしたのは、プログラミングなしで色々できそうな手軽さがあったからです。検討段階でGoogleスプレッドシートと連携できる事が分かり、情報の取得もGoogleスプレッドシートのIMPORTXML関数を使うことにしました。

まず最初の問題はIMPORTXML関数を定期的に実行させることでした。GASのトリガーを使えば簡単にできると思いましたが、関数の引数が同じだと再読込されないようで、毎回最後にクリアしておくようにしました。

次にIMPORTXML関数の不安定さです。最初はIMPORTXML関数を3つ使って、天獄が開いているか、開始時間、終了時間を取得していましたが、IMPORTXML関数は複数使うと動作が不安定になるようでした。1〜2秒で終わるようなGASの実行が30秒かかる時があったので、1回で済むようにXPathについて調べる必要がありました。

そして誤算だったのがIFTTTの遅延です。Googleスプレッドシートのセルが更新されたら連携するようにしていましたが、普通に15分とか遅れるので、トリガーをメールかWebhooksに変更する必要がありました。これのおかげでGAS側に関数を追加することになってしまいました。

最終的にはGoogleスプレッドシートさえ触ればツイート内容を変えられるようになったので、共有して他の人に保守してもらう事もできるようになったのが良かったかなと思います。プログラムに触らなくてもExcelの関数が分かる人ならすぐ使えるようになるでしょう。その時は行と列を可変にした方がいいかもしれませんね。あと完全に無料枠で使えるのが良かったです。

IFTTTのWebhooksがトリガーなのでGoogleスプレッドシート以外の方法で連携できるのも汎用性が高くていいかなと思います。

だいぶ技術的な説明に寄ってしまいましたが、ドラクエXプレイヤーの方、どうぞ天獄Botをフォローしてみてください。多分便利だと思います。

天獄bot(@dqx_tengoku_bot