Corredor

ウェブ、プログラミングの勉強メモ。

はてなブログを更新したら Mastodon に投稿する IFTTT を作る → mstdn.jp 対策に GAS も併用

はてなブログに限らずだが、RSS フィードを発行するブログ等の媒体を更新した時に、その RSS フィードの更新を検知して IFTTT の Webhook が発火し、Mastodon API を使ってブログの更新内容をトゥートする、そんな仕組みを作ってみた。

前半は通常のマストドン・インスタンスで成功するであろう構成で、後半は mstdn.jp 固有の問題を解消するための対策版を紹介する。

準備するモノ

  • 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 的にはエラー扱いになってしまうのが残念なところ。まぁトゥートは正常にできているし、コレで良しとする。

参考文献

Emperor of Sand

Emperor of Sand

  • アーティスト:Mastodon
  • 発売日: 2017/03/31
  • メディア: CD