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 提交的版本,檢視 最新版本

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


document.getElementById("openLanguagePopup").addEventListener("click", () => {
    const panelRect = panel.getBoundingClientRect(); // Получаем актуальные координаты панели

    // Устанавливаем позицию попапа относительно панели
    languagePopup.style.top = `${panelRect.top + 50}px`; // Смещение вниз на 50px от панели
    languagePopup.style.left = `${panelRect.left}px`; // Выравниваем по левому краю панели
    languagePopup.style.display = "block"; // Показываем попап
});


// Переменная для отслеживания клика на кнопку "Перевести пост"
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 });
})();