細かい説明とかは面倒なんで書きません。さっさとはじめましょう。
ニコニコ生放送でしか確認できていません。
あ、私 C# しか書けないんで無いと思いますけど「他の言語でもお願いします」とか無理です。はい。
ここで書いたコードはまとめて GitHub に上げておきますので下手くそな説明は必要ないという方は GitHub に飛んでくださいな。
また、ここでは詳細な説明は省いているので GitHub のサンプルコードをご活用いただけるとありがたいです。
コメント取得に必要な処理
- ログイン
- 生放送の lv を取得 (チャンネル名、コミュニティ名から取得する場合)
- API で諸々の情報を取得
- 接続
ひとつずつ書いていきましょう。
ログイン
var cookie_container = new CookieContainer();
using (var h = new HttpClientHandler() { CookieContainer = cookie_container })
using (var c = new HttpClient(h) { BaseAddress = new Uri("https://secure.nicovideo.jp/secure/login?site=niconico") })
{
var content = new FormUrlEncodedContent(
new Dictionary<string, string> {
{ "next_url", "" },
{ "mail", "メールアドレス" },
{ "password", "パスワード" }
}
);
var login = c.PostAsync("", content).Result;
}
はい、これで cookie_container
にニコニコのログインで取得したクッキーが入っています。
lv を取得
最初に lv を用意しておく場合はこの処理は必要ありません。
冒頭にも書いたように、ニコニコチャンネル・コミュニティ名のアドレスから取得する場合に使います。
string lv;
using (var h = new HttpClientHandler() { CookieContainer = cookie_container })
using (var c = new HttpClient(h) { BaseAddress = new Uri("生放送のページURL") })
{
var ss = c.GetAsync("").Result.Content.ReadAsStringAsync().Result;
lv = new Regex("<meta property=\"og:url\" content=\"http://live.nicovideo.jp/watch/(.*?)\"", RegexOptions.Multiline).Match(ss).Groups[1].Value;
}
これで string
変数 lv
に lv000000000
のかたちで入っています。
API から情報を取得
GetPlayerStatus
から情報を取得します。
string
変数 lv
に lv
を lv000000000
のかたちで入れておいてください。
using (var h = new HttpClientHandler() { CookieContainer = cookie_container })
using (var c = new HttpClient(h) { BaseAddress = new Uri("http://live.nicovideo.jp/api/getplayerstatus?v=" + lv) })
{
var gps = c.GetAsync("").Result.Content.ReadAsStringAsync().Result.Replace("\n", "");
var gps_match = new Regex(@"<addr>(.*?)</addr><port>(.*?)</port><thread>(.*?)</thread>", RegexOptions.None).Match(gps);
var address = gps_match.Groups[1].Value;
var port = gps_match.Groups[2].Value;
var thread = gps_match.Groups[3].Value;
var time_match = new Regex(@"<open_time>(.*?)</open_time><start_time>(.*?)</start_time>", RegexOptions.None).Match(gps);
var open_time = time_match.Groups[1].Value;
var start_time = time_match.Groups[2].Value;
}
はい、これで接続に必要な情報を取得することができました。
接続してコメントを受信する
とりあえず接続してみましょう。
var request = "<thread thread=\"" + thread + "\" version=\"20061206\" res_from=\"-1000\" /> ";
TcpClient tcp = new TcpClient(address, int.Parse(port));
NetworkStream ns = tcp.GetStream();
byte[] sendBytes = Encoding.UTF8.GetBytes(request);
sendBytes[sendBytes.Length - 1] = 0;
ns.Write(sendBytes, 0, sendBytes.Length);
はい、接続できました。
ここからコメントが逐次受信されるわけですが、それを処理するのにすこし手こずりました。
相変わらずのウンコードですが私はこのコードでやりたかったことができました。
var resSize = 0;
var merge = "";
while (ns.CanRead)
{
var resBytes = new byte[500];
resSize = ns.Read(resBytes, 0, resBytes.Length);
if (resSize == 0) break;
var message = Encoding.UTF8.GetString(resBytes);
var elements = message.Split(new string[] { "\0" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var r in elements)
{
var receive = r;
if (!receive.EndsWith("</chat>"))
{
merge += receive;
continue;
}
if (!receive.StartsWith("<chat")) { receive = merge + receive; merge = ""; }
else if (receive.EndsWith("</chat>")) { receive = merge + receive; merge = ""; }
else if (merge != "") { receive = merge + receive; merge = ""; }
if (receive.StartsWith("<chat") && receive.EndsWith("</chat>"))
{
var comment = new Regex(">(.*)<", RegexOptions.None).Match(receive).Groups[1].Value.Replace("<", "<").Replace(">", ">");
if (comment != "")
{
if (comment.Contains("/hb ifseetno")) continue;
var no = double.Parse(new Regex("no=\"(.*?)\"", RegexOptions.None).Match(receive).Groups[1].Value);
var vpos = DateTimeOffset.FromUnixTimeSeconds((long)(double.Parse(new Regex("date=\"(.*?)\"", RegexOptions.None).Match(receive).Groups[1].Value) - double.Parse(open_time))).ToString("HH:mm:ss");
var date = DateTimeOffset.FromUnixTimeSeconds(long.Parse(new Regex("date=\"(.*?)\"", RegexOptions.None).Match(receive).Groups[1].Value)).LocalDateTime.ToString("HH:mm:ss");
var id = new Regex("user_id=\"(.*?)\"", RegexOptions.None).Match(receive).Groups[1].Value;
Console.WriteLine($"[{no}] ({id}) {comment} 時刻:{date} 経過時刻:{vpos}");
}
}
}
}
変な感じに分割されて送られてくるので結構面倒でした。
それと、ただ受信しているだけだとニコニコ側から切断されてしまいます。荒らし対策でしょうかね。
コメント投稿もできなくなってしまうので、定期的(1~3分おき)に以下のコードを実行して HeartBeat
処理をします。
lv
に lv000000000
のかたちで lv
を入れておいてください。
using (var h = new HttpClientHandler() { CookieContainer = cookie_container })
using (var c = new HttpClient(h))
{
var content = new FormUrlEncodedContent(new Dictionary<string, string> { { "v", lv } });
var heartbeat = c.PostAsync("http://ow.live.nicovideo.jp/api/heartbeat", content).Result;
}
過去コメント取得
when
に取得するコメントの範囲を指定します。
var waybackkey = "";
using (var h = new HttpClientHandler() { CookieContainer = cookie_container })
using (var c = new HttpClient(h)) waybackkey = c.GetStringAsync("http://watch.live.nicovideo.jp/api/getwaybackkey?thread=" + thread).Result.Replace("waybackkey=", "");
var sendBytes_ = Encoding.UTF8.GetBytes("<thread thread=\"" + thread + "\" version=\"20061206\" res_from=\"-1000\" waybackkey=\"" + waybackkey + "\" user_id=\"64924091\" when=\"" + when + "\"/> ");
sendBytes_[sendBytes_.Length - 1] = 0;
ns.Write(sendBytes_, 0, sendBytes_.Length);
これで過去コメントが流れてきます。
コメント投稿
接続した状態でないとコメント投稿できないのでまずは接続してください。
comment
にコメントしたい文章を入れておいてください。
var unixtime = (long)DateTime.Now.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds - (long)double.Parse(open_time);
using (var h = new HttpClientHandler() { CookieContainer = cookie_container })
using (var c = new HttpClient(h))
{
var postkey = c.GetStringAsync("http://ow.live.nicovideo.jp/api/getpostkey?thread=" + thread).Result.Replace("postkey=", "");
var sendBytes_ = Encoding.UTF8.GetBytes("<chat thread=\"" + thread + "\" ticket=\"" + ticket + "\" vpos=\"" + unixtime + "\" postkey=\"" + postkey + "\" mail=\"184\" user_id=\"" + "64924091" + "\" premium=\"\">" + comment + "</chat> ");
sendBytes_[sendBytes_.Length - 1] = 0;
ns.Write(sendBytes_, 0, sendBytes_.Length);
}
ここまででコメント受信・投稿ができました。
お読みいただきありがとうございました。
ご質問・ご指摘等ございましたらコメントをお願いいたします。
ingen084様のこちらのページ
を参考にさせていただきました。ありがとうございます。
では、ノシ