メインコンテンツまでスキップ

はてなブログAtomPub

本ドキュメントに関する注意事項

本ドキュメントははてなブログにおける Atom Publishing Protocol の仕様を解説するものです。

Atom Publishing Protocol とは

Atom Publishing Protocol(以下 AtomPub) はウェブリソースを公開、編集するためのアプリケーション・プロトコル仕様です。はてなブログのAtomPubと通じて、開発者ははてなブログのエントリを参照、投稿、編集、削除するようなオリジナルのアプリケーションを作成できます。

AtomPub について詳しくは http://www.ietf.org/rfc/rfc5023.txt (英語)などを参照してください。

URIの表記について

本仕様解説中に現れる URI は URI Template の記法に基づいて以下のように表記されます。

https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id}

各変数の意味と書式は次のようになります。

  • はてなID
    • 意味:あなたのはてなid
  • ブログID
    • 意味:ブログのID
    • 書式:ブログのドメイン (例: hoge.hatenablog.com)
  • entry_id:
    • 意味:ブログエントリのID
    • 書式:epochを表す数値 (例: 1227232862) または、英数字文字列

はてなブログProの独自ドメイン機能をご利用の方は、独自ドメイン設定前のブログのドメインがブログのIDとなります。ブログの詳細設定のAtomPub項内のルートエンドポイントをご参照ください。

はてなブログにおける AtomPub 実装の概要

HTTP の GET/POST/PUT/DELETE を特定の URI に対してリクエストし、そのリクエストに規定の XML 文書を加えて送信することでインタフェースが用意している操作を行うことができます。

AtomPub では、基本的に記事の集合を表す「コレクション」と、個々の操作の対象記事にあたる「メンバ」があります。コレクションとメンバはそれぞれに URI を持ち、その URI に対して操作を行います。はてなブログにおける コレクションURI と メンバURI は以下のとおりです。URIはすべてhttpsになっておりますのでご注意ください。

コレクションURIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry
メンバURIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry/{entry_id}

どのようなコレクションが存在するかを記述するサービスに関しては、以下のURIを用いる事が出来ます。

サービス文書URIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom

また、はてなブログの AtomPub ではコレクションで利用されるカテゴリを記述する文書を以下の URI で提供しています。

カテゴリ文書URIhttps://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/category

一部の操作はそのレスポンスとして規定の XML 文書を返却します。

現時点で API がサポートしている操作は以下です。

  • ブログの操作 (コレクション)
    • ブログエントリ一覧の取得 (コレクションURI への GET)
    • ブログエントリの新規投稿 (コレクションURIへの POST)
  • ブログエントリの操作 (メンバ)
    • ブログエントリの取得 (メンバURI の GET)
    • ブログエントリの更新 (メンバURI への PUT)
    • ブログエントリの削除 (メンバURI への DELETE)
  • サービスの操作 (サービス文書)
    • コレクション一覧の取得 (サービス文書URI の GET)
  • カテゴリの操作 (カテゴリ文書)
    • カテゴリ一覧の取得 (カテゴリ文書URI の GET)

以下、はてなブログ 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#">
&lt;div class=&quot;section&quot;&gt;
&lt;h4&gt;記事本文&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;リスト1&lt;/li&gt;
&lt;li&gt;リスト2&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;内容&lt;/p&gt;
&lt;/div&gt;
</hatena:formatted-content>
<app:control>
<app:draft>no</app:draft>
</app:control>
</entry>
<entry>
...
</entry>
...
</feed>

ブログエントリの投稿

コレクションURIに対して XML 文書を POST することで、ブログエントリを投稿できます。このブログエントリは、ブログに登録された記法で書かれたものであると解釈されます。

「寄稿者」権限のブログメンバーは、下書きエントリのみ投稿可能です。

以下の例ははてな記法で記述すると登録したブログにおける例です。

リクエスト

リクエストXML文書に必要なパラメータは以下です。

  • title要素 ブログエントリのタイトル
  • content要素 記述されたブログエントリの本文
  • updated要素 ブログエントリを投稿する日時を指定します。この指定を行わなかった場合、現在時刻が投稿日時として設定されます。
  • category要素ブログ エントリのカテゴリを指定出来ます。(複数可)
    • term属性 カテゴリ名を指定します。
  • app:control/app:draft要素 ブログエントリを下書きにするか指定出来ます。指定を行わなかった場合、下書きで無いものとみなされます。「寄稿者」権限のブログメンバーは下書きにする必要があります。
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ステータス201を返します。 Locationヘッダに新しく作成したブログエントリのメンバURIが含まれます。 ブログエントリの内容については、ブログエントリの取得の項目を参照してください。

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#">
&lt;div class=&quot;section&quot;&gt;
&lt;h4&gt;記事本文&lt;/h4&gt;
</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}

レスポンス

  • レスポンスは正常時にHTTPステータス200を返します。
  • レスポンスXML文書はHatena XML名前空間によって拡張されています。
  • summaryは最大140文字の文書要約が記載されます。
  • contentにははてなブログに登録されたシンタックスと解釈されたオリジナルな文面が記載されます
    • type属性には以下の3つの値のいずれかが付与されます。
      • text/html 見たままモード
      • text/x-hatena-syntax はてな記法
      • text/x-markdown マークダウン記法
  • hatena:formatted-contentには常にエンコードされたhtmlが返却されます。
<?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#">
&lt;div class=&quot;section&quot;&gt;
&lt;h4&gt;記事本文&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;リスト1&lt;/li&gt;
&lt;li&gt;リスト2&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;内容&lt;/p&gt;
&lt;/div&gt;
</hatena:formatted-content>
<category term="Scala" />
<category term="Perl" />
<app:control>
<app:draft>no</app:draft>
</app:control>
</entry>

ブログエントリの編集

メンバURIに対してXML文書をPUTすることで、ブログエントリを編集できます。投稿されたブログエントリの日時は現在時刻になります。新規ブログエントリの投稿と同じく、編集時にもブログに登録された記法が適用されます。

「寄稿者」権限のブログメンバーは自身が投稿した下書きエントリのみ編集可能です。

リクエスト

リクエストXML文書に必要なパラメータは以下です。

  • title要素 ブログエントリのタイトル
  • content要素 記述されたブログエントリの本文
  • updated要素 ブログエントリの投稿日時を指定します。この指定を行わなかった場合、現在時刻が投稿日時として設定されます。投稿日時を維持したい場合は既存の投稿日時を指定してください。
  • category要素 ブログエントリのカテゴリを指定出来ます。(複数可)
    • term属性 カテゴリ名を指定します。
  • app:control/app:draft要素 ブログエントリを下書きにするか指定出来ます。指定を行わなかった場合、下書きで無いものとみなされます。「寄稿者」権限のブログメンバーは下書きにする必要があります。
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#">
&lt;div class=&quot;section&quot;&gt;
&lt;h4&gt;新しい本文&lt;/h4&gt;
</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 では、ブログのオーナー以外にもブログメンバーがアクセスすることができます。

「管理者」「編集者」権限のブログメンバーはブログオーナーと同じくすべてのAtomPubの操作が可能です。

「寄稿者」権限のブログメンバーは以下の制限があります。

操作制限
ブログエントリの一覧取得寄稿者自身が作成した下書きエントリのみ取得可能
ブログエントリの新規投稿下書きエントリのみ投稿可能
ブログエントリの取得寄稿者自身が作成した下書きエントリのみ取得可能
ブログエントリの更新寄稿者自身が作成した下書きエントリのみ編集可能。下書きを公開することはできない
ブログエントリの削除寄稿者自身が作成した下書きエントリのみ削除可能
カテゴリ文書取得可能

エラーレスポンス

はてなブログ AtomPub に対して不正な操作を行った場合にエラーレスポンスが返却されます。各エラーレスポンスには次のような意味があります。

400 Bad Request

リクエストが不正な場合に返却されます。リクエストに含まれるXMLの内容等を確認してください。

401 Unauthorized

WSSEによる認証が失敗した際に返却されます。

404 Not Found

存在しないリソースにアクセスした際に返却されます。URLに含まれるはてなID、dateおよびentry_id等を確認してください。

405 Method Not Allowd

リソースに対して許可されていないメソッドによるリクエストを発行した際に返却されます。指定のリソースに対して可能な操作を確認してください。

500 Internal Server Error

はてなブログAtomPub 側でなんらかの問題が発生した際に返却されます。

はてなブログ AtomPub を利用したプログラムの例

Perlを用いた例 (WSSE認証)

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キー

として実行できます。

Perl を用いた例 (OAuth認証)

上記の 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 を用いた例

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キー

として実行できます。

Scala を用いた例

ここでは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",
)

参考

変更履歴

  • 2013年09月04日 リリース
  • 2013年10月03日 OAuth認証に対応
  • 2013年10月15日 OAuth認証を用いたサンプルコードを追加
  • 2013年10月17日 Basic認証に対応/APIのエンドポイントをhttpsに変更(Basic認証が利用できないことを除いて、httpのエンドポイントも引き続き利用できます)
  • 2022年6月16日 ブログメンバーに対応