ニコニコ動画 コメントウィンドウ表示

コメント一覧をポップアップウィンドウで表示します。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==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="[&amp;_:where(button,a)]:d_flex [&amp;_:where(button,a)]:gap_x0_5 [&amp;_:where(button,a)]:ai_center [&amp;_:where(button,a)]:w_100% [&amp;_:where(button,a)]:h_x5 [&amp;_:where(button,a)]:px_base [&amp;_:where(button,a)]:bdr_s [&amp;_:where(button,a)]:c_action.textOnBase [&amp;_:where(button,a)]:fill_action.textOnBase [&amp;_:where(button,a)]:[&amp;:hover:not(:disabled)]:bg-c_action.baseHover [&amp;_:where(button,a)]:[&amp;:disabled,&amp;[aria-disabled=true]]:c_action.textOnBase_disabled [&amp;_:where(button,a)]:[&amp;:disabled,&amp;[aria-disabled=true]]:fill_action.textOnBase_disabled [&amp;_:where(button,a)]:[&amp;_svg]:w_auto [&amp;_:where(button,a)]:[&amp;_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});
})();