PanelControl - 1.8 Filtration Posts by language by Keywords - XFilter Twitter X-com (c) tapeavion

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

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         PanelControl - 1.8 Filtration Posts by language by Keywords  - XFilter Twitter X-com (c) tapeavion
// @namespace    http://tampermonkey.net/
// @version      1.8
// @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 = '0px';
        panel.style.right = '440px';
        panel.style.width = '450px';
        panel.style.height = '545px'; // Увеличена высота панели
        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;">
<button id="addKeyword" style="position: relative; width: 80px; padding: 8px; margin-bottom: 5px; background: #203142; color: white; border: none; border-radius: 5px; height: 45px; left: 5px;">
    Add it
</button>
        <button id="clearKeywords" style="width: 75px; padding: 8px; margin-bottom: 5px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">Clear all</button>
        <button id="exportKeywords" style="width: 60px; padding: 8px; margin-bottom: 5px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;">Export</button>
        <button id="importKeywords" style="width: 60px; padding: 8px; background: #203142; color: white; border: none; border-radius: 5px; margin-bottom: 10px; height: 40px;">Import</button>
        <button id="toggleVerifiedPosts" style="width: 242px;  padding: 8px; margin-bottom: 5px; background: #203142; color: white; border: none; border-radius: 5px; height: 40px;  font-size: 13px;
    font-weight: bold;">
    Hide verified accounts: Click to Disable
</button>

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

        <div style="margin-top: 20px; font-size: 14px; color: #fff;">
            <p>Select Language to Hide Posts:</p>
            <div id="languageButtons" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                ${Object.keys(languageFilters).map(language => `
                    <label style="display: flex; align-items: center; color: #fff;">
                        <input type="checkbox" id="lang-${language}" style="margin-right: 8px;">
                        ${language.charAt(0).toUpperCase() + language.slice(1)}
                    </label>
                `).join('')}
            </div>
        </div>
    `;
        document.body.appendChild(panel);

        // Обработчик для кнопок языков
        Object.keys(languageFilters).forEach(language => {
            const checkbox = document.getElementById(`lang-${language}`);
            checkbox.addEventListener('change', () => {
                updateLanguageFilter(language); // Включение/выключение фильтра для языка
            });
        });

       // Стили для подсветки
       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';
}

// Добавляем обработчик на клик по переключателю
toggleLabel.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') === 'false'; // По умолчанию скрыта, если в localStorage не сохранено другое значение

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

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

toggleButton.addEventListener('click', togglePanel);

// При загрузке восстанавливаем состояние панели
if (isPanelVisible) {
  panel.style.height = '545px'; // Разворачиваем панель
  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.maxHeight = '150px';
        list.style.overflowY = 'auto';
        list.style.paddingRight = '10px';
        list.style.border = '1px solid #ccc';
        list.style.borderRadius = '5px';
        list.style.backgroundColor = '#15202b';
        list.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.3)';
        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 });
})();