Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Twitter/X User Media Tab Deduplicator
- // @namespace https://x.com
- // @version 2.0
- // @description Hide all duplicate media in user profiles media tab
- // @author KBD99
- // @match https://x.com/*/media*
- // @match https://twitter.com/*/media*
- // @grant none
- // ==/UserScript==
- (function () {
- 'use strict';
- let dedupEnabled = true;
- const HASH_KEY = 'twitterMediaHashes';
- let seenHashes = JSON.parse(localStorage.getItem(HASH_KEY) || '{}');
- const processedElements = new WeakSet();
- // UI Toggle Button
- const toggleButton = document.createElement('button');
- toggleButton.textContent = 'Dedup ON';
- Object.assign(toggleButton.style, {
- position: 'fixed',
- top: '10px',
- right: '10px',
- zIndex: 9999,
- padding: '8px 12px',
- fontSize: '12px',
- backgroundColor: '#1da1f2',
- color: 'white',
- border: 'none',
- borderRadius: '6px',
- cursor: 'pointer',
- fontWeight: 'bold',
- boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
- });
- toggleButton.onclick = () => {
- dedupEnabled = !dedupEnabled;
- toggleButton.textContent = dedupEnabled ? 'Dedup ON' : 'Dedup OFF';
- toggleButton.style.backgroundColor = dedupEnabled ? '#1da1f2' : '#657786';
- if (dedupEnabled) {
- runDeduplication();
- } else {
- // Show all hidden elements
- document.querySelectorAll('[data-dedup-hidden="true"]').forEach(el => {
- el.style.display = '';
- el.removeAttribute('data-dedup-hidden');
- });
- }
- };
- // Clear Cache Button
- const clearButton = document.createElement('button');
- clearButton.textContent = 'Clear Cache';
- Object.assign(clearButton.style, {
- position: 'fixed',
- top: '10px',
- right: '120px',
- zIndex: 9999,
- padding: '8px 12px',
- fontSize: '12px',
- backgroundColor: '#e0245e',
- color: 'white',
- border: 'none',
- borderRadius: '6px',
- cursor: 'pointer',
- fontWeight: 'bold',
- boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
- });
- clearButton.onclick = () => {
- localStorage.removeItem(HASH_KEY);
- seenHashes = {};
- processedElements.clear();
- // Show all hidden elements
- document.querySelectorAll('[data-dedup-hidden="true"]').forEach(el => {
- el.style.display = '';
- el.removeAttribute('data-dedup-hidden');
- });
- alert('Media cache cleared! Duplicates will be detected again.');
- setTimeout(runDeduplication, 1000);
- };
- document.body.appendChild(toggleButton);
- document.body.appendChild(clearButton);
- // Generate hash from image URL and dimensions
- function getMediaHash(el) {
- try {
- let src = el.src || el.querySelector('img')?.src;
- if (!src) return null;
- // Remove size parameters and query strings that don't affect content
- src = src.replace(/[?&](name|format)=[^&]*/g, '');
- src = src.replace(/[?&]$/g, '');
- // For Twitter/X images, extract the base URL without size modifiers
- const twitterImageMatch = src.match(/^(https:\/\/pbs\.twimg\.com\/media\/[^?]+)/);
- if (twitterImageMatch) {
- src = twitterImageMatch[1];
- }
- // Include element dimensions for better duplicate detection
- const width = el.naturalWidth || el.videoWidth || 0;
- const height = el.naturalHeight || el.videoHeight || 0;
- return `${src}_${width}x${height}`;
- } catch (e) {
- return null;
- }
- }
- // Find the tweet container for a media element
- function findTweetContainer(el) {
- // Look for the tweet article container
- let container = el.closest('article[data-testid="tweet"]');
- if (container) return container;
- // Fallback: look for common tweet container patterns
- container = el.closest('div[data-testid]');
- if (container) return container;
- // Last resort: find a container with reasonable size
- let current = el.parentElement;
- while (current && current !== document.body) {
- const rect = current.getBoundingClientRect();
- if (rect.width > 300 && rect.height > 100) {
- return current;
- }
- current = current.parentElement;
- }
- return null;
- }
- // Deduplication Logic
- function runDeduplication() {
- if (!dedupEnabled) return;
- // More specific selectors for Twitter/X media
- const mediaSelectors = [
- 'img[src*="pbs.twimg.com"]',
- 'img[src*="video.twimg.com"]',
- 'video[src*="video.twimg.com"]',
- 'div[data-testid="tweetPhoto"] img',
- 'div[data-testid="videoPlayer"] video',
- 'div[aria-label*="Image"] img'
- ];
- mediaSelectors.forEach(selector => {
- const mediaElements = document.querySelectorAll(selector);
- mediaElements.forEach(el => {
- // Skip if already processed
- if (processedElements.has(el)) return;
- // Skip if not loaded yet
- if (el.tagName === 'IMG' && (!el.complete || el.naturalWidth === 0)) {
- // Try again when image loads
- el.addEventListener('load', () => {
- if (!processedElements.has(el)) {
- processMediaElement(el);
- }
- }, { once: true });
- return;
- }
- processMediaElement(el);
- });
- });
- }
- function processMediaElement(el) {
- if (processedElements.has(el)) return;
- processedElements.add(el);
- const hash = getMediaHash(el);
- if (!hash) return;
- const container = findTweetContainer(el);
- if (!container) return;
- if (seenHashes[hash]) {
- // Hide the duplicate
- container.style.setProperty('display', 'none', 'important');
- container.setAttribute('data-dedup-hidden', 'true');
- console.log('Hidden duplicate media:', hash);
- } else {
- // Mark as seen
- seenHashes[hash] = true;
- localStorage.setItem(HASH_KEY, JSON.stringify(seenHashes));
- console.log('New media found:', hash);
- }
- }
- // Observe for lazy-loaded media with throttling
- let observerTimeout;
- const observer = new MutationObserver(() => {
- clearTimeout(observerTimeout);
- observerTimeout = setTimeout(runDeduplication, 500);
- });
- // Start observing
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- attributes: false,
- attributeOldValue: false,
- characterData: false
- });
- // Initial run with multiple attempts
- function initialRun() {
- setTimeout(runDeduplication, 1000);
- setTimeout(runDeduplication, 3000);
- setTimeout(runDeduplication, 5000);
- }
- // Run when page loads
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initialRun);
- } else {
- initialRun();
- }
- // Re-run when scrolling stops (for infinite scroll)
- let scrollTimeout;
- window.addEventListener('scroll', () => {
- clearTimeout(scrollTimeout);
- scrollTimeout = setTimeout(runDeduplication, 1000);
- });
- console.log('Twitter/X Media Deduplicator loaded');
- })();
Advertisement
Add Comment
Please, Sign In to add comment