今更ですか!?という感じもしますが、mixi for iPhoneから発掘されたmixi日記投稿用API « kuで紹介されていたAPI を使ってmixi に日記を投稿するruby スクリプトを書きました。
使い方
- 環境
- ruby 1.8.7 (2008-06-20 patchlevel 22) [i686-darwin9]
- gem 1.0.1
- API の認証にWSSE を使っているので、WSSE 認証に使うgem を入れる
$ sudo gem install wsse
- 使う
mixi = Mixi.new('hoge@address.com', 'password').authenticate mixi.post_diary('タイトル', '内容')
ソース
参考
- wig.rb
- Rubyist Magazine - 標準添付ライブラリ紹介 【第 7 回】 net/http
- Re: mixi for iPhoneから発掘されたmixi日記投稿用API - ユーウツな雨がふりつづいても雪がハートを曇らせてもドアの中で待っていた君に魔法をかけたいのさ
wig.rb は、id:cho45 さん作のnet-irc モジュールの中に含まれているやつです。WassrIrcGateway#api メソッド周りの実装に感動しました。凄い勉強になります。
# -*- coding: utf-8 -*- require 'net/http' require 'nkf' require 'rexml/document' require 'rubygems' require 'uri' require 'wsse' Net::HTTP.version_1_2 class Mixi def self.base_uri URI('http://mixi.jp/') end attr_reader :member_id, :nickname, :mailaddress, :password def initialize(mailaddress, password) @mailaddress = mailaddress @password = password end def authenticate begin api('updates', Net::HTTPOK) do |res| doc = REXML::Document.new(res.body) @nickname = doc.elements['/service/workspace/atom:author/atom:name'].text @member_id = doc.elements['/service/workspace/atom:author/atom:uri'].text.match(/id=(\d+)/).to_a[1] end self rescue ApiFailure false end end def post_diary(title, content) body = <<-XML <?xml version='1.0' encoding='utf-8'?> <entry xmlns='http://purl.org/atom/ns#'> <title>#{NKF.nkf('-w', title)}</title> <summary>#{NKF.nkf('-w', content)}</summary> </entry> XML begin api("diary/member_id=#{@member_id}", Net::HTTPCreated, body) do notify('日記を投稿しました') end rescue ApiFailure notify('日記の投稿ができませんでした') end end private def require_post?(path) !!Regexp.union(%r|/atom/diary/member_id=\d+|).match(path) end def api(point, success_code, query = nil) uri = self.class.base_uri uri.path = "/atom/#{point}" if require_post?(uri.path) req = Net::HTTP::Post.new(uri.path) req.body = query else if query uri.query = query.map{|k, v| "#{k}=#{URI.encode(v)}"}.join('&') end req = Net::HTTP::Get.new(uri.request_uri) end req['X-WSSE'] = WSSE::header(@mailaddress, @password) Net::HTTP.start(uri.host) do |http| res = http.request(req) case res when success_code yield(res) else raise ApiFailure, "#{res.code}: #{res.message}" end end end def notify(msg) puts msg end class ApiFailure < StandardError; end end
メモ
Atom Publishing Protocol
WSSE 認証
- Atom API の認証に使われる
- Basic 認証よりセキュア
- X-WSSE ヘッダとして送信する
- 構成要素
- Username
- ユーザー名。今回の場合はmixi に登録してあるメールアドレス
- Nonce
- Created
- Nonceが作成された日時をISO-8601表記で記述したもの
- PasswordDigest
- Username
- 参考
- ruby での実装例(wsse のgem を参考)
def wsse(username, password) #セキュリティトークンの例として、ランダムな16進数のバイナリ表現 nonce = Array.new(10){rand(0x100000000)}.pack('I*') created = Time.now.utc.iso8601 pass_digest = [Digest::SHA1.digest(nonce + created + password)].pack('m').chomp %W(UsernameToken\ Username="#{username}" PasswordDigest="#{pass_digest}" Nonce="#{[nonce].pack('m').chomp}" Created="#{created}").join(', ') end end
wig.rb
- base_uri を文字列でなく、URI のオブジェクトとして定義してある。これによって、path とかquery をそこに追加していくことができる。追加していく様子がかっこいい
- post とget はNet::HTTP::(Get|Post) のオブジェクトを生成することで分けている。リクエスト自体は、Net::HTTP#request メソッドで一元化している。Net::HTTP#(get|post) でわけてないので統一されて見える。メソッドで分けると引数の形式異なるしね
- post が必要なAPI はrequire_post? メソッドにエンドポイントのpath を送って判定
- ここかっこいい。仕様にきちんとそうようにメソッドを使い分けている
if require_post?(path) req = Net::HTTP::Post.new(uri.path) req.body = uri.query else req = Net::HTTP::Get.new(uri.request_uri) end
-
- post の場合は、クエリがURL に含まれることはないのd、uri.path を使っている
- データはbody に入れる
- get の場合はクエリがURL に含まれるのでrequest_uri を使っている
- post の場合は、クエリがURL に含まれることはないのd、uri.path を使っている
- API のエラーは「raise ApiFailed, "#{ret.code}: #{ret.message}"」のようにして処理