LINE LIVEのコメビュを作った話

いつも通り、ニッチ過ぎてほぼ誰も得しないブログです(´・ω・`)

とかい育ちは、LINE LIVEでも同時生放送をさせていただいているのですが、
LINE LIVEコメントビューアーを探しても一向に見つからなかったので、
なんとかコメントを取得する方法はないかと調べてみました。

LINE LIVEはコメントが盛んなライブ配信サービスです。
サーバーの仕様では、1分間に30,000件以上のコメントを捌けるようになっているそうです。しゅごい。

その仕組みは、
YouTubeFRESH!などのように、コメントサーバーのAPIからJSON形式で定期的にダウンロードしてくるものとは違い、
各ブラウザがWebSocketでコメントサーバーに接続し、新着コメントをemitしてくるようです。

そうなると、Chromeの検証ツールでAPIの接続先を調べて〜という具合にはいかず、
難読化された.jsファイルを読む気もしなく、接続先も接続方法もわからず諦めかけていました。

そもそも何故コメビュが必要かと言うと、HTML5メントジェネレーターというものを使って、
生放送の画面に、新着コメントを表示させたいのですが、
このコメントジェネレーターにコメントを渡してあげる必要があるためです。
仕組みとしては、comment.xmlというファイルに新着コメントデータを追加してあげると、
コメントジェネレーターがそれを定期的に読み込んで、新着があれば画面に反映させる、というものです。

こんな感じ(⊃∪`*)

各サイト、色んなコメビュがありますが、
このコメントジェネレーターの仕様に準拠しているものがほとんどなので、
複数のコメビュ(別々のサイト用)を立ち上げていても、
全てのコメントをジェネレーターが拾うことができるというわけです。

 

本題

やりたいことはこんな感じ

でもできない(´・ω・`)

WebSocketとかなんか怖いし仕様変更くるたびに修正するのめんどっちい。。

 

だから考えた(´;ω;`)

ブラウザで普通にLINE LIVEを開いて、
ブックマークレットjavascriptを実行、
新着コメントを、ローカル環境のスクリプトに投げちゃう作戦(*′\/`*)

$(function () {
    var target = document.querySelector('.mdMN15Scroll');
    if (!target) {
        setTimeout(arguments.callee, 1000);
        return false;
    }

    var observer = new MutationObserver(function (mutations) {
        if (mutations.some(x =>
                x.addedNodes &&
                x.addedNodes instanceof NodeList &&
                x.addedNodes.length > 0 &&
                x.type == 'childList'
            )) {
            if (mutations[0].addedNodes[0].innerText) {
                let rawText = mutations[0].addedNodes[0].innerText;
                let rawHtml = mutations[0].addedNodes[0].innerHTML;
                let matchName = rawHtml.match(/mdMN17Ttl\">(\D+)<\/span/);
                checkComment(rawText, matchName[1]);

            } else if (mutations[0].addedNodes[0].nextElementSibling.innerText) {
                let rawText = mutations[0].addedNodes[0].nextElementSibling.innerText;
                checkComment(rawText);
            }
        }
    });

    observer.observe(target, {
        childList: true,
        subtree: true
    });
});

LINE LIVE 放送ページで実行)

ちょっとわかりにくいですが、新着コメントが来てチャット欄(.mdMN15Scroll)が更新されたら、
checkComment()というfunctionに投げてます。

rawText(名前とコメント内容の両方)と、
name(正規表現でhtmlから抜き出した名前のデータ)の両方がある場合は両方送信。
そうじゃなければrawTextだけ送信します。

let checkComment = (rawText, name) => {
    if (!name) { // 名前がnullならお知らせ
        name = "お知らせ";
        let text = rawText; // お知らせならrawTextがそのまま本文
    } else {
        let text = rawText.replace(commentObj[0] + ' ', ''); // rawTextからコメント本文だけ抜き出す
    }
    submitText(name, text);
}

・お知らせの場合の整形
・コメントの場合の整形
これが終わったらいよいよsubmitText()に送信します。

let submitText = (name, text) => {
    let t = new Date();
    let d = Math.floor(t.getTime() / 1000);

    let url = '//localhost/generator.php';
    let request = new XMLHttpRequest();
    request.open('POST', url, true);

    request.onload = function (event) {
        if (request.readyState === 4) {
            if (request.status === 200) {
                console.log(name + ': ' + text);
            } else {
                console.log(request.statusText);
            }
        }
    };

    request.onerror = function (event) {
        console.log(event.type);
    };

    request.send('name=' + name + '&comment=' + text + '&site=linelive&time=' + d + '&no=' + textNo);
    textNo++;
}

よくあるajaxです。
注意点としては、LINE LIVEhttpsサイトなので、
httpsからhttpへは通信ができないこと。
つまりローカル環境はhttps://localhostでないといけないw

chromeの起動オプションでセキュリティを切れる(?)かもだけど、
運用上不便なのでローカルhttps環境を構築。
windowsのxamppなら最初から繋がるの(⊃∪`*)
timeはphp側で設定した方がいいと思う…。noは多分適当で大丈夫。

だんだんめんどくさくなってきた(´・ω・`)

このjavascriptlinelive.jsという名前で保存。
全体のソースはこんな感じ。

console.log('コメントの取得を開始します');
let textNo = 0;

let checkComment = (rawText, name) => {
    if (!name) { // 名前がnullならお知らせ
        name = "お知らせ";
        let text = rawText; // お知らせならrawTextがそのまま本文
    } else {
        let text = rawText.replace(name + ' ', ''); // rawTextからコメント本文だけ抜き出す
    }
    submitText(name, text);
}

let submitText = (name, text) => {
    let t = new Date();
    let d = Math.floor(t.getTime() / 1000);

    let url = '//localhost/generator.php?name=' + name + '&comment=' + text + '&site=linelive&time=' + d + '&no=' + textNo;
    let request = new XMLHttpRequest();
    request.open('GET', url, true);

    request.onload = function (event) {
        if (request.readyState === 4) {
            if (request.status === 200) {
                console.log(name + ': ' + text);
            } else {
                console.log(request.statusText);
            }
        }
    };

    request.onerror = function (event) {
        console.log(event.type);
    };

    request.send();
    textNo++;
}

$(function () {
    var target = document.querySelector('.mdMN15Scroll');
    if (!target) {
        setTimeout(arguments.callee, 1000);
        return false;
    }

    var observer = new MutationObserver(function (mutations) {
        if (mutations.some(x =>
                x.addedNodes &&
                x.addedNodes instanceof NodeList &&
                x.addedNodes.length > 0 &&
                x.type == 'childList'
            )) {
            if (mutations[0].addedNodes[0].innerText) {
                let rawText = mutations[0].addedNodes[0].innerText;
                let rawHtml = mutations[0].addedNodes[0].innerHTML;
                let matchName = rawHtml.match(/mdMN17Ttl\">(\D+)<\/span/);
                checkComment(rawText, matchName[1]);

            } else if (mutations[0].addedNodes[0].nextElementSibling.innerText) {
                let rawText = mutations[0].addedNodes[0].nextElementSibling.innerText;
                checkComment(rawText);
            }
        }
    });

    observer.observe(target, {
        childList: true,
        subtree: true
    });
});

そしてphpファイルはこんな感じ(´・ω・`)

<?php
$filename = "comment.xml";
try {
    $name = $_GET['name'];
    $text = $_GET['comment'];
    $site = $_GET['site'];
    $textNo = (int) $_GET['no'];
    $time = (int) $_GET['time'];
    
} catch (Exception $e) {
    exit();
}
$xml = simplexml_load_file($filename);

$target = $xml->xpath("/log");
$item = $target[0]->addChild("comment",$text);
$item->addAttribute("no", $textNo);
$item->addAttribute("time", $time);
$item->addAttribute("owner", 0);
$item->addAttribute("service", $site);
$item->addAttribute("handle", $name);

$xml->asXML($filename);
?>

あとはブックマークレットからlinelive.jsを呼び出す(´・ω・`)

javascript:(function()%7Bvar time = new Date().getTime();var s = document.createElement("script");s.src = "//localhost/js/linelive.js?v=" + time;s.charset="UTF-8";document.body.appendChild(s);%7D)();

これもhttpsのローカル環境になってないとエラーが出るので注意。
jsファイルのキャッシュを拾わないようにパラメータを与えているらしい(´・ω・`)

あとはコメントジェネレーターにxmlを拾わせたり、
comment.xmlを読み込むコメビュを使えば、
こんな感じでマルチサイトに対応したコメビュができるのだ!!

ここまで書いて、いまかるくゲロを吐きそうだから
しばらくはこういうブログを書かないと思う(⊃∪`*)ではまた。

参考:
LINE LIVE チャット機能を支えるアーキテクチャ
LINE LIVEで棒読み機能などを追加する手順


あわせて読みたい