PanelControl Filtration Posts language popup by Keywords XFilter (c) tapeavion

Hide posts by keywords with the dashboard and hide posts from verified accounts

当前为 2025-02-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         PanelControl Filtration Posts language popup by Keywords XFilter (c) tapeavion
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Hide posts by keywords with the dashboard and hide posts from verified accounts
// @author       gullampis810
// @match        https://x.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// @icon         https://www.pinclipart.com/picdir/big/450-4507608_twitter-circle-clipart.png
// ==/UserScript==

(function () {
  "use strict";

  // ===== Настройки и инициализация ===== //
  const STORAGE_KEY = "hiddenKeywords";
  console.log("STORAGE_KEY =", STORAGE_KEY);
  let hiddenKeywords = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
  let hideVerifiedAccounts = true; // Скрывать подтвержденные аккаунты
  const languageFilters = {
    english: /[a-zA-Z]/,
    russian: /[А-Яа-яЁё]/,
    japanese: /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u,
    ukrainian: /[А-Яа-яІіЄєЇїҐґ]/,
    belarusian: /[А-Яа-яЎўЁёІі]/,
    tatar: /[А-Яа-яӘәӨөҮүҖҗ]/,
    mongolian: /[\p{Script=Mongolian}]/u,
    chinese: /[\p{Script=Han}]/u,
    german: /[a-zA-ZßÄäÖöÜü]/,
    polish: /[a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ]/,
    french: /[a-zA-Zàâçéèêëîïôûùüÿ]/,
    swedish: /[a-zA-ZåäöÅÄÖ]/,
    estonian: /[a-zA-ZäõöüÄÕÖÜ]/,
    danish: /[a-zA-Z帿ŨÆ]/,
    turkish: /[a-zA-ZıİçÇğĞöÖşŞüÜ]/,
    portuguese: /[a-zA-Zàáâãçéêíóôõúü]/,
  };

  let activeLanguageFilters = {}; // Словарь активных языков

  // ===== Сохранение в localStorage ===== //
  function saveKeywords() {
    console.log("saveKeywords called with", ...arguments);

    localStorage.setItem(STORAGE_KEY, JSON.stringify(hiddenKeywords));
  }

  // ===== Функция для обновления отображения по языкам ===== //
  function updateLanguageFilter(language) {
    if (activeLanguageFilters[language]) {
      delete activeLanguageFilters[language];
    } else {
      activeLanguageFilters[language] = languageFilters[language];
    }
    hidePosts(); // Пересчитываем скрытие постов
  }

  // ===== Проверка языка текста ===== //
  function isTextInLanguage(text) {
    for (const [language, regex] of Object.entries(activeLanguageFilters)) {
      if (regex.test(text)) {
        return true; // Возвращаем true, если текст на этом языке
      }
    }
    return false;
  }

// ===== Функция Скрытия постов ===== //
function hidePosts() {
  document.querySelectorAll("article").forEach((article) => {
    // Если был клик по кнопке "Перевести пост", пропускаем скрытие для этого поста
    if (isTranslateButtonClicked && article.contains(document.querySelector('button[aria-expanded="false"]'))) {
      return; // Пропустить скрытие
    }

    const textContent = article.innerText.toLowerCase();
    console.log("textContent =", textContent);
    const isVerifiedAccount =
      hideVerifiedAccounts && article.querySelector('[data-testid="icon-verified"]');

    if (
      hiddenKeywords.some((keyword) => textContent.includes(keyword.toLowerCase())) ||
      isTextInLanguage(textContent) ||
      isVerifiedAccount
    ) {
      article.style.display = "none";
    }
  });
}


  // ===== Создание панели управления ===== //
  const panel = document.createElement("div");
  panel.style.position = "fixed";
  panel.style.bottom = "62px";
  panel.style.right = "180px";
  panel.style.width = "335px";
  panel.style.height = "310px";
  panel.style.padding = "8px";
  panel.style.fontFamily = "Arial, sans-serif";
  panel.style.backgroundColor = "#34506c";
  panel.style.color = "#fff";
  panel.style.borderRadius = "8px";
  panel.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)";
  panel.style.zIndex = "9999";
  panel.style.overflow = "auto";
  panel.style.transition = "height 0.3s ease";

  panel.innerHTML = `
       <h3 style="margin: 0; font-size: 16px;">Hiding Control</h3>
       <input id="keywordInput" type="text" placeholder="Enter the word" style="width: calc(100% - 95px); height: 30px; padding: 5px; margin: 10px 0; border-radius: 5px; border: none;">

       <div style="display: flex; flex-wrap: wrap; gap: 5px; position: relative;">
           <button id="addKeyword" style="flex: 1; min-width: 70px; max-width: 70px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">
               Add it
           </button>
           <button id="exportKeywords" style="flex: 1; min-width: 60px; max-width: 70px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">
               Export
           </button>
           <button id="importKeywords" style="flex: 1; min-width: 60px; max-width: 70px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">
               Import
           </button>
           <button id="clearKeywords" style="flex: 1; min-width: 60px; max-width: 80px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; bottom: 0px; position: relative; left: 1px;">
               Clear all
           </button>
           <button id="toggleVerifiedPosts" style="width: 242px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; font-size: 13px; font-weight: bold;">
               Hide verified accounts: Click to Disable
           </button>
           <button id="openLanguagePopup" style="width: 80px; display: flex; align-content: center; flex-wrap: wrap; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px; font-size: 13px; font-weight: bold;">
               Language Filtering
           </button>
       </div>

       <ul id="keywordList" style="list-style: position: relative; inside; padding: 0; margin-top: 10px; font-size: 14px; color: #fff;"></ul>
    `;

document.body.appendChild(panel);
console.log("panel =", panel);

// ===== Перетаскивание панели ===== //
let isDragging = false;
let offsetX = 0;
let offsetY = 0;

// Функция для начала перетаскивания
panel.addEventListener("mousedown", (event) => {
  isDragging = true;
  offsetX = event.clientX - panel.offsetLeft;
  offsetY = event.clientY - panel.offsetTop;
  panel.style.cursor = "grabbing";
});

// Функция для перемещения панели
document.addEventListener("mousemove", (event) => {
  if (isDragging) {
    panel.style.left = event.clientX - offsetX + "px";
    panel.style.top = event.clientY - offsetY + "px";
  }
});

// Функция для завершения перетаскивания
document.addEventListener("mouseup", () => {
  isDragging = false;
  panel.style.cursor = "grab";
});

  document.body.appendChild(panel);
  console.log("panel =", panel);

  // Создаем попап для выбора языков
  const languagePopup = document.createElement("div");
  console.log("languagePopup =", languagePopup);
  languagePopup.style.display = "none";
  languagePopup.style.position = "fixed";
  languagePopup.style.top = "50%";
  languagePopup.style.left = "67%";
  languagePopup.style.right = "67%";
  languagePopup.style.transform = "translate(-60%, -55%)";
  languagePopup.style.backgroundColor = "rgb(52, 80, 108)";
  languagePopup.style.padding = "20px";
  languagePopup.style.borderRadius = "8px";
  languagePopup.style.zIndex = "10000";
  languagePopup.style.width = "8%";
  languagePopup.style.boxShadow = "rgba(0, 0, 0, 0.5) 0px 0px 10px";
  languagePopup.style.fontFamily = "Arial, sans-serif"; // Устанавливаем шрифт Arial

  // Добавляем чекбоксы для каждого языка
  for (const language in languageFilters) {
    const checkbox = document.createElement("input");
    console.log("checkbox =", checkbox);
    checkbox.type = "checkbox";
    checkbox.id = `lang-${language}`;
    checkbox.name = language;

    const label = document.createElement("label");
    console.log("label =", label);
    label.htmlFor = `lang-${language}`;
    label.textContent = language.charAt(0).toUpperCase() + language.slice(1);

    const wrapper = document.createElement("div");
    console.log("wrapper =", wrapper);
    wrapper.appendChild(checkbox);
    wrapper.appendChild(label);

    languagePopup.appendChild(wrapper);
  }

  // Добавляем кнопку закрытия попапа
  const closeButton = document.createElement("button");
  console.log("closeButton =", closeButton);
  closeButton.textContent = "X";

  // Стили для круглой кнопки
  closeButton.style.position = "relative";
  closeButton.style.width = "40px";
  closeButton.style.height = "40px";
  closeButton.style.borderRadius = "50%";
  closeButton.style.backgroundColor = "rgb(32, 49, 66)";
  closeButton.style.color = "rgb(255, 255, 255)";
  closeButton.style.border = "none";
  closeButton.style.display = "flex";
  closeButton.style.alignItems = "center";
  closeButton.style.justifyContent = "center";
  closeButton.style.cursor = "pointer";
  closeButton.style.marginTop = "10px";
  closeButton.style.boxShadow = "rgba(0, 0, 0, 0.1) 0px 4px 6px";
  closeButton.style.transition = "background-color 0.3s";
  closeButton.style.fontFamily = "Arial, sans-serif";
  closeButton.style.left = "82%";
  closeButton.style.top = "56px";
  closeButton.addEventListener("click", () => {
    languagePopup.style.display = "none";
  });
  languagePopup.appendChild(closeButton);

  document.body.appendChild(languagePopup);

  // Обработчик для открытия попапа
  document.getElementById("openLanguagePopup").addEventListener("click", () => {
    languagePopup.style.display = "block";
  });

  // Добавляем текст с предупреждением
  const warningText = document.createElement("div");
  console.log("warningText =", warningText);
  warningText.textContent = "⚠️it may stops working";
  warningText.style.color = "#ffcc00"; // Желтый цвет для предупреждения
  warningText.style.fontSize = "14px";
  warningText.style.marginBottom = "10px";
  warningText.style.textAlign = "end";
  warningText.style.right = "38px"; // Сдвигаем вправо на 50px
  warningText.style.position = "relative"; // Устанавливаем позиционирование
  warningText.style.top = "20px"; // Сдвигаем вниз на 15px

  languagePopup.appendChild(warningText);

  // Обработчики для чекбоксов
  for (const language in languageFilters) {
    document
      .getElementById(`lang-${language}`)
      .addEventListener("change", (event) => {
        updateLanguageFilter(language);
      });
  }



  // Стили для подсветки
  const style = document.createElement("style");
  console.log("style =", style);
  style.textContent = `
    button {
        transition: box-shadow 0.3s, transform 0.3s;
    }
    button:hover {
        box-shadow: 0 0 10px rgba(255, 255, 255, 0.7);
    }
    button:active {
        transform: scale(0.95);
        box-shadow: 0 0 5px rgba(255, 255, 255, 0.7);
    }
`;
  document.head.appendChild(style);

  // ======== Кнопка iOS-переключатель Panel FilterX ========= //

  // Проверяем сохраненное состояние переключателя в localStorage
  let isSwitchOn = localStorage.getItem("isSwitchOff") === "true"; // Начальное состояние переключателя из localStorage

  // Создание элементов панели
  const toggleButton = document.createElement("div");
  console.log("toggleButton =", toggleButton);
  toggleButton.style.position = "fixed";
  toggleButton.style.top = "94%";
  toggleButton.style.right = "90px";
  toggleButton.style.width = "192px";
  toggleButton.style.display = "flex";
  toggleButton.style.alignItems = "center";
  toggleButton.style.gap = "10px";
  toggleButton.style.zIndex = "1";
  toggleButton.style.background = "#15202b";
  toggleButton.style.border = "4px solid #6c7e8e";
  toggleButton.style.borderRadius = "18px";
  toggleButton.style.boxSizing = "border-box";

  // Создаем label для переключателя
  const toggleLabel = document.createElement("label");
  console.log("toggleLabel =", toggleLabel);
  toggleLabel.style.display = "inline-block";
  toggleLabel.style.width = "50px";
  toggleLabel.style.height = "25px";
  toggleLabel.style.borderRadius = "25px";
  toggleLabel.style.backgroundColor = "#0d1319";
  toggleLabel.style.position = "relative";
  toggleLabel.style.cursor = "pointer";
  toggleLabel.style.transition = "background-color 0.3s";

  // Создаем кружок переключателя
  const toggleSwitch = document.createElement("div");
  console.log("toggleSwitch =", toggleSwitch);
  toggleSwitch.style.position = "absolute";
  toggleSwitch.style.width = "21px";
  toggleSwitch.style.height = "21px";
  toggleSwitch.style.borderRadius = "50%";
  toggleSwitch.style.backgroundColor = "#6c7e8e";
  toggleSwitch.style.top = "2px";

  // Устанавливаем начальное положение кружка в зависимости от состояния
  toggleSwitch.style.left = isSwitchOn ? "calc(100% - 23px)" : "2px"; // Если выключено, то слева, если включено — справа

  // Плавная анимация
  toggleSwitch.style.transition = "left 0.3s ease";
  toggleSwitch.style.boxShadow = "rgb(21, 32, 43) -1px 1px 4px 1px";

  // Устанавливаем начальное состояние фона
  toggleLabel.style.backgroundColor = isSwitchOn ? "#425364" : "#0d1319";

  // Функция для изменения состояния переключателя
  function toggleSwitchState() {
    isSwitchOn = !isSwitchOn;
    localStorage.setItem("isSwitchOn", isSwitchOn.toString()); // Сохраняем состояние в localStorage (как строку)

    // Обновляем стиль переключателя
    toggleSwitch.style.left = isSwitchOn ? "calc(100% - 23px)" : "2px";
    toggleLabel.style.backgroundColor = isSwitchOn ? "#425364" : "#0d1319";
  }

  // Добавляем обработчик на клик по переключателю
  toggleButton.addEventListener("click", toggleSwitchState);

  // Добавляем элементы на страницу
  toggleLabel.appendChild(toggleSwitch);
  toggleButton.appendChild(toggleLabel);
  document.body.appendChild(toggleButton);

  // Добавление текста к переключателю
  const toggleText = document.createElement("span");
  console.log("toggleText =", toggleText);
  toggleText.style.position = "relative";
  toggleText.style.right = "5px";
  toggleText.textContent = "Panel FilterX";
  toggleText.style.color = "#6c7e8e";
  toggleText.style.fontFamily = "Arial, sans-serif";
  toggleText.style.fontSize = "16px";
  toggleText.style.marginLeft = "10px";
  toggleText.style.fontWeight = "bold";
  toggleButton.appendChild(toggleText);

  //====================== Управление высотой панели =======================//
  let isPanelVisible = localStorage.getItem("panelVisible") === "truefalse"; // По умолчанию скрыта, если в localStorage не сохранено другое значение

  function togglePanel() {
    if (isPanelVisible) {
      panel.style.height = "0px"; // Сворачиваем панель
      setTimeout(() => {
        panel.style.display = "none"; // Скрываем панель после анимации
      }, 300);
    } else {
      panel.style.display = "block"; // Показываем панель
      setTimeout(() => {
        panel.style.height = "310px"; // Разворачиваем панель
      }, 10);
    }

    isPanelVisible = !isPanelVisible; // Переключаем состояние
    localStorage.setItem("panelVisible", isPanelVisible.toString()); // Сохраняем состояние
  }

  toggleButton.addEventListener("click", togglePanel);

  // При загрузке восстанавливаем состояние панели
  if (isPanelVisible) {
    panel.style.height = "310px"; // Разворачиваем панель
    panel.style.display = "block";
  } else {
    panel.style.height = "0px"; // Сворачиваем панель
    panel.style.display = "none";
  }

  // ===== Обработчики событий ===== //
  document.getElementById("addKeyword").addEventListener("click", () => {
    const input = document.getElementById("keywordInput");
    console.log("input =", input);
    const keyword = input.value.trim();
    console.log("keyword =", keyword);
    if (keyword && !hiddenKeywords.includes(keyword)) {
      hiddenKeywords.push(keyword);
      saveKeywords();
      updateKeywordList();
      hidePosts();
      input.value = "";
    }
  });

  document.getElementById("clearKeywords").addEventListener("click", () => {
    if (confirm("Are you sure you want to clear the list?")) {
      hiddenKeywords = [];
      saveKeywords();
      updateKeywordList();
      hidePosts();
    }
  });

  document.getElementById("exportKeywords").addEventListener("click", () => {
    const dataStr = `data:text/json;
console.log('dataStr =', dataStr);charset=utf-8,${encodeURIComponent(
      JSON.stringify(hiddenKeywords),
    )}`;
    const downloadAnchor = document.createElement("a");
    console.log("downloadAnchor =", downloadAnchor);
    downloadAnchor.setAttribute("href", dataStr);
    downloadAnchor.setAttribute("download", "hidden_keywords.json");
    document.body.appendChild(downloadAnchor);
    downloadAnchor.click();
    document.body.removeChild(downloadAnchor);
  });

  document.getElementById("importKeywords").addEventListener("click", () => {
    const input = document.createElement("input");
    console.log("input =", input);
    input.type = "file";
    input.accept = "application/json";
    input.addEventListener("change", (event) => {
      const file = event.target.files[0];
      console.log("file =", file);
      const reader = new FileReader();
      console.log("reader =", reader);
      reader.onload = () => {
        try {
          const importedKeywords = JSON.parse(reader.result);
          console.log("importedKeywords =", importedKeywords);
          if (Array.isArray(importedKeywords)) {
            hiddenKeywords = [
              ...new Set([...hiddenKeywords, ...importedKeywords]),
            ];
            saveKeywords();
            updateKeywordList();
            hidePosts();
          } else {
            alert("Incorrect file format.");
          }
        } catch (e) {
          alert("Error reading the file.");
        }
      };
      reader.readAsText(file);
    });
    input.click();
  });

// Переменная для отслеживания клика на кнопку "Перевести пост"
let isTranslateButtonClicked = false;

// Функция для отслеживания клика по кнопке "Перевести пост"
function handleTranslateButtonClick(event) {
  isTranslateButtonClicked = true;
  setTimeout(() => { isTranslateButtonClicked = false; }, 500); // Сбросить флаг через 500 мс
}

// ===== Наблюдатель за кнопками "Перевести пост" ===== //
function observeTranslateButton() {
  document.querySelectorAll('button[aria-expanded="false"]').forEach(button => {
    button.removeEventListener('click', handleTranslateButtonClick); // Убираем старые обработчики
    button.addEventListener('click', handleTranslateButtonClick); // Добавляем новый обработчик
  });
}



  // ===== Кнопка для включения/выключения скрытия подтвержденных аккаунтов ===== //
  document
    .getElementById("toggleVerifiedPosts")
    .addEventListener("click", () => {
      hideVerifiedAccounts = !hideVerifiedAccounts;
      document.getElementById("toggleVerifiedPosts").textContent =
        `Hide verified accounts: ${
          hideVerifiedAccounts ? "Turn OFF" : "Turn ON"
        }`;
      hidePosts(); // Перепроверка всех постов с новыми настройками
    });

  // ===== Функция для применения кастомного скроллбара ===== //
  function applyCustomScrollbar() {
    const style = document.createElement("style");
    style.id = "custom-scrollbar-style";
    style.innerHTML = `
      #keywordList::-webkit-scrollbar {
          width: 8px;
      }
      #keywordList::-webkit-scrollbar-thumb {
          background:rgb(143, 23, 79);
          border-radius: 4px;
      }
      #keywordList::-webkit-scrollbar-track {
          background:rgb(44, 173, 55);
      }
    `;
    if (!document.getElementById("custom-scrollbar-style")) {
      document.head.appendChild(style);
    }
  }

  // ===== Функция для наблюдения за изменениями в списке ===== //
  function observeKeywordList() {
    const list = document.getElementById("keywordList");
    if (!list) return;

    const observer = new MutationObserver(() => {
      console.log(
        "Обнаружены изменения в списке ключевых слов, обновляем стили...",
      );
      applyCustomScrollbar();
    });

    observer.observe(list, { childList: true, subtree: true });
  }

  // ===== Обновление списка ключевых слов ===== //
  function updateKeywordList() {
    const list = document.getElementById("keywordList");
    console.log("list =", list);
    list.style.listStyle = "inside";
    list.style.padding = "0px 10px 0px 0px";
    list.style.marginTop = "10px";
    list.style.fontSize = "14px";
    list.style.color = "rgb(255, 255, 255)";
    list.style.maxHeight = "135px";
    list.style.overflowY = "auto";
    list.style.border = "1px solid rgb(204, 204, 204)";
    list.style.borderRadius = "5px";
    list.style.backgroundColor = "rgb(21, 32, 43)";
    list.style.boxShadow = "rgba(0, 0, 0, 0.3) 0px 2px 5px";
    list.style.position = "relative";
    list.style.width = "315px";
    list.innerHTML = "";

    hiddenKeywords.forEach((keyword, index) => {
      const listItem = document.createElement("li");
      console.log("listItem =", listItem);
      listItem.textContent = keyword;
      listItem.style.marginBottom = "5px";

      const deleteButton = document.createElement("button");
      console.log("deleteButton =", deleteButton);
      deleteButton.textContent = "❌";
      deleteButton.style.marginLeft = "10px";
      deleteButton.style.backgroundColor = "#f44336";
      deleteButton.style.color = "#fff";
      deleteButton.style.border = "none";
      deleteButton.style.borderRadius = "3px";
      deleteButton.style.cursor = "pointer";
      deleteButton.addEventListener("click", () => {
        hiddenKeywords.splice(index, 1);
        saveKeywords();
        updateKeywordList();
        hidePosts();
      });

      listItem.appendChild(deleteButton);
      list.appendChild(listItem);
    });

    if (hiddenKeywords.length === 0) {
      list.textContent = "Нет";
    }

    applyCustomScrollbar(); // Применяем кастомный скроллбар
    observeKeywordList(); // Повторно запускаем наблюдатель
  }

  // Запуск кастомного скролла и наблюдателя при загрузке страницы
  applyCustomScrollbar();
  observeKeywordList();

  // ===== Инициализация ===== //
  updateKeywordList(); // Обновление списка при загрузке страницы
  hidePosts();

  // ===== Наблюдатель для динамического контента ===== //
  const observer = new MutationObserver(() => {
  hidePosts();
  observeTranslateButton(); // Обновляем наблюдатель за кнопками
});

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