// ==UserScript==
// @name Twitter Search Filter
// @namespace https://x.com/pollowinworld
// @version 1.0
// @description Filter tweets in Twitter search. Visual management, mask hint, full hide, draggable and collapsible panel!
// @author pollowinworld
// @match https://x.com/search*
// @license MIT
// @grant none
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
let blockedWords = JSON.parse(localStorage.getItem('blockedWords') || '["AI Alert &"]');
let filterEnabled = JSON.parse(localStorage.getItem('filterEnabled') || 'true');
let fullHideEnabled = JSON.parse(localStorage.getItem('fullHideEnabled') || 'false');
function saveBlockedWords() {
localStorage.setItem('blockedWords', JSON.stringify(blockedWords));
}
function saveFilterStatus() {
localStorage.setItem('filterEnabled', JSON.stringify(filterEnabled));
}
function saveFullHideStatus() {
localStorage.setItem('fullHideEnabled', JSON.stringify(fullHideEnabled));
}
function shouldBlock(tweetText) {
return blockedWords.find(word => tweetText.toLowerCase().includes(word.toLowerCase()));
}
function createMask(article, matchedWord) {
const mask = document.createElement('div');
mask.style.position = 'absolute';
mask.style.top = '0';
mask.style.left = '0';
mask.style.width = '100%';
mask.style.height = '100%';
mask.style.background = 'rgba(255, 255, 255, 0.9)';
mask.style.display = 'flex';
mask.style.flexDirection = 'column';
mask.style.justifyContent = 'center';
mask.style.alignItems = 'center';
mask.style.zIndex = '100';
mask.innerHTML = `
已屏蔽推文
匹配关键词:${matchedWord}
`;
const button = mask.querySelector('button');
button.addEventListener('click', () => {
mask.remove();
});
article.style.position = 'relative';
article.appendChild(mask);
}
function filterTweets() {
if (!filterEnabled) return;
const tweets = document.querySelectorAll('article div[lang]');
tweets.forEach(tweet => {
const textContent = tweet.innerText;
const matchedWord = shouldBlock(textContent);
if (matchedWord) {
const article = tweet.closest('article');
if (article && !article.dataset.filtered) {
article.dataset.filtered = 'true';
if (fullHideEnabled) {
article.style.display = 'none';
} else {
createMask(article, matchedWord);
}
}
}
});
}
function createControlPanel() {
const panel = document.createElement('div');
panel.style.position = 'fixed';
panel.style.top = '100px';
panel.style.right = '20px';
panel.style.width = '280px';
panel.style.background = 'white';
panel.style.border = '1px solid #ccc';
panel.style.borderRadius = '8px';
panel.style.padding = '10px';
panel.style.zIndex = '9999';
panel.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
panel.style.fontSize = '14px';
panel.style.fontFamily = 'Arial, sans-serif';
panel.style.cursor = 'move';
panel.id = 'filter-panel';
panel.innerHTML = `
🛡️ 屏蔽词管理
`;
document.body.appendChild(panel);
const collapseBtn = panel.querySelector('#collapse-btn');
const panelBody = panel.querySelector('#panel-body');
let collapsed = false;
collapseBtn.addEventListener('click', () => {
collapsed = !collapsed;
if (collapsed) {
panelBody.style.display = 'none';
collapseBtn.textContent = '➕';
panel.style.width = '60px';
} else {
panelBody.style.display = '';
collapseBtn.textContent = '➖';
panel.style.width = '280px';
}
});
const wordListDiv = panel.querySelector('#word-list');
const input = panel.querySelector('#new-word');
const addButton = panel.querySelector('#add-word');
const toggleButton = panel.querySelector('#toggle-filter');
const toggleFullHideButton = panel.querySelector('#toggle-fullhide');
const exportButton = panel.querySelector('#export-words');
const importButton = panel.querySelector('#import-words');
const clearButton = panel.querySelector('#clear-words');
function refreshWordList() {
wordListDiv.innerHTML = '';
blockedWords.forEach((word, index) => {
const wordItem = document.createElement('div');
wordItem.style.display = 'flex';
wordItem.style.justifyContent = 'space-between';
wordItem.style.marginBottom = '5px';
wordItem.innerHTML = `
${word}
`;
wordListDiv.appendChild(wordItem);
});
wordListDiv.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = parseInt(e.target.getAttribute('data-index'));
blockedWords.splice(idx, 1);
saveBlockedWords();
refreshWordList();
});
});
}
addButton.addEventListener('click', () => {
const word = input.value.trim();
if (word && !blockedWords.includes(word)) {
blockedWords.push(word);
saveBlockedWords();
refreshWordList();
input.value = '';
}
});
toggleButton.addEventListener('click', () => {
filterEnabled = !filterEnabled;
saveFilterStatus();
toggleButton.innerText = filterEnabled ? '✅ 过滤开启' : '❌ 过滤关闭';
});
toggleFullHideButton.addEventListener('click', () => {
fullHideEnabled = !fullHideEnabled;
saveFullHideStatus();
toggleFullHideButton.innerText = fullHideEnabled ? '✅ 完全隐藏开启' : '❌ 完全隐藏关闭';
});
exportButton.addEventListener('click', () => {
const blob = new Blob([JSON.stringify(blockedWords, null, 2)], {type: "application/json"});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'blocked_words.json';
a.click();
URL.revokeObjectURL(url);
});
importButton.addEventListener('click', () => {
const inputFile = document.createElement('input');
inputFile.type = 'file';
inputFile.accept = 'application/json';
inputFile.onchange = e => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = event => {
try {
const imported = JSON.parse(event.target.result);
if (Array.isArray(imported)) {
blockedWords = imported;
saveBlockedWords();
refreshWordList();
alert('✅ 成功导入屏蔽词');
} else {
alert('❌ 文件格式错误');
}
} catch (err) {
alert('❌ 解析失败');
}
};
reader.readAsText(file);
};
inputFile.click();
});
clearButton.addEventListener('click', () => {
if (confirm('确定要清空所有屏蔽词吗?')) {
blockedWords = [];
saveBlockedWords();
refreshWordList();
}
});
refreshWordList();
// 拖拽 + 自动靠边吸附
let isDragging = false;
let offsetX, offsetY;
panel.addEventListener('mousedown', function(e) {
isDragging = true;
offsetX = e.clientX - panel.offsetLeft;
offsetY = e.clientY - panel.offsetTop;
panel.style.transition = 'none';
});
document.addEventListener('mousemove', function(e) {
if (isDragging) {
panel.style.left = (e.clientX - offsetX) + 'px';
panel.style.top = (e.clientY - offsetY) + 'px';
panel.style.right = 'auto';
}
});
document.addEventListener('mouseup', function() {
if (isDragging) {
isDragging = false;
const windowWidth = window.innerWidth;
const panelRect = panel.getBoundingClientRect();
const middle = windowWidth / 2;
if (panelRect.left + panelRect.width / 2 < middle) {
// 靠左
panel.style.left = '10px';
panel.style.right = 'auto';
} else {
// 靠右
panel.style.right = '10px';
panel.style.left = 'auto';
}
panel.style.transition = 'left 0.2s, right 0.2s, top 0.2s'; // 平滑回弹
}
});
}
createControlPanel();
setInterval(filterTweets, 1000);
})();