AtomAPIを使用してサクっとはてなフォトライフへ画像をアップロードする

# TL;DR
ローカルからサクっとはてなフォトライフへ画像をアップロードするためのrubyスクリプトです。
ユーザ名とAPIキーを使用したWSSE認証方式を用います。
下記コードにて、ENVにユーザ名とAPIキーをセットして、ファイルパスを引数で渡せばアップロード完了です。
https://gist.github.com/chanibarin/4c95dff8858e25cda588115e8c0634ad

# はじめに
ローカルからサクっとはてなへ写真をアップロードしたかったのですが、最近のサンプルが全然なく、サクっと動きそうなものが見当たらなかったので調査して作りました。
はてなの資料を愚直に実装するのみです。
http://developer.hatena.ne.jp/ja/documents/fotolife/apis/atom
http://developer.hatena.ne.jp/ja/documents/auth/apis/wsse

# WSSE認証
現在(2016/6/9)のはてなのAtomAPIはユーザ名とAPIキーによるWSSE認証もしくはOAuth認証のどちらかでアクセスするのですが、ローカルからサクっとアップロードしたいだけなのでWSSE認証を選択しました。（OAuthの方が最近のサンプルが多そうなので僕自身は結果的にはサクっと終わらなかったという）
WSSE認証はリクエストヘッダにX-WSSEを送ります
> ```X-WSSE: UsernameToken Username="hatena", PasswordDigest="ZCNaK2jrXr4+zsCaYK/YLUxImZU=", Nonce="Uh95NQlviNpJQR1MmML+zq6pFxE=", Created="2005-01-18T03:20:15Z"```

これらに入れる値ですが、
> **Username**
はてなID
**Nonce**
HTTPリクエスト毎に生成したセキュリティ・トークン
**Created**
Nonceが作成された日時をISO-8601表記で記述したもの
**PasswordDigest**
Nonce, Created, APIキーを文字列連結しSHA1アルゴリズムでダイジェスト化して生成されたオクテット列を、Base64エンコードした文字列。APIキーは[投稿メールアドレス](https://www.hatena.ne.jp/my/config/mail/upload)の"@"や"+"以前の文字列になります。はてなブログの[管理画面](http://blog.hatena.ne.jp/my/config/detail)でも確認できます。

ということでこれらをそのまま用意すると良さそうなのですが…ハマりポイントが存在します。

## ハマりポイント
*Nonce*とPasswordDigestの一部として入る*Nonce*は同一ではありません…
PasswordDigestに付加するNonceをbase64エンコードしたものがNonceになります。
```PasswordDigest = "AAAA"(Nonce) + Created + API_KEY```
とすると、
```Nonce = Base64.encode64("AAAA")```
となります。

## APIキーの取得
[リンク](http://blog.hatena.ne.jp/my/config/detail)から飛んでもらえると取得できると思いますが、念のためスクショを貼っておきます。

ブログ設定の詳細設定を選択し、
><img width="332" alt="スクリーンショット 2016-06-09 1.51.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/68048/5be106bb-7f27-51f4-b81a-3aeffb70c02b.png">

下の方に行くと**AtomPub**という項があるので、そこに記載されているAPIキーの値になります。ちなみに、ルートエンドポイントにはてなIDが書かれているので(USERNAME)、こちらからはてなIDも確認できます。
><img width="672" alt="スクリーンショット 2016-06-09 1.52.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/68048/72c2024f-407e-8ab2-9fea-97170297135e.png">

## Nonce
ノンスはランダム値を適当に作成して下さい。

```rb
base64nonce = SecureRandom.base64(64) # PasswordDigestに使用
nonce = Base64.decode64(base64nonce)
```

## Created
こちらはISO-8601表記で作成してください。

```rb
Time.now.utc.iso8601
```

## トークン生成
以上を踏まえて、下記コードにてトークンを生成出来ます。

```rb
require 'time'
require 'securerandom'
require 'base64'

timestamp = Time.now.utc.iso8601

base64nonce = SecureRandom.base64(64)
nonce = Base64.decode64(base64nonce)
token = nonce + timestamp + API_KEY
password_digest = Base64.encode64(Digest::SHA1.digest(token)).chomp!

wsse = "UsernameToken Username=\"#{USERNAME}\", PasswordDigest=\"#{password_digest}\", Nonce=\"#{base64nonce}\", Created=\"#{timestamp}\""
```

# アップロード処理
ここまで来たら後は普通にHTTPでポストするだけです。

>```xml
>POST /atom/post
>
<entry xmlns="http://purl.org/atom/ns#">
  <title>Sample</title>
  <content mode="base64" type="image/jpeg">/9j/2wCEAAQDAwQDAw.../9n/AA==</content>
</entry>
```

仕様書には書いてありませんが…、ホストは自アカウントのurlとかではなく、フォトライフのurlになります。
```f.hatena.ne.jp```

## 画像ファイルの読み込み
サンプルはゴロゴロ転がってますが一応書いておきます。

```rb
require 'mime/types'
require 'base64'

file_path = ARGV[0] # 第一引数を画像ファイルパスとする
content = Base64.encode64(open(file_path).read) # 画像ファイルをロードしてBase64エンコード
mime_type = MIME::Types.type_for(file_path).first # MIMEタイプ取得
title = File.basename(file_path, '.*') # ファイルタイトル取得
```

## アップロード
お待たせしました！アップロードしましょう！

```rb
header = {'X-WSSE' => wsse} # ヘッダにWSSEトークンを渡す
entry = <<-"ENTRY" # エントリデータ生成
<entry xmlns="http://purl.org/atom/ns#">
  <title>#{title}</title>
  <content mode="base64" type="#{mime_type}">
    #{content}
  </content>
</entry>
ENTRY


client = Net::HTTP.new('f.hatena.ne.jp', 80)
response = client.post('/atom/post', entry, header)
puts response
```

# アップロード確認
``` #<Net::HTTPCreated:0xXXXXX>``` が出力されることを確認したら、フォトライフを見てみましょう。

><img width="330" alt="スクリーンショット 2016-06-09 2.13.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/68048/5605432f-b076-a8e2-09e9-4eeada4131f5.png">

アップロードされていたら成功です！
お疲れ様でした。


# 実行コード
こちらから
https://gist.github.com/chanibarin/4c95dff8858e25cda588115e8c0634ad