ニコニコ動画にコメントを書き込む手順

未来のために、ニコニコ動画にHTTPリクエストでコメントする手順をまとめてみることにしました。

目次

  • 概要
  • 認証
  • スレッドIDとメッセージサーバを取得
  • ブロックナンバーとチケットを取得
  • ポストキーを取得
  • 書き込み

概要

ニコニコ動画のコメント書き込みは「メッセージサーバ」と呼ばれるサーバに対して行います。
「www.nicovideo.jp」ではなく「msg.nicovideo.jp」にHTTPリクエストを送信するわけですね。
リクエストを送信する先のURLは動画によって違うので、まずメッセージサーバのURLを取得してから、そこにコメントを送信する流れになります。

コメントを書き込むまでにいろんなAPIを呼ぶわけですが、「www.nicovideo.jp」と「msg.nicovideo.jp」でデータ形式が異なります。
「www.nicovideo.jp」はGETメソッドでリクエストパラメータを送信し、レスポンスデータは「key1=value1&key2=value2&...」という形式。
「msg.nicovideo.jp」はPOSTメソッドでXMLデータを送信し、レスポンスとデータもXML形式で返ってきます。
この違いを押さえておくと混乱することが少なくて済むかもしれません。

認証

まずはユーザ認証をしなくてはいけません。以下のURLにPOSTメソッドでメールアドレスとパスワードを送信します。

https://secure.nicovideo.jp/secure/login?site=niconico

送信するデータはこんな形式です。

mail=[メールアドレス]&password=[パスワード]

ユーザ認証に成功するとレスポンスコード302が返ってきてニコニコ動画のトップページにリダイレクトされます。
その際CookieにセッションIDが書き込まれるのでこれをメモしておきます。
Cookieに書き込まれるセッションIDの値はこんな形式です。

user_session=user_session_******_****************

最初の「******」がログインしたユーザのユーザIDになっているようです。後々必要になってくるのでこれもメモしておきます。
以降のリクエストには全てこのセッションIDをCookieとして送信します。

スレッドIDとメッセージサーバを取得

「getflv」というAPIを使って動画の情報を取得します。
CookieでセッションIDを送りつつ以下のURLにリクエストを送信。「sm9」のところは対象動画の番号で置き換えてください。

http://www.nicovideo.jp/api/getflv/sm9

こんなデータが返ってきます。

thread_id=1173108780&l=320&url=http%3A%2F%2Fsmile-com00.nicovideo.jp%2Fsmile%3Fv%3D9.0468&link=http%3A%2F%2Fwww.smilevideo.jp%2Fview%2F9%2F123456&ms=http%3A%2F%2Fmsg.nicovideo.jp%2F10%2Fapi%2F&user_id=123456&is_premium=0&nickname=%E3%83%8B%E3%83%83%E3%82%AF%E3%83%8D%E3%83%BC%E3%83%A0&time=1244475413&done=true&ng_rv=1&hms=hiroba-test4.nicovideo.jp&hmsp=2528&hmst=1000000064&hmstk=1244475443.Vqtzb_e9yV5_Z72ZyH_XJ1284Ps

見やすくするとこう

thread_id=1173108780
l=320
url=http://smile-com00.nicovideo.jp/smile?v=9.0468
link=http://www.smilevideo.jp/view/9/123456
ms=http://msg.nicovideo.jp/10/api/
user_id=123456
is_premium=0
nickname=ニックネーム
time=1244475413
done=true
ng_rv=1
hms=hiroba-test4.nicovideo.jp
hmsp=2528
hmst=1000000064
hmstk=1244475443.Vqtzb_e9yV5_Z72ZyH_XJ1284Ps

取得したデータから「ms」と「thread_id」の値をメモしておきます。

thread_id=1173108780
ms=http://msg.nicovideo.jp/10/api/

「thread_id」動画についたコメントの一覧を「スレッド」と呼ぶみたいです。2ちゃんねるの「スレ」と同じような感じですね。そのスレッドを識別するIDが「thread_id」です。コメントを書き込むときにはこの「スレッド」に対して書き込むわけですね。
「ms」はメッセージサーバのURLです。コメントを書き込むときにリクエストを送信する対象になります。

ブロックナンバーとチケットを取得

次にメッセージサーバにリクエストをPOSTで送信してスレッドの情報を得ます。
リクエストの送信先はさっき取得した「ms」の値です。
POSTするデータは以下のXMLです。認証で取得したユーザIDと、getflvで取得したスレッドIDを指定します。
CookieでセッションIDを送信するのも忘れずに。

<packet>
  <thread thread="[スレッドID]" 
         version="20061206"
         res_from="-1"
         user_id="[ユーザID]"/>
</packet>

上のデータは見やすいように改行してありますが、実際にこのまんまリクエストを送信したら失敗しました。改行しないで空白もあけず、以下のようにビッチリと書いたらうまくいきました。

<packet><thread thread="[スレッドID]" version="20061206" res_from="-1" user_id="[ユーザID]"/></packet>

レスポンスでこんなデータが返ってきます。(実際は一行です)

<?xml version="1.0" encoding="UTF-8"?>
<packet>
  <thread click_revision="17708" last_res="3442189" num_clicks="4" resultcode="0" revision="1" server_time="1244477788" thread="1173108780" ticket="0x8c37668"/>
  <view_counter id="sm9" mylist="63949" video="5765320"/>
  <chat anonymity="1" date="1244477408" mail="184" no="3442189" thread="1173108780" user_id="-4CwxVGMJZCYf7rl_i5VzCQPh4U" vpos="19422">うううううううううううううううううううううううううううううううううううううううううううううううううう</chat>
  <num_click count="37" no="3426371" thread="1173108780"/>
  <num_click count="97" no="3426399" thread="1173108780"/>
  <num_click count="10" no="3426457" thread="1173108780"/>
  <num_click count="45" no="3426468" thread="1173108780"/>
</packet>

レスポンスデータのXMLから<thread>要素の「last_res」と「ticket」属性をメモしておきます。
「last_res」は最後のコメント番号、というかコメントの数ですね。これを100で割って小数点以下を切り捨てた値がブロックナンバーになるようです。このブロックナンバーが何だかよく分からずに困ってたところをコメント欄で教えてもらいました。感激です。
「ticket」はよくわかりませんけどコメントするときに必要なのでメモしておきます。

ポストキーを取得

ここまでがんばってもまだコメントを書き込むことができません。くじけそうです。あと一息なのでがんばりましょう。
getpostkeyというAPIを呼んでポストキーを取得します。
CookieでセッションIDを送りつつ以下のURLにリクエストを送信。getflvで取得したスレッドIDと、さっきlast_resを100で割って求めたブロックナンバーを指定します。

http://www.nicovideo.jp/api/getpostkey/?yugi=&block_no=[ブロックナンバー]&thread=[スレッドID]

値のない「yugi」というパラメータはなんなんでしょう。リバースなカードをオープンする人でしょうか。よくわかりません。

こんな形式でポストキーが返ってきます。メモしておきましょう。

postkey=***************************

書き込み

長い道のりでしたが、以上でやっとコメントを書き込む準備ができました。
getflvで取得したメッセージサーバのURLに向けて以下のデータをPOSTします。
何度も繰り返しでしつこいですが、CookieでセッションIDも送ります。

<chat thread="[スレッドID]"
     vpos="[動画内の時間(1/100秒)]"
     mail="184 "
     ticket="[チケット]"
     user_id="[ユーザID]"
     postkey="[ポストキー]">
  [コメント]
</chat>

これもやっぱり改行空白をあけずにビッチリと書いた方がいいような気がします。

<chat thread="[スレッドID]" vpos="[時間]" mail="184 " ticket="[チケット]" user_id="[ユーザID]" postkey="[ポストキー]">[コメント]</chat>

指定する値の説明

threadgetflvで取得したスレッドID
vposコメントを書き込む動画内の時間を指定します。
単位は1/100秒です。
2分34秒56の位置に書き込むなら 2*60*100 + 34*100 + 56 = 15456 といった具合です。
ticketスレッド情報で取得したチケットの値
user_id認証で取得したユーザID
postkeygetpostkeyで取得したポストキー

書き込みが成功するとこんなデータが返ってきます。

<?xml version="1.0" encoding="UTF-8"?>
<packet>
  <chat_result no="3442191" status="0" thread="1173108780"/>
</packet>

失敗するとこうなります。

<?xml version="1.0" encoding="UTF-8"?>
<packet>
  <chat_result status="1" thread="1173108780"/>
</packet>

サンプルコード

せっかくなのでサンプルコードを書いてみました。

# 使い方
# 
#   ruby -Ku nico_comment.rb [mail] [password] [vid] [comment] [vpos]
#     mail    : メールアドレス
#     password: パスワード
#     vid     : 動画ID(ex.sm9)
#     comment : コメント
#     vpos    : コメント時間(1/100秒)
# 
# 
# 注意
# 
#   スクリプトと同じフォルダにhttps://secure.nicovideo.jp/の証明書を
#   ファイル名「nicovideo.pem」で保存しておく必要があります
# 
#   日本語のコメントはutf-8で書かないといけないみたいなのでDOSプロンプトから実行
#   するのは難しそうです...

require 'cgi'
require 'net/http'
require 'net/https'
require "rexml/document"

class NicoClient
  attr_accessor :session, :flv, :thread, :postkey

  def initialize
    @https = Net::HTTP.new('secure.nicovideo.jp',443)
    @https.use_ssl = true
    @https.ca_file = './nicovideo.pem'
    @https.verify_mode = OpenSSL::SSL::VERIFY_PEER
    @https.verify_depth = 5

    Net::HTTP.version_1_2

  end

  def login(mail, password)
    @https.start do |w|
      response = w.post('/secure/login?site=niconico',
        "mail=#{mail}&password=#{password}")
      return nil if(response.code.to_i != 302)
      pattern = /user_session=(user_session_\d+_\d+)/
      session_cookie = response.get_fields('set-cookie').find do |cookie|
        pattern.match(cookie)
      end
      @session = pattern.match(session_cookie)[1]
    end
  end

  def getflv(vid)
    Net::HTTP.start('www.nicovideo.jp', 80) do |http|
      response = http.get("/api/getflv/#{vid}",
          "Cookie" => "user_session=#{@session}")
      @flv = {}
      response.body.split(/&/).each do |elem|
        key, val = elem.split(/=/)
        @flv[key] = CGI.unescape(val)
      end
    end
  end

  def getthread
    Net::HTTP.start('msg.nicovideo.jp', 80) do |http|
      path = @flv['ms'].gsub(/^http\:\/\/msg\.nicovideo\.jp/, '')
      response = http.post(path, self.getthread_data,
          "Cookie" => "user_session=#{@session}")
      doc = REXML::Document.new response.body
      @thread = {}
      doc.elements['/packet/thread'].attributes.each do |k,v|
        @thread[k] = v
      end
    end
  end

  def getthread_data
    "<packet>" +
      "<thread thread=\"#{@flv['thread_id']}\" " +
              "version=\"20061206\" " +
              "res_from=\"-1\" " +
              "user_id=\"#{@flv['user_id']}\"/>" +
    "</packet>"
  end
  
  def getpostkey
    block_no = @thread['last_res'].to_i / 100
    thread_id = @flv['thread_id']
    path = "/api/getpostkey/?yugi=&block_no=#{block_no}&thread=#{thread_id}"
    Net::HTTP.start('www.nicovideo.jp', 80) do |http|
      response = http.get(path,
          "Cookie" => "user_session=#{@session}")
      response.body.split(/&/).each do |elem|
        key, val = elem.split(/=/)
        @postkey = CGI.unescape(val)
      end
    end
  end

  def comment(message, vpos)
    Net::HTTP.start('msg.nicovideo.jp', 80) do |http|
      path = @flv['ms'].gsub(/^http\:\/\/msg\.nicovideo\.jp/, '')
      response = http.post(path, self.comment_data(message, vpos),
          "Cookie" => "user_session=#{@session}")
      doc = REXML::Document.new response.body
      result = {}
      doc.elements['/packet/chat_result'].attributes.each do |k,v|
        result[k] = v
      end
      if(result['status'] == '1') then
        nil
      else
        result['no']
      end
    end
  end

  def comment_data(message, vpos)
    "<chat thread=\"#{@flv['thread_id']}\" " +
          "vpos=\"#{vpos}\" " +
          "mail=\"184 \" " +
          "ticket=\"#{@thread['ticket']}\" " +
          "user_id=\"#{@flv['user_id']}\" " +
          "postkey=\"#{@postkey}\">" + 
      message +
    "</chat>"
  end
end



email    = ARGV[0]
password = ARGV[1]
vid      = ARGV[2]
message  = ARGV[3]
vpos     = ARGV[4]

client = NicoClient.new

if(client.login(email, password) == nil) then
  puts 'ログイン失敗'
  exit
end

client.getflv(vid)
client.getthread
client.getpostkey

result = client.comment(message, vpos)
if(result == nil) then
  puts 'コメント書き込み失敗'
else
  puts "コメント成功 resno=#{result}"
end

Trackback URL for this post:

http://blog.smartnetwork.co.jp/staff/trackback/22
from 雪羽の発火後忘失 on 日, 2009/09/06 - 07:03
iPhone/iPod touchアプリ “ニコニコ動画” をパケット解析する(1)より。 結果 キャプチャしたパケットを操作順に見ていく。 起動~ランキング表示 まず、アプリケーションを起動した際に以...

プロペシア 販売

プロペシアは、女性や子供で撮影されるべきではありません。フィナステリドは、皮膚から吸収することができます女性や子どもたちがフィナステリド錠を処理するためには許されるべきではない、- プロペシア 販売