mixi APIを使う

30分プログラム、その98。mixi APIを使ってみよう。

mixiツールバーが新着日記を表示していたので、パケットキャプチャをしてみた。そしたら、新着日記をAtomAPIっぽいやつで取得していたので、Rubyで取得できるようにしてみた。

使い方

$ ruby mixi.rb ユーザ名 パスワード [メンバーID]
# マイミク日記
....
....

# お知らせ
運営者からのお知らせ

# あしあと
....

# マイミク
....
....

メンバーIDは省略できます。その場合、自動で取得されます。

また、短時間にたくさんアクセスすると、しばらくログインできなくなります。(10分程度?)

ソースコード

mixiツールバーはクッキーで認証していたけれど、WSSEでも認証できる。

#! /opt/local/bin/ruby -w
# -*- mode:ruby; coding:utf-8 -*-
#
# mixi.rb -
#
# Copyright(C) 2007 by mzp
# Author: MIZUNO Hiroki <hiroki1124@gmail.com> 
# http://mzp.sakura.ne.jp/
#
# Timestamp: 2007/08/02 22:17:38
#
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Ruby itself.
#

require 'openssl'
require 'open-uri'
require 'base64'
require 'rexml/document'
require 'rss'

class Mixi
  def initialize(username,password,member_id=nil)
    @username = username
    @password = password
    @member_id = member_id
    @member_id = get_member_id username,password unless member_id
  end

  def self.entry(*types)
    types.each{|type|
      module_eval <<-END
      def #{type}
        get_entry "#{type}"
      end
      END
    }
  end

  entry :updates,:notify,:tracks,:friends

  private
  def get_entry(type)
    io = open("http://mixi.jp/atom/#{type}/member_id=#{@member_id}",
              'X-WSSE' => wsse(@username,@password))
    REXML::Document.new io
  end

  def get_member_id(username,password)
    io = open('http://mixi.jp/atom/updates',
              'X-WSSE'=>wsse(username,password))
    doc = REXML::Document.new io
    href = doc.elements['/service/workspace/collection'].attributes['href']
    if href =~ /member_id=(\d+)\Z/ then
      $1
    else
      raise "must not happen"
    end
  end

  def wsse(username,password)
    nonce =
      OpenSSL::Digest::SHA1.digest(
        OpenSSL::Digest::SHA1.digest(Time.now.to_s + rand.to_s))
    now = Time.now.utc.iso8601
    digest =
      Base64.encode64(OpenSSL::Digest::SHA1.digest(nonce+now+password)).chop
    %(UsernameToken Username="#{username}", PasswordDigest="#{digest}",)+
      %(Nonce="#{Base64.encode64(nonce).chop}", Created="#{now}")
  end
end

def put_entry(doc)
  doc.elements.each('//entry'){|item|
    puts item.elements['title'].text
  }
end

Username,Password,MemberID = *ARGV
abort "need username/password" unless Username and Password

mixi = Mixi.new Username,Password,MemberID
puts "# マイミク日記"
put_entry mixi.updates
puts ''

puts "# お知らせ"
put_entry mixi.notify
puts ''

puts "# あしあと"
put_entry mixi.tracks
puts ''

puts '# マイミク'
put_entry mixi.friends
puts ''