Vimからはてなフォトライフに画像を投稿する

Vimからはてなフォトライフに画像を投稿するvimスクリプトを書いてみました。hatena.vimに追加して使用してください。画像を投稿すると、その画像のfotolife記法がVimの本文に追加されます。「Vimからはてなハイクに投稿する」も合わせてどうぞ。

画像の投稿には、はてなフォトライフAtomAPIを使用しました。WSSE認証ヘッダはシェルスクリプトでは難しそうでしたので、こちらの記事を元にrubyで生成しています。AtomAPIを使っているためcookieが使用できず、画像投稿時には毎回パスワードを要求されます。

動作には、rubyhatena.vimが使用するcurlコマンドの他に、fileコマンドが必要です。

" WSSE認証ヘッダの生成
function s:WSSECreds(user, pass)
    let creds = system('ruby -r "digest/sha1" -r "time" -e "' .
                \'nonce = Array.new(10){ rand(0x100000000) }.pack(%!I*!);' .
                \'nonce_base64 = [nonce].pack(%!m!).chomp;' .
                \'now = Time.now.utc.iso8601;' .
                \'digest = [Digest::SHA1.digest(nonce + now + %!' . a:pass . '!)].pack(%!m!).chomp;' .
                \'print %!UsernameToken !' .
                \'+ %!Username=\"%s\", ! % %!' . a:user . '!' .
                \'+ %!PasswordDigest=\"%s\", ! % digest' .
                \'+ %!Nonce=\"%s\", ! % nonce_base64' .
                \'+ %!Created=\"%s\"! % now;' .
                \'"')
    if v:shell_error
        return
    else
        return creds
    endif
endfunction

" 指定したファイルをはてなフォトライフに投稿する
" :Usage
"   :FotolifePost <filename>
command! -nargs=1 -complete=file FotolifePost   call <SID>FotolifePost('<args>')

function s:FotolifePost(filename)

    let mimetype = system('file -bi "' .  a:filename . '"')
    if v:shell_error
        echo "指定したファイルがありません"
        return
    endif

    let tmpfile = tempname()
    let title = input('タイトル: ')

    " リクエストXMLの作成
    let result_encode = system('ruby -r "base64" -e "' .
                \'request_xml = %!<entry xmlns=\"http://purl.org/atom/ns#\">\n' .
                \'<title>' . title . '</title>\n' .
                \'<content mode=\"base64\" type=\"' . mimetype . '\">\n' .
                \'%s</content>\n' .
                \'</entry>!;' .
                \'open(%!' . tmpfile . '!, %!w!){|io|' .
                \'io << request_xml % Base64.encode64(open(%!' . a:filename .'!).read)' .
                \'}' .
                \'"')

    if v:shell_error
        echo "Base64 エンコードに失敗しました"
        return
    endif

    let password = inputsecret('Password: ')
    let header_wsse = 'X-WSSE: ' . s:WSSECreds(g:hatena_user, password)
    let endpoint = 'http://f.hatena.ne.jp/atom/post'
    let header_ac = 'Accept: application/x.atom+xml, application/xml, text/xml, */*'
    let header_cont = 'Content-Type: application/x.atom+xml; charset="utf-8"'


    let request = s:curl_cmd . ' -f --header "' . header_cont .
                \'" --header "' . header_ac . '" --header "' . header_wsse .
                \'" --data "@' . tmpfile .
                \'" "' . endpoint . '"'

    echo "画像のアップロード中です..."

    " 画像の投稿を実行
    let ret = system(request)

    if v:shell_error
        echo "画像の投稿に失敗しました"
        return
    endif

    let imgsyntax = matchstr(ret, '<hatena:syntax>\zs[^>]*\ze</hatena:syntax>')
    " 投稿した画像の記法を本文に追加
    let ret = append( line('.'), imgsyntax)

    if strlen(imgsyntax) != 0
        echo imgsyntax . " の投稿に成功しました"
    else
        echo "画像の投稿に失敗しました"
    endif

    return

endfunction

実行方法は次のとおり。

:FotolifePost <filename>