AbemaTV Quick NG Comment

AbemaTVのコメントを一時的にNG。連投コメントをブロック

// ==UserScript==
// @name        AbemaTV Quick NG Comment
// @namespace   https://greasyfork.org/ja/scripts/22040
// @description AbemaTVのコメントを一時的にNG。連投コメントをブロック
// @include     https://abema.tv/now-on-air/*
// @version     3
// @grant       GM_addStyle
// ==/UserScript==

(function() {
  'use strict';

  function id(a) {
    return document.getElementById(a);
  }

  function log(s, t) {
    if (ls.debug) {
      if (t) console[t]('QuickNG', s);
      else console.log('QuickNG', s);
    }
  }

  //デスクトップ通知
  function notify(a, t) {
    var title = 'AbemaTV Quick NG Comment',
      message = a,
      notifi;
    log(['notify', a, t]);
    if (t === 'ng') message += '\nをNGワード登録しました。';
    else if (t === 'duplicate') message += '\nはNGワード登録済みです。';
    else if (t === 'consecutive') message = '連投コメントを' + ((a) ? 'ブロック' : '表示') + 'します。';
    if ('Notification' in window) {
      if (Notification.permission === 'granted') {
        notifi = new Notification(title, { body: message });
      } else if (Notification.permission !== 'denied') {
        Notification.requestPermission(function(permission) {
          if (permission === 'granted') {
            notifi = new Notification(title, { body: message });
          }
        });
      }
      if (notifi) setTimeout(notifi.close.bind(notifi), 3000);
    } else alert('AbemaTV Quick NG Comment\n\n' + message);
  }

  //コメントをクリックしたとき
  function clickComment(a) {
    a.addEventListener('click', function(e) {
      //Ctrl+クリックでNGワード追加
      if (e.ctrlKey && !e.shiftKey) {
        var el = e.target;
        if (a === defaultListComment) {
          if (/^styles__message/.test(el.className)) addNgComment(el.textContent);
        } else {
          if (/comment|movingComment/.test(el.className) && !el.classList.contains('quickng')) addNgComment(el.textContent, el);
        }
      }
      //Shift+クリックで連投コメントブロックのON・OFF
      else if (!e.ctrlKey && e.shiftKey) {
        ls.blockconsecutivecomment = (ls.blockconsecutivecomment) ? false : true;
        localStorage.setItem('QuickNG', JSON.stringify(ls));
        notify(ls.blockconsecutivecomment, 'consecutive');
      }
    });
  }

  //NGワードを登録
  function addNgComment(s, e) {
    if (ss.ngcomment.some(function(val) {
        return (val === s);
      })) {
      notify(s, 'duplicate');
    } else {
      if (e) e.classList.add('quickng');
      ss.ngcomment.unshift(s);
      sessionStorage.setItem('QuickNG', JSON.stringify(ss));
      notify(s, 'ng');
    }
  }

  //新しいコメントを読み込んだときにコメントの内容を調べる
  function checkComment(r) {
    var word = '',
      checkPreWord = function(val, ind, arr) {
        if (val.word === word) {
          val.count++;
          if (ind > 0) {
            arr.splice(ind, 1);
            arr.unshift(val);
          }
          if (val.count === 5) {
            arr.splice(ind, 1);
            ss.concecutivecomment.unshift(word);
            sessionStorage.setItem('QuickNG', JSON.stringify(ss));
          }
          log(['連投', val.count, word], 'info');
          return true;
        }
        return false;
      },
      checkConcecutive = function(val) {
        return (val === word);
      },
      addNgClass = function(elm) {
        if (!elm.classList.contains('quickng')) elm.classList.add('quickng');
      };
    //変更があった要素の回数分ループ
    for (var i1 = 0, l1 = r.length, lNg = ss.ngcomment.length, type; i1 < l1; i1++) {
      if (!r[i1].addedNodes.length) continue;
      switch (r[i1].addedNodes[0].parentNode) {
        case id('floatComments'):
          type = 'list';
          break;
        case id('scrollComments'):
        case id('moveContainer'):
          type = 'scroll';
          break;
        default:
          return;
      }
      //新しいコメントの回数分ループ
      for (var i2 = 0, l2 = r[i1].addedNodes.length, node; i2 < l2; i2++) {
        node = r[i1].addedNodes[i2];
        word = node.textContent;
        log(['check', r.length, r[i1].addedNodes.length, word, type]);
        //連投コメントなら表示しない
        if (ls.blockconsecutivecomment) {
          if (ss.concecutivecomment.some(checkConcecutive)) {
            addNgClass(node);
            log(['連投NGコメント', word], 'info');
            continue;
          }
          if (preWord[type].some(checkPreWord)) {
            addNgClass(node);
            continue;
          }
        }
        if (preWord[type].length >= 50) preWord[type].pop();
        preWord[type].unshift({ word: word, count: 1 });
        //登録したNGワードの回数分ループ
        for (var i3 = 0; i3 < lNg; i3++) {
          //新しいコメントがNGワードを含んでいるなら表示しない
          if (word.indexOf(ss.ngcomment[i3]) !== -1) {
            addNgClass(node);
            log(['NGワード', word], 'info');
            break;
          }
        }
      }
    }
  }

  //最初の処理
  function start() {
    log('start');
    preWord.list = [];
    preWord.scroll = [];
    if (!ls.blockconsecutivecomment) ls.blockconsecutivecomment = true;
    if (!ss.ngcomment) ss.ngcomment = [];
    if (!ss.concecutivecomment) ss.concecutivecomment = [];
    GM_addStyle(style);
    //フッターが表示されるまで待つ
    interval = setInterval(function() {
      var footer = document.querySelector('p[class^="styles__program-highlight"] > span > span');
      if (footer) {
        clearInterval(interval);
        observerP.observe(footer, moConfig2);
      }
    }, 200);
  }

  //フッターが表示されたら実行
  function pre(r) {
    log('pre');
    //左下に番組名が表示されたとき
    if (r[0].addedNodes[0].length && r[0].addedNodes[0].textContent) {
      observerP.disconnect();
      init();
    }
  }

  //視聴者数などのデータが読み込まれたら実行
  function init() {
    log('init');
    defaultListComment = document.querySelector('div[class^="styles__comment-list-wrapper"] > div');
    //拡張機能が動作してるとき
    if (id('moveContainer')) scrollComment = id('moveContainer');
    //スクリプトが動作しているとき
    else if (id('floatComments')) {
      listComment = id('floatComments');
      scrollComment = id('scrollComments');
      observerL.observe(listComment, moConfig);
      clickComment(listComment);
    }
    clickComment(defaultListComment);
    log(['init', listComment, scrollComment]);
    if (scrollComment) {
      clickComment(scrollComment);
      observerS.observe(scrollComment, moConfig);
    }
  }

  var observerP = new MutationObserver(pre),
    observerL = new MutationObserver(checkComment),
    observerS = new MutationObserver(checkComment),
    moConfig = { childList: true },
    moConfig2 = { childList: true, subtree: true },
    ls = JSON.parse(localStorage.getItem('QuickNG')) || {},
    ss = JSON.parse(sessionStorage.getItem('QuickNG')) || {},
    style = '.quickng {display: none !important;}' +
    '#moveContainer {z-index: 11 !important; pointer-events: auto !important;}' +
    '#moveContainer > .movingComment {z-index: 13 !important;}' +
    '#scrollComments > .scroll:hover, #moveContainer > .movingComment:hover {transition-duration: 9999s !important; left: 0 !important; background-color: rgba(200, 0, 0, 0.4); }' +
    '#floatComments > .comment:hover { background-color: rgba(200, 0, 0, 0.4); }' +
    'div[class^="styles__comment-list-wrapper"] p[class^="styles__message"]:hover {background-color: rgba(200, 0, 0, 0.2); }',
    preWord = {},
    defaultListComment, listComment, scrollComment, interval;
  start();
})();