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

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

目前為 2025-01-30 提交的版本,檢視 最新版本

// ==UserScript==
// @name         PanelControl Filtration Posts language popup by Keywords XFilter (c) tapeavion
// @namespace    http://tampermonkey.net/
// @version      1.9
// @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";
  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帿ŨÆ]/,
  };

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

  // ===== Сохранение в localStorage ===== //
  function saveKeywords() {
    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) => {
      const textContent = article.innerText.toLowerCase();
      const isVerifiedAccount =
        hideVerifiedAccounts &&
        article.querySelector('[data-testid="icon-verified"]');

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

  // ===== Создание панели управления ===== //
  function createControlPanel() {
    const panel = document.createElement("div");
    panel.style.position = "fixed";
    panel.style.bottom = "52px";
    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: inside; padding: 0; margin-top: 10px; font-size: 14px; color: #fff;"></ul>
    `;


    document.body.appendChild(panel);


    // Создаем попап для выбора языков
        const languagePopup = document.createElement("div");
    languagePopup.style.display = "none";
    languagePopup.style.position = "fixed";
    languagePopup.style.top = "50%";
    languagePopup.style.left = "50%";
    languagePopup.style.transform = "translate(-50%, -50%)";
    languagePopup.style.backgroundColor = "#34506c";
    languagePopup.style.padding = "20px";
    languagePopup.style.borderRadius = "8px";
    languagePopup.style.zIndex = "10000";
    languagePopup.style.width = "330px";
    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");
      checkbox.type = "checkbox";
      checkbox.id = `lang-${language}`;
      checkbox.name = language;

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

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

      languagePopup.appendChild(wrapper);
    }

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

            // Стили для круглой кнопки
        closeButton.style.position = "relative";
        closeButton.style.width = "40px";
        closeButton.style.height = "40px";
        closeButton.style.borderRadius = "50%";
        closeButton.style.backgroundColor = "#203142";
        closeButton.style.color = "#fff";
        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 = "0 4px 6px rgba(0, 0, 0, 0.1)";
        closeButton.style.transition = "background-color 0.3s ease";
        closeButton.style.fontFamily = "Arial, sans-serif"; // Устанавливаем шрифт Arial
        closeButton.style.left = "300px"; // Добавлен стиль left
        closeButton.style.top = "40px"; // Добавлен стиль top
        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");
    warningText.textContent = "⚠️ Be careful, this may cause an error";
    warningText.style.color = "#ffcc00"; // Желтый цвет для предупреждения
    warningText.style.fontSize = "14px";
    warningText.style.marginBottom = "10px";
    warningText.style.textAlign = "center";
    warningText.style.right = "50px"; // Сдвигаем вправо на 50px
    warningText.style.position = "relative"; // Устанавливаем позиционирование
    warningText.style.top = "15px"; // Сдвигаем вниз на 15px

    languagePopup.appendChild(warningText);

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

    //===== Функция обновления фильтрации по языкам ==========//
    function updateLanguageFilter(language) {
      const checkbox = document.getElementById(`lang-${language}`);
      if (checkbox.checked) {
        activeLanguageFilters[language] = languageFilters[language];
      } else {
        delete activeLanguageFilters[language];
      }
      hidePosts(); // Пересчитываем скрытие постов
    }

    // Стили для подсветки
    const style = document.createElement("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");
    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");
    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");
    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");
    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");
      const keyword = input.value.trim();
      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;charset=utf-8,${encodeURIComponent(
        JSON.stringify(hiddenKeywords)
      )}`;
      const downloadAnchor = document.createElement("a");
      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");
      input.type = "file";
      input.accept = "application/json";
      input.addEventListener("change", (event) => {
        const file = event.target.files[0];
        const reader = new FileReader();
        reader.onload = () => {
          try {
            const importedKeywords = JSON.parse(reader.result);
            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();
    });

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

  // ===== Обновление списка ключевых слов ===== //
  function updateKeywordList() {
    const list = document.getElementById("keywordList");
    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");
      listItem.textContent = keyword;
      listItem.style.marginBottom = "5px";

      const deleteButton = document.createElement("button");
      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 = "Нет";
    }
  }

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

  // ===== Наблюдатель для динамического контента ===== //
  const observer = new MutationObserver(hidePosts);
  observer.observe(document.body, { childList: true, subtree: true });
})();