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で棒読み機能などを追加する手順


あわせて読みたい