まめ畑

ゆるゆると書いていきます

mixiボイスにRubyでアクセスしていじってみた

先日、mixiエコーがmixiボイスに名前を変えて正式リリースされました。(アドレスはrecent_echo.plのようにechoのままですが)
しかし、APIの提供も今のところなくRSSの配信もないので、いちいち見に行くのも面倒だったので、オレオレUtilをRubyで作ってみました。
自分用に簡単に作ったものなので、もし使用される場合はお好きに改造してください。(特にエラー処理まわり)

  • 機能
    • ボイスに投稿(返信機能)
    • マイミクのボイス・自分のボイス・自分あての返信を取得
    • メッセージの削除
  • ボイスの取得はページ指定と指定ページからの全件取得が可能です
  • ボイスに投稿する時に、返信相手のIDと返信したいメッセージのtimeを指定すると返信になります(Webの方ではどちらかのパラメータがかけていても返信が成功するようですが、リンクに初期値が挿入されるため返信先を閲覧する事が出来ません)


今は、RSSを吐き出す物を作ってCronでまわして定期的にRSSを取得しています。あまり頻繁にアクセスしないようにしたほうがいいかと思います。
このようなRSSが吐き出されます。(例では自分のボイスを取得したものです)

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>mixiボイスRSS</title>
    <link>https://mixi.jp/</link>
    <description>mixiボイスのRSSだよ!</description>
    <image>
      <url>http://img.mixi.jp/img/basic/common/logo_mixi001.gif</url>
      <title>mixi</title>
      <link>https://mixi.jp/</link>
    </image>
    <item>
      <title>Nick: ゆたのボイス</title>
      <link>http://mixi.jp/view_echo.pl?id=2737220&post_time=20090917221329</link>
      <description>とりあえず説明を読んでみよう</description>
      <pubDate>Thu, 17 Sep 2009 22:13:29 +0900</pubDate>
    </item>
    <item>
      <title>Nick: ゆたのボイス</title>
      <link>http://mixi.jp/view_echo.pl?id=2737220&post_time=20090917221011</link>
      <description>これがボイスという物かどきどきする</description>
      <pubDate>Thu, 17 Sep 2009 22:10:11 +0900</pubDate>
    </item>
  </channel>
</rss>

こんな感じです。


使い方などはソースを見てもらえればわかると思います。
mixiボイスのページの構造が変わったら動かなくなるので適時修正をお願いします。

オレオレmixiボイスUtil

# -*- coding: utf-8 -*-

require "jcode"
require "uri"
require "rubygems"
require "mechanize"

$KCODE = "U"


class Mixi_Voice
   LOGIN_URL = "https://mixi.jp/"
   BASE_URL = "http://mixi.jp/"
   PUBLIC_VOICE_PATH = "recent_echo.pl"
   USER_VOICE_LIST_PATH = "list_echo.pl"
   RES_VOICE_PATH = "res_echo.pl"
   DELETE_VOICE_PATH = "delete_echo.pl"


   def initialize(id = nil, pass = nil)
      raise "Require id AND Password" if id.nil? || pass.nil?

      $agent = WWW::Mechanize.new
      $agent.max_history = 1
      $agent.user_agent_alias = "Windows IE 7"
      top_page = $agent.get(LOGIN_URL)
      login_form = top_page.forms[0]
      login_form["email"] = id
      login_form["password"] = pass
      login = $agent.submit(login_form)
      raise "Login Error" unless login.title.nil?
   end

   def get_voice_list(type = :public, option = {})
      voice_info = []
      path = ""
      page = option[:page] || 1
      get_all = option[:get_all] || false

      case type
      when :public
         path = PUBLIC_VOICE_PATH
      when :my
         path = USER_VOICE_LIST_PATH
      when :res
         path = RES_VOICE_PATH
      else
         raise "Voice Type Error (:public, :my or :res)"
      end

      path += "?page" + page.to_s if page > 1
      voices_page = $agent.get(BASE_URL + path)

      loop do
         voice_table = (voices_page/"//div[@class=\"archiveList\"]/table/tr")

         voice_table.each do |voice|
            user_info = (voice/"td[@class=\"thumb\"]/a")[0]
            break if user_info.nil?

            user_info["href"] =~ /list_echo.pl\?id=(\d+)$/
            user = $1

            info = (voice/"td[@class=\"thumb\"]/a/img")[0]
            thumb = info["src"]
            nick = info["alt"]

            comment_info = (voice/"td[@class=\"comment\"]")[0]
            comment = (comment_info/"div[@class=\"echo_body\"]").inner_text
            time = (comment_info/"div[@class=\"echo_post_time\"]").inner_text
            link = "http://mixi.jp/view_echo.pl?id=#{user}&post_time=#{time}"

            voice_info << {:user=>user, :thumb=>thumb, :nick=>nick, :comment=> comment, :link=> link, :time=>time}
         end
         break unless get_all
         next_page = voices_page.link_with(:text => "次を表示")
         break if next_page.nil?
         voices_page = next_page.click
      end

      voice_info
   end

   def post_message(message, parent_member_id = nil, parent_post_time = nil)
      return if message.nil?
      raise "Over 150 Words" if message.jsize > 150   #1.9系ならlength使えばおk

      voices_page = $agent.get(BASE_URL + PUBLIC_VOICE_PATH)

      if((!parent_member_id.nil? && parent_post_time.nil?) || (parent_member_id.nil? && !parent_post_time.nil?))
         raise "Require parent_member_id AND parent_post_id"
      elsif (!parent_member_id.nil? && !parent_post_time.nil?)
         raise "Invalid Number parent_member_id OR parent_post_time" unless parent_member_id.to_s =~ /^[\d]+$/ && parent_post_time.to_s =~ /^[\d]+$/
      end

      post_voice = voices_page.forms[1]
      post_voice["body"] = message
      post_voice["parent_member_id"] = parent_member_id unless parent_member_id.nil?
      post_voice["parent_post_time"] = parent_post_time unless parent_post_time.nil?
      result = $agent.submit(post_voice)
      raise "Post Error" unless (result/"//div[@id=\"error\"]")[0].nil?
   end

   def delete_voice(post_time = nil)
      raise "Require Post Date" if post_time.nil?
      raise "Invalid Number post_time" unless post_time.to_s =~ /^[\d]+$/

      voices_page = $agent.get(BASE_URL + PUBLIC_VOICE_PATH)
      post_key = post_voice = voices_page.forms[1]["post_key"]
      query = "?post_time=#{post_time}&post_key=#{post_key}&redirect=recent_echo"

      $agent.get(BASE_URL + DELETE_VOICE_PATH + query)
   end
end


RSSを吐くもの

# -*- coding: utf-8 -*-

require "mixi_voice"
require "rubygems"
require "rss"

$KCODE = "U"


begin
   mixi_voice = Mixi_Voice.new("ID", "PASS")
   all_voice = mixi_voice.get_voice_list(:my, {:page=>1, :get_all=>true})

   rss = RSS::Maker.make("2.0") do |make|
         make.channel.title = "mixiボイスRSS"
         make.channel.description = "mixiボイスのRSSだよ!"
         make.channel.link = "https://mixi.jp/"
         make.image.title = "mixi"
         make.image.url = "http://img.mixi.jp/img/basic/common/logo_mixi001.gif"
         make.items.do_sort = true

         all_voice.each do |voice|
            make.items.new_item do |item|
               voice[:time].to_s =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/
               time = Time.parse("#{$1}/#{$2}/#{$3} #{$4}:#{$5}:#{$6}")
               item.link = voice[:link]
               item.title = "Nick: " + voice[:nick] + "のボイス"
               item.date = time
               item.description = voice[:comment]
            end
         end
      end

   File.open("my_voice.xml", "w") do |f|
      f.write(rss)
   end
rescue Exception => e
   puts "Error: " + e
end
  • get_voice_listで使用するtype
    • :my 自分のボイス
    • :public マイミクのボイス
    • :res 自分への返信

メッセージを取得すると以下の様なHashの配列で返ってきます。

{:nick=>"ゆた", :comment=>"とりあえず説明を読んでみよう", :time=>"20090917221329", :thumb=>"http://profile.img.mixi.jp/photo/member/72/20/2737220_1247381845m.jpg", :user=>"2737220"}
  • nick ニックネーム
  • comment コメント
  • time 投稿時間(削除や返信に使用します)
  • thumb 投稿者のサムネ
  • user ユーザID

post_messageメソッドの「parent_member_id」はuserを、「parent_post_time」はtimeを使用します。(それぞれ返信したいメッセージのものです)
delete_voiceメソッドの「post_time」は自分の刑したいメッセージのtimeを使用します。


早くAPIを提供して欲しいです。