esa.io という wiki 的なサービスがあります.Markdown 記法で気軽にサクサク記事が作れて,なかなか便利に使わせてもらっています.カテゴリ機能はもちろんあるのですが,もう少し記事一覧を見やすくしたいなと思っていました.そこで,コーディングの練習とか色々込み込みで記事生成の自動化をやってみました.(このページを投稿した後,README記事が記事一覧ページの上部に表示されるようにアップデートが入っています.ご了承を.)

esa.io とは

チーム内での情報共有を図るとか,そういう目的で使う基本的にクローズドなwikiサービスです.記事はMarkdown記法で書きます.数式とかUMLとかガントチャートとかも書けてしまうくらいには独自拡張されていますが,GitHub Flavored Markdown1)GitHub Flavored Markdown Spec https://github.github.com/gfm/のつもりで書けば特に困ることはないでしょう.ドラッグ&ドロップで画像を挿入できたり,GoogleDriveのドキュメント類をインラインフレームに表示させたりもできて,結構便利です.

記事の整理

esa において記事がどのように整理されていくのか,簡単に紹介します.(使っていて何となくそうなんだろうなぁと認識したことベースなので,間違っているかもしれません.)

まず,全ての記事にはユニークな番号が自動的に付与されます.これは必ず行われます.この番号のことをエサ番と呼んだりしますが,エサ番のない記事はないということです.

これに追加して「/」で階層を区切ったディレクトリのようなカテゴリを設定することができます.ディレクトリのようなと書いたのは,記事のないディレクトリだけを先に用意したりというようなことができないからです.また,記事を削除することでそのカテゴリに所属していた記事が1つもない状態になれば,そのカテゴリもまた自動的に削除されたように見えます.逆にカテゴリなしの記事を作成することはできます.

記事の名前の中には特別な扱いが約束されたものがあります.記事の名前に「README」を含むものは,そうでない記事とは少し異なる扱いになります.右に示した Fig.1 はカテゴリメニュー(ダークグレー背景・白文字の部分)の一部を切り出したスクリーンショットです.カテゴリメニューは名前の通りカテゴリの閲覧・選択に利用する UI です.ページをロードした時にはトップレベルのカテゴリしか表示されていませんが,表示されているカテゴリにその直下のカテゴリが存在する場合には,そのカテゴリ名をクリックすることで1レベル深いカテゴリまで表示させることができます.この時,カテゴリメニューにはカテゴリ名が表示されるだけで,記事名まで表示されることはありません.しかし,README 記事のみ例外的に表示されます.ただ表示されるだけでなく先頭に表示されます.これにより,カテゴリの概要が記載されているであろう README 記事に素早くアクセスすることができます.

Fig.1 カテゴリメニュー

やりたいこと

先ほど紹介したカテゴリメニューを通して,記事の一覧をカテゴリ毎に見ることができます.Fig.2 は esa の公式パブリックチームの「help」カテゴリを表示させた時のスクリーンショットです.この通り,特定のカテゴリに属する記事の一覧を見ることができます.ここではディレクトリ的により深い階層にある記事も再帰的にピックアップされるので,選択したカテゴリ配下の記事全ての一覧と言うべきでしょうか.

Fig.2 記事一覧

これで十分と言ってしまえばそれでお終いなのですが,もう少し色々あってもいいと思います.というのも,README 記事を見た時に,その記事が属しているカテゴリ配下にどのような記事があるのかを,README 記事内にもう少し自由度を持って表記させることができればいいなと思います.もちろん手作業でそのような記事を書くことなく,自動的にそうなって欲しいということです.

できたこと

記事名が README である記事の中に特定のタグを設置することで,定期的にそのタグに囲まれた範囲内が下の Fig.3 に示したようなサマリー記事と置き換わるような自動化の手段が構築できました.

1階層のみ掘り下げたカテゴリ毎にテーブルを分割し,それぞれにカテゴリ名と記事数を見出しに取った,更新日時が近い順の記事一覧表を組むようにしました.この辺りの表組みの詳細はもう少し改良の余地があるかもれませんが,サービス標準の機能と比較して,1つの記事当たりのスペースをあまり取らず多くの記事が画面内に収まるようにしています.

Fig.3 自動更新される記事一覧表

esa には API を利用した操作の手段が用意されています.Google Apps Script(以降 GAS と表記)でその API を通した記事の取得と編集を行うスクリプトを作成し,GAS に含まれるトリガー機能を利用して定期的な実行をするようにしました.置き換え用タグそのものは HTML コメントとして解釈されるので,記事閲覧時に目障りになることもありません.

詳細

ここでは少し詳しく技術的背景を説明します.

esa API

API とは

詳しい事は分かっていないのですが簡単に説明すると,ここで言うところの API というのは HTTP メッセージ2)HTTP メッセージ – HTTP | MDN https://developer.mozilla.org/ja/docs/Web/HTTP/Messagesを利用したデータのやり取りの中でも,ユーザーが操作中のブラウザと web サーバ間で行われるものではなく,アプリケーションと web サーバーの間で行われるような類のものに焦点を当てた時に,その web サーバーが用意しているインターフェースを指したものです.

このエントリの読者がこれを目にしている時も,当然ですが読者が利用しているブラウザがこの web ページに対する HTTP リクエストを送り,このブログを設置しているサーバの HTTP デーモンがこの web ページを表す html を HTTP レスポンスとして返していて,それがブラウザにより紙面に変換されています.これと同じことがアプリケーションと web サーバーの間でもできる訳です.この時,問い合わせる内容は URL だけではなく,末尾に付加されたクエリ文字列や HTTP ヘッダなども担うことになります.

例えば,次のような URL に対して「GET メソッド」によるリクエストを投げると,タイトルが README の記事を更新日で降順にソートした記事一覧のうち最初の50個を取得することができます.チーム固有の URL の部分を伏せていること,リクエストヘッダにアクセストークンを含める必要があること,などの理由でこの URL は実際には機能しませんが,このようにサービス提供側が取り決めた URL に対して規定に則ったフォーマットでクエリ文字列を構成し,必要な情報をヘッダに含め,指定されたメソッドでアクセスすることで欲しいレスポンスを受け取ることができます.

https://api.esa.io/v1/teams/************/posts?q=title:README&include=comments&sort=updated&order=desc&page=1&per_page=50

試しても認証に失敗するので,次のような JSON 文字列3)JSON https://www.json.org/json-ja.htmlが返ってきます.

{"error":"unauthorized","message":"Unauthorized"}

ちなみに,JSON 文字列には馴染みがあったつもりでしたが,JavaScript と非常に関係の深いものであったことは今回初めて知りました.

esa API の仕様

esa の API 仕様は次のページで公開されています.つまり,どの URL に対してどのメソッドでアクセスするのか,またそのときの問い合わせ内容をクエリ文字列でどう表すのか,またヘッダとボディに含める情報とその書き方が示してあります.更に,レスポンスに含まれる情報とその構文が書かれています.

主にこの仕様書に則ってコーディングしましたが,この手のことはしたことがなかったので,関連したことをされているエントリを大変参考にさせていただきました.読み解けなかった仕様をコードから推測したりもしました.紹介しておきます.

Google Apps Script

JavaScript で記述された web アプリっぽいものを作成できるサービスです.Microsoft Office に対する VBA の関係性を,Google ドキュメントに適用したようなサービスでしょうか.実行環境が Google のサーバー側にあるので,その点で出来ることが違ってきます.

Apps Script は、G Suite の統合、自動化、拡張のためのビジネス ソリューションをすばやく簡単に構築するための唯一のローコード プラットフォームです。Apps Script を使えば、ビジネス ユーザーは本格的なソフトウェア開発の経験がなくても、G Suite 上にカスタム ソリューションを構築できます。Apps Script は、Gmail アカウントをお持ちであればどなたでもご利用いただけます。

https://gsuite.google.co.jp/intl/ja/products/apps-script/

リファレンス4)Reference overview | Apps Script | Google Developers https://developers.google.com/apps-script/referenceも充実しているように感じました.欲しかった情報は探せば大体出てきました.

もちろんというのもおかしな話ですが JavaScript も書いたことがなかったので,mozilla の JavaScript リファレンス5)JavaScript リファレンス – JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Referenceを事あるごとに調べ倒しました.ただ,実行環境により対応している JavaScript のバージョンが違い,GAS 独自の流儀もあるようで,その点はちょっと面倒でした.

書いたコード

実際に書いたコードを示します.構成としては,esa API を取り扱うクラスと,記事に関するデータをまとめた JSON オブジェクトを取り扱い Markdown 記事を編集するためのクラスの2つを実装しました.この2つのクラスを使って処理の流れを記述しました.

class EsaApi

HTTP リクエストに直接関わる部分をラップして,API の URL 毎に設定されている機能の集合となるようにパブリックなメソッドを実装しました.使うものしか実装していないので,次の3つの機能しかありません.

  • 特定のエサ番の記事を取得する
  • 記事検索文字列に該当する記事一覧の取得
  • 特定のエサ番の記事を更新する

ページネーションに関わる部分は隠蔽していますが,API の残弾切れを起こすようなリクエストに対する処理は書けていません.開発中の機能の名残として,指定回数の API を浪費するだけのメソッドもありますが,これは相手方に無用な負荷をかけるだけです……

認証に関しては,その方が簡単なので(OAuth 認証後のリダイレクトをGASで扱う方法に躓いたので),事前にアプリから手動で取得したアクセストークンをコンストラクタから与えてもらう方式としました.HTTP リクエスト URL と HTTP レスポンスステータスコードと API の残弾数は GAS 上にログを残すようにしています.オプションでレスポンスボディーのサマリー的な情報も残せるようにしています.

class EsaUtil

このクラスの実装がやりたかったことの本体と言っていいでしょう.置き換え範囲は次に示す HTML コメントを利用することにしました.デバッグ用途として,取得したJSON形式の記事一覧をまるごと指定された GoogleDrive のフォルダに保存するメソッドも残しています.

<!-- DESCENDANT-POSTS-SUMMARY-START -->
<!-- DESCENDANT-POSTS-SUMMARY-END -->

function main()

全体的な処理の流れはこのようにしました.

TEAM_NAME,TOKEN,GOOGLE_DRIVE_FOLDER_ID の部分を各々の環境に合わせた文字列に書き換えて,これまでの3つのファイルをこの順に GAS にコピペすれば動くはずです.

まとめ(感想)

GAS の雰囲気がつかめて,HTTPリクエスと REST API の扱いにちょっと慣れて,JavaScript のコーディングが経験できて,ついでに esa も便利になったということで,楽しかったです.

おしまい.

References

References
1 GitHub Flavored Markdown Spec https://github.github.com/gfm/
2 HTTP メッセージ – HTTP | MDN https://developer.mozilla.org/ja/docs/Web/HTTP/Messages
3 JSON https://www.json.org/json-ja.html
4 Reference overview | Apps Script | Google Developers https://developers.google.com/apps-script/reference
5 JavaScript リファレンス – JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference

コメントを残す