// ==UserScript== // @name r18.dev duplicate movie remover // @namespace Violentmonkey Scripts // @match https://r18.dev/videos/vod/movies/list/* // @grant none // @version 1.12 // @author Lukáš Kucharczyk // @description Removes duplicate entries in the list of movies on r18.dev. // @downloadURL https://git.kucharczyk.xyz/lukas/userscripts/raw/branch/main/r18dev_remove_duplicates.user.js // @supportURL https://git.kucharczyk.xyz/lukas/userscripts // @run-at document-idle // ==/UserScript== function filterVideos() { // Select all .video containers const videos = document.querySelectorAll('.video'); // Only log if we find videos to avoid spamming console during observation if (videos.length > 0) { console.log(`Found videos: ${videos.length}`); } // Regex to match "Letters-Numbers" (e.g., AAA-111) const regex = /[a-zA-Z]+-[0-9]+/; let removedCount = 0; // Changed to 'let' so it can be incremented videos.forEach(video => { const label = video.querySelector('.video-label'); // Safety check if label exists if (label) { const text = label.textContent.trim(); const linkCount = label.querySelectorAll('a').length; // Remove if text doesn't match pattern OR label has more than 1 link if (!regex.test(text) || linkCount > 1) { video.remove(); removedCount++; } } }); if (removedCount > 0) { console.log(`Removed videos: ${removedCount}`); } } // 1. Run immediately in case elements are already there filterVideos(); // 2. Set up a MutationObserver to handle dynamic content (AJAX/Infinite Scroll) const observer = new MutationObserver((mutations) => { let shouldRun = false; // Check if nodes were actually added for (const mutation of mutations) { if (mutation.addedNodes.length) { shouldRun = true; break; } } if (shouldRun) { filterVideos(); } }); // Start observing the body for changes observer.observe(document.body, { childList: true, subtree: true });