本ドキュメントははてなブログにおける Atom Publishing Protocol の仕様を解説するものです。
Atom Publishing Protocol(以下 AtomPub) はウェブリソースを公開、編集するためのアプリケーション・プロトコル仕様です。はてなブログのAtomPubと通じて、開発者ははてなブログのエントリを参照、投稿、編集、削除するようなオリジナルのアプリケーションを作成できます。
AtomPub について詳しくは http://www.ietf.org/rfc/rfc5023.txt (英語)などを参照してください。
本仕様解説中に現れる URI は URI Template の記法に基づいて以下のように表記されます。
https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id}
各変数の意味と書式は次のようになります。
はてなブログProの独自ドメイン機能をご利用の方は、独自ドメイン設定前のブログのドメインがブログのIDとなります。ブログの詳細設定のAtomPub項内のルートエンドポイントをご参照ください。
HTTP の GET/POST/PUT/DELETE を特定の URI に対してリクエストし、そのリクエストに規定の XML 文書を加えて送信することでインタフェースが用意している操作を行うことができます。
AtomPub では、基本的に記事の集合を表す「コレクション」と、個々の操作の対象記事にあたる「メンバ」があります。コレクションとメンバはそれぞれに URI を持ち、その URI に対して操作を行います。はてなブログにおける コレクションURI と メンバURI は以下のとおりです。URIはすべてhttpsになっておりますのでご注意ください。
コレクションURI | https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry |
---|---|
メンバURI | https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id} |
どのようなコレクションが存在するかを記述するサービスに関しては、以下のURIを用いる事が出来ます。
サービス文書URI | https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom |
---|
また、はてなブログの AtomPub ではコレクションで利用されるカテゴリを記述する文書を以下の URI で提供しています。
カテゴリ文書URI | https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/category |
---|
一部の操作はそのレスポンスとして規定の XML 文書を返却します。
現時点で API がサポートしている操作は以下です。
以下、はてなブログ AtomPub の詳細を解説します。
はてなブログAtomPub を利用するために、クライアントは OAuth 認証、WSSE認証、Basic認証のいずれかを行う必要があります。
OAuth認証の詳細に関しては、はてなのOAuthを利用する方法を参照してください。はてなブログAtomPub では、全操作に関して read_private 操作 及び write_private 操作の承認を得ている必要があります。
WSSE認証の詳細に関してははてなサービスにおける WSSE認証 を御覧ください。
Basic認証についてはユーザ名としてはてなIDを、パスワードとしてAPIキーを利用することで認証できます。Basic認証はAPIのURLがhttpsではじまる場合にのみ利用できます。
本WSSE認証における、ユーザ名にははてなIDを、パスワードには、ブログの詳細設定に記載されたAPIキーをご利用下さい。
はてなブログ AtomPub では文字コードとして UTF-8 を利用します。リクエストXML 、レスポンスXML 共に UTF-8 として扱ってください。
はてなブログ AtomPub で操作できるコレクションの一覧を含むサービス文書を取得できます。
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom
HTTP/1.1 200 OK Content-Type: application/atomsvc+xml; charset=utf-8 <?xml version="1.0" encoding="utf-8"?> <service xmlns="http://www.w3.org/2007/app"> <workspace> <atom:title xmlns:atom="http://www.w3.org/2005/Atom">ブログタイトル</atom:title> <collection href="https://blog.hatena.ne.jp/はてなID/ブログID/atom/entry"> <atom:title xmlns:atom="http://www.w3.org/2005/Atom">ブログエントリリスト</atom:title> <accept>application/atom+xml;charset=utf-8;type=entry</accept> </collection> </workspace> </service>
はてなブログのエントリを操作するためのコレクションです。ブログエントリの一覧取得、新規ブログエントリの投稿を行うことができます。
コレクション URI を GET することで、ブログエントリ一覧を取得できます。一度に7件のブログエントリを取得できます。また、取得したブログエントリ一覧が、コレクションの部分的リストである場合には、 page パラメータを付与する事で、7件目以降のブログエントリも取得出来ます。続きについては、AtomPub の仕様に基づき、部分的リストの続きは rel=next となる atom:link の href 属性がその URI となります。page パラメータを付与しない場合には、最新の7件を取得します。
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry?page=1377575606
一覧取得のレスポンスは以下のようになります。ブログエントリの内容については、ブログエントリの取得の項目を参照してください。
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app"> <link rel="first" href="https://blog.hatena.ne.jp/{はてなID}}/{ブログID}/atom/entry" /> <link rel="next" href="https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry?page=1377584217" /> <title>ブログタイトル</title> <link rel="alternate" href="http://{ブログID}/"/> <updated>2013-08-27T15:17:06+09:00</updated> <author> <name>{はてなID}</name> </author> <generator uri="http://blog.hatena.ne.jp/" version="100000000">Hatena::Blog</generator> <id>hatenablog://blog/2000000000000</id> <entry> <id>tag:blog.hatena.ne.jp,2013:blog-{はてなID}-20000000000000-3000000000000000</id> <link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/ ブログID}/atom/edit/2500000000"/> <link rel="alternate" type="text/html" href="http://{ブログID}/entry/2013/09/02/112823"/> <author><name>{はてなID}</name></author> <title>記事タイトル</title> <updated>2013-09-02T11:28:23+09:00</updated> <published>2013-09-02T11:28:23+09:00</published> <app:edited>2013-09-02T11:28:23+09:00</app:edited> <summary type="text"> 記事本文 リスト1 リスト2 内容 </summary> <content type="text/x-hatena-syntax"> ** 記事本文 - リスト1 - リスト2 内容 </content> <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#"> <div class="section"> <h4>記事本文</h4> <ul> <li>リスト1</li> <li>リスト2</li> </ul><p>内容</p> </div> </hatena:formatted-content> <app:control> <app:draft>no</app:draft> </app:control> </entry> <entry> ... </entry> ... </feed>
コレクションURIに対して XML 文書を POST することで、ブログエントリを投稿できます。このブログエントリは、ブログに登録された記法で書かれたものであると解釈されます。以下の例ははてな記法で記述すると登録したブログにおける例です。
リクエストXML文書に必要なパラメータは以下です。
POST https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry <?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> <author><name>name</name></author> <content type="text/plain"> ** エントリ本文 </content> <updated>2008-01-01T00:00:00</updated> <category term="Scala" /> <app:control> <app:draft>{yes | no}</app:draft> </app:control> </entry>
ブログエントリの内容については、ブログエントリの取得の項目を参照してください。
HTTP/1.1 201 Created Content-Type: application/atom+xml;type=entry Location: https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id} <entry> <id>tag:blog.hatena.ne.jp,2013:blog-{はてなID}-20000000000000-3000000000000000</id> <link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/ ブログID}/atom/edit/2500000000"/> <link rel="alternate" type="text/html" href="http://{ブログID}/entry/2013/09/02/112823"/> <author><name>{はてなID}</name></author> <title>記事タイトル</title> <updated>2013-09-02T11:28:23+09:00</updated> <published>2013-09-02T11:28:23+09:00</published> <app:edited>2013-09-02T11:28:23+09:00</app:edited> <summary type="text"> エントリ本文 </summary> <content type="text/x-hatena-syntax"> ** エントリ本文 </content> <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#"> <div class="section"> <h4>記事本文</h4> </hatena:formatted-content> <category term="Scala" /> <app:control> <app:draft>no</app:draft> </app:control> <entry>
はてなブログのブログエントリを操作するためのメンバです。ブログエントリの取得、編集、削除を行うことができます。
メンバURIをGETすることで、ブログエントリを取得できます。
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id}
<?xml version="1.0" encoding="utf-8"?> <entry> <id>tag:blog.hatena.ne.jp,2013:blog-{はてなID}-20000000000000-3000000000000000</id> <link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/ ブログID}/atom/edit/2500000000"/> <link rel="alternate" type="text/html" href="http://{ブログID}/entry/2013/09/02/112823"/> <author><name>{はてなID}</name></author> <title>記事タイトル</title> <updated>2013-09-02T11:28:23+09:00</updated> <published>2013-09-02T11:28:23+09:00</published> <app:edited>2013-09-02T11:28:23+09:00</app:edited> <summary type="text"> 記事本文 リスト1 リスト2 内容 </summary> <content type="text/x-hatena-syntax"> ** 記事本文 - リスト1 - リスト2 内容 </content> <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#"> <div class="section"> <h4>記事本文</h4> <ul> <li>リスト1</li> <li>リスト2</li> </ul><p>内容</p> </div> </hatena:formatted-content> <category term="Scala" /> <category term="Perl" /> <app:control> <app:draft>no</app:draft> </app:control> </entry>
メンバURIに対してXML文書をPUTすることで、ブログエントリを編集できます。投稿されたブログエントリの日時は投稿を行った日時になります。新規ブログエントリの投稿と同じく、編集時にもブログに登録された記法が適用されます。
リクエストXML文書に必要なパラメータは以下です。
PUT https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id} <?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> <author><name>name</name></author> <content type="text/plain"> ** 新しい本文 </content> <updated>2008-01-01T00:00:00</updated> <category term="Scala" /> <app:control> <app:draft>no</app:draft> </app:control> </entry>
HTTP/1.1 200 OK Content-Type: application/atom+xml;type=entry <?xml version="1.0" encoding="utf-8"?> <entry> <id>tag:blog.hatena.ne.jp,2013:blog-{はてなID}-20000000000000-3000000000000000</id> <link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/ ブログID}/atom/edit/2500000000"/> <link rel="alternate" type="text/html" href="http://{ブログID}/entry/2013/09/02/112823"/> <author><name>{はてなID}</name></author> <title>新しいタイトル</title> <updated>2008-01-01T00:00:00</updated> <published>2013-09-02T11:28:23+09:00</published> <app:edited>2008-01-01T00:00:00</app:edited> <summary type="text"> 新しい本文 </summary> <content type="text/x-hatena-syntax"> ** 新しい本文 </content> <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#"> <div class="section"> <h4>新しい本文</h4> </hatena:formatted-content> <category term="Scala" /> <app:control> <app:draft>no</app:draft> </app:control> </entry>
メンバURIに対してDELETEすることで、ブログエントリを削除できます。
DELETE https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id}
HTTP/1.1 200 OK Content-Type: application/atom+xml;type=entry
はてなブログAtomPub で利用しているカテゴリ一覧を含むカテゴリ文書を取得できます。
GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/category
HTTP/1.1 200 OK Content-Type: application/atomcat+xml; charset=utf-8 <?xml version="1.0" encoding="utf-8"?> <app:categories xmlns:app="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" fixed="no"> <atom:category term="Perl" /> <atom:category term="Scala" /> ... </app:categories>
はてなブログ AtomPub に対して不正な操作を行った場合にエラーレスポンスが返却されます。各エラーレスポンスには次のような意味があります。
CPANモジュールのXML::Atom::ClientはAtomPubクライアントを実装するための、WSSE認証やリクエスト、レスポンスに必要なXML文書の組み立てなどを抽象化したモジュールです。
XML::Atom::Clientを用いて、はてなブログにエントリを投稿するサンプルコードは以下です。
#!/usr/bin/env perl use strict; use warnings; use utf8; use XML::Atom::Entry; use XML::Atom::Client; my $username = shift or die 'need username'; my $blog_domain = shift or die 'need blog_domain'; my $api_key = shift; my $PostURI = "https://blog.hatena.ne.jp/$username/$blog_domain/atom/entry"; my $client = XML::Atom::Client->new; $client->username($username); $client->password($api_key); my $entry = XML::Atom::Entry->new; $entry->title('テストエントリだよー'); my $content = " # マークダウンも書けますよ - こんな - ふうに - ね"; $entry->content($content); my $EditURI = $client->createEntry($PostURI, $entry) or die $client->errstr; print $EditURI;
XML::Atom::Client はWSSE認証を抽象化しているため、username/blog_domain/passwordメソッドでそれぞれをセットするだけで認証を通過できます。また、XML文書の組み立てはXML::Atom::Entryインスタンスを生成して行い、それを最後にXML::Atom::Clientインスタンスに渡せば完了です。
このスクリプトはコマンドラインから、
$ perl atompost.pl はてなID ブログドメイン APIキー
として実行できます。
上記の XML::Atom::Client はOAuth認証には対応していません。ここでは、フレームワークとして Mojolicious::Lite、OAuthライブラリとして OAuth::Lite を利用したサンプルコードを紹介します。
以下のスクリプトを oauth-sample.plとして保存して、以下のように実行します。
$ CONSUMER_KEY='<YOUR CONSUMER KEY>' \\ CONSUMER_SECRET='<YOUR CONSUMER SECRET>' \\ API_URL='<YOUR BLOG API ROOT ENDPOINT>' \\ perl oauth-sample.pl daemon
<YOUR CONSUMER KEY>, <YOUR CONSUMER SECRET> には、自分のOAuthアプリケーションの consumer key, consumer secretを設定してください。<YOUR BLOG API ROOT ENDPOINT> には、サービス文章のURIを設定してください。特に、はてなブログ AtomPub APIは、はてなのOAuth認証における、read_private, write_privateの両方を要求することに注意してください。詳しくは、認証についての章を参照してください。
スクリプトが起動したのち、http://localhost:3000へアクセスしてください。
#!/usr/bin/env perl use strict; use warnings; use utf8; use Mojolicious::Lite; use OAuth::Lite::Consumer; use OAuth::Lite::Token; use XML::Atom::Feed; use Encode; my $consumer_key = $ENV{CONSUMER_KEY}; my $consumer_secret = $ENV{CONSUMER_SECRET}; my $api_url = $ENV{BLOG_API_ROOT_ENDPOINT}; if (!($consumer_key && $consumer_secret && $api_url )) { print STDERR <<"ENDUSAGE"; Usage: CONSUMER_KEY=<YOUR CONSUMER KEY> \\ CONSUMER_SECRET=<YOUR CONSUMER SECRET> \\ API_URL=<BLOG API ROOT ENDPOINT> \\ plackup $0 ENDUSAGE exit 1; } my $consumer = OAuth::Lite::Consumer->new( consumer_key => $consumer_key, consumer_secret => $consumer_secret, site => q{https://www.hatena.com}, request_token_path => q{/oauth/initiate}, access_token_path => q{/oauth/token}, authorize_path => q{https://www.hatena.ne.jp/oauth/authorize}, ); get '/' => sub { my $self = shift; $self->stash(access_token => $self->session('access_token') || ''); } => 'root'; # リクエストトークン取得から認証用URLにリダイレクトするためのアクション get '/oauth' => sub { my $self = shift; $self->stash(consumer => $consumer); # リクエストトークンの取得 my $request_token = $consumer->get_request_token( callback_url => q{http://localhost:3000/callback}, scope => 'read_private,write_private', ) or die $consumer->errstr; # セッションへリクエストトークンを保存しておく $self->session(request_token => $request_token->as_encoded); # 認証用URLにリダイレクトする $self->redirect_to( $consumer->url_to_authorize( token => $request_token, ) ); }; # 認証からコールバックされ、アクセストークンを取得するためのアクション get '/callback' => sub { my $self = shift; $self->stash(consumer => $consumer); my $verifier = $self->param('oauth_verifier'); my $request_token = OAuth::Lite::Token->from_encoded($self->session('request_token')); # リクエストトークンとverifierなどを用いてアクセストークンを取得 my $access_token = $consumer->get_access_token( token => $request_token, verifier => $verifier, ) or die $consumer->errstr; $self->session(request_token => undef); # アクセストークンをセッションに記録しておく $self->session(access_token => $access_token->as_encoded); $self->redirect_to('/'); } => 'callback'; # アクセストークンを利用して、OAuthに対応したAPIを利用するためのアクション get '/list' => sub { my $self = shift; $self->stash(consumer => $consumer); my $access_token = OAuth::Lite::Token->from_encoded($self->session('access_token')) or return; # access_tokenなどを使ってAPIにアクセスする my $res = $consumer->request( method => 'GET', url => $api_url . '/entry', token => $access_token, params => {}, ) or die $consumer->errstr; my $xml = $res->content; my $entries = [ map { my ($link) = map { $_->href } grep { $_->rel eq 'alternate' } $_->link; +{ title => ( decode_utf8($_->title) ||'' ), link => ( $link || '' ) } } XML::Atom::Feed->new(\$xml)->entries ]; $self->stash(entries => $entries); } => 'list'; app->start; __DATA__ @@ root.ep <a href="/oauth">はてなOAuth認証をする</a> <br /> <% if ($access_token) { %> <a href="/list">エントリ一覧を表示</a> <% } %> @@ callback.ep % my $token = $self->stash('token'); <%= $token->token %></br> <%= $token->secret %> @@ list.ep % my $entries = $self->stash('entries'); <p>エントリ一覧</p> <ul> % for my $entry ( @$entries ) { <li> <a href="<%= $entry->{link} %>"><%= $entry->{title} %></a> </li> % } </ul> @@ exception.ep REQUEST ERROR: <%= $consumer->errstr %> </br> WWW-Authenticate: <%= $consumer->oauth_res && $consumer->oauth_res->header('www-authenticate') %>
Ruby gemsで公開されているatomutilというモジュールもXML::Atom::Clientと同様に、AtomPubクライアントを実装するための、WSSE認証やリクエスト、レスポンスに必要なXML文書の組み立てなどを抽象化したモジュールです。
atomutilを用いて、はてなブログにエントリを投稿するサンプルコードは以下です。
#!/usr/bin/env ruby # -*- encoding: utf-8 -*- require 'atomutil' USERNAME = ARGV[0] BLOG_DOMAIN = ARGV[1] API_KEY = ARGV[2] POST_URI = "https://blog.hatena.ne.jp/#{USERNAME}/#{BLOG_DOMAIN}/atom/entry" auth = Atompub::Auth::Wsse.new( username: USERNAME, password: API_KEY ) client = Atompub::Client.new(auth: auth) entry = Atom::Entry.new( title: "テストエントリ".encode('BINARY', 'BINARY'), content: <<'ENDOFCONENT'.encode('BINARY', 'BINARY')) *もちろん - はてな記法も - 書けます ENDOFCONENT p client.create_entry(POST_URI, entry);
このスクリプトはコマンドラインから、
$ ruby atompost.rb はてなID ブログドメイン APIキー
として実行できます。
ここではJavaのモジュールであるApache Abderaを用いた記事の投稿を、Scalaで実装した例を紹介します。
はてなブログにエントリを投稿するサンプルコードは以下の通りです。
import java.util.Date import org.apache.abdera.Abdera import org.apache.abdera.protocol.client.AbderaClient import org.apache.abdera.ext.wsse.WSSEAuthScheme; import org.apache.commons.httpclient.UsernamePasswordCredentials; object AtomPubEntryPost { def main(args: Array[String]): Unit = { val username = ""; val blogDomain = ""; val apiKey = ""; val abdera = new Abdera val abderaClient = new AbderaClient(abdera) WSSEAuthScheme.register(abderaClient, true); abderaClient.addCredentials("https://blog.hatena.ne.jp", null, "WSSE", new UsernamePasswordCredentials(username, apiKey)); val factory = abdera.getFactory // 記事の記述 val entry = factory.newEntry entry.setTitle("タイトル") entry.setUpdated(new Date()) entry.addAuthor(username) entry.setContent("本文") // 記事の投稿 val response = abderaClient.post(s"https://blog.hatena.ne.jp/$username/$blogDomain/atom/entry", entry) println("STATUS CODE : " + response.getStatus) } }
依存モジュールは、sbtを用いた場合には、以下のような記述を加えてください。
libraryDependencies ++= Seq ( "org.apache.abdera" % "abdera-bundle" % "1.1.3", )