はてなブログを更新したら Mastodon に投稿する IFTTT を作る → mstdn.jp 対策に GAS も併用
はてなブログに限らずだが、RSS フィードを発行するブログ等の媒体を更新した時に、その RSS フィードの更新を検知して IFTTT の Webhook が発火し、Mastodon API を使ってブログの更新内容をトゥートする、そんな仕組みを作ってみた。
前半は通常のマストドン・インスタンスで成功するであろう構成で、後半は mstdn.jp 固有の問題を解消するための対策版を紹介する。
目次
- 準備するモノ
- Mastodon API の準備をする
- IFTTT の Applet を作成する
- mstdn.jpは IFTTT からのリクエストを拒否している模様
- GAS を経由して送信する
- 参考文献
準備するモノ
- Mastodon のアカウント : 以降の例では mstdn.jpのテイで記載している →mstdn.jpは IFTTT 連携不可能 (後述)
- IFTTT のアカウント
- トゥートしたい RSS フィードの URL : はてなブログの場合、https://【ブログ URL】/rssに該当する
- mstdn.jpの場合のみ : Google Apps Script を作成するための環境 (Gmail アカウントを持っておけば良い)
Mastodon API の準備をする
何やら Mastodon の管理画面に「アプリ」という画面があり、ココで作ったアプリでも Access Token とかが発行できたのだけど、コレとは違うやり方で Access Token を発行しようと思う。多分結果は同じだと思う。
まず、次のようなパラメータを組み立てて curl する。
$ curl -X POST -d 'client_name=【任意のアプリ名】&redirect_uris=urn:ietf:wg:oauth:2.0:oob&scopes=read write follow' https://mstdn.jp/api/v1/apps
# 以下のような内容がレスポンスされる
{
  "id": "000000",
  "name": "【先程入力した「任意のアプリ名」】",
  "website": null,
  "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
  "client_id": "【クライアント ID】",
  "client_secret": "【クライアント・シークレット】",
  "vapid_key": "【Vapid Key・特に使わないので無視】"
}
続いて、レスポンス内容を組み合わせて次のような URL を構築し、その URL にブラウザでアクセスする。
https://mstdn.jp/oauth/authorize?client_id=【クライアント ID】&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=read%20write%20follow
表示されたページで「承認」を押すと、「認証コード」が発行されるので控えておく。
そしたら次のような URL を組み立てて、再度 curl を叩く。
$ curl -X POST -d 'grant_type=authorization_code&redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id=【クライアント ID】&client_secret=【クライアント・シークレット】&code=【ブラウザで発行した「認証コード」】' https://mstdn.jp/oauth/token
# 次のような内容がレスポンスされる
{
  "access_token": "【Access Token】",
  "token_type": "Bearer",
  "scope": "read write follow",
  "created_at": 1500000000
}
こうして Access Token が発行できたので、コレを控えておく。
ココまでやると、マストドンの設定画面の「アカウント」→「認証済アプリ」に、作成したアプリの情報が表示される。「開発」の「アプリ」の方には何も表示されない。
IFTTT の Applet を作成する
Access Token が用意できたら、IFTTT の Applet を作成していく。
- If That
    - 「RSS Feed」→「New feed item」を選択し、RSS フィードの URL を指定する
 
- Then This
    - 「Webhooks」→「Make a web request」を選択する
- URL : https://mstdn.jp/api/v1/statuses
- Method : POST
- Content Type : application/x-www-form-urlencoded
- Body : access_token=【Access Token】&status=<<<{{EntryTitle}}>>> {{EntryUrl}}&visibility=public
 
IFTTT の画面上は << >> (2重の山カッコ) で囲むと URI エンコード (エスケープ) できる、といった記載があるが、実際は <<< >>> (3重の山カッコ) でないといけないらしい。
mstdn.jp は IFTTT からのリクエストを拒否している模様
本来はこのように設定すれば、RSS フィードが更新された時に、Mastodon API を使ってトゥートされるはずなのだが、mstdn.jp へのリクエストは 403 エラーになってしまい、うまくいかなかった。
どうやら mstdn.jp 側が IFTTT からのリクエストを拒否しているようで、避けられない。同じ Access Token を使って curl で投げたり iOS ショートカットに組み込んだりする分には正常に動作するので、IFTTT だけが拒否されているようだ。
GAS を経由して送信する
ということで、mstdn.jp にトゥートする場合は、
- RSS フィードの更新 → IFTTT で検知 → GAS に情報連携 → GAS から Mastodon API をコールしてトゥート
という手順にしてみる。
まず GAS を作成し、次のようなコードを作成する。
function doPost(e) {
  try {
    // JSON パースする (パラメータがない場合はココで例外が発生する)
    const params = JSON.parse(e.postData.getDataAsString());
    
    // IFTTT からの連携フラグがない場合、投稿文字列がない場合は何もしない
    if(params.post_from !== 'ifttt' || params.status == null || params.status === '') {
      return ContentService.createTextOutput(JSON.stringify({ error: 'Invalid Parameter' })).setMimeType(ContentService.MimeType.JSON);
    }
    
    const result = UrlFetchApp.fetch('https://mstdn.jp/api/v1/statuses', {
      'method': 'POST',
      'payload': {
        'access_token': '【アクセストークンを指定する】',
        'status'      : params.status,
        'visibility'  : 'public'
      }
    });
    
    Logger.log(result);
    return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON);
  }
  catch(error) {
    Logger.log(error);
    return ContentService.createTextOutput(JSON.stringify({ error: error })).setMimeType(ContentService.MimeType.JSON);
  }
}
コードを記述したら「公開」→「ウェブアプリケーションとして導入」を選択し、次のように指定する。
- Execute the app as: 「Me (【自分の Gmail アドレス】)」
- Who has access to the app: 「Anyone, even anonymous」
初回のみ謎のダイアログが出るが、気にせず承認し、URL を発行する。
そしたら IFTTT の Applet を次のように設定する。
- If That
    - 「RSS Feed」→「New feed item」を選択し、RSS フィードの URL を指定する
 
- Then This
    - 「Webhooks」→「Make a web request」を選択する
- URL : 【先程発行した GAS の URL】
- Method : POST
- Content Type : application/json
- Body : { "post_from": "ifttt", "status": "{{EntryTitle}} {{EntryUrl}}" }
 
GAS の URL に向けて JSON を POST するよう書き換えている。その際、自分で用意した post_from というパラメータによって、一応余計な POST を拒否するように GAS 側で制御している。
コレで IFTTT 側も準備 OK。ただ、GAS はスクリプト実行後に 302 をレスポンスするので、IFTTT の Activity 的にはエラー扱いになってしまうのが残念なところ。まぁトゥートは正常にできているし、コレで良しとする。
参考文献
- ブログの記事を投稿したらMastodonでトゥートする(IFTTT使用) - ぼくにがうりくん。 … 主にコチラが参考になった
- How to toot via IFTTT webhook · GitHub
- Atsushi's Homepage 〜 Mastodon API を使ってみる
- apps - Mastodon documentation
- Twitter の投稿を IFTTT で Mastodon へ転送する。 - Qiita
- IFTTTでif `Google Assistant` then `Webhook`でBad Requestが発生したときの対応メモ - Qiita< >>>` が正しいらしい
- IFTTTとGASを連携させると夢が広がる!今すぐ連携させるぞ - ポンコツエンジニアのごじゃっぺ開発日記。
- IFTTT→GAS→PushBulletで通知を受け取る | 株式会社イーガオ
- IFTTTのアクションをGoogle Apps ScriptにしてIFTTTを拡張する。 - Qiita
- IFTTTのWebhookをGASのPOSTで受け取るときのパラメータ覚書 - Qiita
- GASのdoPost関数をcurlでテストする時リダイレクトが必要なら-Xオプションを使わない - Qiita
- Google Apps Script - GASへのアクセスに対してJSONをコールバック|teratail
- GASへデータをPOSTし結果をjsonで受け取る時はリダイレクトされる - なんも分からないのでしらべた