// ==UserScript==
// @name ニコニコ動画 コメントウィンドウ表示
// @namespace https://yyya-nico.co/
// @version 1.0.7
// @description コメント一覧をポップアップウィンドウで表示します。
// @author yyya_nico
// @license MIT License
// @match https://www.nicovideo.jp/watch/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=nicovideo.jp
// @grant none
// ==/UserScript==
(() => {
'use strict';
let nicoApiDataElem = document.querySelector('[name="server-response"]');
const origin = 'https://yyya-nico.co';
let nicoApiData = JSON.parse(nicoApiDataElem.content).data.response;
let popup = null;
const observerWrap = (findTargetSelector, sendTargetSelector, callback) => {
return new MutationObserver(records => {
records.forEach(record => {
record[sendTargetSelector ? 'addedNodes' : 'removedNodes'].forEach(node => {
if (node.nodeType == 1/*ELEMENT*/ && node.matches(findTargetSelector)) {
callback(
sendTargetSelector ? node.querySelector(sendTargetSelector)
: record.target
);
}
});
});
});
}
const createTask = target => {
target.insertAdjacentHTML('afterend', '<div data-scope="menu" data-part="item" id="comments-list" role="menuitem" aria-disabled="false" data-ownedby="menu::rt::content" class="[&_:where(button,a)]:d_flex [&_:where(button,a)]:gap_x0_5 [&_:where(button,a)]:ai_center [&_:where(button,a)]:w_100% [&_:where(button,a)]:h_x5 [&_:where(button,a)]:px_base [&_:where(button,a)]:bdr_s [&_:where(button,a)]:c_action.textOnBase [&_:where(button,a)]:fill_action.textOnBase [&_:where(button,a)]:[&:hover:not(:disabled)]:bg-c_action.baseHover [&_:where(button,a)]:[&:disabled,&[aria-disabled=true]]:c_action.textOnBase_disabled [&_:where(button,a)]:[&:disabled,&[aria-disabled=true]]:fill_action.textOnBase_disabled [&_:where(button,a)]:[&_svg]:w_auto [&_:where(button,a)]:[&_svg]:h_x3"><button data-scope="dialog" data-part="trigger" dir="ltr" id="dialog::r14::trigger" aria-haspopup="dialog" aria-expanded="false" data-state="closed" class="cursor_pointer" tabindex="0" type="button" data-element-page="watch" data-element-area="comment_list" data-element-name="open_ng_setting" data-element-click="1">コメント一覧をウィンドウで表示</button></div>');
const openBtn = document.getElementById('comments-list');
openBtn.addEventListener('click', () => {
const w = 480;
const h = screen.height * .8;
const lef = (screen.width - w) * .9;
const top = (screen.height - h) * .25;
popup = window.open(`${origin}/nv-comment-viewer`, 'comment-list', 'width='+ w +',height='+ h +',left='+ lef +',top='+ top);
});
}
const waitCreatePlayerPanelContainerObserver = observerWrap('[data-scope="menu"][data-part="positioner"]', '#ng', target => {
if (target) {
waitCreatePlayerPanelContainerObserver.disconnect();
createTask(target);
}
});
let lastProgress = null;
const timeObserver = new MutationObserver(records => {
records.forEach(record => {
if (!popup.closed) {
const progressPercentage = Number(record.target.style.transform.slice(7, -1)); // scaleX(****)
if (progressPercentage !== lastProgress) {
popup.postMessage({
eventName: 'playerMetadataChange',
data: {
progressPercentage: progressPercentage
}
}, origin);
lastProgress = progressPercentage;
}
} else {
timeObserver.disconnect();
}
});
});
window.addEventListener('message', e => {
if (e.origin === origin) {
switch (e.data.eventName) {
case 'ready':
// console.log('ready');
if (nicoApiData) {
popup.postMessage({
eventName: 'sendData',
data: nicoApiData
}, origin);
} else {
const cutIndex = location.pathname.lastIndexOf('/') + 1;
const videoId = location.pathname.slice(cutIndex);
popup.postMessage({
eventName: 'sendVideoId',
data: {
videoId
}
}, origin);
}
case 'returned':
// console.log('returned');
const played = document.querySelector('[aria-label="video - currentTime"]');
timeObserver.observe(played, {attributes: true});
break;
case 'bye':
// console.log('bye');
timeObserver.disconnect();
break;
case 'keyDown':
// console.log('keyDown');
['ArrowUp', 'ArrowRight', 'ArrowDown', 'ArrowLeft', ' '];
switch (e.data.data) {
case 'ArrowUp':
break;
case 'ArrowRight':
document.querySelector('[aria-label="10 秒送る"]')?.click();
break;
case 'ArrowDown':
break;
case 'ArrowLeft':
document.querySelector('[aria-label="10 秒戻る"]')?.click();
break;
case ' ':
document.querySelector('[aria-label="再生する"], [aria-label="一時停止する"]')?.click();
break;
}
break;
}
}
});
const clearNicoApiData = () => {
nicoApiData = null;
};
(history => {
const pushState = history.pushState;
history.pushState = (...args) => {
clearNicoApiData();
return pushState.apply(history, args);
};
})(window.history);
window.addEventListener('popstate', clearNicoApiData);
waitCreatePlayerPanelContainerObserver.observe(document.body, {childList: true, subtree: true});
})();