// ==UserScript== // @name NicoLiveCleaner // @namespace https://greasyfork.org/ja/users/292779-kinako // @version 1.09 // @description ニコ生各種ページにおいて特定生主を非表示にする。NGワード(正規表現)でも可能。 // @author kinako // @include http*://live.nicovideo.jp/* // @include http*://live2.nicovideo.jp/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @run-at document-body // @compatible chrome // @downloadURL none // ==/UserScript== (function() { 'use strict'; /*       汚 ( ヾハハヘ       物 (  |W/ヘヘ       は (  |‖//ヘヘ       消 (  ハ‖イ///彡       毒 ( /丶/ ̄\ミ       だ ( /ヘ / ̄‖ミ       | ( レ=  ̄ =、 ‖ミミ       | ( Y三八三>=ヘミミ       !! (〈 L_ソ 〉ノミミ、       ⌒⌒ Y 戸弌 i|\ミミ        __i kェェノ / //       /  /`ー―イ //       ヒ_(王)二二二二(王)ノ       L_|_‖  /  | ‖ ̄       彡彡\\ |  |o‖       彡彡彡L||  |o‖       彡彡//ー仝ー-ヽ/ ̄       |イ二‾\____/ /       |i二 |   | |.       丶て_ム___| |.   */ class Controller { constructor(model) { this._model = model; } main(targets) { targets = (Object.prototype.toString.call(targets) === '[object Array]')? targets: [targets]; this._model.setBodyMObserver(targets); } receive(flag, data = null) { switch(flag) { case 'record': this._model.setData('ng_numbers', data); this._model.iniNGData(); break; case 'delete': this._model.deleteData('ng_numbers', data); break; case 'ngreg': this._model.setRegData('ng_keywords', data); location.reload(); break; case 'InitializeData': this._model.iniNGData(); break; } } } class Model { constructor() { this.ng_comment_keywords = null; this.targets = null; this._ng = {pattern:null, c_pattern:null, numbers:null, keywords:null, c_keywords:null}; this.iniNGData(); } set ng(val){this._ng.pattern = val}; get ng(){return this._ng.pattern}; set ng_numbers(val){this._ng.numbers = val}; get ng_numbers(){return this._ng.numbers}; set ng_keywords(val){this._ng.keywords = val}; get ng_keywords(){return this._ng.keywords}; //ページロード setBodyMObserver(targets, options={childList: true}) { this.targets = targets; const mo = new MutationObserver(this.setMObserver.bind(this)); mo.observe(document.body, options); window.addEventListener("DOMContentLoaded", function(e){ mo.disconnect(); }); } // 動的部分監視 setMObserver(mr, mo) { for(let i = 0; i < this.targets.length; i++) { if (this.targets[i].Loaded()) { this.dispatchEvent(this.targets[i]); if (this.targets[i].mo_options) { let mo = new MutationObserver( (mr, _mo)=>{ _mo.disconnect(); this.dispatchEvent(this.targets[i]); _mo.observe(this.targets[i].element, this.targets[i].mo_options); }); mo.observe(this.targets[i].element, this.targets[i].mo_options); } } } } dispatchEvent(target) { let el = (target.element.children)? target.element.children: target.element; let event = new Event(target.eventType); for (let x = el.length; x > 0; x--){ el[x-1].addEventListener(target.eventType, target.callback); el[x-1].dispatchEvent(event); } } // NGデータ設定 iniNGData() { // NGコミニュティ番号 let nums, ls = this.getLocalStorage('ng_numbers'); if(ls) { nums = ls.map(function(str) { if (/^(co|ch)[0-9]+$/.test(str)) { // コミュニティ&チャンネル return str + "\\."; } else if (/^[0-9]+$/.test(str)) { // ユーザー番号 return '/' + str + "\\."; } }); this.ng_numbers = ls; nums = (nums)? nums.join('|'): ''; } else { nums = ''; this.ng_numbers = []; } // NGキーワード let ls2 = this.getLocalStorage('ng_keywords'); if (ls2) { ls2 = ls2.filter(function(str) { return (str !== "" && str !== undefined) ; }); this.ng_keywords = ls2; ls2 = (ls2)? ls2.join('|'): ''; } else { ls2 = ''; this.ng_keywords = []; } // 結合 let sep = (nums != '' && ls2 != '')? '|': ''; this.ng = nums + sep + ls2; } 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]); } } 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)) { return this.checkArray(JSON.parse(data)); } else { return data; } } setLocalStorage(key,data){ GM_setValue(key, JSON.stringify(data)); } } class View { constructor(controller) { this.controller = controller; this.targets = []; this.comment_ng = null; this.video_ng = null; this.css_prefix = 'nlc'; this._ng = {pattern:null, c_pattern:null, numbers:null, keywords:null, c_keywords:null}; this.setStyle(); } get ng(){return this._ng.pattern}; get ng_numbers(){return this._ng.numbers}; get ng_keywords(){return this._ng.keywords}; // スタイルシート設定 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; } /* ライブページ 状態コンテナ */ span[id^="${this.css_prefix}-btncontainer"], span[id^="${this.css_prefix}-state"] { margin-left: 1em; } /* ライブページ NG中状態 */ span[id^="${this.css_prefix}-state"], span[id^="${this.css_prefix}-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; } /* ライブページ ユーザー状態コンテナ */ span[id^="${this.css_prefix}-usercontainer"], span[id^="${this.css_prefix}-userstate"] { margin-left:0.3em; } /* ライブページ ユーザーNG中状態 */ span[id^="${this.css_prefix}-userstate"] { padding: 0 0.2em; letter-spacing: 0; font-size: 11px; } /* ライブページザッピング 削除ボタン*/ button[id^="${this.css_prefix}-user"] { 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}-top"] { opacity: 0.5; } /* 番組一覧 削除ボタン */ button[id^="${this.css_prefix}-proglist"] { padding: 0 3px 0 2px; } /* ボタンコンテナ */ 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}-search-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 { margin: -1.5em 0 0 0; width : 100%; text-align: right; position: relative; } div.${this.css_prefix}-top-btn-container { margin: -2px 0 0 0px; position: absolute; } 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}-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: auto; text-align: left; padding: 2em; transform: translate(-50%, -50%); background: #fff; overflow-x: hidden; /* overflow-y: scroll;*/ border-radius: 10px; } #${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 3px 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: 10em; padding : 0.5em 0 0.5em 0.3em; } /* モーダル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}-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; } `); css.media = 'screen'; css.type = 'text/css'; if (css.styleSheet) { css.styleSheet.cssText = rule.nodeValue; } else { css.appendChild(rule); }; document.getElementsByTagName('head')[0].appendChild(css); } // ページ分岐 main() { this.insertMenu(); this.liveTopPage(); this.liveFocusPage(); this.liveListPage(); this.liveSearchPage(); this.liveProgramPage(); this.liveProgramPage2(); this.liveRankingPage(); this.liveTimetablePage(); this.controller.main(this.targets); } // 削除 clean(target, ng = this.ng) { if (this.checkNG(target, ng)) { target.remove(); } } // HTMLCollection削除(監視無し) cleanAll(target, ng = this.ng) { const t = new Target(); t.element = (toString.call(target) === '[object Function]')? target: ()=> target; t.eventType = 'cleanAllLoaded'; t.callback = (event)=>{this.clean(event.target, ng);}; this.targets.push(t); } // HTMLCollection用ループ削除 loopClean(targets, ng = this.ng) { for (let i = targets.length; i > 0; i--) { this.clean(targets[i-1], ng); } } // 削除だと表示が乱れる場合は非表示 hide(target, ng = this.ng) { if (this.checkNG(target, ng)) { const style = target.getAttribute('style'); const r = new RegExp('display:none'); if (!r.test(style)) { target.setAttribute('style', 'display:none;' + style); } } } // HTMLCollection用 非表示 loopHide(targets, ng = this.ng) { for (let i = targets.length; i > 0; i--) { this.hide(targets[i-1], ng); } } checkNG(target, ng) { if (ng != null && ng.length > 0 && target) { const r = new RegExp(ng); return (r.test(target.outerHTML))? true: false; } } parseCoNumber(t, id = null) { if(t != null) { let m = /[\/>]{1}((co|ch)[0-9]+)/mg.exec(t.outerHTML); if (!m) { m = /\/user\/([0-9]+)/mg.exec(t.outerHTML); if (m) { m[2] = 'user'; } } if (m && id) { const reg = new RegExp('^'+this.css_prefix+'-'); for (let i = 0; i < t.children.length; i++) { if (reg.test(t.children[i].getAttribute('class')) || reg.test(t.children[i].id)) { t.children[i].remove(); } } } return (m)? {number:m[1], prefix:m[2]}: null; } return null; } convertID(id, number) { return this.css_prefix +'-' + id + '-' + number; } displayDeleteButtonLivePage(t, id, container_name, state_name) { if (t == null) return; let pcn = this.parseCoNumber(t, id); if(pcn) { let _id = this.convertID(id, pcn.number); let pb = document.createElement('span'); pb.id = this.convertID(container_name, pcn.number); let text = document.createElement('span'); text.id = this.convertID(state_name, pcn.number); text.textContent = 'NG中'; // 追加済み if (this.ng.length > 0 && this.ng_numbers.includes(pcn.number)) { pb.appendChild(text) // 追加する } else { let b = this.iniButton(_id, 'x'); b.addEventListener('click', (e)=>{ pb.removeChild(b); pb.appendChild(text); this.controller.receive('record', pcn.number); }); pb.appendChild(b); } t.appendChild(pb); } } displayDeleteButtonLiveRoom(t, id) { this.displayDeleteButtonLivePage(t, id, 'btncontainer', 'state'); } displayDeleteButtonLiveUser(t, id) { this.displayDeleteButtonLivePage(t, id, 'usercontainer', 'userstate'); } displayDeleteButtonSingle(t, id) { if (t == null) return; let pcn = this.parseCoNumber(t, id); if(pcn) { let _id = this.convertID(id, pcn.number); let pb = document.createElement('div'); pb.setAttribute('class', this.css_prefix +'-'+id+ '-btn-container'); let b = this.iniButton(_id, 'x'); pb.appendChild(b); t.appendChild(pb); b.addEventListener('click', (e)=>{ e.stopPropagation(); t.remove(); this.controller.receive('record', pcn.number); }); } } displayDeleteButton(targets, id) { for (let i = 0; i < targets.length; i++) { this.displayDeleteButtonSingle( targets[i], id); } } 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); }); }.bind(this)); } displayModal(id) { let overlay, modal, close; // オーバーレイ overlay= document.createElement('div'); overlay.setAttribute('id', this.css_prefix + '-overlay'); // モーダルボックス modal= document.createElement('div'); modal.setAttribute('id', this.css_prefix + '-modal-contents'); // クローズリンク let cp = document.createElement('div'); cp.id = this.css_prefix + '-close'; close = document.createElement('a'); close.setAttribute('id', 'close'); close.setAttribute('href', '#'); close.innerHTML = 'CLOSE'; // 見出し let ng_num_title = document.createElement('h2'); ng_num_title.textContent = 'NGコミュニティ&チャンネル&ユーザー'; let ng_reg_title = document.createElement('h2'); ng_reg_title.textContent = 'NGキーワード(正規表現)'; modal.appendChild(ng_num_title); let ng_container = document.createElement('div'); this.ng_numbers.reverse(); ng_container.appendChild(this.iniNgNumber(this.ng_numbers, 50, 0)); ng_container.appendChild(this.iniNavi(this.ng_numbers, 50, 0)); modal.appendChild(ng_container); modal.appendChild(ng_reg_title); modal.appendChild(this.iniNgRegEx(this.ng_keywords)); cp.appendChild(close); 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); }); } iniNavi(items, block, position) { let page = Math.ceil(items.length / block); let navi = document.createElement('div'); navi.id = this.css_prefix + '-navi' let navi_on = this.css_prefix + '-navi-on'; if (page > position){ for (let i = 0; i < page; i++) { let span = document.createElement('span'); let a = document.createElement('a'); if (i == position) { a.id = navi_on; } a.addEventListener('click', function(e) { console.log(navi); 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; } } let parent_id = this.css_prefix + '-ngnumbers'; let base = document.getElementById(parent_id).parentNode; document.getElementById(parent_id).remove(); this.controller.receive('InitializeData'); this.ng_numbers.reverse(); base.insertBefore(this.iniNgNumber(this.ng_numbers, block, i), base.firstChild); }.bind(this)); span.textContent = i + 1; a.appendChild(span); navi.appendChild(a); } } return navi; } iniNgNumber(items, block, position = 0) { let outer = document.createElement('div'); outer.id = this.css_prefix + '-ngnumbers'; const start = block * position; const end = start + block; const block_items = items.slice(start, end); if (block_items) { for (let i = 0; i < block_items.length; i++) { // URL生成 let f = new RegExp('(co|ch)[0-9]+').exec(block_items[i]); let img_pref, a_url; let img_url = 'https://secure-dcdn.cdn.nimg.jp/comch/'; if(f != null) { if (f[1] == 'co') { img_pref = 'community'; a_url =['com', img_pref]; } else if (f[1] == 'ch') { img_pref = 'channel'; a_url = ['ch', img_pref]; } img_url += img_pref + '-icon/64x64/'+block_items[i]+'.jpg'; } else if (/^[0-9]+/.test(block_items[i])){ img_url = 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/s/' img_url += block_items[i].substr(0, block_items[i].length-4)+'/'+block_items[i]+'.jpg'; a_url = ['www', 'user']; } let img = document.createElement('img'); img.onerror = function() { img.src = 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank_s.jpg'; //代用 } img.src = img_url; img.alt = block_items[i]; img.title = img.alt; let a = document.createElement('a'); a.href = 'https://'+ a_url[0] +'.nicovideo.jp/'+ a_url[1] +'/'+ block_items[i]; a.target = '_blank'; let pb = document.createElement('span'); pb.setAttribute('class', this.css_prefix + '-modal-img-container'); let b = this.iniButton(this.convertID('ngimg', block_items[i]), 'x'); a.appendChild(img); pb.appendChild(a); pb.appendChild(b); outer.appendChild(pb); b.addEventListener('click', (e)=>{ e.stopPropagation(); pb.remove(); this.controller.receive('delete', block_items[i]); }); } } return outer; } 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; } 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; } /* * ニコ生トップページ */ liveTopPage() { if (!location.href.match(/http(s)*:\/\/live\.nicovideo\.jp(\/\?header|\/)$/)) return; const programs = new Target(); programs.element = ()=> document.querySelector('div[class^="___contents-area___"]'); programs.eventType = 'focus-program-listMoLoaded'; programs.mo_options = {childList: true, subtree:true}; programs.callback = (event)=>{ this.loopHide(event.target.querySelectorAll('li[class^="___item___"]')); this.displayDeleteButton(event.target.querySelectorAll( 'div[class^="___program-card___"]'), 'top'); }; this.targets.push(programs); } /* * ニコ生注目番組一覧ページ */ liveFocusPage() { if (!location.href.match(/http(s)*:\/\/live2\.nicovideo\.jp\/focus$/)) return; const programs = new Target(); programs.element = ()=> document.querySelector('div[class^="___contents-area___"]'); programs.eventType = 'contentslistMoLoaded'; programs.mo_options = {childList: true, subtree:true}; programs.callback = (event)=>{this.loopHide(event.target.querySelectorAll('li[class^="___item___"]'));}; this.targets.push(programs); const programs2 = new Target(); programs2.element = ()=> document.querySelector('section[class^="___feature-program-section___"]'); programs2.eventType = 'featurelistMoLoaded'; programs2.mo_options = {childList: true, subtree:true}; programs2.callback = (event)=>{this.loopHide(event.target.querySelectorAll('li[class^="___item___"]'));}; this.targets.push(programs2); } /* * ニコ生番組一覧ページ */ liveListPage() { if (!location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/recent\?.*/)) return; // 番組一覧監視 const programs = new Target(); programs.element = ()=> document.getElementById('onair_stream_list'); programs.eventType = 'user-program-listMoLoaded'; programs.mo_options = {childList: true}; programs.callback = (event)=>{ this.loopClean(event.target.getElementsByClassName('user-program-item')); this.displayDeleteButton(event.target.getElementsByClassName('user-program-item'), 'proglist'); }; this.targets.push(programs); // ランキング&ピックアップ監視 const ranking = new Target(); ranking.element = ()=> document.getElementById('contents-block'); ranking.eventType = 'rankinglistMoLoaded'; ranking.mo_options = {childList: true, subtree:true}; ranking.callback = (event)=>{ this.loopClean(event.target.getElementsByTagName('li')); this.displayDeleteButton(event.target.getElementsByTagName('li'), 'rankpic'); }; this.targets.push(ranking); // チャンネル部分削除(ページ下部) const channel = new Target(); channel.element = ()=> document.getElementById('channel-block'); channel.eventType = 'channelMoLoaded'; channel.callback = (event)=>{ this.loopClean(event.target.getElementsByTagName('li')); this.displayDeleteButton(event.target.getElementsByTagName('li'), 'channel'); }; this.targets.push(channel); // PS4配信ピックアップ欄 const ps4 = new Target(); ps4.element = ()=> document.getElementById('eden_pickup_program_area'); ps4.eventType = 'ps4-program-listMoLoaded'; ps4.callback = (event)=>{ this.loopClean(event.target.getElementsByClassName('ps4-program-item')); this.displayDeleteButton(event.target.getElementsByClassName('ps4-program-item'), 'ps4list'); }; this.targets.push(ps4); } /* * ニコ生検索ページ */ liveSearchPage() { if (!location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/search\?.*/)) return; // 検索結果 const t = new Target(); t.element = ()=> document.getElementsByClassName('result-item'); t.eventType = 'cleanAllLoaded'; t.callback = (event)=>{ this.clean(event.target, this.ng); this.displayDeleteButtonSingle(event.target, 'search'); }; this.targets.push(t); // 関連チャンネル const channel = new Target(); channel.element = ()=> document.getElementsByClassName('recommend-channel-list')[0]; channel.eventType = 'recommend-channelMoLoaded'; channel.mo_options = {childList: true, subtree:true, attributes: true}; channel.callback = (event)=>{ this.hide(event.target); this.displayDeleteButtonSingle(event.target, 'search2'); }; this.targets.push(channel); // 関連コミュニティ const community = new Target(); community.element = ()=> document.getElementsByClassName('recommend-community-list')[0]; community.eventType = 'recommend-communityMoLoaded'; community.mo_options = {childList: true, subtree:true, attributes: true}; community.callback = (event)=>{ this.hide(event.target); this.displayDeleteButtonSingle(event.target, 'search3'); }; this.targets.push(community); } /* * ニコ生旧ライブページ&放送終了後ページ */ liveProgramPage() { if (!location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/(watch|gate)\/lv.*/)) return; // ザッピング番組監視 const zapping = new Target(); zapping.element = ()=> document.getElementById('zapping_area'); zapping.eventType = 'zappingMoLoaded'; zapping.mo_options = {childList: true, subtree:true}; zapping.callback = (event)=>{this.loopHide(event.target.getElementsByClassName('zapping_stream'));}; this.targets.push(zapping); // こんな番組も見ています const after = new Target(); after.element = ()=> document.getElementsByClassName('gyokuon_container')[0]; after.eventType = 'gyokuonMoLoaded'; after.mo_options = {childList: true}; after.callback = (event)=>{ this.loopHide(event.target.getElementsByClassName('gyokuon_list_item')); }; this.targets.push(after); } /* * ニコ生html5ライブページ */ liveProgramPage2() { if (!location.href.match(/http(s)*:\/\/live2\.nicovideo\.jp\/watch\/lv.*/)) return; // コミュニティ付近にNGボタン表示 const info = new Target(); info.element = ()=> document.querySelector('div[class^="___social-group-information___"] div[class^="___header-area___"]'); info.eventType = 'infoMoLoaded'; info.callback = (event)=>{ this.displayDeleteButtonLiveRoom(event.target.parentNode, 'live'); } this.targets.push(info); // ユーザー付近にNGボタン表示 const user = new Target(); user.element = ()=> document.querySelector('div[class^="___user-summary___"]'); user.eventType = 'userLoaded'; user.callback = (event)=>{ this.displayDeleteButtonLiveUser(event.target.querySelector('div[class^="___user-name-area___"]'), 'user'); }; this.targets.push(user); // ザッピング番組監視 const zapping = new Target(); zapping.element = ()=> document.querySelector('div[class^="___zapping-list-group___"]'); zapping.eventType = 'zappingMoLoaded'; zapping.mo_options = {childList: true, subtree:true}; zapping.callback = (event)=>{ this.loopClean( event.target.querySelectorAll('div[class^="___zapping-item___"]') ); this.displayDeleteButton( event.target.querySelectorAll('div[class^="___zapping-item___"]'), 'zapp');}; this.targets.push(zapping); } /* * ニコ生ランキングページ */ liveRankingPage() { if (!location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/ranking.*/)) return; const ranking = new Target(); ranking.element = ()=> document.getElementById('wrapper'); ranking.eventType = 'rankingMoLoaded'; ranking.mo_options = {childList: true}; ranking.callback = (event)=>{ const t = event.target.getElementsByClassName('rk-ProgramCard'); this.loopClean(t); this.displayDeleteButton(t, 'ranking'); }; this.targets.push(ranking); } /* * ニコ生番組表ページ */ liveTimetablePage() { if (!location.href.match(/http(s)*:\/\/live\.nicovideo\.jp\/timetable.*/)) return; const list = new Target(); list.element = ()=> document.getElementsByTagName('tbody')[0]; list.eventType = 'TimetableMoLoaded'; list.mo_options = {childList: true, subtree:true}; list.callback = (event)=>{this.clean(event.target);}; this.targets.push(list); } } class Target { constructor() { this._element = null; // 対象部分 this.eventType = null; // イベント名 this.callback = null; // イベントコールバック this.mo_options = null; // MutationObserverオプション {childList: true, subtree:true, attributes: true}; } set element(val) { this._element = val; } get element() { return (typeof this._element == 'function')? this._element() : this._element; } Loaded(name = 'loaded', val='') { if (Object.prototype.toString.call(this.element) === "[object HTMLCollection]") { if (this.element.length > 0) { let last = this.element[this.element.length - 1]; return this.checkload(last); } else { return false; } } else { return this.checkload(this.element); } } checkload(element, name = 'loaded', val='') { if(element && element.hasAttribute(name) == false) { element.setAttribute(name, val); return true; } else { return false; } } } const model = new Model(); const con = new Controller(model); const view = new View(con); view._ng = model._ng; view.main(); })();