NicoLiveCleaner

ニコニコ生放送上の特定生主やゲームなどのアイテム欄をワンクリックで非表示にし、クリーンなニコ生を目指すスクリプト。NGワード(正規表現)でも可能。

// ==UserScript==
// @name         NicoLiveCleaner
// @namespace    https://greasyfork.org/ja/users/292779-kinako
// @version      1.37
// @description  ニコニコ生放送上の特定生主やゲームなどのアイテム欄をワンクリックで非表示にし、クリーンなニコ生を目指すスクリプト。NGワード(正規表現)でも可能。
// @author       kinako
// @include      http*://live.nicovideo.jp/*
// @include      http*://live2.nicovideo.jp/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addValueChangeListener
// @run-at       document-start
// @compatible   chrome
// ==/UserScript==

(function() {
    'use strict';

    /*
      汚 ( ヾハハヘ
      物 (  |W/ヘヘ
      は (  |‖//ヘヘ
      消 (  ハ‖イ///彡
      毒 ( /丶/ ̄\ミ
      だ ( /ヘ / ̄‖ミ
      | ( レ=  ̄ =、 ‖ミミ
      | ( Y三八三>=ヘミミ
      !! (〈 L_ソ 〉ノミミ、
      ⌒⌒ Y 戸弌 i|\ミミ
       __i kェェノ / //
      /  /`ー―イ //
      ヒ_(王)二二二二(王)ノ
      L_|_‖  /  | ‖ ̄
      彡彡\\ |  |o‖
      彡彡彡L||  |o‖
      彡彡//ー仝ー-ヽ/ ̄
      |イ二‾\____/ /
      |i二 |   | |.
      丶て_ム___| |.
  */

    class Controller
    {
        constructor(model)
        {
            this._model = model;
            GM_registerMenuCommand('設定', this._model.settings.bind(model));
        }

        router(flag = null, data = null)
        {
            switch (flag)
            {
                case 'add_number':
                    this._model.setData('ng_numbers', data);
                    break;
                case 'delete_number':
                    this._model.deleteData('ng_numbers', data);
                    break;

                case 'update_ngkeywords':
                    this._model.setRegData('ng_keywords', data);
                    break;

                case 'update_ngitems':
                    this._model.setRegData('ng_items', data);
                    break;
                case 'add_ngitem':
                    this._model.setData('ng_items', data);
                    break;

                case 'update_settings':
                    this._model.setObjData('settings_data', data);
                    break;

                case 'display_modal':
                    this._model.settings();
                    break;

                default:
                    this.pageManeger()
                    break;
            }
        }

        pageManeger()
        {
            // トップページ
            if (location.href.match(/http(s)*:\/\/live\.nicovideo\.jp(\/\?header|\/)$/))
            {
                this._model.topPage();
                // 注目番組一覧ページ
            } else if(location.href.match(/http(s)*:\/\/live2\.nicovideo\.jp\/focus$/)) {
                this._model.focusPage();
                // 番組一覧ページ
            } else if (location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/recent\?.*/)) {
                this._model.listPage();
                // 検索ページ
            } else if (location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/search\?.*/)) {
                this._model.searchPage();
                // 旧ライブページ&放送終了後ページ
            //} else if (location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/(watch|gate)\/lv.*/)) {
            //    this._model.liveEndPage();
                // ニコ生html5ライブページ
            } else if (location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/watch\/lv.*/)) {
                this._model.livePage();
                // ニコ生ランキングページ
            } else if (location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/ranking.*/)) {
                this._model.rankingPage();
                // ニコ生番組表ページ
            } else if (location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/timetable.*/)) {
                this._model.timeTablePage();
            }
        }
    }


    class Model
    {
        constructor()
        {
            this.ng_keywords = null;
            this.ng_numbers = null;
            this.ng_items = null;
            this.settings_data = {debug:false};

            this.initializePattern('ng_keywords',null,null,null,null);
            GM_addValueChangeListener('ng_keywords', this.initializePattern.bind(this));

            this.initializePattern('ng_numbers',null,null,null,null);
            GM_addValueChangeListener('ng_numbers', this.initializePattern.bind(this));

            this.initializePattern('ng_items',null,null,null,null);
            GM_addValueChangeListener('ng_items', this.initializePattern.bind(this));

            this.initializePattern('settings_data',null,null,null,null);
            GM_addValueChangeListener('settings_data', this.initializePattern.bind(this));
        }

        initializePattern(name, old_val, new_val, remote)
        {
            const ls = this.getLocalStorage(name);
            if (ls && (ls.length > 0 || Object.keys(ls).length > 0))
            {
                const pattern = function(ls){
                    return (ls.length == 0)? null: new RegExp(ls.filter((str)=>{return (str !== '')}).join('|'));
                };
                switch (name)
                {
                    case 'ng_keywords':
                        this.ng_keywords = pattern(ls);
                        break;
                    case 'ng_items':
                        this.ng_items = pattern(ls);
                        break;
                    case 'ng_numbers':
                        this.ng_numbers = new RegExp(
                            ls.filter((str)=>{
                                return (str !== '')
                            }).map((str)=>{
                                return '/'+ str + '(\\.|"|\\/)'
                            }).join('|')
                        );
                        break;
                    case 'settings_data':
                        this.settings_data = ls;
                        break;
                }
            }
        }

        convertPattern(name)
        {
            const ls = this.getLocalStorage(name);
            if (ls)
            {
                return ls.join('\n');
            } else {
                return '';
            }
        }

        setData(key, data)
        {
            let ls = this.getLocalStorage(key);
            if (ls) {
                if (!ls.includes(data))
                {
                    ls.push(data);
                    this.setLocalStorage(key, ls);
                }
            } else {
                this.setLocalStorage(key, [data]);
            }
        }

        setObjData(key, obj)
        {
            let ls = this.getLocalStorage(key);
            const obj_key = (Object.keys(obj).length == 1)? Object.keys(obj)[0]: null;
            if (obj_key)
            {
                if (ls)
                {
                    ls[obj_key] = obj[obj_key];
                    this.setLocalStorage(key, ls);
                } else {
                    this.setLocalStorage(key, obj);
                }
            }
        }

        setRegData(key, data)
        {
            data = data.trim().split('\n');
            data = data.filter(function(str) {
                if (str !== "" || str !== undefined) return str;
            });
            this.setLocalStorage(key, data);
        }

        deleteData(key, data)
        {
            let ls = this.getLocalStorage(key);
            if (ls)
            {
                let newArray = ls.filter(n => n !== data);
                this.setLocalStorage(key, newArray);
            }

        }

        clearData(key){ GM_deleteValue(key); }

        getLocalStorage(key)
        {
            let ls = GM_getValue(key);
            if (ls)
            {
                //return (!Array.isArray(ls))? JSON.parse(ls): ls;
                return this.checkArray(ls);
            }
            return null;
        }

        checkArray(data)
        {
            if (
                (!Array.isArray(data) && Object.prototype.toString.call(data) !== '[object Object]')
               || (Object.prototype.toString.call(data) == '[object String]' && /\[.*\]/.test(data))
            )
            {
                return this.checkArray(JSON.parse(data));
            } else {
                return data;
            }
        }

        setLocalStorage(key,data){
            GM_setValue(key, JSON.stringify(data));
        }

        ngRegEx(target)
        {
            return (this.numbersRegEx(target))? true: this.keywordsRegEx(target);
        }

        numbersRegEx(target)
        {
            if(this.ng_numbers && this.ng_numbers.test(target.outerHTML))
            {
                return true;
            }
            return false;
        }

        keywordsRegEx(target)
        {
            if(this.ng_keywords && this.ng_keywords.test(target.outerHTML))
            {
                return true;
            }
            return false;
        }

        itemsRegEx(target)
        {
            if(this.ng_items && this.ng_items.test(target.outerHTML))
            {
                return true;
            }
            return false;
        }

        dispatchEvent(eventType, target, regArray=[])
        {
            if (target)
            {
                let result = false;
                for (const reg of regArray)
                {
                    switch (reg) {
                        case 'number':
                            result = (this.numbersRegEx(target))? true: false;
                            break;
                        case 'keyword':
                            result = (this.keywordsRegEx(target))? true: false;
                            break;
                        case 'item':
                            result = (this.itemsRegEx(target))? true: false;
                            break;
                    }
                    if (result) break;
                }
                //if (this.ngRegEx(target) || regArray.includes(true))
                if (result)
                {
                    eventType += 'FilterMatched';
                }
                if (this.settings_data.debug)
                {
                     eventType += 'Debug';
                }
                //console.log(eventType, target)
                target.dispatchEvent(new Event(eventType, {"bubbles":true}));
            }
        }

        settings()
        {
            const ng_numbers = document.createTextNode( JSON.stringify(this.getLocalStorage('ng_numbers')) );
            const ng_keywords = document.createTextNode( JSON.stringify(this.convertPattern('ng_keywords')) );
            const ng_items = document.createTextNode( JSON.stringify(this.convertPattern('ng_items')) );
            const others = document.createTextNode( JSON.stringify(this.settings_data) );

            document.body.appendChild(ng_numbers);
            ng_numbers.dispatchEvent(new Event('ngNumbersSettingLoaded', {"bubbles":true}));

            document.body.appendChild(ng_keywords);
            ng_keywords.dispatchEvent(new Event('ngKeywordsSettingLoaded', {"bubbles":true}));

            document.body.appendChild(ng_items);
            ng_items.dispatchEvent(new Event('ngItemsSettingLoaded', {"bubbles":true}));

            document.body.appendChild(others);
            others.dispatchEvent(new Event('othersSettingLoaded', {"bubbles":true}));

            document.body.dispatchEvent(new Event('settings', {"bubbles":true}));
        }

        topPage()
        {
            const mo_option = {childList: true, subtree: true};

            const mo = new MutationObserver(function(mr, mo){
                //console.log('mr',mr);
                const items = document.querySelectorAll('li[class^="___item___"]:not([loaded])');
                for(const item of items)
                {
                    if (!item.querySelector('div[class^="___program-card___"]')) continue;
                    mo.disconnect();
                    item.setAttribute('loaded', '');
                    this.dispatchEvent('topPage', item, ['number', 'keyword']);
                    mo.observe(document, mo_option);
                }

                for (const r of mr)
                {
                    switch(r.target.localName)
                    {
                        case 'div':
                            if (r.target.className && /^___program\-card___/.test(r.target.className)
                                && r.removedNodes.length == 1 && /\-btn\-container/.test(r.removedNodes[0].className))
                            {
                                mo.disconnect();
                                this.dispatchEvent('topPage', r.target.parentNode, ['number', 'keyword']);
                                mo.observe(document, mo_option);
                            }
                            break;
                    }
                }

            }.bind(this));
            mo.observe(document, mo_option);
        }

        focusPage()
        {
            const mo_option = {childList: true, subtree: true};

            const mo = new MutationObserver(function(mr, mo){
                //console.log('mr',mr);
                const items = document.querySelectorAll('li[class^="___item___"]:not([loaded])');
                for(const item of items)
                {
                    mo.disconnect();
                    item.setAttribute('loaded', '');
                    this.dispatchEvent('focusPage', item, ['number', 'keyword']);
                    mo.observe(document, mo_option);
                }

                for (const r of mr)
                {
                    switch(r.target.localName)
                    {
                        case 'div':
                            if (r.target.className && /^___program\-card___/.test(r.target.className)
                                && r.removedNodes.length == 1 && /\-btn\-container/.test(r.removedNodes[0].className))
                            {
                                mo.disconnect();
                                this.dispatchEvent('focusPage', r.target.parentNode, ['number', 'keyword']);
                                mo.observe(document, mo_option);
                            }
                            break;
                    }
                }
            }.bind(this));
            mo.observe(document, mo_option);
        }

        listPage()
        {
            const mo_option = {childList: true, subtree: true};
/*
            Array.from(document.querySelectorAll('[class^="___program-card-list___"] li[class^="___item___"]')).map((node)=>{
                if (node.querySelector('[class^="___name-label___"]') && !node.hasAttribute('loaded'))
                {
                    node.setAttribute('loaded','');
                    this.dispatchEvent('listPagePrograms', node, ['number', 'keyword']);
                }
            });
*/
            function getParent(node)
            {
                if (node == null || node.nodeName =='body') {
                    return false;
                }
                else if (node.nodeName == 'LI' && /^___item___/.test(node.className))
                {
                    return node;
                }
                else
                {
                    return getParent(node.parentNode);
                }
            };

            const mo = new MutationObserver((mr, mo)=>{
                //console.log('mr',mr);
                for (const r of mr)
                {
                    switch (r.target.nodeName)
                    {
                        case 'A':
                            if ( /^___name-label___/.test(r.target.className)) {
                                const parent = getParent(r.target);
                                if (parent && !parent.hasAttribute('loaded'))
                                {
                                    mo.disconnect();
                                    parent.setAttribute('loaded','');
                                    this.dispatchEvent('listPagePrograms', parent, ['number', 'keyword']);
                                    mo.observe(document, mo_option);
                                }
                            }
                            break;

                        case 'DIV': // 削除対策
                            if (/^___program-card___/.test(r.target.className)
                                && r.removedNodes.length > 0 && r.removedNodes[0].className == 'nlc-proglist-btn-container') {
                                const parent = getParent(r.target);
                                mo.disconnect();
                                parent.setAttribute('loaded','');
                                this.dispatchEvent('listPagePrograms', parent, ['number', 'keyword']);
                                mo.observe(document, mo_option);
                            }
                            break;

                        case 'UL': // デフォルト&もっと見る
                            if (/^___program-card-list___/.test(r.target.className)
                                && r.addedNodes.length && /^___item___/.test(r.addedNodes[0].className))
                            {
                                if(r.addedNodes[0].querySelector('[class^="___name-label___"]'))
                                {
                                    mo.disconnect();
                                    const parent = getParent(r.addedNodes[0]);
                                    if (parent && !parent.hasAttribute('loaded'))
                                    {
                                        mo.disconnect();
                                        parent.setAttribute('loaded','');
                                        this.dispatchEvent('listPagePrograms', parent, ['number', 'keyword']);
                                        mo.observe(document, mo_option);
                                    }
                                    mo.observe(document, mo_option);
                                }
                            }
                            break;
                        default:
                            break;
                    }
                }

            });
            mo.observe(document, mo_option);
        }

        searchPage()
        {
            const mo_option = {childList: true, subtree: true};

            const mo = new MutationObserver(function(mr, mo){
                //console.log('mr',mr);
                const items = document.querySelectorAll('li[class="searchPage-ProgramList_Item"]:not([loaded])');
                for(const item of items)
                {
                    mo.disconnect();
                    item.setAttribute('loaded', '');
                    this.dispatchEvent('searchPage', item, ['number', 'keyword']);
                    mo.observe(document, mo_option);
                }

                const comm = document.querySelectorAll('li[class="recommend-community-item"]:not([loaded])');
                for(const item of comm)
                {
                    if (item.querySelector('.description').textContent.length > 0)
                    {
                        mo.disconnect();
                        item.setAttribute('loaded', '');
                        this.dispatchEvent('searchPageComm', item, ['number', 'keyword']);
                        mo.observe(document, mo_option);
                    }
                }

            }.bind(this));
            mo.observe(document, mo_option);
        }

        livePage()
        {
            const mo_option = {childList: true, subtree: true};

            document.addEventListener("DOMContentLoaded", function(e){
                const comm = document.querySelector('div[class^="___social-group-information___"] div[class^="___header-area___"]');
                if (comm) this.dispatchEvent('livePageComm', comm, ['number']);

                const user = document.querySelector('div[class^="___user-information-area___"]');
                if (user) this.dispatchEvent('livePageUser', user, ['number']);

                //document.querySelector('button[class^="___comment-button___"]').click();
            }.bind(this));

            const mo = new MutationObserver((mr, mo)=>{
                //console.log(mr);
                for (const r of mr)
                {
                    if (r.target.localName == 'div')
                    {
                        // ザッピング番組
                        if (/^___zapping-list-group___/.test(r.target.className) && r.addedNodes.length ==1 &&
                            /^___zapping-list___/.test(r.addedNodes[0].className))
                        {
                            for (const item of r.addedNodes[0].children)
                            {
                                mo.disconnect();
                                this.dispatchEvent('livePageZapp', item, ['number', 'keyword']);
                                mo.observe(document, mo_option);
                            }
                        }

                        // アイテム
                        else if (/^___queue-item-area___/.test(r.target.className)&& r.addedNodes.length == 1 &&
                                 /^___item___/.test(r.addedNodes[0].className)
                                )
                        {
                            mo.disconnect();
                            this.dispatchEvent('livePageItem', r.addedNodes[0], ['item']);
                            mo.observe(document, mo_option);
                        }
                    }
                }
            });
            mo.observe(document, mo_option);
        }

        liveEndPage()
        {
            const mo_option = {childList: true, subtree: true};
            const mo = new MutationObserver(function(mr, mo){
                const items = document.querySelectorAll('li[class="gyokuon_list_item"]:not([loaded])');
                 if (items)
                {
                    mo.disconnect();
                    for(const item of items)
                    {
                        item.setAttribute('loaded', '');
                        this.dispatchEvent('liveEndPage', item, ['number', 'keyword']);
                    }
                    mo.observe(document, mo_option);
                }
            }.bind(this));
            mo.observe(document, mo_option);
        }

        rankingPage()
        {
            const mo_option = {childList: true, subtree: true};
            const mo = new MutationObserver(function(mr, mo){
                const items = document.querySelectorAll('div[class^="___rk-program-card___"]:not([loaded])');
                if (items)
                {
                    mo.disconnect();
                    for(const item of items)
                    {
                        item.setAttribute('loaded', '');
                        this.dispatchEvent('rankingPage', item, ['number', 'keyword']);
                    }
                    mo.observe(document, mo_option);
                }
            }.bind(this));
            mo.observe(document, mo_option);
        }

        timeTablePage()
        {
            const mo_option = {childList: true, subtree: true};
            const mo = new MutationObserver(function(mr, mo){
                const items = document.querySelectorAll('tr[id^="stream_"]:not([loaded])');
                if (items)
                {
                    mo.disconnect();
                    for(const item of items)
                    {
                        item.setAttribute('loaded', '');
                        this.dispatchEvent('timeTablePage', item, ['number', 'keyword']);
                    }
                    mo.observe(document, mo_option);
                }
            }.bind(this));
            mo.observe(document, mo_option);
        }
    }


    class View
    {
        constructor(controller)
        {
            this._controller = controller;
            this.css_prefix = 'nlc';

            this.setStyle();
            this.settings();
            //this.insertMenu();
            this.debug();

            this.topPage();
            this.focusPage();
            this.listPage();
            this.searchPage();
            this.livePage();
            this.liveEndPage();
            this.rankingPage();
            this.timeTablePage();
        }

        // スタイルシート設定
        setStyle()
        {
            let css = document.createElement('style')
            let rule = document.createTextNode(`
/* 削除ボタン */
button.${this.css_prefix}-btn {
  font-size: 12px;
  color: #fff;
  padding: 0 3px;
  border-radius: 4px;
  opacity: 0.05;
  background-color: #404040;
  border: none;
}
/* 削除ボタンホバー時 */
button.${this.css_prefix}-btn:hover {
  border: none;
  opacity: 1;
}

/* ライブページ 削除ボタン */
button[id^="${this.css_prefix}-live-"] {
  opacity: 1;
}
/* ライブページ 削除ボタンホバー時 */
button[id^="${this.css_prefix}-live-"]:hover {
  border: none;
  opacity: 0.8;
}

/* ライブページ 状態コンテナ */
div[class^="${this.css_prefix}-livepagecomm-btn-container"],
span[id^="${this.css_prefix}-livepagecomm-commstate"] {
  margin-left: 1em;
}
/* ライブページ NG中状態 */
span[id^="${this.css_prefix}-livepagecomm-commstate"], span[id^="${this.css_prefix}-livepageuser-userstate"] {
  letter-spacing: 0.2em;
  width: 4em;
  text-align: center;
  color: #FF0066;
  background-color: #FFF;
  border: 1px solid #FF0066;
  padding: 0em 0.5em;
  border-radius: 4px;
}

/* ライブページ ユーザー状態コンテナ */
 div[class^="${this.css_prefix}-livepageuser-btn-container"], span[id^="${this.css_prefix}-livepageuser-userstate"] {
  margin-left:0.3em;
}
/* ライブページ ユーザーNG中状態 */
span[id^="${this.css_prefix}-livepageuser-userstate"] {
    padding: 0 0.2em;
    letter-spacing: 0;
    font-size: 11px;
}

/* ライブページザッピング 削除ボタン*/
button[id^="${this.css_prefix}-proguser"] {
  opacity: 0.1;
  z-index: 2;
}

/* ライブページザッピング 削除ボタンコンテナ */
div.${this.css_prefix}-zapp-btn-container {
  margin: -20px 0 0 0;
  width: 100%;
  text-align: right;
  position: absolute;
  z-index: 2;
}
/* ライブページザッピング 削除ボタン*/
button[id^="${this.css_prefix}-zapp"] {
  opacity: 0.1;
  z-index: 2;
}

/* 検索ページ 削除ボタン */
button[id^="${this.css_prefix}-search"] {
  height: 1.5em;
  opacity: 0.1;
}
/* 番組一覧 削除ボタン */
button[id^="${this.css_prefix}-proglist"] {
  padding: 0 3px;
}
/* 削除ボタン */
button[id^="${this.css_prefix}-top"],
button[id^="${this.css_prefix}-focus"]
{
  opacity: 0.5;
}
/* 番組表 削除ボタン */
button[id^="${this.css_prefix}-timetable"] {
  opacity: 0.8;
}
/* アイテム 削除ボタン */
button[id^="${this.css_prefix}-item"] {
  opacity: 0.8;
  position:absolute;
  z-index:10;
  visibility:hidden;
}
button[id^="${this.css_prefix}-item"]:hover {
  opacity: 0.5;
  background-color:red;
}
/* ボタンコンテナ */
div.${this.css_prefix}-proglist-btn-container,
div.${this.css_prefix}-ps4list-btn-container,
div.${this.css_prefix}-rankpic-btn-container,
div.${this.css_prefix}-channel-btn-container,
div.${this.css_prefix}-top-btn-container,
div.${this.css_prefix}-focus-btn-container,
div.${this.css_prefix}-search2-btn-container,
div.${this.css_prefix}-search3-btn-container,
span.${this.css_prefix}-modal-img-containner,
div.${this.css_prefix}-left-btn-container,
div.${this.css_prefix}-programComm-btn-container,
div.${this.css_prefix}-liveend-btn-container,
div.${this.css_prefix}-timetable-btn-container,
div.${this.css_prefix}-liveitem-btn-container
{
  margin: -1.5em 0 0 0;
  width : 100%;
  text-align: right;
  position: relative;
}
div.${this.css_prefix}-top-btn-container,
div.${this.css_prefix}-focus-btn-container
{
    margin: -2px 0 0 0;
    position: absolute;
    z-index: 1000;
}
div.${this.css_prefix}-search-btn-container {
    display:inline;
}
div.${this.css_prefix}-search3-btn-container {
  margin: 0 0 0 -1em;
  position: absolute;
}
span.${this.css_prefix}-modal-img-containner {
  margin:0em 0 0 0;
}
div.${this.css_prefix}-ranking-btn-container {
    margin-top: 1.8em;
}
div.${this.css_prefix}-liveitem-btn-container {
    width: 95%;
    margin: -25px 0 0 0;
}
div.${this.css_prefix}-timetable-btn-container {
    margin: unset;
    text-align: unset;
}


/* オーバーレイ */
div#${this.css_prefix}-overlay {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.7);
  opacity: 0;
  transition: opacity 0.3s;
  z-index: 10000;
}

/* モーダル */
div#${this.css_prefix}-modal-contents {
  position: fixed;
  top: 50%;
  left: 50%;
  width: 500px;
  height: 500px;/*auto*/
  text-align: left;
  padding: 2em;
  transform: translate(-50%, -50%);
  background: #fff;
  overflow-x: hidden;
 /* overflow-y: scroll;*/
  border-radius: 10px;
}
div#${this.css_prefix}-modal-contents h1{
  font-size: 18px;
  margin-bottom: 1em;
}
div#${this.css_prefix}-modal-contents h1,
div#${this.css_prefix}-modal-contents h2
{
  font-weight: bold;
}

#${this.css_prefix}-modal-contents > div:nth-child(2) {
  margin-bottom: 1em;
}

div#${this.css_prefix}-modal-contents a {text-decoration: none;}
div#${this.css_prefix}-modal-contents a:link,
div#${this.css_prefix}-modal-contents a:visited{
  color:#808080;
}
div#${this.css_prefix}-modal-contents a:hover{
  color:#A6A6A6;
}

div#${this.css_prefix}-modal-contents h2 {
  margin: 0 0 5px 0;
  font-size: 13px;
}
div#${this.css_prefix}-modal-contents img {
  width: 40px;
  height: 40px;
  border-radius: 5px;
}
div#${this.css_prefix}-modal-contents textarea {
  width: 100%;
  height: 20em;
  padding : 0.5em 0 0.5em 0.3em;
}

/* モーダルupdateボタン */
button.${this.css_prefix}-update, button.${this.css_prefix}-update-click{
  width: 8em;
  padding: 0 1em;
  background-color: #002863;
  border-radius: 3px;
  border: none;
  color: #FFF;
  opacity: 1;
  margin: 0 0 0 0;
  cursor: pointer;
}
/* モーダルupdateボタンclick */
button.${this.css_prefix}-update-click {
  background-color: #4CB5E8;
  color: #FFF;
  /*opacity: 0.2;*/
  transition: 0.3s;
}
button.${this.css_prefix}-update:hover { opacity: 0.5;}

/* モーダルNGコミュニティ削除ボタン */
button[id^="${this.css_prefix}-ngimg"] {
  margin: 0 0 0 -18px;
}
/* モーダルupdateボタン */
button#${this.css_prefix}-regb {
  padding: 0 1em;
  background-color: #0080FF;
  opacity: 1;
  margin: 1em 0 0 0;
}
button#${this.css_prefix}-regb:hover { opacity: 0.5;}

/* モーダルcloseボタン */
#${this.css_prefix}-close {
  text-align: center;
  width: 100%;
  margin-top: 2em;
  font-size: 15px;
}

* NGコミュ表示切替 */
#${this.css_prefix}-block-container-on {
  display: none;
}

/* NGコミュナビゲーション */
#${this.css_prefix}-navi { margin: 0.5em 0 2.5em 0; }
#${this.css_prefix}-navi a,
#${this.css_prefix}-navi-on{
  font-size: 12px;
  display: inline-block;
  width: 2em;
  border: 1px solid #0080FF;
  color: #0080FF;
  text-align: center;
  border-radius: 6px;
  background-color: #FFF;
  margin-right: 0.2em;
}
#${this.css_prefix}-navi-on {
  background-color:#0080FF !important;
  color: #FFF !important;
}

/* 設定画面メニュー */
div#${this.css_prefix}-modal-contents ul#menu {
    margin-bottom: 1em;
    /* background-color: #008be6; */
    padding: 4px 1em 2px;
    border-radius: 5px;
    color: #0080ff;
    font-size: 15px;
    border: 1px solid #008be6;
}
div#${this.css_prefix}-modal-contents ul#menu li{
  display: inline-block;
  font-size: 0.8em;
  margin-right: 1.5em;
  text-transform: capitalize;
  cursor: pointer;
}
div#${this.css_prefix}-modal-contents ul#menu li:hover{
  color:#E6E6E6;
}
div#${this.css_prefix}-modal-contents dl dt,
div#${this.css_prefix}-modal-contents dl dd{
  display: inline-block;
}
div#${this.css_prefix}-modal-contents dl dt{
  font-size: 13px;
  margin-right: 1em;
}
div#${this.css_prefix}-modal-contents dl dd{
  vertical-align: middle;
}
div#${this.css_prefix}-modal-contents dl dd input[type="checkbox"]{
  margin:0;
}
.${this.css_prefix}-hidden {
  display: none;
}
.${this.css_prefix}-visible {
  display: block;
}

}
`);
            css.media = 'screen';
            css.type = 'text/css';
            if (css.styleSheet) {
                css.styleSheet.cssText = rule.nodeValue;
            } else {
                css.appendChild(rule);
            };

            const mo_option = {childList: true, subtree: true};
            const mo = new MutationObserver((mr, mo)=>{
                if (document.getElementsByTagName('head')[0] !== undefined) {
                    //console.log('head');
                    mo.disconnect();
                    document.getElementsByTagName('head')[0].appendChild(css);
                }
            });
            mo.observe(document, mo_option);
        }
/*
        insertMenu()
        {
            if (!location.href.match(/http(s)*:\/\/live(2*)\.nicovideo\.jp\//)) return;

            window.addEventListener("DOMContentLoaded", function(e){
                // メニュー部分取得
                const menu = document.getElementById('siteHeaderRightMenuContainer');

                const li = document.createElement('li');
                const a = document.createElement('a');
                a.id = 'NicoLiveCleaner';
                a.textContent = 'NicoLiveCleaner';
                li.appendChild(a);

                // ログインアウトリンクの一つ手前に挿入
                menu.lastElementChild.parentNode.insertBefore(li, menu.lastElementChild);

                a.addEventListener('click', (e)=>{
                    //メニュー非表示に
                    //menu.style.marginRight = '-100em';
                    //this.displayModal(a.id);
                    this._controller.router('display_modal');
                });
            }.bind(this));

        }
 */
        createNgNumbers(data)
        {
            if(data.ngNumbers) {
            if (Object.prototype.toString.call(data.ngNumbers) == '[object String]')
            {
                data.ngNumbers = JSON.parse(data.ngNumbers);
            }
            data.ngNumbers.reverse();
            }
            const title = document.createElement('h2');
            title.textContent = 'NG生主&チャンネル';
            const ngNumList = this.iniNgNumber(data, 50, 0);
            const ngNumNavi = this.iniNavi(data, 50, 0);

            const outer = document.createElement('div');
            outer.id = this.css_prefix+'-ngnumbers-container';
            outer.setAttribute('class', this.css_prefix + '-visible');

            const blockOuter = document.createElement('div');

            outer.appendChild(title);
            blockOuter.appendChild(ngNumList);
            outer.appendChild(blockOuter);
            outer.appendChild(ngNumNavi);
            return outer;
        }

        iniNgNumber(items, block, position = 0)
        {
            const outer = document.createElement('div');
            outer.id = this.css_prefix + '-ngnumbers';

            const start = block * position;
            const end = start + block;
            const block_items = (items.ngNumbers)? items.ngNumbers.slice(start, end): null;

            if (block_items)
            {
                for (let i = 0; i < block_items.length; i++)
                {
                    const item = this.convertImage(block_items[i]);
                    if (!item) continue;

                    const img = document.createElement('img');
                    img.onerror = function() {
                        img.src = 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank_s.jpg'; //代用
                    }
                    img.src = item.imgSrc;
                    img.alt = img.title = block_items[i];

                    const a = document.createElement('a');
                    a.href = item.link;
                    a.target = '_blank';

                    const inner = document.createElement('span');
                    inner.setAttribute('class', this.css_prefix + '-modal-img-container');

                    const b = this.iniButton(this.convertID('ngimg', block_items[i]), 'x');

                    a.appendChild(img);
                    inner.appendChild(a);
                    inner.appendChild(b);
                    outer.appendChild(inner);

                    b.addEventListener('click', (e)=>{
                        e.stopPropagation();
                        inner.remove();
                        const index = items.ngNumbers.findIndex(num => num == block_items[i]);
                        if (index !== -1) items.ngNumbers[index] = null;
                        this._controller.router('delete_number', block_items[i]);
                    });
                }
            }
            return outer;
        }

        iniNavi(items, block, position)
        {

            const page = (items.ngNumbers)? Math.ceil(items.ngNumbers.length / block): -1;
            const navi = document.createElement('div');
            navi.id = this.css_prefix + '-navi'
            const navi_on = this.css_prefix + '-navi-on';
            const parent_id = this.css_prefix + '-ngnumbers';

            if (page > position){
                for (let i = 0; i < page; i++) {
                    const span = document.createElement('span');
                    const a = document.createElement('a');
                    if (i == position) a.id = navi_on;

                    a.addEventListener('click', function(e) {
                        // 現在地
                        for (let x = 0; x < navi.children.length; x++)
                        {
                            if (navi.children[x].id == navi_on) {
                                navi.children[x].removeAttribute('id');
                            } else if (x == i) {
                                navi.children[x].id = navi_on;
                            }
                        }
                        //表示アイテム切替
                        const base = document.getElementById(parent_id).parentNode;
                        document.getElementById(parent_id).remove();
                        base.appendChild(this.iniNgNumber(items, block, i));
                    }.bind(this));

                    span.textContent = i + 1;
                    a.appendChild(span);
                    navi.appendChild(a);
                }
            }
            return navi;
        }

        createTextArea(name, titele_str, data)
        {
            const outer = document.createElement('div');
            const title = document.createElement('h2');
            const ta = document.createElement('textarea');
            const update = document.createElement('button');
            ta.textContent = data;

            title.textContent = titele_str;
            outer.id = this.css_prefix+'-'+ name+'-container';
            outer.setAttribute('class', this.css_prefix + '-hidden');

            update.textContent = '更新';
            update.setAttribute('class', this.css_prefix + '-update');

            outer.appendChild(title);
            outer.appendChild(ta);
            outer.appendChild(update);

            update.addEventListener('mousedown', function(e){
                update.setAttribute('class', this.css_prefix + '-update-click');
                setTimeout(function() {
                    update.textContent = '完了';
                }, 200);
                this._controller.router('update_'+name, ta.value);
            }.bind(this));

            update.addEventListener('mouseout', function(e){
                setTimeout(function() {
                    update.setAttribute('class', this.css_prefix + '-update');
                    update.textContent = '更新';
                }.bind(this), 700);
            }.bind(this));

            return outer;
        }

        createOthers(data)
        {
            const others = document.createElement('div');
            others.id = this.css_prefix+'-others-container';
            others.setAttribute('class', this.css_prefix + '-hidden');
            const title = document.createElement('h2');
            title.textContent = 'その他';

            const dl = document.createElement('dl');
            const dt = document.createElement('dt');
            dt.textContent = 'デバッグモード';
            const dd = document.createElement('dd');
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.checked = (data.debug)? true: false;

            dl.appendChild(dt);
            dd.appendChild(checkbox);
            dl.appendChild(dd);

            others.appendChild(title);
            others.appendChild(dl);

            checkbox.addEventListener('change', function(e){
                //console.log('change', e.target.checked);
                this._controller.router('update_settings', {debug: e.target.checked});
            }.bind(this));
            return others;
        }

        settings()
        {
            const settingData = {};
            this.loadSettingData(settingData, 'ngNumbers');
            this.loadSettingData(settingData, 'ngKeywords');
            this.loadSettingData(settingData, 'ngItems');
            this.loadSettingData(settingData, 'others');

            document.addEventListener('settings', function(e){
                const title = document.createElement('h1');
                title.textContent = 'NicoLiveCleaner設定画面';
                // メニュー
                const settingMenu = this.createSttingMenu();
                // NG生主&ch
                const ngNumbers = this.createNgNumbers(settingData);
                // NGキーワード
                const ngKeywords = this.createTextArea('ngkeywords', 'NGキーワード(正規表現)', settingData.ngKeywords);
                // NGアイテム
                const ngItems = this.createTextArea('ngitems', 'NG市場&アイテム(正規表現)', settingData.ngItems);
                // その他
                const others = this.createOthers(settingData.others);

                // オーバーレイ
                const overlay =document.createElement('div');
                overlay.setAttribute('id', this.css_prefix + '-overlay');
                // モーダルボックス
                const modal= document.createElement('div');
                modal.setAttribute('id', this.css_prefix + '-modal-contents');

                // クローズリンク
                const cp = document.createElement('div');
                cp.id = this.css_prefix + '-close';
                const close = document.createElement('a');
                close.setAttribute('id', 'close');
                close.setAttribute('href', '#');
                close.innerHTML = 'CLOSE';
                cp.appendChild(close);

                modal.appendChild(title);
                modal.appendChild(settingMenu);
                modal.appendChild(ngNumbers);
                modal.appendChild(ngKeywords);
                modal.appendChild(ngItems);
                modal.appendChild(others);
                modal.appendChild(cp);
                overlay.appendChild(modal);

               // コンテンツを表示
                document.body.appendChild(overlay);
                overlay.style.display = 'block';
                overlay.style.opacity = '1';

                // モーダル親要素への伝播防止
                modal.addEventListener('click', function(e) {
                    e.stopPropagation();
                });

                // オーバーレイ削除
                overlay.addEventListener('click', function() {
                    this.style.opacity = '0';
                    setTimeout(function() {
                        this.remove();
                        location.reload();
                    }.bind(this), 300);
                });

                // クローズ経由でオーバーレイ削除
                close.addEventListener('click', function(e) {
                    e.preventDefault();
                    overlay.style.opacity = '0';
                    setTimeout(function() {
                        overlay.remove();
                        location.reload();
                    }, 300);
                });

            }.bind(this));
        }

        loadSettingData(obj, name)
        {
             document.addEventListener(name + 'SettingLoaded', function(e){
                obj[name] = JSON.parse(e.target.textContent);
                //console.log(name+'SettingLoaded', obj);
                e.target.remove();
            });
        }

        convertImage(item)
        {
            const result = /(co|ch|.*?)([0-9]+)/.exec(item);
            if (result)
            {
                const data = {};
                switch (result[1])
                {
                    case 'co':
                        data.imgSrc = 'https://secure-dcdn.cdn.nimg.jp/comch/community-icon/64x64/' + item + '.jpg';
                        data.link = 'https://com.nicovideo.jp/community/'+ item;
                        break;
                    case 'ch':
                        data.imgSrc = 'https://secure-dcdn.cdn.nimg.jp/comch/channel-icon/64x64/' + item + '.jpg';
                        data.link = 'https://ch.nicovideo.jp/channel/'+ item;
                        break;
                    case '':
                        data.imgSrc = 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/s/'
                            + item.substr(0, item.length-4) + '/' + item + '.jpg'
                        data.link = 'https://www.nicovideo.jp/user/'+ item;
                        break;
                }
                return data;
            }
            return null;
        }

        convertID(id, number)
        {
            return this.css_prefix +'-' + id + '-' + number;
        }

        iniNgRegEx(items)
        {
            let outer = document.createElement('div');
            let btn = this.iniButton(this.css_prefix+'-regb', 'UPDATE');

            let ta = document.createElement('textarea');

            if (Array.isArray(items))
            {
                ta.value = items.join('\n');
            }
            outer.appendChild(ta);
            outer.appendChild(btn);

            btn.addEventListener('mousedown', (e)=> {
                btn.style.opacity = '0.2';
                this.controller.receive('ngreg', ta.value);
            });
            return outer;
        }

        createSttingMenu()
        {
            const category = [
                {id:'ngnumbers', text:'生主&CH'},
                {id:'ngkeywords', text:'キーワード'},
                {id:'ngitems', text:'アイテム'},
                {id:'others', text:'その他'}
                              ];
            const menu = document.createElement('ul');
            menu.id = 'menu';
            for (const c of category)
            {
                const li = document.createElement('li');
                li.id = c.id;
                li.textContent = c.text;
                menu.appendChild(li);

                li.addEventListener('click', function(e){
                    const id = this.css_prefix+'-'+ c.id +'-container';
                    const target = document.getElementById(id);
                    const visible = document.querySelectorAll(`div#${this.css_prefix}-modal-contents div.${this.css_prefix}-visible`);
                    for (const v of visible)
                    {
                        v.setAttribute('class', this.css_prefix + '-hidden');
                    }
                    target.setAttribute('class', this.css_prefix + '-visible');
                }.bind(this));
            }
            return menu;
        }

        iniButton(id, value)
        {
            let btn = document.createElement('button');
            btn.setAttribute('id', id);
            btn.setAttribute('class', this.css_prefix + '-btn');
            btn.setAttribute('type', 'button');
            btn.textContent = value;
            return btn;
        }

        parseNumber(target)
        {
            const nodes = target.querySelectorAll('img, a, div[class="js_like_dialog"]');
            let num;
            for (const node of nodes)
            {
                const result = /\/((co|ch)([0-9]+)\.?|(usericon)\/[0-9]+\/([0-9]+)\.|user\/([0-9]+))|data\-twitter\-tag="#nico(ch)([0-9]+)/.exec(node.outerHTML);

                if (result) {
                    // co&ch
                    if (result[2] && result[3]) {
                        num = result[2] + result[3];
                        break;
                    // user
                    } else if(result[4] && result[5]) {
                        num = result[5];
                        break;
                    } else if (result[6]) {
                        num = result[6];
                        break;
                    } else if (result[7] && result[8]) {
                        num = result[7] + result[8];
                        break;
                    }
                }
            }
            return num;
        }

        createDeleteButton(id, target, buttonTarget, debug=false)
        {
            if (target== null) return;

            const number = this.parseNumber(target);
            if (number)
            {
                const check = buttonTarget.querySelector('div[class$="-btn-container"]');
                if (check) check.remove();

                const outer = document.createElement('div');
                outer.setAttribute('class', this.css_prefix +'-'+id+ '-btn-container');
                const b = this.iniButton(this.convertID(id, number), 'x');
                outer.appendChild(b);
                buttonTarget.appendChild(outer);

                b.addEventListener('click', (e)=>{
                    e.stopPropagation();
                    if (debug) {
                        const border = '1px solid red';
                        if (target.style.border == border)
                        {
                            this._controller.router('delete_number', number);
                            target.removeAttribute('style');
                        } else {
                            this._controller.router('add_number', number);
                            target.style.border = border;
                        }
                    } else {
                        this._controller.router('add_number', number);
                        target.remove();
                    }

                });
            }
        }

        createItemDeleteButton(target, debug=false)
        {
            const border = '1px solid red';
            const id = this.css_prefix + '-item-' + target.getAttribute('data-target-order');
            let button;

            target.addEventListener('mouseover', function(e){
                if (target.style.border == border) return;

                button = this.iniButton(id, 'x');
                document.body.appendChild(button);

                const clientRect = target.getBoundingClientRect() ;
                const x = clientRect.left ;
                const y = clientRect.top ;

                // 右端
                const _right = x + document.scrollingElement.scrollLeft
                    + Math.round(clientRect.width) - Math.round(button.getBoundingClientRect().width);
                // 上端
                const _top = y+document.scrollingElement.scrollTop;

                button.style.left = _right + 'px';
                button.style.top = _top + 'px';
                button.style.visibility = 'visible';

                button.setAttribute('data', target.getAttribute('aria-label'));


                button.addEventListener('mouseover', function(e){
                    e.target.style.visibility = "visible";
                    e.target.setAttribute('over', '');
                });

                button.addEventListener('mouseout', function(e){
                    e.target.remove();
                });
                button.addEventListener('click', function(e){
                    //console.log(e.target.getAttribute('data'));
                    e.stopPropagation();
                    if (e.target.hasAttribute('data'))
                    {
                        this._controller.router('add_ngitem', this.escapeRegEx(e.target.getAttribute('data')));
                        if (debug) {
                            target.style.border = border;
                        } else {
                            target.style.display = 'none';
                        }
                        e.target.remove();
                    }
                }.bind(this));
            }.bind(this));

            target.addEventListener('mouseout', function(e){
                setTimeout(function(){
                    if (button.style.visibility == 'visible' && !button.hasAttribute('over')) button.remove();
                },1);
            });
        }

        hidden(e)
        {
            e.target.style.display = 'none';
        }
        hiddenDebug(e)
        {
            console.log('debugFilterMatched',e.target.innerText);
            e.target.style.border = '1px solid red';
        }

        escapeRegEx(str)
        {
            return str.trim().replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&');
        }

        debug()
        {
            document.addEventListener('debug', function(e){
                console.log('debug',e.target.innerText);
                e.target.style.border = null;

                this.createDebugDeleteButton(e.target, 'top');
                //console.log('debug after', e.target.innerHTML);
            }.bind(this));

            document.addEventListener('debugFilterMatched', function(e){
                console.log('debugFilterMatched',e.target.innerText);
                e.target.style.border = '1px solid red';
                //e.target.style.visibility = 'hidden';
            });
        }

        topPage()
        {
            document.addEventListener('topPage', function(e) {
                this.createDeleteButton('top', e.target, e.target.querySelector('div[class^="___program-card___"]'));
            }.bind(this));

            document.addEventListener('topPageDebug', function(e) {
                this.createDeleteButton('top', e.target, e.target.querySelector('div[class^="___program-card___"]'), true);
            }.bind(this));

            document.addEventListener('topPageFilterMatched', this.hidden);
            //            document.addEventListener('topPageFilterMatched', (e)=>e.target.remove());
            //document.addEventListener('topPageFilterMatchedDebug', this.hiddenDebug);
            document.addEventListener('topPageFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('top', e.target, e.target.querySelector('div[class^="___program-card___"]'), true);
            }.bind(this));
        }

        focusPage()
        {
            document.addEventListener('focusPage', function(e) {
                this.createDeleteButton('focus', e.target, e.target.querySelector('div[class^="___program-card___"]'));
            }.bind(this));

            document.addEventListener('focusPageDebug', function(e) {
                this.createDeleteButton('focus', e.target, e.target.querySelector('div[class^="___program-card___"]'), true);
            }.bind(this));

            document.addEventListener('focusPageFilterMatched', this.hidden);
            document.addEventListener('focusPageFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('focus', e.target, e.target.querySelector('div[class^="___program-card___"]'), true);
            }.bind(this));
        }

        listPage()
        {
            // 番組一覧 --------------------------------------------------------------------------
            document.addEventListener('listPagePrograms', function(e) {
                const buttonParent = e.target.querySelector('div[class^="___program-card___"]');
                this.createDeleteButton('proglist', e.target, buttonParent);
            }.bind(this));
            document.addEventListener('listPageProgramsFilterMatched', (e)=>{
                e.target.style.display = 'none';
            });

            document.addEventListener('listPageProgramsDebug', function(e) {
                const buttonParent = e.target.querySelector('div[class^="___program-card___"]');
                this.createDeleteButton('proglist', e.target, buttonParent, true);
            }.bind(this));
            document.addEventListener('listPageProgramsFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                const buttonParent = e.target.querySelector('div[class^="___program-card___"]');
                this.createDeleteButton('proglist', e.target, buttonParent, true);
            }.bind(this));
        }

        searchPage()
        {
            // 検索結果 --------------------------------------------------------------------------
            document.addEventListener('searchPage', function(e) {
                const buttonTarget = e.target.querySelector('.searchPage-ProgramList_UserName');
                this.createDeleteButton('search', e.target, buttonTarget);
            }.bind(this));
            document.addEventListener('searchPageFilterMatched', this.hidden);

            document.addEventListener('searchPageDebug', function(e) {
                const buttonTarget = e.target.querySelector('.searchPage-ProgramList_UserName');
                this.createDeleteButton('search', e.target, buttonTarget, true);
            }.bind(this));
            document.addEventListener('searchPageFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                const buttonTarget = e.target.querySelector('.searchPage-ProgramList_UserName');
                this.createDeleteButton('search', e.target, buttonTarget, true);
            }.bind(this));

            // 関連コミュ --------------------------------------------------------------------------
            document.addEventListener('searchPageComm', function(e) {
                this.createDeleteButton('search3', e.target, e.target);
            }.bind(this));
            document.addEventListener('searchPageCommFilterMatched', this.hidden);

            document.addEventListener('searchPageCommDebug', function(e) {
                this.createDeleteButton('search3', e.target, e.target, true);
            }.bind(this));
            document.addEventListener('searchPageCommFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('search3', e.target, e.target, true);
            }.bind(this));

        }

        displayLivePageNgState(target, container_name, state_name)
        {
            let base = document.createElement('span');
            base.id = this.css_prefix + '-'+ container_name + '-container';

            let text = document.createElement('span');
            text.id = this.css_prefix + '-' + container_name +'-' +state_name;
            text.textContent = 'NG中';

            base.appendChild(text)
            target.appendChild(base);
        }

        livePage()
        {
            // コミュ --------------------------------------------------------------------------
            document.addEventListener('livePageComm', function(e) {
                this.createDeleteButton('livepagecom', e.target, e.target);
            }.bind(this));
            document.addEventListener('livePageCommFilterMatched', function(e){
                this.displayLivePageNgState(e.target, 'livepagecomm', 'commstate');
            }.bind(this));

            document.addEventListener('livePageCommDebug', function(e) {
                this.createDeleteButton('progcomm', e.target, e.target, true);
            }.bind(this));
            document.addEventListener('livePageCommFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('progcomm', e.target, e.target, true);
            }.bind(this));

            // ユーザー --------------------------------------------------------------------------
            document.addEventListener('livePageUser', function(e) {
                this.createDeleteButton('proguser', e.target, e.target);
            }.bind(this));
            document.addEventListener('livePageUserFilterMatched', function(e){
                this.displayLivePageNgState(e.target, 'livepageuser', 'userstate');
            }.bind(this));

            document.addEventListener('livePageUserDebug', function(e) {
                this.createDeleteButton('proguser', e.target, e.target, true);
            }.bind(this));
            document.addEventListener('livePageUserFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('proguser', e.target, e.target, true);
            }.bind(this));


            // ザッピング --------------------------------------------------------------------------
            document.addEventListener('livePageZapp', function(e) {
                this.createDeleteButton('zapp', e.target, e.target);
            }.bind(this));
            document.addEventListener('livePageZappFilterMatched', this.hidden);

            document.addEventListener('livePageZappDebug', function(e) {
                this.createDeleteButton('zapp', e.target, e.target, true);
            }.bind(this));
            document.addEventListener('livePageZappFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('zapp', e.target, e.target, true);
            }.bind(this));


            // アイテム --------------------------------------------------------------------------
            document.addEventListener('livePageItem', function(e) {
                this.createItemDeleteButton(event.target);
            }.bind(this));
            document.addEventListener('livePageItemFilterMatched', this.hidden);

            document.addEventListener('livePageItemDebug', function(event) {
                this.createItemDeleteButton(event.target, true);
            }.bind(this));

            document.addEventListener('livePageItemFilterMatchedDebug', this.hiddenDebug);
        }

        liveEndPage()
        {
            document.addEventListener('liveEndPage', function(e) {
                this.createDeleteButton('liveend', e.target, e.target);
            }.bind(this));
            document.addEventListener('liveEndPageFilterMatched', this.hidden);

            document.addEventListener('liveEndPageDebug', function(e) {
                this.createDeleteButton('liveend', e.target, e.target, true);
            }.bind(this));
            document.addEventListener('liveEndPageFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('liveend', e.target, e.target, true);
            }.bind(this));
        }

        rankingPage()
        {
            document.addEventListener('rankingPage', function(e) {
                this.createDeleteButton('ranking', e.target, e.target);
            }.bind(this));
            document.addEventListener('rankingPageFilterMatched', this.hidden);

            document.addEventListener('rankingPageDebug', function(e) {
                this.createDeleteButton('ranking', e.target, e.target, true);
            }.bind(this));
            document.addEventListener('rankingPageFilterMatchedDebug', function(e){
                this.hiddenDebug(e);
                this.createDeleteButton('ranking', e.target, e.target, true);
            }.bind(this));
        }

        timeTablePage()
        {
            document.addEventListener('timeTablePage', function(e) {
                const btnParent = e.target.querySelector('div[class="js_like_dialog"]');
                this.createDeleteButton('timetable', e.target, btnParent);
            }.bind(this));
            document.addEventListener('timeTablePageFilterMatched', this.hidden);

            document.addEventListener('timeTablePageDebug', function(e) {
                const btnParent = e.target.querySelector('div[class="js_like_dialog"]');
                this.createDeleteButton('timetable', e.target, btnParent, true);
            }.bind(this));
            document.addEventListener('timeTablePageFilterMatchedDebug', function(e){
                e.target.style.border = '2px solid red';
            });
        }
    }


    const model = new Model();
    const con = new Controller(model);
    const view = new View(con);
    con.router();
})();