您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Filters VR models on Stripchat
// ==UserScript== // @name Stripchat VR Filter // @namespace http://tampermonkey.net/ // @version 1.0 // @description Filters VR models on Stripchat // @author Sergi0 // @match https://*.stripchat.com/* // @grant GM_addStyle // @icon https://stripchat.com/favicon.ico // @license MIT // @run-at document-idle // @homepageURL https://greasyfork.org/es/scripts/538673-stripchat-vr-filter // @supportURL https://greasyfork.org/es/scripts/538673-stripchat-vr-filter/feedback // ==/UserScript== (function() { 'use strict'; let vrFilterEnabled = false; let observer = null; let currentPageUrl = window.location.href; // Function to apply the VR filter function applyVrFilter() { const models = document.querySelectorAll('.model-list-item'); models.forEach(model => { const vrBadge = model.querySelector('span[class*="ModelListItemBadge__vr"]'); const hasVrBadge = !!vrBadge; if (vrFilterEnabled) { if (hasVrBadge) { // Show VR models model.style.setProperty('display', 'block', 'important'); } else { // Hide non-VR models model.style.setProperty('display', 'none', 'important'); } } else { // If filter is disabled, show all models model.style.setProperty('display', 'block', 'important'); // Ensure all models are visible } }); } // Function to create and add the toggle to the DOM function addVrToggleButton() { const existingToggle = document.getElementById('vrFilterToggleContainer'); if (existingToggle) { existingToggle.remove(); } const toggleContainer = document.createElement('div'); toggleContainer.id = 'vrFilterToggleContainer'; toggleContainer.style.position = 'fixed'; toggleContainer.style.bottom = '10px'; toggleContainer.style.right = '10px'; toggleContainer.style.zIndex = '9999'; toggleContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; toggleContainer.style.color = 'white'; toggleContainer.style.padding = '10px 15px'; toggleContainer.style.borderRadius = '5px'; toggleContainer.style.fontFamily = 'Arial, sans-serif'; toggleContainer.style.display = 'flex'; toggleContainer.style.alignItems = 'center'; toggleContainer.style.gap = '10px'; toggleContainer.style.cursor = 'pointer'; const label = document.createElement('span'); label.textContent = 'Show VR Models Only'; label.style.marginRight = '10px'; label.style.whiteSpace = 'nowrap'; const slider = document.createElement('label'); slider.className = 'switch'; const input = document.createElement('input'); input.type = 'checkbox'; input.id = 'vrFilterCheckbox'; input.checked = vrFilterEnabled; input.addEventListener('change', (event) => { vrFilterEnabled = event.target.checked; applyVrFilter(); localStorage.setItem('vrFilterEnabled', vrFilterEnabled); }); const spanSlider = document.createElement('span'); spanSlider.className = 'slider round'; slider.appendChild(input); slider.appendChild(spanSlider); toggleContainer.appendChild(label); toggleContainer.appendChild(slider); document.body.appendChild(toggleContainer); } // Add styles for the toggle GM_addStyle(` .switch { position: relative; display: inline-block; width: 40px; height: 24px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: .4s; transition: .4s; } .slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: .4s; transition: .4s; } input:checked + .slider { background-color: #2196F3; } input:focus + .slider { box-shadow: 0 0 1px #2196F3; } input:checked + .slider:before { -webkit-transform: translateX(16px); -ms-transform: translateX(16px); transform: translateX(16px); } /* Rounded sliders */ .slider.round { border-radius: 24px; } .slider.round:before { border-radius: 50%; } `); // Mutation Observer to detect DOM changes and URL changes function startObserver() { if (observer) { observer.disconnect(); } const targetNode = document.querySelector('.js-list-content') || document.body; observer = new MutationObserver(function(mutations) { let modelsChangeDetected = false; let urlChanged = (window.location.href !== currentPageUrl); // Check for added nodes that are model-list-item or contain them for (const mutation of mutations) { if (mutation.type === 'childList') { if (mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { // Check if the added node itself is a model-list-item or contains one if (node.classList.contains('model-list-item') || node.querySelector('.model-list-item')) { modelsChangeDetected = true; break; } } } } // Also check if existing model-list-items had their 'style' or 'class' attributes changed. // This is less common for "showing/hiding" issues, but good for robustness. } else if (mutation.type === 'attributes' && mutation.target.classList.contains('model-list-item')) { modelsChangeDetected = true; } if (modelsChangeDetected) break; } // If URL changed, re-initialize everything (likely a new page load) if (urlChanged) { currentPageUrl = window.location.href; console.log('URL changed, re-initializing script.'); // Debug initializeScript(); } else if (modelsChangeDetected) { // If models were added/changed (e.g., infinite scroll or dynamic update), apply filter console.log('Models changed, applying filter.'); // Debug setTimeout(applyVrFilter, 100); // Increased delay slightly } }); // Observe changes on the targetNode: child additions/removals and attribute changes // Use subtree: true for deep observation observer.observe(targetNode, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] }); } // Initialize the script function initializeScript() { const savedState = localStorage.getItem('vrFilterEnabled'); if (savedState !== null) { vrFilterEnabled = savedState === 'true'; } else { vrFilterEnabled = false; // Filter is OFF by default } addVrToggleButton(); // Apply filter after a slightly longer delay on initial load/re-init setTimeout(applyVrFilter, 200); // Increased initial delay startObserver(); } // Execute initialization when the DOM is fully loaded, with an initial delay if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => setTimeout(initializeScript, 200)); } else { setTimeout(initializeScript, 200); } // Listen for SPA navigation events window.addEventListener('popstate', () => { console.log('Popstate event detected, re-initializing script.'); // Debug setTimeout(initializeScript, 200); }); window.addEventListener('hashchange', () => { console.log('Hashchange event detected, re-initializing script.'); // Debug setTimeout(initializeScript, 200); }); // Also consider using a custom event listener if Stripchat dispatches one // when content is fully loaded after an SPA navigation. This is site-specific. // For now, the combination of URL changes and MutationObserver should cover most cases. })();