さんだすメモ

さメモ

技術ブログでは、ない・・・

はてなブログをローカルで管理する[Ruby]

はじめに

ローカルで作業した方が便利なことが多いと思ったので作りました。

大まかな流れ

1つ目と3つ目については、Ruby ではてな OAuth のアクセストークンを取得するを参考にしました。

はてなにログインし、アプリを認証する

OAuth認証を使った連携では、アプリケーションとは別枠ではてなにログイン・認証しないといけません。今回のアプリは自作であり、信用できるので、認証作業もアプリにやらせます。

host_hatena = 'www.hatena.com'
port_hatena = 443
https_hatena = Net::HTTP.new(host_hatena, port_hatena)
https_hatena.use_ssl = true
https_hatena.verify_mode = OpenSSL::SSL::VERIFY_PEER

# ログインし、Cookieを取得
path_login = '/login'
query_login = "name=#{HATENA_ID}&password=#{PASSWORD}"
response_login = https_hatena.start { |https_login|
  https_hatena.post(path_login, query_login)
}
cookie_hatena = response_login['set-cookie']

# request_token.authorize_urlにアクセスして、フォームに入力する
uri_oauth = URI.split(request_token.authorize_url)
path_oauth = uri_oauth[5]
query_oauth = uri_oauth[7]
response_oauth = https_hatena.start { |https|
  request_oauth = Net::HTTP::Post.new(path_oauth, {'Cookie'=>cookie_hatena})
  request_oauth.set_form_data(
    {
      'rkm'=>'j7eJ2tohvlHmKr2D4lL4WA',
      'oauth_token' => URI.unescape(query_oauth.split('=')[1]),
      'name' => '許可する'
    }
  )
  https.request(request_oauth)
}

# レスポンスからoauth_verifierを抜き出す
body_response_oauth = response_oauth.body.force_encoding('utf-8')
oauth_verifier = body_response_oauth.match(/<div class=verifier><pre>([^<>]*)<\/pre><\/div>/)[1]

適当に調べて試行錯誤したところ、こんな感じになりました。
ちなみに、フォームは次のような形式でした。 送り先がhttps://www.hatena.com/oauth/authorizeであることと、送るべき内容がわかります。

<form action="/oauth/authorize" method="post">
  <input type="hidden" name="rkm" value="xxxxxxxxxxxxxxxxxx">         <!--固定されてました-->
  <input type="hidden" name="oauth_token" value="xxxxxxxxxxxxxxxxxx"> <!--毎回変わります-->
  <input class="oauth-btn btn-yes" type="submit" name="name" value="許可する" />
</form>

記事の管理

  • ブログエントリの一覧の取得
  • mdファイルを1つずつ調べ、投稿・編集する

APIについては、はてなブログAtomPubを参照しました。

ブログエントリの一覧の取得

記事の確認・編集に使うメンバURIは次のような形になっています。

"https://blog.hatena.ne.jp/#{HATENA_ID}/#{BLOG_ID}/atom/entry/#{entry_id}"

エントリ一覧のリクエストは次のように送りました。

access_token.request(:get, "https://blog.hatena.ne.jp/#{HATENA_ID}/#{BLOG_ID}/atom/entry?page=xxxxxxxx")

記事をインポートするときは、ここからentry_idやタイトルなどを抽出し、適切な形に保存します。一度に7件しかブログエントリを取得できないので、?page=xxxxxxを付け加えて、必要な情報を順次とりだします。

投稿・編集する

Markdown記法で書いているので、mdファイルを操作します。

mdファイルの管理のための書式

オプションにあたる部分は書式を自分で決めてコメントアウトなどで書くようにします。memberURIupdateddraftYNtitlecategoryなどがあれば十分だと思います。memberURIはリクエストを送るのに使用し、他はリクエストの中身に使用します。

投稿する

mdファイルからupdateddraftYNcategorytitle、本文の情報を抜き出します。そして、それらを反映したXML形式の文字列xml_entryを作り、投稿します。xml_entryは次のような内容になります。

xml_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>#{data_entry[:title].encode(xml: :text)}</title><author><name>#{HATENA_ID}</name></author><content type=\"text/x-markdown\">#{data_entry[:markdown].encode(xml: :text)}</content><updated>#{data_entry[:updated]}</updated>"
data_entry[:category].each { |category|
  xml_entry += "<category term=\"#{category.encode(xml: :text)}\"/>"
}
xml_entry += "<app:control><app:draft>#{data_entry[:draftYN]}</app:draft></app:control></entry>"

リクエストは次のように送りました。

response = access_token.post( "https://blog.hatena.ne.jp/#{HATENA_ID}/#{BLOG_ID}/atom/entry", xmlEntry, {'Content-Type'=>'application/xml'})

response.code"201"ならokです。
投稿したら、entry_idupdatedを取得してmdファイルに追加します。私はresponse['location']respose.bodyから抽出しました。

編集する

投稿と同じように、必要な情報を集めてリクエストを送ります。今回は次のように送りました。

response = access_token.put( "https://blog.hatena.ne.jp/#{HATENA_ID}/#{BLOG_ID}/atom/entry", xmlEntry, {'Content-Type'=>'application/xml'})

こちらもresponse.codeを確認しておくといいと思います。

最後に

大体のことを付け焼刃でやっているので、OAuth認証の作業、投稿・編集のリクエストの送り方は特に苦労しました。
投稿・編集をすべての記事でやると時間がかかるので、更新時にバックアップを取るような形にして、変化があれば更新というようにしました。