package nagochi.blog;

主にプログラミングの備忘録を書いていこうと思います。最近はAndroidアプリがメインです。

Livetube のコメント周りのシステム仕様まとめ

Livetube のコメントを読み書きするツールを作ろうと思い、コメント周りのシステム仕様を調べてみました。分かったことを出来る限りまとめておきます。

ストリームIDを取得する

Livetubeのコメントを読み書きするためには、まずは配信のストリームIDを取得する必要があります。ストリームIDは全ての配信に1つずつ、固有の値が割り振られています。

http://livetube.cc/hatenaテスト/配信テスト

例えばこちらの配信の場合、ストリームIDは「aaaacwrg3amcm」です。

ではどうやって配信のストリームIDを調べるのかですが、以下の2通りの方法があるようです。

配信ページのHTMLソースから取得する

1つ目は、配信ページのHTMLソースからストリームIDを取得する方法です。例えば上記の配信ページのHTMLソースを見てみると、冒頭がこのようになっています。

<!doctype html><html itemscope="itemscope" itemtype="http://schema.org/WebPage"><head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta itemprop="image" content="/stream/aaaacwrg3amcm.snapshot.jpg">
<meta name="viewport" content="width=964, user-scalable=yes">

3行目に早速ストリームIDが出現しているので、これをHTMLパーサなどで抜き出してしまうのが簡単でしょう。

注: この<meta itemprop="image" content="/stream/aaaaxxxxxxxxx.snapshot.jpg">という行は、どの配信にも必ず出現するようです。ただし必ず3行目に出現するとは限らないので、何行目に現れても取得できるような柔軟なプログラムにした方がいいかと思われます。

index.live.jsonから取得する

http://livetube.cc/index.live.json

2つ目はこのindex.live.jsonから取得する方法です。こちらのURLには、現在放送中の配信がJSON形式で並んでいます。minaideタグが付いている配信であってもこの一覧に掲載されます。

URLを開いてみると、JSONデータの中に「"id": “aaaaxxxxxxxxx"」という欄がありますが、こちらがストリームIDです。

注: http://livetube.cc/index.live.xml というURLもあります。こちらはXML形式での提供となります。

サンプルソース

前者の「配信ページのHTMLソースから取得する」のサンプルソースです。エラー処理などはサボってます。

from bs4 import BeautifulSoup
import requests
import re

def get_stream_id(url):
    # リクエストを生成
    r = requests.get(url)

    # htmlを取得    
   HTML= r.text

    # HTMLパーサで解析
    soup = BeautifulSoup(html, "html.parser")  # <!doctype html><html itemscope="itemscope"...
    meta = soup.find("meta", itemprop="image") # <meta itemprop="image" content="/stream/aa...
    meta_conetnt = meta["content"]             # /stream/aaaacwrg3amcm.snapshot.jpg
    
    stream_id = re.split(u'\.|/', meta_conetnt)[2] # aaaacwrg3amcm

    return stream_id

if __name__ == '__main__':
    url = "http://livetube.cc/hatenaテスト/配信テスト"
    stream_id = get_stream_id(url)
    
    print("ストリームID:" + stream_id)

実行結果:

$ python get_stream_id.py

ストリームID:aaaacwrg3amcm

コメントを取得する

上で取得したストリームIDを使い、コメントを取得します。

APIのURL

  • 全てのコメントを取得:
     http://livetube.cc/stream/ストリームID.comments
  • n番目以降のコメントを取得:
     http://livetube.cc/stream/ストリームID.comments.番号n

例: http://livetube.cc/stream/aaaacwrg3amcm.comments.1
→コメント番号2~最新のコメントが返ってくる

こちらのURLにGETリクエストをするだけです。

取得したコメントの構成

取得したコメントはHTML形式になっています。他の形式では取得できません。他の形式として扱いたい場合は、HTMLパーサなどでHTMLを上手く分解する必要があります。

HTMLの細かい構成は以下のとおりです。

<div style="border-bottom:solid 1px #808080;">
<div style="margin-top: 5px;"><span style="float:right;font-size:60%;font-style:italic;">334F65I</span>
<img src="/userimage/hatenaTest.png" width="64" height="71" align="left" />
2 : <a href="/hatena%E3%83%86%E3%82%B9%E3%83%88"><span style="font-size:1em;color:green; font-weight:bolder;">hatenaテスト</span></a> <span style="">2/10 16:07:19</span><br />
<span style="font-weight:bold;margin-bottom:0px; padding-bottom:0px;">コメント2</span><br clear="left" />
</div>
</div>
  1. 背景色
    • 放送中に書き込まれたコメントは背景色が白になります。
      <div style="background-color: #eee;">
    • 放送終了後に書き込まれたコメントは、背景色は設定されず、代わりに下線が引かれます。
      <div style="border-bottom:solid 1px #808080;">
  2. ID
    • タグに「nanasiid」が設定されている場合は、この場所にIDが表示されます。
    • 設定されていない場合は空欄になります。
  3. アイコン画像
    • 緑コテの方でアイコン画像を設定している人は、ここにアイコン画像の情報が書き出されます。
      <img src="/userimage/nago.png" width="64" height="71" align="left" />
    • 緑コテでもアイコン画像を設定していない人、または名無しの場合ですとここは空欄になります。
  4. レス番号
    • レス番号です。
  5. 名前
    • 緑コテの方はフォントが緑色の太文字になり、リンクも設定されます。
      <a href="/hatenaTest"><span style="font-size:1em;color:green; font-weight:bolder;">hatenaTest</span></a>
    • 緑コテではないが名前を入力している方は、装飾はされず名前だけが書き出されます。
      hatenaTest
    • 名無しの場合は空欄になります。
  6. 時間
    • 投稿時間が「M/d H:mm:ss」形式で書き出されます。
  7. 本文
    • 本文です。

BAN情報を取得

コメントを取得する際、同時に BAN 情報も受け取ることができます。BAN情報はコメント取得時のレスポンスヘッダに格納されています。

http://livetube.cc/stream/aaaacwrg3amcm.comments.1

例えばこちらのコメントを取得する際のレスポンスヘッダを見てみると、以下のようになっています。

Transfer-Encoding: chunked
Access-Control-Expose-Headers: X-JSON
Server: LTWS
Access-Control-Allow-Origin: *
Content-Encoding: gzip
X-JSON: {"n":0,"vi":0,"v":5,"m":0,"b":"0K","r":[],"cc":[2,3],"ei":0,"marks":{"2":[-48],"3":[-48]},"mark_thresh":24}
Date: Fri, 10 Feb 2017 10:36:00 GMT
Content-Type: text/html; charset=UTF-8

注目するべきは X-JSONmarksmark_thresh です。

marks
マークされているコメント番号とそのスコアです。

注: この marks に格納されているコメント番号は、実際のコメント番号よりも1少ない値になっています。つまり、この例ですと marks の値は2と3になっていますが、実際にコメント欄でマークされている番号は3と4だということです。

mark_thresh
BANしきい値です。この値以上のマイナススコアを付けられたコメントはBANされます。

よってこの例では、コメント番号3と4がBANされていることが分かります。

新着コメントをリアルタイムで取得する

「新着コメントが付いた瞬間にコメントを取得する」という機能は、コメントクライアントを作る上ではまず間違いなく必要になると思います。実はこの問題は簡単に解決できて、以下のURLに対し、while文などで間を置かずにアクセスし続けるだけでできます。

http://livetube.cc/stream/ストリームID.comments.現在のコメント数

おさらいになりますが、このURLの意味は、例えば現在コメントが4件付いている配信でしたら、「コメント番号4以降(5~最新)のコメントを送ってくれ」ということになります。

このURLに対し間を置かずにアクセスし続けると、新着がない間は返信は0件ですが、新着が付いた瞬間に返信が1件に切り替わります。これによりリアルタイムで新着コメントを受け取ることができるのです。

そんなことをしたらサーバーに負荷がかかるのでは?と思うかもしれませんが、Livetubeのサーバーにはある工夫が施されているので負荷がかからないようになっています。

Livetubeのサーバーは、返せるコメントが0件の場合、次のコメントが付くか180秒間が経過するまでレスポンスを返さないという仕様になっています。これにより1回の接続がなかなか終わらなくなるので、間を置かずにアクセスし続けたとしても、実際は数分に1回しかアクセスしていないことになるのです。

注: ただしクライアント側でタイムアウトを180秒以内に設定している場合は、当然ですがタイムアウトエラーが発生してしまいます。タイムアウトは無しか180秒以上に設定しておきましょう。

180秒以内に新着コメントが付いた場合、取得したコメントの構成で説明した通り、HTML形式のコメントが返ってきます。180秒経ってもコメントが付かなかった場合は空のレスポンス(X-JSONなどのヘッダー情報は書かれているが、本文は空欄)が返ってきます。

サンプルソース

コメントリーダーの超簡易的なサンプルソースです。BAN情報と新着コメントをリアルタイムで取得し、コンソール上に書き出し続けます。

from bs4 import BeautifulSoup
import requests
import json

def read(stream_id, count):
    # APIのURL
    api_url = "http://livetube.cc/stream/" + stream_id + ".comments." + str(count)

    # リクエストを生成
    r = requests.get(api_url, timeout=None)
    
    # HTMLを取得。ここで最大180秒待機することになります
   HTML= r.text

    # ヘッダーからBAN情報を取得
    x_json = json.loads(r.headers["X-JSON"])
    marks = json.dumps(x_json["marks"])
    mark_thresh = json.dumps(x_json["mark_thresh"])

    return html, marks, mark_thresh

if __name__ == '__main__':
    count = 0
    
    # 間を置かずにアクセスし続ける
    while True:
        html, marks, mark_thresh = read("aaaacwrg3amcm", count)
        print("マーク情報:" + marks)
        print("BANしきい値:" + mark_thresh)
        print("HTML:" + html)

        # コメント総数を更新
        # ルート直下のdivタグの数がコメント数に等しいことを利用しています
        soup = BeautifulSoup(html, "html.parser")
        elems = soup.find_all("div", recursive=False)
        count += len(elems)

実行結果:

$ python read.py

マーク情報:{"2": [-48], "3": [-48]}
BANしきい値:24
HTML:<div style="background-color: #eee;">
<div style="margin-top: 5px;">
<img src="/userimage/hatenaTest.png" width="64" height="71" align="left" />
1 : <a href="/hatena%E3%83%86%E3%82%B9%E3%83%88"><span style="font-size:1em;color:green; font-weight:bolder;">hatenaテスト</span></a> <span style="">2/10 16:06:51</span><br />
<span style="font-weight:bold;margin-bottom:0px; padding-bottom:0px;">コメント1</span><br clear="left" />
</div>
</div><div style="border-bottom:solid 1px #808080;">
...

コメントを書き込む

APIのURL

http://livetube.cc/stream/ストリームID.comments

コメントを取得するときのAPIと同じURLです。GETリクエストの場合はコメント読み込みとなり、POSTリクエストの場合は書き込みになります。

パラメータ

name
名前です。名無しの場合はここを空欄にします。**緑コテを付けたい場合はここで緑コテの名前を入力し、加えてLivetubeにログインしておく必要があります**。詳しくは下記の「Livetubeにログインする」を御覧ください。
c
コメントです。ここが空欄ですと書き込みに失敗し、以下のレスポンスが返ってきます。

書き込めません:コメントの文字がないようです。

サンプルソース

指定したストリームIDにコメントを送信するだけの単純なサンプルソースです。

import requests

def send(stream_id, name, comment):    
    # APIのURL
    api_url = "http://livetube.cc/stream/" + stream_id + ".comments"

    # パラメータ
    params = {"name": name, "c": comment}

    # リクエストを生成
    r = requests.post(api_url, data=params)

if __name__ == '__main__':
    send("aaaacwrg3amcm", "hatenaテスト", "テスト")

Livetubeにログインする

緑コテでコメントを書き込みたい場合、事前にLivetubeにログインしておく必要があります。

URL

http://livetube.cc/login

こちらのURLに対し、以下のパラメーターをPOSTリクエストすることでログインできます。

パラメータ

user
ユーザー名です。
password
パスワードです。

セッションIDを取得

ログインに成功すると、レスポンスヘッダの Set-Cookie にセッションIDがセットされて返ってきます。

Transfer-Encoding: chunked
Server: LTWS
Content-Encoding: gzip
Set-Cookie: u=P7EFTUXXOQ7CXWPDVOBXWV3RVIF7TWDLOMWH6R4QFUVJAQKIE76AZK66HEQ2OQHFMBIK5FFF3G6QRUKG3F2L4J34PBQKCH7UQZ6IN4U5LNZQQLVTNBWQDNDGIVKS5EBQCQSQAYSEKNTWX7WWMBBPCJDCNXQOBPUJSW5CPXAJT3TQLCDZDO***************************; Expires=Tue, 16-Feb-2021 14:49:47 GMT; Path=/
Date: Fri, 17 Feb 2017 14:49:47 GMT
Content-Type: text/html; charset=UTF-8

Set-Cookie が空欄の場合はログインに失敗しています。

あとはこの Cookie 情報を、コメント送信時の Cookie にそのままセットするだけで、緑コテとして書き込むことができます。ただし上でも書きましたが、このログイン処理に加えて、コメント送信時の name パラメータにも緑コテの名前をセットする必要があります。どちらか片方でも欠けていたら緑コテで書き込んだことにはなりません。

サンプルソース

ログイン処理後にコメントを送信します。

import requests

s = requests.session()

def login(user, password):
    global s
    
    # ログインURL
    login_url = "http://livetube.cc/login"

    # パラメータ
    params = {"user": user, "password": password}

    # リクエストを生成
    r = s.post(login_url, data=params)

    # セッションIDを書き出す
    print("セッションID:" + s.cookies.get("u"))

def send(stream_id, name, comment):
    global s
    
    # APIのURL
    api_url = "http://livetube.cc/stream/" + stream_id + ".comments"

    # パラメータ
    params = {"name": name, "c": comment}

    # リクエストを生成
    r = s.post(api_url, data=params)

if __name__ == '__main__':
    login("hatenaテスト", "**********")
    send("aaaacwrg3amcm", "hatenaテスト", "テスト")

実行結果:

$ python login_and_send.py

セッションID:P7EFTUXXOQ7CXWPDVOBXWV3RVIF7TWDLOMWH6R4QFUVJAQKIE76AZK66HEQ2OQHFMBIK5FFF3G6QRUKG3F2L4J34PBQKCH7UQZ6IN4U5LNZQQLVTNBWQDNDGIVKS5EBQCQSQAYSEKNTWX7WWMBBPCJDCNXQOBPUJSW5CPXAJT3TQLCDZDO***************************