Crowi/Growi編集用のツールを作成する

Crowiを使っていると文書のバックアップが取りたくなることがあります。
GitlabなどのGithubクローンではWiki機能をgitで管理できたりしますが、Crowiでもバックアップやローカルでの編集ができれば便利ですよね。

そこで、今回はCrowiのAPIを使って記事を編集するツールを作成します。

はじめに

Crowi にはドキュメント化・サポートはされていませんが Web 経由でたたける API があります。そしてエンハンスされたcrowi-plus/Growiでも同じものが使えるようですね。

複雑になるわりに私はあまり使わないので、基本的な本文のアップロード・ダウンロードが出来るようにし、添付やコメントなどの操作は考えないことにします。
一応本家 Crowi だけではなくcrowi-plus/Growi でも動作を確認しています。環境は Windows 10 Pro + Python 3.6 です。

[追記]
Growi の最近のバージョンで API の仕様が若干変わったようなので新しい方にも対応できるよう修正しました。

スクリプト

今回もまずはスクリプトを記載しておきます。
なお、Webクライアントはrequestsモジュールを使っていますので、なければインストールする必要があります。

使い方

基本はスクリプトで定義しているCrClientクラスを使います。
設定を渡してインスタンス化したのち、各メソッドを呼びます。エラーハンドリングはしていないので、問題があれば例外を出して止まるはずです。

ユーザ名は記事の編集権限を持ったユーザ名、API tokenはCrowiの管理画面で発行したもの、Webクライアントに渡すためのCrowiのIP アドレス(解決できる場合はホスト名)、およびローカルの作業ディレクトリへのパス、がここでの設定項目になっています。
ポート名は決め打ち(3000番)になっているので、別の番号を指定している場合は private メソッド__urlを変更して下さい。

基本的な動作は、ローカルの作業ディレクトリと Crowi で当該ユーザが作成したページとを同期する、というものになっています。
ローカルの作業ディレクトリについては、パスは相対パスでも絶対パスでも構いませんが、スクリプト実行時に実在する必要があります。ローカルの作業ディレクトリを markdown の原稿作成用として git 等で管理しておくと便利だと思います。

本文として認識されるのは作業ディレクトリにある markdown ファイル(とディレクトリ構造)のみなので、その他のファイルがあっても一切無視されます。

以降に詳細を記載しておきます。

APIを使ったCrowiの操作

本家Crowiのソースで言うと、routeを見れば定義されているAPIがわかります。

記事(ページ)一覧の取得

CrClient.fetch()メソッドで、Crowiに作成されているページ一覧を取得できます。

一覧として取得できるのは指定したユーザが作成した記事のみのようです。この動作は以降の操作でも同じなようで、例えば、試していませんが、他のユーザが既に作成してしまっているページと同じ名前のページを作ろうとするとエラーになるはず。

Crowi の/_api/pages.listの URL を Get すると、JSON でページ一覧が返ってきます。
本文など全部コミコミなので、Crowi にたくさんページを作成している場合には結構ツライかも。

内部動作としては本文やページの ID を取り出しているだけです。
後ほどアップロード時の変更管理に使うため、ハッシュ(MD5)も計算しています。

ダウンロード

CrClient.fetch()メソッドの引数にdownload=Trueを渡すと、ダウンロードの動作になります。

コンストラクタに渡したパスをローカルの作業ディレクトリとし、Crowiサーバから作業ディレクトリへ、取得したページ本文のみをツリー構造そのままにダウンロードします。作業ディレクトリに配置する場合は拡張子.mdが付きます。

ローカルの作業ディレクトリに同じ名前の Markdown ファイルがあった場合には、上書きされますので注意。

Crowi の場合、ポータルページがサポートされている(crowi-plus は設定に依存)ので、存在する場合は”(portal).md”の名前でカテゴリの直下にダウンロードされます。逆に、この仕様では”(portal)”という名前の記事を作成することはできません。

処理の途中までは上に書いた一覧の取得と同じで、private のCrClient.__write()メソッドを使ってファイル保存を実行するか否かのみ異なります。
ローカルの作業ディレクトリに markdownファイルを書き込む際に、ファイルパスを計算します。
まあ何やらゴニョゴニョやっていますが、URLをローカルのパス+.mdに直したり、ポータルページのファイル名を指定する処理をしています。

ファイル出力は UTF-8、なので Windows のメモ帳では開かないほうが無難です。
Atom やら VS Code で Markdown を編集する分には困らない、、、はず。

アップロード

CrClient.upload()メソッドがアップロードを実装しています。

ダウンロードとは逆に、ローカルの作業ディレクトリにある markdown ファイルを Crowi の同じパスへアップロードします。
既に同じパスに記事が存在する場合は更新、存在しない場合は新規作成になります。

アップロードのややこしいのはこの更新 or 新規作成でAPIを投げ分けなければいけないところ。
両方とも POSTリクエストですが、それぞれ"_api/pages.update""_api/pages.create"と、別の URI(API)を使う必要があります。
特に、更新(update)の場合は、Crowi 上のパスではなく当該ページの ID("page_id")を指定しなければなりません。
直打ちする根性のある方以外の私のようなひとは、事前に取得 API をスクリプト中で呼んで指定します。今回の実装では、一覧の取得と同じくCrClient.fetch()を使って ID を取得させています。

また、更新の有無はハッシュを計算し、比較することで判別します。MD5ですがまあ実用上は十分でしょう。

ダウンロード時と同じく、ポータルページは”(portal).md”という名前のファイルとして管理されます。
このアップロードの操作は、ローカルのファイルでCrowi側のページを上書きします。が、ダウンロード時と異なりCrowi自身がページの変更履歴を管理しているので、うっかり想定外の上書きをしても(手動で)復元はできます。

おわり。