女子WEBエンジニアのTechメモ

都内の某企業でマーケッターやってます。営業→WEBエンジニア・オフショア開発チーム駐在→マーケッター(SEO)ちょっと便利だなと思ったことをメモしています。

WordPressの記事をはてなブログに自動で連携【AtomPubを使う】

※2017/02/14コード修正しました。

以前、はてなブログのメール投稿機能をつかってWordPressとはてなブログを連携する記事を書きました。
www.wegirls.tech

これを使っているうちに、

「画像ちゃんと入れたい」

「編集したい」

とかの不満がたまってきたので模索していたときに、はてなブログAtomPubの存在を知ったので、使い方をメモ。

実装してみたけど、短時間でめちゃめちゃ便利なものになったので、最初からこっちを使っておけばよかったーー。

はてなブログAtomPubとは?

公式はこちら。
はてなブログAtomPub - Hatena Developer Center

  • 記事の一覧を取得
  • 記事を投稿
  • 記事の編集、削除

などができる、便利なはてなブログのAPIです。ブログによってはxmlrpcをつかっているけど、はてなはAtomPubというタイプのAPIをつかっているので「はてなブログAtomPub」です。

メール投稿ではなくて、はてなブログAtomPubを使うメリット

(はよサンプルコード見たいんじゃ、という人は飛ばしてください。)

メリットその1:画像が何枚でも、指定した位置に入る

メール投稿と比べて、画像が(WordPressで)指定した通りの位置に、何枚でも入る!

メール投稿だと、本文中にimgタグで画像を入れても、添付扱いになってしまい、
(画像を添付したときと同様)文頭にぽーんと入れておわり、となってしまうんです。

せっかく意識して選んだ画像がっ。

と悲しく思っていたのですが、AtomPubならそんなことありません。送信した内容そのまま反映されたので、文中に入れた画像も無事です。

(通常のはてなブログ投稿みたいに、はてなフォトにアップロードされるわけではないので、自サーバー削除してしまって画像が非表示になってしまった・・・とならないように注意してください。)

メリットその2:編集もできる

一度送信したものを編集・削除できる!!

(この記事では行っていませんが、同じAPIで可能です。)

メール投稿だと、一方的にはてなブログに送るのみで、追記したいときも編集ができません。
一度はてなブログに移動して、WordPressと同じようにちまちま直す・・・という手間がかかってしまっていました。

でも、このAPIなら編集・削除ができるものもあるので、記事投稿と組み合わせることでさらに便利になりそうです。

メリットその3:カテゴリもつけられる

WordPressでつけたカテゴリを、そのまま同じようにはてなブログにもつけられる!

これは嬉しいですね。カテゴリがあることで、SEO的にも良いことがありそうな気がします。


AtomPubで、WordPress投稿時にはてなブログに同時投稿する(サンプルコード)

はてなブログで、API keyを確認

はてなブログにログインし、投稿したいブログのAPI keyなどを確認します。

「設定」>「詳細設定」にある、API keyと、hatena_id、blog_idを使います。
f:id:bombomprin:20170203205717p:plain

WordPressに、連携用のコードを書く

/wp-content/themes/テーマ名/functions.phpの中身に以下を追記する。

ちなみに今回は、カテゴリで連携先を判別することで、複数のはてなブログに連携する、というようにしています。
※連携先のはてなブログを判別しなくてよい、1ブログだけしかない!というためのコードは、記事の最後にあります。

function send_hatena_api($post_id){
  // WordPressの投稿内容を取得
  $post = get_post($post_id);

  //カテゴリ取得して連携先を判別
  $cats = get_the_category($post_id);
  $category = "";

  // デフォルトは雑記用ブログへ
  $target = "life";

  if(!empty($cats)){
    foreach($cats as $cat){
      $cat_name = $cat->cat_name;
      // 投稿用XMLを作成しておく
      $category .= '<category term="'.$cat_name.'" />';

      // プログラミング のカテゴリがあれば、エンジニア用ブログへ
      if($cat_name == "プログラミング"){
        $target = "engineer";
      }elseif($cat_name == "アフィリエイト"){
      // アフィリエイト のカテゴリがあれば、アフィリエイト用ブログへ
        $target = "affiliate";
      }
    }
  }

  if($target == "life"){
    $hatena_id = "bombomprin";
    $blog_id   = "wegirls.hatenablog.com"; //サブドメ以下までちゃんと書く
    $api_key   = "APIキーをここに書きます";
    $author    = "らっこ"; //ブログ表示には反映されていない
  }elseif($target == "engineer"){
    $hatena_id = "bombomprin";
    $blog_id   = "wegirls.hatenablog.com";
    $api_key   = "APIキーをここに書きます";
    $author    = "あじゃ";
  }elseif($target == "affiliate"){
    $hatena_id = "bombomprin";
    $blog_id   = "affirakko.hatenablog.com";
    $api_key   = "APIキーをここに書きます";
    $author    = "あふぃりさん";
  }

  $subject = $post->post_title;

  // タイトルタグを変換
  $search = array('<h7','/h7>','<h6','/h6>','<h5','/h5>','<h4','/h4>','<h3','/h3>','<h2','/h2>','/p>');
  $replace = array('<h8','/h8>','<h7','/h7>','<h6','/h6>','<h5','/h5>','<h4','/h4>','<h3','/h3>','/p>');
  $entrybody = mb_convert_encoding(htmlspecialchars(nl2br(str_replace($search,$replace,$post->post_content)),"UTF-8",auto));

  //PEAR::HTTP_Requestを使う
  require_once("HTTP/Request.php");
  $posturl = "https://blog.hatena.ne.jp/".$hatena_id."/".$blog_id."/atom/entry";
  $created = date("Y-m-d\TH:i:s\Z");

  //WSSE認証データをセット
  $nonce = pack('H*', sha1(md5(time())));
  $pass = base64_encode(pack('H*', sha1($nonce.$created.$api_key)));
  $wsse = 'UsernameToken Username="'.$hatena_id.'", PasswordDigest="'.$pass.'", Created="'.$created.'", Nonce="'.base64_encode($nonce).'"';

  // XMLデータを生成
  // 下書きにしたい場合は <app:draft>の中身をyesにする
  $postdata = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:app="http://www.w3.org/2007/app">
  <title>{$subject}</title>
  <author><name>{$author}</name></author>
  <content type="text/plain">{$body}</content>
  <updated>{$created}</updated>
  {$category}
  <app:control>
    <app:draft>no</app:draft>
  </app:control>
</entry>
EOF;


  //HTTP_Requestで投稿する
  $req = new HTTP_Request();
  $req->addHeader('Accept','application/x.atom+xml, application/xml, text/xml, */*');
  $req->addHeader('Authorization', 'WSSE profile="UsernameToken"');
  $req->addHeader('X-WSSE',$wsse );
  $req->addHeader('Content-Type', 'application/x.atom+xml');
  $req->setMethod(HTTP_REQUEST_METHOD_POST);
  $req->setURL($posturl);
  $req->addRawPostData($postdata);
  $req->sendRequest();
  return $req->getResponseBody();

  //うまくいかない場合は、return...の行をコメントアウトして、 $req->getResponseBody(); の中身を見てみる
}
add_action( 'pending_to_private', 'send_hatena_api', 1 ,6);

*上記コードは、WordPressの記事ステータスが「レビュー待ち」から「非公開」になるときに自動連携されるようになっています。
変更したい場合は、こちらの記事を参考にしてください↓
WordPressの記事をはてなブログに自動で連携【メール投稿を使う】 - 女子WEBエンジニアのTechメモ

500エラーの対処法:PEAR::HTTP_Requestが使えなかった!

上記を実行したときに、500エラーになりました。。

原因は、HTTP_Requestをサーバーにインストールしていなかったから・・・

そりゃそうだーーー。゚(゚^ω^゚)゚。

というわけで、インストールしました。

CentOSにHTTP_Requestを(コマンドで)インストールする。

下記をコマンドラインに入力。
pear install HTTP_Request

実行結果はこんな感じでした。

[dev@myapp ~]# pear install HTTP_Request
WARNING: "pear/HTTP_Request" is deprecated in favor of "pear/HTTP_Request2"
WARNING: "pear/Net_URL" is deprecated in favor of "pear/Net_URL2"
downloading HTTP_Request-1.4.4.tgz ...
Starting to download HTTP_Request-1.4.4.tgz (17,233 bytes)
......done: 17,233 bytes
downloading Net_URL-1.0.15.tgz ...
Starting to download Net_URL-1.0.15.tgz (6,393 bytes)
...done: 6,393 bytes
downloading Net_Socket-1.0.14.tgz ...
Starting to download Net_Socket-1.0.14.tgz (5,655 bytes)
...done: 5,655 bytes
install ok: channel://pear.php.net/Net_URL-1.0.15
install ok: channel://pear.php.net/Net_Socket-1.0.14
install ok: channel://pear.php.net/HTTP_Request-1.4.4

インストール後に再度実行すると・・・

動いたーー!!!

できない!というときはXML Parse Error(パースエラー)かも

そのほか、WordPressの投稿は問題なく動作しているのに、はてなブログに投稿されていない、というときは、XMLのパースエラーになっているかもしれません。

一番最後の行の、$req->getResponseBody();の中身を見てみましょう。
var_dumpでもなんでもOKです。

わたしはここで、400 - XML Parse Errorとなってしまっていました。

投稿内容を「ほげほげ」とかに変えると正常に連携できるので、おそらくこれは文字の問題・・・。

HTMLタグが変換されているか再度確認

というわけで、HTMLタグを変換するhtmlspecialchars()を追加しました。(サンプルコード内にはすでに入れています。)

文字コードはUTF-8になっている?

また、サンプルコード内に書いてはいますが、mb_convert_encoding()で文字コードをUTF-8にしておきます。

はてなブログのAtomPubはUTF-8しか受け付けていないそうなのですが、
WordPressからの投稿だと、文字コードがShift-Jisになっていることがあるので要注意です。

はてなブログへ連携するときの注意点

タイトルタグの変換が必要

わたしは、WordPressで記事をかくとき、「見出し2」以降を使うようにしています。これは、htmlタグのH2に相当します。(テーマによって違うかも)
でも、はてなブログの大見出しは、H3なんですよね。
このように、記事内で使用されているタイトルタグが違うので、そのままWordPressの記事をはてなブログに送ってしまうと、はてなブログでデザインが崩れて表示されます。

それを避けるために、下記の部分で変換をしています。

  // タイトルタグを変換
  $search = array('<h7','/h7>','<h6','/h6>','<h5','/h5>','<h4','/h4>','<h3','/h3>','<h2','/h2>','/p>','><br />');
  $replace = array('<h8','/h8>','<h7','/h7>','<h6','/h6>','<h5','/h5>','<h4','/h4>','<h3','/h3>','/p>','>');
  $entrybody = mb_convert_encoding(htmlspecialchars(str_replace($search,$replace,nl2br($post->post_content)),"UTF-8",auto));

改行の変換が必要(nl2br)

WordPressの記事をそのまま反映すると、改行がはいらず、みっちりした見た目になってしまいます。
さっきの↑の部分で、ついでにnl2br()をいれているのは、WordPressのビジュアルエディタで挿入した改行を、はてなブログにも反映させるためです。

連携先のはてなブログが1つの人は、こちらのコードをどうぞ

function send_hatena_api($post_id){
  // WordPressの投稿内容を取得
  $post = get_post($post_id);

  $hatena_id = "bombomprin";
  $blog_id   = "affirakko.hatenablog.com";
  $api_key   = "APIキーをここに書きます";
  $author    = "あふぃりさん";

  $subject = $post->post_title;

  // タイトルタグを変換
  $search = array('<h7','/h7>','<h6','/h6>','<h5','/h5>','<h4','/h4>','<h3','/h3>','<h2','/h2>','/p>','><br />');
  $replace = array('<h8','/h8>','<h7','/h7>','<h6','/h6>','<h5','/h5>','<h4','/h4>','<h3','/h3>','/p>','>');
  $body = htmlspecialchars("[:contents]<br>".str_replace($search,$replace,nl2br($post->post_content)));

  //PEAR::HTTP_Requestを使う
  require_once("HTTP/Request.php");
  $posturl = "https://blog.hatena.ne.jp/".$hatena_id."/".$blog_id."/atom/entry";
  $created = date("Y-m-d\TH:i:s\Z");

  //WSSE認証データをセット
  $nonce = pack('H*', sha1(md5(time())));
  $pass = base64_encode(pack('H*', sha1($nonce.$created.$api_key)));
  $wsse = 'UsernameToken Username="'.$hatena_id.'", PasswordDigest="'.$pass.'", Created="'.$created.'", Nonce="'.base64_encode($nonce).'"';

  // XMLデータを生成
  // 下書きにしたい場合は <app:draft>の中身をyesにする
  $postdata = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:app="http://www.w3.org/2007/app">
  <title>{$subject}</title>
  <author><name>{$author}</name></author>
  <content type="text/plain">{$body}</content>
  <updated>{$created}</updated>
  {$category}
  <app:control>
    <app:draft>no</app:draft>
  </app:control>
</entry>
EOF;

  //HTTP_Requestで投稿する
  $req = new HTTP_Request();
  $req->addHeader('Accept','application/x.atom+xml, application/xml, text/xml, */*');
  $req->addHeader('Authorization', 'WSSE profile="UsernameToken"');
  $req->addHeader('X-WSSE',$wsse );
  $req->addHeader('Content-Type', 'application/x.atom+xml');
  $req->setMethod(HTTP_REQUEST_METHOD_POST);
  $req->setURL($posturl);
  $req->addRawPostData($postdata);
  $req->sendRequest();
  return $req->getResponseBody();

}
add_action( 'pending_to_private', 'send_hatena_api', 1 ,6);

感想+WordPressおすすめ本

次は編集もできるようにバージョンアップしたい。

主要なブログサービスはなにかしら更新・投稿のためのAPIが用意されてるみたいなので、触ってみたいなー。

エンジニアだと意外と馴染みがうすいWordPressですが、改修とか構築とか結構要望のあるものなので、触ったことない人はこのあたりの本がおすすめです。↓

エンジニアのためのWordPress開発入門 (Engineer's Library)

エンジニアのためのWordPress開発入門 (Engineer's Library)

  • 作者: 野島祐慈,菱川拓郎,杉田知至,細谷崇,枢木くっくる
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/01/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る
一歩先にいくWordPressのカスタマイズがわかる本

一歩先にいくWordPressのカスタマイズがわかる本

  • 作者: 相原知栄子,大曲仁,プライム・ストラテジー株式会社
  • 出版社/メーカー: 翔泳社
  • 発売日: 2016/04/09
  • メディア: 大型本
  • この商品を含むブログを見る