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

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

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

目次

準備するモノ

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 を作成していく。

IFTTT の画面上は << >> (2重の山カッコ) で囲むと URI エンコード (エスケープ) できる、といった記載があるが、実際は <<< >>> (3重の山カッコ) でないといけないらしい。

mstdn.jp は IFTTT からのリクエストを拒否している模様

本来はこのように設定すれば、RSS フィードが更新された時に、Mastodon API を使ってトゥートされるはずなのだが、mstdn.jp へのリクエストは 403 エラーになってしまい、うまくいかなかった。

どうやら mstdn.jp 側が IFTTT からのリクエストを拒否しているようで、避けられない。同じ Access Token を使って curl で投げたり iOS ショートカットに組み込んだりする分には正常に動作するので、IFTTT だけが拒否されているようだ。

GAS を経由して送信する

ということで、mstdn.jp にトゥートする場合は、

という手順にしてみる。

まず 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);
  }
}

コードを記述したら「公開」→「ウェブアプリケーションとして導入」を選択し、次のように指定する。

初回のみ謎のダイアログが出るが、気にせず承認し、URL を発行する。

そしたら IFTTT の Applet を次のように設定する。

GAS の URL に向けて JSON を POST するよう書き換えている。その際、自分で用意した post_from というパラメータによって、一応余計な POST を拒否するように GAS 側で制御している。

コレで IFTTT 側も準備 OK。ただ、GAS はスクリプト実行後に 302 をレスポンスするので、IFTTT の Activity 的にはエラー扱いになってしまうのが残念なところ。まぁトゥートは正常にできているし、コレで良しとする。

参考文献