PHP+AtomPub+テキストではてなブログに投稿する

PHPでAtomPub(Atom Publishing Protocol)を使って、テキストファイルから はてなブログに投稿するスクリプトを書きました。

はてなブログAtomPub - Hatena Developer Center

先に言っておきますと、画像は使わず文章のみ書ければいい、 PHPが使いたいという人だけに限定されます。とりあえずやってみました感が満載です。

2014/04/11 0:22 追記

この記事ではOpenSSLを使用しており、PHP5.5.11においても配布されているdllが脆弱性に対応していないバージョンのため、dllを最新版(1.0.1g以降)のものに差し替えるか、PHPが更新されるまでは念の為利用しないでください

Shining Light Productions - Win32 OpenSSL

追記ここまで

実行環境

  • Windows 7 Professional 64bit
  • PHP 5.5.4*1

投稿処理の概要

処理の大ざっぱな流れは以下の通り。

  1. エントリ用のXMLを作成
  2. WSSE認証を通してエントリをPOST

エントリ用のデータをXMLで作成

冒頭のAtomPubのページに説明がありますが、テキストファイルから 投稿用のXMLを作成するため、以下の書式にしました。

  • 記事のタイトル
  • 記事のカテゴリ(カンマ区切りで複数設定可)
  • 空行
  • 本文

ファイル名をそのままタイトルに使わなかったのは、Windowsだとファイル名の文字コードが 内部的にShift-JISで、UTF-8に変換すると文字化けするケースがあるためです。 この書式に従ってテキストファイルを読み込んで、XMLにデータを流しこんでいます。

エントリ用のXML(hatena-blog-entry.php)

<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">
 <title><?= $title ?></title>
  <author><name><?= $hatena_id ?></name></author>
  <content type="text/plain">
<?= $content ?>

  </content>
  <updated><?= $update ?></updated>
<?php foreach($category as $val){ echo "  <category term=\"$val\" />\n";} ?>
  <app:control>
    <app:draft><?= $draft ?></app:draft>
  </app:control>
</entry>

WSSE認証を通してエントリをPOST

はてなサービスにおけるWSSE認証 - Hatena Developer Center

AtomPubを使用するためには、「OAuth認証」「WSSE認証」「Basic認証」のいずれかが必要。 WSSE認証を選択したのは、OAuth認証はアプリケーションの登録が必要なので除外、 Basic認証よりは安全そうだという理由からです。

スクリプトからのPOSTについてはPEARの「HTTP_Request2」というパッケージを使います。 HTTP_Requestをインストールしようとしたら、こちらはすでに更新が止まっているらしく、 HTTP_Request2を使うよう促されました。

準備

HTTP_Request2のインストール

PEARで以下のコマンドを実行。

pear install HTTP_Request2

Windows版PHPでのPEARの導入についてはこちらをどうぞ。

Windows版のPHPでPEARを使うためのメモ - Sprint Life

php.iniの変更

httpsで接続するためopen_sslを使用します。 php.iniの以下の行の;を削除。

変更前 ;extension=php_openssl.dll  
変更後 extension=php_openssl.dll

投稿処理のスクリプトの変更

コメントにも書いてありますが、以下の変数を自分のものに置き換えてください。 管理画面の「詳細設定」→「AtomPub」に記載されています。

$hatena_id = ""; // はてなID
$blog_id = ""; // ブログID
$api_key = ""; // APIキー

また、投稿か下書きかについては$draft(yes: 下書き no: 投稿)で制御しています。

使い方

  1. 投稿処理のスクリプトとXML、テキスト(entry.txt)を同じディレクトリに置く
  2. コマンドプロンプトからスクリプトを実行する

実行例

php hatena-atom-post.php > result.txt

処理が正常終了すると、投稿または下書きされたエントリがレスポンスとして返ってくるので、 実行結果を別のファイルに出力するようにしています。

注意事項

ローカルPCで自分だけが使うことを想定していますので、入力値のチェックなどは行なっていませんが、 試しにこの記事を投稿してみたところ、以下の状況を確認しました。投稿の内容によっては、 意図した結果にならない可能性があります。

  • 本文中にHTMLが含まれている場合は、あらかじめ特殊文字の変換をしないとエラーになる
  • 本文中のPHPのソースが表示されない

残念ながらこの記事自体は管理画面からの投稿です。

また、WSSE認証にはパスワードではなくAPIキーを使用しているので、 万が一APIキーがバレてもアカウントがのっとられることはないと思いますが、 APIキーを変更するまで、第三者がブログに投稿し放題の状態になってしまいます。

はてな各種APIでのパスワードによるWSSE認証を2014年3月5日に終了します(開発者向け) - Hatena Developer Blog

ソース

投稿処理のスクリプト(ファイル名は任意で拡張子はPHP。文字コードはUTF-8)

<?php
mb_internal_encoding("UTF-8");
mb_language("Japanese");
date_default_timezone_set("Asia/Tokyo");
require_once("HTTP/Request2.php"); 

// https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry
// 管理画面の「詳細設定」→「AtomPub」を参照
$hatena_id = ""; // はてなID
$blog_id = ""; // ブログID
$api_key = ""; // APIキー

// テキストファイルからエントリを作成
$fp = fopen('./entry.txt', 'r');
$title = trim(fgets($fp, 4096));
$category = fgetcsv($fp, 4096,',');
$blank = fgets($fp, 4096); 

$content = "";
while (($buffer = fgets($fp, 4096)) !== false) {
    $content = $content . $buffer;
}
fclose($fp);

$update = date('c');
$draft = "yes"; // yes: 下書き no: 投稿

ob_start();
include('./hatena-blog-entry.php');  
$entry = ob_get_contents();  
ob_end_clean();

//WSSEヘッダをつけてPOST
$nonce = sha1(md5(time()),true);
$nonce_base64 = base64_encode($nonce);
$created = date('c');
$pass_digest = base64_encode(sha1($nonce . $created . $api_key,true));
$wsse_header = "UsernameToken Username=\"$hatena_id\", PasswordDigest=\"$pass_digest\", Nonce=\"$nonce_base64\", Created=\"$created\" ";

$req = new HTTP_Request2();
$req->setConfig('ssl_verify_peer', false);
$req->setURL("https://blog.hatena.ne.jp/$hatena_id/$blog_id/atom/entry");
$req->setMethod(HTTP_Request2::METHOD_POST);

$req->setHeader('Accept','application/x.atom+xml, application/xml, text/xml, */*');
$req->setHeader('Content-Type', 'application/atom+xml');
$req->setHeader('Authorization', 'WSSE profile="UsernameToken"');
$req->setHeader('X-WSSE',$wsse_header);
$req->setBody($entry);
$response = $req->send();
    
echo $response->getBody();

?>

テキストファイルについては固定にしていますが、スクリプト実行時に引数として ファイルのパスを渡すことで、任意のファイルを使用できます。

変更前: $fp = fopen('./entry.txt', 'r');
変更後: $fp = fopen($argv[1], 'r');

おわりに

本当はエディタから直接投稿するところまでやるつもりでいたんですが、 スクリプトそのものの使いどころが、文章のみを投稿する場合に限定されそうなのでやめました。

PHPでAtomPubを使うサンプルとしてどこか役に立つところがあれば幸いです。

参考

*1:最低でも5.0以上。4系だと動きません