2ch autoHide

Автоскрытие кремлеботов и срыночных дегенератов.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @description  Автоскрытие кремлеботов и срыночных дегенератов.
// @exclude      https://2ch.hk/po/catalog.html
// @exclude      https://2ch.hk/news/catalog.html
// @include      https://2ch.hk/po/*
// @include      https://2ch.hk/news/*
// @icon         https://2ch.hk/favicon.ico
// @name         2ch autoHide
// @name:ru      2ch автохайд по списку спеллов
// @namespace    poRussia
// @run-at       document-end
// @version      190306
// ==/UserScript==


// ВНИМАНИЕ! СКРИПТ НЕСОВМЕСТИМ С ДРУГИМИ ВАРИАНТАМИ АВТОСКРЫТИЯ!
//    (такими как через куклоскрипт или через настройки 2ch)

const POST = 1;
const BOTH = 2;
const HEAD = 3;
const RAGE = 1;
const HIDE = 3;

// ============[НАЧАЛО НАСТРОЕК]============
// клики по дизлайкам ставятся в случайном интервале между этими двумя значениями
const minClickDelay = 5500;    // минимальная задержка между кликами, мс
const maxClickDelay = 7000;    // максимальная задержка между кликами, мс

const showInTitle = 1;         // Показывать счётчик дизлайков в заголовке вкладки браузера? 1 = да, 0 = нет
const ignoreQuotes = 1;        // Игнорировать совпадения выражений в >цитируемом тексте? 1 = да, 0 = нет
/*  ignoreQuotes = 0 проверяет чистый текст, поэтому будет работать быстрее и проще, но не позволит
      игнорировать совпадения регулярных выражений в цитируемом тексте и будет скрывать посты, в которых
      наивный анон цитирует животных и ботов, чтобы им ответить.
    ignoreQuotes = 1 проверяет текст с тегами, поэтому не будет ставить RAGE и скрывать посты из-за цитат,
      также конструкции типа св[b][/b]инья будут корректно определены, но этот метод работает чуть медленнее
      (разница составляет миллисекунды, но на некрокомпах может быть заметно).
    Рекомендуется использовать ignoreQuotes = 1.
*/
const popupChars = 500;        // Количество символов во всплывающей подсказке над скрытым постом.
                               // Наведи на слово 'hide', чтобы увидеть подсказку.

const highlight = 1;           // Подсвечивать посты зелёным/красным? 1 = да, 0 = нет

/*Вложенный массив с регулярными выражениями для скрытия/лайков.
  Первый элемент - место поиска выражения:
    POST - искать только в тексте поста;
    BOTH - искать и в тексте, и в заголовке;
    HEAD - искать только в заголовке треда(для скрытия номерных тредов например).
  Второй элемент - действие при обнаружении:
    RAGE - ставить дизлайк;
    BOTH - ставить дизлайк и скрывать;
    HIDE - просто скрытие.
  Третий элемент - регулярное выражение.
  Четвёртый элемент - краткое описание, которое появится в заголовке скрытого поста/треда.

  Узнать больше про регулярные выражения можно тут:
    https://www.google.com/search?q=regex+javascript
  Потестировать работоспособность и создать спеллы можно тут:
    https://regex101.com/
    (не забыть слева выбрать "ECMAScript (JavaScript)", справа от строки во флагах выбрать /imu)

  [где искать, что делать, /регулярное выражение/imu,                                                                    "описание"]*/
const regexArray = [
  [BOTH, BOTH, /(^|\s)[сc]?с[аaоo]в[оo]?[кk]?с/imu,                                                                      "савок"],
   [BOTH, BOTH, /[кk][оo][мm][мm][иu]/imu,                                                                               "комми"],
  [BOTH, BOTH, /перефорс/imu,                                                                                            "перефос"],
  [BOTH, BOTH, /(^|\s)[еe][лlь]ц[иu]н/imu,                                                                               "Ельцин"],
  [BOTH, BOTH, /[рp]ы[нh][оoь]?[кk]/imu,                                                                                 "рынок"],
  [BOTH, BOTH, /[Яя]/imu,                                                                                                 "я"],
[BOTH, BOTH, /сша/imu,                                                                                                 "сша"],
  [BOTH, BOTH, /чистые/imu,                                                                                              "чистые"],
  [BOTH, BOTH, /(([уy]|[кk][оo][пn])[рp]у?[аaоo]|\S[аaиоoуy][рp][уyоo]|[уy][рp][кk][аa])(и[нh]|нд)/imu,                  "украина"],
  [BOTH, BOTH, /[пn][уy][кk][вbкkнhрpсcшю]/imu,                                                                          "пук"],
   [HEAD, HIDE, /Европа/imu,                                                                                              "Европа"],
  [HEAD, BOTH, /[вb].{0,20}[аa].{0,20}[тt].{0,20}[нh].*[иu].*[кk]/imu,                                                    "ватник"]
];
// ============[КОНЕЦ  НАСТРОЕК]============

var clicksArray = [];
var clicksTaskActive = 0;
var timeoutID;

const displayBlock = document.getElementById("fullscreen-container");
const title = document.title;
const pager = document.getElementsByClassName("pager")[0];
const inputListener = () => { delayClicksAfterUserInput(event.target, event.button); };

if (!document.URL.includes("res")) {
  hideOpPosts();
  hidePosts(0);

  if (pager.style.display == "") { return; }

  var callback = function(mutationsList, observer) {
    for(let m of mutationsList) {
      if (!m.addedNodes.length || m.addedNodes[0].tagName != "DIV" || m.addedNodes[0].className != "thread") { continue; }

      hideOpPosts(m.addedNodes[0].firstChild.firstChild);
      for (let i = 1; i < m.addedNodes[0].childNodes.length; i++) {
        hidePosts(0, m.addedNodes[0].childNodes[i].firstChild.firstChild);
      }
    }
  };

  var observer = new MutationObserver(callback);
  observer.observe(document.getElementById('posts-form'), { attributes: false, childList: true, subtree: false });
} else {
  var hideTotalSpan = document.createElement("span");
  hideTotalSpan.className = "post__anon";
  var opPostEnd = document.getElementsByClassName("post post_type_oppost")[0].childNodes[1];
  hideTotalSpan = opPostEnd.insertBefore(hideTotalSpan, opPostEnd.childNodes[opPostEnd.childNodes.length - 2]);

  var hiddenCount = 0;
  hidePosts(1);

  var callback = function(mutationsList, observer) {
    for(let m of mutationsList) {
      if (!m.addedNodes.length || m.addedNodes[0].tagName != "DIV" || m.addedNodes[0].className != "" || m.addedNodes[0].firstChild.className != "thread__post") { continue; }
      hidePosts(1, m.addedNodes[0].firstChild.firstChild);
    }
  };

  var observer = new MutationObserver(callback);
  observer.observe(document.getElementsByClassName('thread')[0], { attributes: false, childList: true, subtree: false });
}

function hideOpPosts(node) {
  var opPost,
      opPostTitle,
      opPostMsg,
      found,
      opPostsCollection = [];

  if (node) { opPostsCollection.push(node); }
  else { opPostsCollection = document.getElementsByClassName("post post_type_oppost"); }

  for (let i = 0; i < opPostsCollection.length; i++) {
    opPost = opPostsCollection[i];
    if (opPost.parentNode.parentNode.style.display == "none") { continue; }

    let opPostTitleText = "";
    let msgText = "";
    if ((opPostTitle = opPost.getElementsByClassName("post__title")).length) { opPostTitleText = opPostTitle[0].textContent.trim(); }
    if ((opPostMsg = opPost.getElementsByClassName("post__message post__message_op")).length) { msgText = opPostMsg[0].innerText.trim(); }
    else if (!opPostTitleText) { continue; }

    found = -1;
    for (let j = 0; j < regexArray.length; j++) {
      if (opPostTitleText && regexArray[j][0] > 1 && regexArray[j][2].test(opPostTitleText)) {
        found = j;
        break;
      } else if (msgText && regexArray[j][0] < 3 && regexArray[j][2].test(msgText)) {
        found = j;
        break;
      }
    }
    if (found > -1) {
      if (regexArray[found][1] < 3) { requestDislike(opPost); }
      if (regexArray[found][1] > 1) {
        let hideDiv = document.createElement("div");
        hideDiv.className = "thread thread_hidden";
        if (msgText.length > 500) { hideDiv.title = msgText.substring(0, 500) + "..."; }
        else { hideDiv.title = msgText; }

        let divPostDetailsSpans = opPost.getElementsByClassName("post__detailpart");
        hideDiv.innerHTML = "Скрытый тред (" + opPostTitleText + ") • hide: " + regexArray[found][3] + " " + divPostDetailsSpans[divPostDetailsSpans.length - 1].innerHTML;
        opPost.parentNode.parentNode.parentNode.insertBefore(hideDiv, opPost.parentNode.parentNode);
        opPost.parentNode.parentNode.style.display = "none";
      }
    }
  }
}

function hidePosts(inThread, node) {
  var post,
      postTitle,
      postMsg,
      found,
      postsCollection = [];

  if (node) { postsCollection.push(node); }
  else { postsCollection = document.getElementsByClassName("post post_type_reply"); }

  if (postsCollection) {
    for (let i = 0; i < postsCollection.length; i++) {
      post = postsCollection[i];
      if (post.className == "post post_type_reply post_type_hidden") { continue; }

      if (highlight && !node) { highlightPosts(post); }

      found = -1;
      let postTitleText = "";
      if ((postTitle = post.getElementsByClassName("post__title")).length && (postTitleText = postTitle[0].textContent.trim())) {
        for (let j = 0; j < regexArray.length; j++) {
          if (regexArray[j][0] > 1 && regexArray[j][2].test(postTitleText)) {
            found = j;
            break;
          }
        }
      }

      let msgText = "";
      if (found == -1) {
        if ((postMsg = post.getElementsByClassName("post__message")).length && (msgText = postMsg[0].innerText.trim())) {
          for (let j = 0; j < regexArray.length; j++) {
            if (regexArray[j][0] < 3 && regexArray[j][2].test(msgText)) {
              found = j;
              break;
            }
          }
          if (found > -1 && ignoreQuotes) { //double check posts
            msgText = postMsg[0].innerHTML;
            msgText = msgText.replace(/<a href=.*?<\/a>|<\/?strong>|<\/?em>|<\/?su[bp]>|<span class="[suo](poiler)?">/g, "");
            msgText = msgText.replace(/<br>/g, " ");

            let splitStart = -1;
            while ((splitStart = msgText.indexOf("<span class=\"unkfunc\">")) > -1 ) {
              msgText = msgText.substring(0, splitStart) + msgText.substring(msgText.indexOf("</span>", splitStart + 22) + 7);
            }
            msgText = msgText.replace(/<\/span>/g, "");
            found = -1;
            for (let j = 0; j < regexArray.length; j++) {
              if (regexArray[j][0] < 3 && regexArray[j][2].test(msgText)) {
                found = j;
                break;
              }
            }
          }
        }
      }

      if (found > -1) {
        if (regexArray[found][1] < 3) { requestDislike(post); }
        if (regexArray[found][1] > 1) {
          let hideSpan = document.createElement("span");
          hideSpan.className = "post__anon";
          if (ignoreQuotes && postMsg.length) { msgText = postMsg[0].innerText.trim(); }
          if (msgText.length > popupChars) { hideSpan.title = msgText.substring(0, popupChars) + "..."; }
          else { hideSpan.title = msgText; }
          hideSpan.textContent = "• hide: " + regexArray[found][3];

          post.getElementsByClassName("post__details")[0].insertBefore(hideSpan, post.getElementsByClassName("turnmeoff")[1]);
          post.className = "post post_type_reply post_type_hidden";
          if (inThread) { hiddenCount++; }
        }
      }
    }
    if (inThread) { hideTotalSpan.textContent = "(скрыто постов: " + String(hiddenCount) + ")"; }
  }
}

function highlightPosts(node) {
var like,
    dislike,
    likeSpan,
    dislikeSpan,
    likeCount,
    dislikeCount,
    r,
    c = 120;

  if ((like = node.getElementsByClassName("post__detailpart post__rate post__rate_type_like")[0]) && (likeSpan = like.children[1])) {
    likeCount = parseInt(likeSpan.innerHTML, 10);
  }
  if ((dislike = node.getElementsByClassName("post__detailpart post__rate post__rate_type_dislike")[0]) && (dislikeSpan = dislike.children[1])) {
    dislikeCount = parseInt(dislikeSpan.innerHTML, 10);
  }
  if (!likeCount) { likeCount = 1; }
  if (!dislikeCount) { dislikeCount = 1; }

  r = likeCount / dislikeCount;
  if (r > 1.33) {
    node.style.backgroundColor = 'rgba('+String(c)+',' + String(Math.min(c+r*13,250)) + ','+String(c)+',0.2)';
  } else if (r < 0.75) {
    node.style.backgroundColor = 'rgba(' + String(Math.min(c+1/r*13,250)) + ','+String(c)+','+String(c)+',0.2)';
  } else if (likeCount + dislikeCount > 30) {
    node.style.backgroundColor = 'rgba('+String(c)+','+String(c)+','+String(c+100)+',0.2)';
  }
}

function delayClicksAfterUserInput(element, mouseButton) {
  if (!clicksTaskActive || !timeoutID || mouseButton != 0) { return; }

  var cname = String(element.className);
  if (String(element.id).includes("like-count") || cname.includes("SVGAnimatedString") || cname.includes("post__rate")) {
    clearTimeout(timeoutID);
    timeoutID = setTimeout(scheduledDislike, minClickDelay + Math.random() * (maxClickDelay - minClickDelay));
  }
}

function requestDislike(post) {
  var element = post.getElementsByClassName("post__detailpart post__rate post__rate_type_dislike")[0];
  if (element && element.className !== "post__detailpart post__rate post__rate_type_dislike post__rate_disliked") {
    clicksArray.push(element);
    if (showInTitle) { document.title = "[👎"+clicksArray.length+"] " + title; }
    if (clicksTaskActive == 0) {
      clicksTaskActive = 1;
      document.addEventListener("click", inputListener);
      timeoutID = setTimeout(scheduledDislike, minClickDelay + Math.random() * (maxClickDelay - minClickDelay));
    }
  }
}

function scheduledDislike() {
  if (displayBlock.style.display == "block") {
    timeoutID = setTimeout(scheduledDislike, 1000);
    return;
  }

  timeoutID = 0;
  var element = clicksArray.shift();
  element.click();
  element.parentNode.parentNode.className = "post post_type_reply post_type_hidden";
  if (clicksArray.length) {
    if (showInTitle) { document.title = "[👎"+clicksArray.length+"] " + title; }
    timeoutID = setTimeout(scheduledDislike, minClickDelay + Math.random() * (maxClickDelay - minClickDelay));
  } else {
    document.title = title;
    clicksTaskActive = 0;
    document.removeEventListener("click", inputListener);
  }
}