🐙

esaの記事をGitHubにpushする高機能webhookを作った(予約投稿&frontmatter自由記述)

2020/05/20に公開

はじめに

このブログは VuePress + Netlifyという構成で運用しています

今まではMarkdownファイルを手元で書いて公開するタイミングでGitHubにpushするという方法をとっていましたが、esaで記事を書いたら自動でGitHubに投稿されるようにしたいと思っていました

リンク先の記事にも書いてあるとおり、はじめは esa標準のGitHub Webhook を使えばできるかと思ったのですが、VuePressが 記事のメタデータをfrontmatterで管理する仕様 なので、esaが標準で付加してくるfrontmatter(記事のURLに .md を付けたとき に先頭に付加されているyaml部分)のままだと正しく記事データとして使えませんでした。

なので、自分でWebhookを書く必要があり、めんどくさがって放置してたのですが、今回重い腰を上げて実装したので紹介させてください🙌

  • frontmatterを付加しないようにできる
  • frontmatterの内容を記事ごとに自由に書ける
  • 指定した日時にGitHubにコミットがpushされるように予約できる

という自分で言うのもなんですが神機能を実装したので、色々と有効活用していただけるのではないかと思います😇

作ったもの

作ったものはこちらです。

https://github.com/ttskch/esa2github

Deploy to Heroku

このボタンをポチッと押すだけでHerokuでWebhookサーバーをホストでき、あとはesaの管理画面で Generic Webhook を設定するだけで使い始められます👍

もちろん、Herokuを使わずに自前のサーバーでホスティングすることもできますし、ngrok を使ってローカルから動作確認してもらうことも可能です。

使い方(基本編)

Herokuを使う場合の使い方を簡単に説明します。

1. Herokuにデプロイ

Deploy to Heroku

このボタンを押すと、以下のような画面が開いて、あなたのHerokuアカウントにesa2githubをデプロイできます。

アプリ名は適当に好きな名前を設定してください。

環境変数の意味は以下のとおりです。

環境変数 意味 必須/任意 設定値の例
GITHUB_OWNER 保存先GitHubリポジトリのオーナー名 必須 ttskch
GITHUB_REPO 保存先GitHubリポジトリのリポジトリ名 必須 blog.ttskch.com
GITHUB_BRANCH 保存先GitHubリポジトリのブランチ名 (空欄にするとデフォルトブランチが使われます)
GITHUB_BASE_PATH 保存先ディレクトリへのパス blog/_posts (空欄にするとルートに保存されます)
GITHUB_FILENAME_BY_TITLE 保存時のファイル名に投稿タイトルを使うか yes (空欄にすると投稿IDが使われます)
GITHUB_ACCESS_TOKEN GitHubのPersonal Access Token(ここで作成 必須
ESA_DISABLE_DEFAULT_FRONTMATTER 作成されるファイルにデフォルトのfrontmatterを付与しなくする yes
ESA_SECRET esaのGeneric Webhookにsecretが設定されている場合はその値

適切に入力したら、 Deploy app ボタンを押してしばらく待てば、以下のようにデプロイが完了します。

2. esaでWebhookを作成

https://{チーム名}.esa.io/team/webhooks を開いて、 Add Webhooks ボタンから Generic を選択して設定を作成します。

設定内容は例えば以下のような感じです。

3. あとは普通にesaで記事を書くだけ

準備はもう完了です。あとは普通にesaで記事を書いてShipItすれば、Webhook経由でGitHubにMarkdownファイルがpushされます🙌

使い方(発展編)

frontmatterを自在に設定する

標準のGitHub Webhook では、作成されるMarkdownファイルの先頭に自動で一定のfrontmatterが付加されます。

記事のURLに .md を付けたとき と同じ内容でファイルが作成されます。

esa2githubでもこれに合わせて、デフォルトでは以下のようなfrontmatterを付加するようにしてあります。

---
title: "タイトル"
category: path/to/category
tags:
  - タグ1
  - タグ2
published: true
number: 123
---

created_at updated_at の2つはGeneric Webhookでは取得することができませんでした。

これに加えて、以下のように記事本文の先頭のコードブロックにfrontmatterを書けば、それがfrontmatterとしてマージされるようにしました。

```
---
title: "上書きタイトル"
date: 2020-05-20
---
```

この場合、上記2つのfrontmatterはマージされて最終的に以下のような内容になります。

---
title: "上書きタイトル"
category: path/to/category
tags:
  - タグ1
  - タグ2
published: true
number: 123
date: 2020-05-20
---

また、 ESA_DISABLE_DEFAULT_FRONTMATTER 環境変数に truthy な値をセットしておくことで、デフォルトのfrontmatterを完全に無効にすることもできます👍

先ほどの例で言うと、 ESA_DISABLE_DEFAULT_FRONTMATTERtrue だった場合は、最終的なfrontmatterはシンプルに以下のとおりになります。

---
title: "上書きタイトル"
date: 2020-05-20
---

予約投稿する

MongoDB + Agenda で予約投稿機能も実装しました。
Redis + Bull で予約投稿機能も実装しました。

HerokuでMongoDBを無料で使える唯一のアドオンだった mLab のMongoDBアドオンが、2020/11/10をもってシャットダウンしてしまったので、Redisを使った実装に変更しました。

使い方はとっても簡単で、本文先頭のfrontmatterに commitAt という項目で指定日時を書いておくだけです👍

```
---
commitAt: 2020-05-20 17:55 +0900
---
```

これが書かれた記事がShipItされた場合、即座にcommit&pushはされず、指定された日時(誤差最大1分)にスケジューリングしてくれます。

ちなみに、指定された日時がすでに過去だった場合は結果的に即座にコミットされます。

日時を表す文字列のパースには dayjs を使っているので、dayjsが正常に解釈できる表記なら何でも指定可能です。

Herokuの無料プランで予約投稿機能を使いたい場合の注意点

esa2githubでは、予約投稿機能の実現のため、Herokuの Worker DynoONにしています

2020/05/21時点で こちらのドキュメント

Worker dynos do not sleep, because they do not respond to web requests. Be mindful of this as they may run 24/7 and consume from your pool of hours.

という記述があり、Worker DynoはWeb Dynoと違って30分放置してもスリープしないっぽく見えるのですが、Herokuの中の人にTwitterで質問してみたところ、Web Dynoがスリープするときは他のDynoも巻き込む仕様 とのことでした💨

https://twitter.com/herokujp/status/1263379997294657536

なので無料プランだと、最後にWebhookにアクセスがあってから30分で予約投稿のキューを処理するWorker Dynoがスリープしてしまって、予約時刻になってもキューが処理されず期待どおりに動作しません😓

次にWebhookにアクセスがあったタイミング(esaで何かしら記事をShipItしたタイミング)でWeb Dynoと一緒にWorker Dynoも再起動して、そこで溜まっていたキューが一気に処理される感じになります。

これを防ぐため、esa2githubはデフォルトで Heroku Scheduler をインストールします。

例えばブログ記事のように投稿時刻が毎日朝9:00とか夕方18:00とか決まっているような場合なら、Heroku Schedulerを使って毎日その直前の時刻にWeb Dyno(とWorker Dyno)を起こしてあげるようにしておくと、一応解決することができます。

毎日夕方17:30(JST)にWeb Dynoを起こすには、以下のようなジョブを登録しておけばよいです。

Heroku Schedulerからcurlで定期的にアクセスすることでWeb Dynoをスリープさせないという裏技はHerokuユーザーの間ではよく知られています。

なお、Web DynoもWorker Dynoもスリープしていない時間は当然に Free Dyno Hours を消費するので、このように定期的に起こすのは無料枠の消費を早めます。

なので、もし予約投稿機能を使わないなら、Heroku Schedulerも使わず、Worker Dyno自体もOFFにしておくとよいでしょう。

おわりに

というわけで、esa2github のご紹介でした。

実はこの記事も早速esaで書いています🙌

やっぱり書き心地がよくて筆がサクサク進みますね!esaサイコー!

GitHubで編集を提案

Discussion