[NikoAd] simplizer

ニコニ広告設定窓の表示物制御

// ==UserScript==
// @name         [NikoAd] simplizer
// @namespace    http://tampermonkey.net/
// @version      2024-12-10-3
// @description  ニコニ広告設定窓の表示物制御
// @author       anonymous
// @match        https://nicoad.nicovideo.jp/video/publish/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=nicovideo.jp
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const collapse_settings = [
        { id: 'header-bar', query: '.header-bar' },
        { id: 'conductor', query: '.conductor' },
        { id: 'secondary-content-info', query: '.secondary-content-info' },
        { id: 'entry-basic-info', query: '#entry-basic-info' },
        { id: 'entry-thanks', query: '#entry-thanks' },
        { id: 'frame-grade-visualizer', query: '.frame-grade-visualizer' },
        { id: 'nicoad-impact', query: '#nicoad-impact' },
        { id: 'campaign-info', query: '#campaign-info' },
        { id: 'next', query: '.next' },
        { id: 'heading', query: '.heading' },
    ];

    const shrink_settings = { id:"shrink", query: '.wrapper' };
    const replace_publish_button_settings = { id: "replace_publish_button", query: '.publish-button' };
    const replace_back_button_settings = { id: "replace_back_button", query: '.back-button[role="button"]' }


    // ------------------------------------------------------------------------------------------------------
    // https://qiita.com/teloppy_com/items/cd483807813af5a4a38a
    const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));

    // ------------------------------------------------------------------------------------------------------

    function saveValueToStorage(id, value) {
        //console.debug(`save ${id} : ${value}` );
        localStorage.setItem(id, value);
    }

    function loadValueFromStorage(id, def_value) {
        const value = localStorage.getItem(id);

        if((value === undefined) || (value === null)){
            return def_value.toString();
        }

        //console.debug(`load ${id} : ${value}` );
        return value;
    }

    function stringToBool(str) {
        return str.toLowerCase() === 'true'; }

    // ------------------------------------------------------------------------------------------------------
    // ドロップダウンメニューをページの最下段に追加する関数を定義
    function addDropdownMenuToBottom() {
        // メニューのコンテナを作成
        const menuContainer = document.createElement('div');
        menuContainer.className = 'bottom-menu-container';

        // スタイルプロパティに直値を代入
        menuContainer.style.position = 'fixed';
        menuContainer.style.bottom = '4px';
        menuContainer.style.left = '14x';
        menuContainer.style.backgroundColor = 'white';
        menuContainer.style.border = '1px solid #ccc';
        menuContainer.style.padding = '4px';
        menuContainer.style.zIndex = '1000';

        // メニューの内容を作成
        const menuContent = document.createElement('div');
        menuContent.id = 'menuContent';
        menuContent.style.display = 'none'; // 初期状態は非表示
        menuContent.style.position = 'absolute';
        menuContent.style.bottom = '24px';
        menuContent.style.left = '4px';
        menuContent.style.backgroundColor = '#f9f9f9';
        menuContent.style.boxShadow = '0px 8px 16px 0px rgba(0,0,0,0.2)';
        menuContent.style.zIndex = '1001';
        menuContent.style.width = '300px'; // メニューの幅を設定

        function createCheckBoxContainer(id, text, checked, onchange){
            const checkboxContainer = document.createElement('div');
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = "cbx_" + id;
            checkbox.checked = checked;

            // チェック状態の保存
            checkbox.addEventListener('change', function(){
                saveValueToStorage(id, checkbox.checked);
            } );

            // 外部指定のアクション
            if(onchange){
                checkbox.addEventListener('change', function(){ onchange(checkbox); } );
            }
           
            const label = document.createElement('label');
            label.htmlFor = checkbox.id;
            label.appendChild(document.createTextNode(text));

            checkboxContainer.appendChild(checkbox);
            checkboxContainer.appendChild(label);

            return checkboxContainer;
        }

        // 項目別の表示非表示
        collapse_settings.forEach((item) => {
            const checked = stringToBool(loadValueFromStorage(item.id, true)); // デフォは表示

            // クリック時に表示非表示
            let visible_func = function(checkbox){
                let elements = getElements(item.query);
                Array.from(elements).forEach((elm)=>{
                    elm.style.display = checkbox.checked ? "block" : "none";
                });
            }

            let container = createCheckBoxContainer(item.id, item.id, checked, visible_func);
            menuContent.appendChild(container);
        });

        {
            const horizontalRule = document.createElement('hr');
            menuContent.appendChild(horizontalRule);
        }

        // 圧縮
        {
            const id = shrink_settings.id;
            const checked = stringToBool(loadValueFromStorage(id, false)); // デフォは機能オフ
            let text = "隙間を詰める(要リロード)";
            let container = createCheckBoxContainer(id, text, checked);
            menuContent.appendChild(container);
        }

        // ボタン上下入れ替え
        {
            const id = replace_publish_button_settings.id;
            const checked = stringToBool(loadValueFromStorage(id, false)); // デフォは機能オフ
            let text = "決定ボタンを上に移動(要リロード)";
            let container = createCheckBoxContainer(id, text, checked);
            menuContent.appendChild(container);
        }

        // ボタン上下入れ替え
        {
            const id = replace_back_button_settings.id;
            const checked = stringToBool(loadValueFromStorage(id, false));
            let text = "再広告ボタンを上に移動(要リロード)";
            let container = createCheckBoxContainer(id, text, checked);
            menuContent.appendChild(container);
        }

        // メニューボタンを作成
        {
            const menuButton = document.createElement('button');
            menuButton.innerText = '表示編集';
            menuButton.onclick = function() {
                menuContent.style.display = menuContent.style.display === 'block' ? 'none' : 'block';
            };

            // コンテナにボタンとメニューの内容を追加
            menuContainer.appendChild(menuButton);
        }
        menuContainer.appendChild(menuContent);

        // コンテナをボディに追加
        document.body.appendChild(menuContainer);
    }

    // ドロップダウンメニューを追加する関数を呼び出し
    addDropdownMenuToBottom();


    // ------------------------------------------------------------------------------------------------------
    // 項目の非表示処理
    // ------------------------------------------------------------------------------------------------------
    function waitForElementAppear(query){
        console.log(" wait for " +query);

        return new Promise((resolve) => {
            // 既に要素が存在する場合も対応
            {
                const query_result = document.querySelectorAll(query);
                if (query_result.length > 0) {
                    //console.log(query + " already exist");

                    resolve(query_result);
                    return;
                }
            }

            // まだないので監視
            //console.log(query + " not exist now, watching...");
            const observer = new MutationObserver((mutations) => {
                Array.from(mutations).some((mutation) => {
                    let found = false;
                    Array.from(mutation.addedNodes).some((node) => {
                        //console.log("add:" + node)

                        if(!node.parentNode.querySelectorAll){
                            return false;
                        }

                        const query_result = node.parentNode.querySelectorAll(query);
                        if(query_result.length > 0){
                            console.log(query + " matche!")

                            observer.disconnect();
                            resolve(query_result);
                            found = true;
                            return true;
                        }
                    });

                    return found;
                });
            });
            observer.observe(document.body, { childList: true, subtree: true });

        }).then((results)=>{
            console.log(query + " found.");
            return results;
        });
    }

    async function waitForElement(query, callback) {
//         return new Promise((resolve, reject) => {
//             console.debug(query + " wait...");

//             var interval = setInterval(function() {
//                 var elements = getElements(query);
//                 if (elements && elements.length > 0) {
//                     if(callback){
//                         Array.from(elements).forEach((elm)=>{
//                             callback(elm); // 要素をコールバック関数に渡す
//                         });
//                     }

//                     clearInterval(interval);
//                     console.debug(query + " done.");
//                     resolve();
//                 } else {
//                     //console.debug(query + " still not found");
//                 }
//             }, 100); // ミリ秒ごとにチェック
//         });

        let elements = await waitForElementAppear(query);
        if(callback){
             Array.from(elements).forEach((elm)=>{
                 callback(elm); // 要素をコールバック関数に渡す
             });
        }
    }


    function getElements(key){
        return document.querySelectorAll(key);
    }

    function collapseElement(element){
        element.style.display = "none";
    }

    function shrinkElement(element){
        element.style.minHeight = "0vh";
        element.style.padding = "0px";
    }

    function replaceElementToFirst(element){
        let parent = element.parentElement;
        parent.insertBefore(element, parent.firstChild);
    }

    async function mainLoop(){
        while(true){
            // 広告ボタンの出現を待つ
            await waitForElementAppear(replace_publish_button_settings.query);

            // 項目非表示
            collapse_settings.forEach((item) => {
                const value = getElements("#cbx_" + item.id)[0].checked;
                if(!value){
                    waitForElement(item.query, collapseElement);
                }
            });

            // 隙間埋め
            {
                const value = getElements("#cbx_" + shrink_settings.id)[0].checked;
                if(value){
                    waitForElement(shrink_settings.query, shrinkElement);
                }
            }

            // 決定ボタン移動
            {
                const value = getElements("#cbx_" + replace_publish_button_settings.id)[0].checked;
                if(value){
                    waitForElement(replace_publish_button_settings.query, replaceElementToFirst);
                }
            }

            // 広告設定が終わり、結果画面で戻るボタンが出現するのを待つ
            await waitForElementAppear(replace_back_button_settings.query);

            // 戻るボタンの移動
            {
                const value = getElements("#cbx_" + replace_back_button_settings.id)[0].checked;
                if(value){
                    waitForElement(replace_back_button_settings.query, replaceElementToFirst);
                }
            }

            // ループして再び広告決定ボタンの出現を待つ
        }
    }

    mainLoop();

    // 遷移の監視
    // 出稿ボタン、再出稿ボタンの存在を監視して、ページが遷移したら再度simplizeをかける


})();