Greasy Fork is available in English.
Automatically hides reposts on X with optional smart filtering by keywords
当前为
// ==UserScript==
// @name Automatic Repost Hider on X
// @description Automatically hides reposts on X with optional smart filtering by keywords
// @namespace https://github.com/xechostormx/repost-hider
// @version 1.02
// @author xechostormx, hearing_echoes (enhanced by Grok)
// @match https://x.com/*
// @match https://www.x.com/*
// @match https://mobile.x.com/*
// @run-at document-end
// @grant none
// @license MIT
// ==/UserScript==
'use strict';
// CSS selectors for tweets and reposts
const selectors = {
tweet: '[data-testid="cellInnerDiv"]',
repost: '[data-testid="socialContext"]',
hidden: '[style*="display: none;"]',
tweetText: '[data-testid="tweetText"]' // For keyword filtering
};
// Configuration
const config = {
checkInterval: 750, // Increased for better performance
debug: false, // Set to true for console logging
keywordFilter: [], // Add keywords to filter specific reposts, e.g., ['spam', 'ad']
};
// Track hidden reposts for performance monitoring
let hiddenCount = 0;
// Debounce function to limit excessive calls during scrolling
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Main function to hide reposts
function hideReposts() {
const reposts = document.querySelectorAll(
`${selectors.tweet}:has(${selectors.repost}):not(${selectors.hidden})`
);
reposts.forEach(tweet => {
// Optional keyword-based filtering
if (config.keywordFilter.length > 0) {
const tweetText = tweet.querySelector(selectors.tweetText)?.textContent.toLowerCase() || '';
if (!config.keywordFilter.some(keyword => tweetText.includes(keyword.toLowerCase()))) {
return; // Skip if no keywords match
}
}
tweet.style.display = 'none';
hiddenCount++;
if (config.debug) {
console.debug(`Hid repost #${hiddenCount}`);
}
});
// Log performance metrics if enabled
if (config.debug && reposts.length > 0) {
console.debug(`Processed ${reposts.length} reposts in this cycle`);
}
}
// Debounced version of hideReposts
const debouncedHideReposts = debounce(hideReposts, config.checkInterval);
// Initialize observer for dynamic content
function initObserver() {
if (!document.body) {
if (config.debug) console.debug('Document body not ready, retrying...');
setTimeout(initObserver, 100);
return;
}
const observerConfig = { childList: true, subtree: true };
const observer = new MutationObserver(() => debouncedHideReposts());
observer.observe(document.body, observerConfig);
// Clean up observer on page unload
window.addEventListener('unload', () => observer.disconnect());
}
// Start the script
function initialize() {
window.addEventListener('scroll', debouncedHideReposts);
initObserver();
debouncedHideReposts(); // Initial run
}
// Run when DOM is ready
if (document.readyState === 'complete' || document.readyState === 'interactive') {
initialize();
} else {
document.addEventListener('DOMContentLoaded', initialize);
}