MediaWikiのAPIを使ってページを自動作成する

筆者は、MediaWikiを自分のための情報収集と整理に使っています。いわば、個人のナレッジマネジメントですが、そのときに重宝しているのが、MediaWikiのAPIを使用するクライアントアプリケーションです。タイトルとページコンテンツを与えて新規ページを作成するという至って簡単なものですが、その割には役にたっています。記述言語はPHPです。

最近ではAPIを使ったCMS管理が必須になっていますが、APIを使ったクライアントアプリケーションの作成は簡単だよ、という例です。

ソースは[GitHub](https://github.com/cnloni/mwtools)にあげています。


#### 週別のテンプレートとは
筆者が最も利用しているのが、図1ようなテンプレートです。これは2020年29週の分ですが、週毎の月日と曜日、リンクをつけただけの簡単なものです。

```html:リスト1「2020年29週」
[[週報:2020年28週|前週]] --- [[週報:2020年30週|次週]]
== 7/13(月) ==
== 7/14(火) ==
== 7/15(水) ==
== 7/16(木) ==
== 7/17(金) ==
== 7/18(土) ==
== 7/19(日) ==
<hr />
[[週報:2020年28週|前週]] --- [[週報:2020年30週|次週]]
```
リスト1のテンプレートは、実際には図1のように表示されます（月曜日の欄には既にリンクメモが付加されています）。こんな具合に、思いついたメモや当日得た情報をその日の欄に順不動で追加して行きます。

<div>
<div><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52659/a515bb88-3f04-b24f-3f22-dc8f26f60893.png" alt="図1" width="480" /></div>
<div><b>図1</b> リスト1に対するMediaWikiの表示画面の一部。メニュー欄などは省略</div>
</div><p></p>

MediaWikiの機能として上部に目次が追加されます。各小見出しにはid属性が付与されますので、外部からリンクすることが可能です。この辺りの基本機能はQiitaとほとんど同じです。

「編集」という青のリンクは、見出し以下の編集機能です。CMSの仲間であるQiitaと違って、各小見出し以下だけを（次の同レベル以上の小見出しまで[^1]）編集できるのが便利です。Qiitaは全文を編集しなければならないので長い記事は書きにくいのですが、MediaWikiは適当に小見出しを入れることにより編集画面を小さくできます。

最初は、リスト１を手書きで追加していたのですが、日付や曜日を調べるのが意外に面倒です。週番号を間違えたりすると、ややこしいことになってしまいます。そこで、テンプレートを自動作成することにしました。


#### 必要条件
以下のアプリケーションを実行するためには

* MediaWikiのデフォルト認証を使っている（PluggableAuthを使用していない）
* 使用するユーザIDにページ作成権限がある

ことが必要です。PluggableAuthによる認証を使用している場合は、OAuth認証が必要になります。MediaWikiのAPIにおけるOAuth認証については、別記事にする予定です。


#### MediaWikiのAPI
MeidaWikiのAPIのドキュメントは、公式サイトの[API:Mainページ](https://www.mediawiki.org/wiki/API:Main_page)以下にあります[^2]。コマンド名に相当するactionプロパティだけで120種類を越える膨大なAPI群です。但し、ここで使うactionは、

* query
* edit
* clientlogin

の3種類だけですので、とても簡単です。


#### 処理の流れ
全体的な処理の流れは図2のようになります。PHPの起動スクリプトはローカルサーバ上に置きます。ページがなければ自動的に作成します。最後にリダイレクトしますので、このURL（図2ではlocalhostの8081番ポートにアクセスしています）にアクセスすれば常にその週のページが表示されるので便利です。筆者はブラウザのホーム画面にしています。

<div>
<div><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52659/7847f1f2-e8a2-b5ea-89b6-79f88ae21a99.png" alt="図2" width="360" /></div>
<div><b>図2</b> 処理の流れ</div>
</div><p></p>

PHPの起動スクリプトをリスト2に示します（例外処理等は省略しています。詳しくはGitHubをみてください）。経験のある方なら、クラスの名前だけで何をしているかおわかりになるでしょう。

```php:リスト2
011: // パラメータを得る
012: $config = require_once 'config/ClientConfig.php';
013: // get()とpost()の実装
014: $client = new MwCurlClient($config);
015: // APIに対するエージェント
016: $agent = new MwAgent($client);
017: // ログイン
018: $agent->clientLogin(
019: 	$config['username'],
020: 	$config['password'],
021: 	$config['url']
022: );
023: // $diff=0は今週をあらわす
024: $diff = isset($_GET['diff'])?$_GET['diff']:0;
025: // タイトルは「20XX年:YY週」,YY=今週の週番号
026: $title = MwWeeklyReport::getTitle($diff);
027: if ($agent->existsPage($title) == false) {
028: 	// ページが無いなら、あらかじめ作成する
029: 	// 週番号に対するテンプレートを得る
030: 	$text = MwWeeklyReport::getBlankPage($diff);
031: 	$agent->createNewPage($title, $text);
032: }
033: // 「20XX年:YY週」のページにリダイレクト
034: header('Location: ' . $agent->getUrl($config['url'], $title));
```

リスト2に登場するオブジェクトを次にあげます。

1. ClientConfig: usernameなどのパラメータを格納する配列
2. MwCurlClient: PHPのcurl関数群を使用してget()とpost()を実装する
3. MwAgent: MediaWikiのAPIにアクセスする各種のメソッド
4. MwWeeklyReport: リスト1でコンテンツマネージャと記したクラス。ページタイトルとテンプレートを作成する

以下では、MwAgentクラスの関連するメソッドについて説明します。

##### clientLogin()
MwAgentクラスのclientLoginメソッドをリスト3に示します。

「action=clientlogin」に必要なpostパラメータについては[clientloginの解説](https://www.mediawiki.org/w/api.php?action=help&modules=clientlogin)を参照してください。パラメータのうち「logintoken」は、事前に取得しておく必要があります[^3]。

実際には、ここで指定したパラメータに加えて「format=json」を指定する必要がありますが[^4]、post()の実装のなかで付加していますので、リスト3で指定していません。

```php:リスト3
101: 	public function clientLogin($user, $password, $rootUrl) {
102: 		$token = $this->getLoginToken();
103: 		$result = $this->client->post(array(
104: 			'action' => 'clientlogin',
105: 			'loginreturnurl' => $rootUrl,
106: 			'username' => $user,
107: 			'password' => $password,
108: 			'logintoken' => $token
109: 		));
110: 		$status = $result['clientlogin']['status'];
111: 		if ($status !== 'PASS') {
112: 			throw new Exception("'clientlogin' failed. STATUS = " . $status);
113: 		};
114: 	}
```


##### getLoginToken()
clientloginに必要なログイン用トークンはリスト4のように取得します。「action=query」はデータを取得するための[API](https://www.mediawiki.org/w/api.php?action=help&modules=query)です。指定するパラメータにより様々なデータが取得できます。「[meta=tokens](https://www.mediawiki.org/w/api.php?action=help&modules=query%2Btokens)」は、セキュリティー上の理由で必要なトークンを取得するのため使用します。

clientLogin()と同様、ここで指定したパラメータに加えて「format=json」を指定する必要がありますが4、get()の実装のなかで付加していますので、リスト4で指定していません。以下も同様です。

```php:リスト4
201: 	protected function getLoginToken() {
202: 		$result = $this->client->get(array(
203: 			'action' => 'query',
204: 			'meta' => 'tokens',
205: 			'type' => 'login'
206: 		));
207: 		return $result['query']['tokens']['logintoken'];
208: 	}
```

##### existsPage()
指定したタイトルを持つページが存在するかどうかを調べるだけのものです。これも「action=query」を使う問い合わせです。

```php:リスト5
301: 	public function existsPage($title) {
302: 		$result = $this->client->get(array(
303: 			'action' => 'query',
304: 			'titles' => $title
305: 		));
306: 		$pages = $result['query']['pages'];
307: 		$id = array_keys($pages)[0];
308: 		return $id >= 0;
309: 	}
```

##### createNewPage()
「[action=edit](https://www.mediawiki.org/w/api.php?action=help&modules=edit)」を用いた操作です。

コンテンツマネージャ（MwWeeklyReport）から得たテンプレートを内容として、新たなページを作成します。ここではページ作成と呼んでいますが、編集の場合も全く同じ手順です（と言っても、編集操作はMediaWiki画面で行うのが通常です）。getCsrfToken()は、getLoginToken()から「type=login」を除いただけですので、省略します。

これまでの例では、get()でもpost()でも良かったのですが[^5]、「action=edit」だけはpost()を使う必要があります。

```php:リスト6
401: 	public function createNewPage($title, $text) {
402: 		$token = $this->getCsrfToken();
403: 		$result = $this->client->post(array(
404: 			'action' => 'edit',
405: 			'token' => $token,
406: 			'title' => $title,
407: 			'text' => $text
408: 		));
409: 		$status = $result['edit']['result'];
410: 		if ($status != 'Success') {
411: 			// 作成に失敗した
412: 			throw new Exception("Failed to create new page.");
413: 		}
414: 	}
```

#### あとがき
ローカルPCの起動スクリプトをサービスとして起動する場合の処理については、「[PHPのビルトインウェブサーバをsystemdのサービスとして登録する](https://qiita.com/cnloni/items/b8d72c217a3941ef7b70)」という記事にしました。ご参考に。


[^1]: MediaWikiの「Wiki markup言語」では、見出しを複数の「=」ではさみます。「=」の数が見出しのレベルで、2個の「=」で挟むとhtmlのh2に変換されます。Qiitaで使う「Markdown言語」では前置きの「#」に相当します。
[^2]: 残念ながら、日本語ページはあまり内容が伴っていないので、なるべく英語ページを参照してください
[^3]: あらかじめトークンを取得する必要があるのは、XSS対策のひとつです
[^4]: 最近では、結果をjsonでAPIにおける事実上の標準になっています。MediaWikiでも、「format=json」がデフォルト設定であるとドキュメントに書かれているのですが、v1.34.2では、まだそうはなっていないようです
[^5]: clientLogin()でpost()を使っているのは、セキュリティー上の理由です