您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sort local media by upvotes/ratings with infinite scroll and custom styling
// ==UserScript== // @name Sxyprn Sorter Plus // @namespace http://sxyprn.com/ // @version 6.1 // @description Sort local media by upvotes/ratings with infinite scroll and custom styling // @author sharmanhall // @match *://sxyprn.com/* // @grant none // @license MIT // ==/UserScript== // ==UserScript== // @name Sxyprn Sorter Plus // @namespace https://greasyfork.org/en/users/866731-sharmanhall // @version 6.0 // @description Enhanced Sxyprn experience with smart sorting (likes, orgasmic, playlist, views), infinite scroll, custom auto-loading (1-100 pages), and toggleable UI improvements. Works on all page types with persistent settings. // @author sharmanhall // @match *://sxyprn.com/* // @match *://*.sxyprn.com/* // @icon https://sxyprn.com/favicon.ico // @license MIT // @grant none // @run-at document-end // @noframes // ==/UserScript== (function() { 'use strict'; // Custom CSS styles const customCSS = ` .sharing_toolbox, .splitter { display: none; } .next_page, .back_to, .show_more, .mysums_blog { background: none; border: none; padding: 12px 10px; margin: 12px 0; } #top_panel { padding: 20px 0; } #top_panel_menu span { padding-left: 32px; margin-left: 2px; } #player_el { height: auto; } .vid_container { border: none; } #wrapper_div { width: auto; margin: 0 100px; } #vid_container_id, .post_el_post.post_el_small { width: 100%; max-width: 100%; margin: 0; } .post_el_small { background: none; width: 556px; max-width: 100%; margin: 0; } .post_vid_thumb { display: inline-block; text-align: center; } .mini_post_vid_thumb { transform: translateY(-50%) translateX(-50%); left: 50%; } .block_header { border: none; } .splitter_block_header { background: none; top: -32px; } .main_footer { font-size: 0.8125rem; text-align: center; border: none; margin: 36px 0 26px; } .main_footer span { font-size: 0.95rem; margin: 0 0.5rem; } #scroll_top_wrap { cursor: default; } #scroll_top_div { background: none; color: #ffffff; opacity: 0.2; } #scroll_top_wrap:hover #scroll_top_div { background: none; color: #ffffff; opacity: 0.5; } .sharing_toolbox, .splitter { display: none; } .next_page, .back_to, .show_more, .mysums_blog { background: none; border: none; padding: 12px 10px; margin: 12px 0; } #top_panel { padding: 20px 0; } #top_panel_menu span { padding-left: 32px; margin-left: 2px; } #player_el { height: auto; } .vid_container { border: none; } #wrapper_div { width: auto; margin: 0 100px; } #vid_container_id, .post_el_post.post_el_small { width: 100%; max-width: 100%; margin: 0; } .post_el_small { background: none; width: 556px; max-width: 100%; margin: 0; } .post_vid_thumb { display: inline-block; text-align: center; } .mini_post_vid_thumb { transform: translateY(-50%) translateX(-50%); left: 50%; } .block_header { border: none; } .splitter_block_header { background: none; top: -32px; } .main_footer { font-size: 0.8125rem; text-align: center; border: none; margin: 36px 0 26px; } .main_footer span { font-size: 0.95rem; margin: 0 0.5rem; } #scroll_top_wrap { cursor: default; } #scroll_top_div { background: none; color: #ffffff; opacity: 0.2; } #scroll_top_wrap:hover #scroll_top_div { background: none; color: #ffffff; opacity: 0.5; } `; let customStyleElement = null; // Apply or remove custom styles function toggleCustomStyles(enable) { if (enable) { if (!customStyleElement) { customStyleElement = document.createElement('style'); customStyleElement.id = 'custom-media-styles'; customStyleElement.textContent = customCSS; document.head.appendChild(customStyleElement); } } else { if (customStyleElement) { customStyleElement.remove(); customStyleElement = null; } } // Save preference localStorage.setItem('customStylesEnabled', enable.toString()); } // Load saved preference function loadCustomStylesPreference() { const saved = localStorage.getItem('customStylesEnabled'); return saved === null ? true : saved === 'true'; // Default to enabled } // Add sorting controls function addSortControls() { const searchResults = document.querySelector('.search_results'); const mainContent = document.querySelector('.main_content'); const targetContainer = searchResults || mainContent; if (!targetContainer) return; // Create sort control container const sortContainer = document.createElement('div'); sortContainer.style.cssText = ` background: #333; padding: 10px; margin-bottom: 10px; border-radius: 5px; display: flex; gap: 10px; align-items: center; `; sortContainer.innerHTML = ` <label style="color: white; font-weight: bold;">Sort by:</label> <button id="sortLikes" style="padding: 5px 10px; background: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer;"> Likes ↓ </button> <button id="sortOrgasmic" style="padding: 5px 10px; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;"> Orgasmic ↓ </button> <button id="sortPlaylist" style="padding: 5px 10px; background: #ffc107; color: white; border: none; border-radius: 3px; cursor: pointer;"> Playlist ↓ </button> <button id="sortViews" style="padding: 5px 10px; background: #6f42c1; color: white; border: none; border-radius: 3px; cursor: pointer;"> Views ↓ </button> <button id="resetSort" style="padding: 5px 10px; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;"> Reset </button> <div style="margin-left: 20px; display: flex; align-items: center; gap: 10px; border-left: 1px solid #555; padding-left: 20px;"> <label style="color: white; font-weight: bold;">Auto-load:</label> <input type="number" id="autoLoadPages" min="1" max="100" value="3" placeholder="Pages" style="padding: 5px; border-radius: 3px; border: none; background: white; width: 60px; text-align: center;"> <span style="color: #ccc; font-size: 12px;">pages</span> <button id="autoLoadStart" style="padding: 5px 10px; background: #17a2b8; color: white; border: none; border-radius: 3px; cursor: pointer;"> Load Pages </button> <span id="autoLoadStatus" style="color: #ccc; font-size: 12px;"></span> </div> <div style="margin-left: 20px; display: flex; align-items: center; gap: 10px; border-left: 1px solid #555; padding-left: 20px;"> <label style="color: white; font-weight: bold;">Custom Styles:</label> <label style="display: flex; align-items: center; gap: 5px; cursor: pointer;"> <input type="checkbox" id="customStylesToggle" style="cursor: pointer;"> <span style="color: white; font-size: 12px;">Enhanced UI</span> </label> </div> `; // Insert before target container targetContainer.parentNode.insertBefore(sortContainer, targetContainer); // Add event listeners document.getElementById('sortLikes').addEventListener('click', () => sortPosts('likes')); document.getElementById('sortOrgasmic').addEventListener('click', () => sortPosts('orgasmic')); document.getElementById('sortPlaylist').addEventListener('click', () => sortPosts('playlist')); document.getElementById('sortViews').addEventListener('click', () => sortPosts('views')); document.getElementById('resetSort').addEventListener('click', resetSort); document.getElementById('autoLoadStart').addEventListener('click', startAutoLoad); // Custom styles toggle const customStylesToggle = document.getElementById('customStylesToggle'); customStylesToggle.checked = loadCustomStylesPreference(); toggleCustomStyles(customStylesToggle.checked); customStylesToggle.addEventListener('change', (e) => { toggleCustomStyles(e.target.checked); }); } // Store original order let originalOrder = null; function sortPosts(type) { const searchResults = document.querySelector('.search_results'); const mainContent = document.querySelector('.main_content'); const targetContainer = searchResults || mainContent; if (!targetContainer) return; const posts = Array.from(targetContainer.querySelectorAll('.post_el_small')); // Store original order if not already stored if (!originalOrder) { originalOrder = posts.slice(); } posts.sort((a, b) => { let valueA = 0; let valueB = 0; switch(type) { case 'likes': valueA = parseInt(a.querySelector('.vid_like_blog_hl')?.textContent || '0'); valueB = parseInt(b.querySelector('.vid_like_blog_hl')?.textContent || '0'); break; case 'orgasmic': valueA = parseInt(a.querySelector('.tm_orgasmic_hl')?.textContent || '0'); valueB = parseInt(b.querySelector('.tm_orgasmic_hl')?.textContent || '0'); break; case 'playlist': valueA = parseInt(a.querySelector('.tm_playlist_hl')?.textContent || '0'); valueB = parseInt(b.querySelector('.tm_playlist_hl')?.textContent || '0'); break; case 'views': const viewsA = a.querySelector('.post_control_time')?.textContent || ''; const viewsB = b.querySelector('.post_control_time')?.textContent || ''; valueA = parseInt(viewsA.match(/(\d+)\s+views/)?.[1] || '0'); valueB = parseInt(viewsB.match(/(\d+)\s+views/)?.[1] || '0'); break; } return valueB - valueA; // Descending order }); // Clear and re-append sorted posts posts.forEach(post => post.remove()); posts.forEach(post => targetContainer.appendChild(post)); // Update button states updateButtonStates(type); } function resetSort() { if (!originalOrder) return; const searchResults = document.querySelector('.search_results'); const mainContent = document.querySelector('.main_content'); const targetContainer = searchResults || mainContent; if (!targetContainer) return; // Remove all posts const currentPosts = targetContainer.querySelectorAll('.post_el_small'); currentPosts.forEach(post => post.remove()); // Re-append in original order originalOrder.forEach(post => targetContainer.appendChild(post)); // Reset button states updateButtonStates(null); } function updateButtonStates(activeType) { const buttons = ['sortLikes', 'sortOrgasmic', 'sortPlaylist', 'sortViews']; const typeMap = { 'likes': 'sortLikes', 'orgasmic': 'sortOrgasmic', 'playlist': 'sortPlaylist', 'views': 'sortViews' }; buttons.forEach(buttonId => { const button = document.getElementById(buttonId); if (button) { if (typeMap[activeType] === buttonId) { button.style.background = '#0056b3'; button.style.fontWeight = 'bold'; } else { // Reset to original colors switch(buttonId) { case 'sortLikes': button.style.background = '#007bff'; break; case 'sortOrgasmic': button.style.background = '#28a745'; break; case 'sortPlaylist': button.style.background = '#ffc107'; break; case 'sortViews': button.style.background = '#6f42c1'; break; } button.style.fontWeight = 'normal'; } } }); } // Initialize when page loads function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', addSortControls); } else { addSortControls(); } } init(); // Also handle dynamic content loading const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { const searchResults = document.querySelector('.search_results'); const mainContent = document.querySelector('.main_content'); const targetContainer = searchResults || mainContent; if (targetContainer && !document.getElementById('sortLikes')) { setTimeout(addSortControls, 100); } } }); }); observer.observe(document.body, { childList: true, subtree: true }); // Infinite scroll functionality let isLoading = false; let currentPageOffset = 0; // Track actual page offset instead of page number let hasNextPage = true; let autoLoadInProgress = false; let autoLoadTarget = 0; let autoLoadCurrent = 0; function getCurrentPageFromURL() { const urlParams = new URLSearchParams(window.location.search); const pageParam = urlParams.get('page'); return parseInt(pageParam || '0'); } function getNextPageURL() { const currentURL = new URL(window.location.href); const nextPageOffset = currentPageOffset + 30; currentURL.searchParams.set('page', nextPageOffset); return currentURL.toString(); } function loadNextPage() { if (isLoading || !hasNextPage) return; isLoading = true; // Show loading indicator const loadingDiv = document.createElement('div'); loadingDiv.id = 'infinite-scroll-loading'; loadingDiv.style.cssText = ` text-align: center; padding: 20px; font-size: 16px; color: #666; background: #f0f0f0; margin: 10px 0; border-radius: 5px; `; if (autoLoadInProgress) { loadingDiv.innerHTML = `Auto-loading page ${autoLoadCurrent + 1} of ${autoLoadTarget}...`; } else { loadingDiv.innerHTML = 'Loading more content...'; } const searchResults = document.querySelector('.search_results'); const mainContent = document.querySelector('.main_content'); const targetContainer = searchResults || mainContent; if (targetContainer) { targetContainer.appendChild(loadingDiv); } fetch(getNextPageURL()) .then(response => response.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const newPosts = doc.querySelectorAll('.post_el_small'); if (newPosts.length === 0) { hasNextPage = false; loadingDiv.innerHTML = 'No more content to load'; if (autoLoadInProgress) { finishAutoLoad(); } return; } // Remove loading indicator loadingDiv.remove(); // Append new posts to current page newPosts.forEach(post => { targetContainer.appendChild(post.cloneNode(true)); }); currentPageOffset += 30; // Increment by 30 for next page // Check if there's a next page link in the fetched content const nextPageLink = doc.querySelector('a[href*="page="]:has(.next_page)'); if (!nextPageLink) { hasNextPage = false; } isLoading = false; // Continue auto-loading if in progress if (autoLoadInProgress) { autoLoadCurrent++; updateAutoLoadStatus(); if (autoLoadCurrent >= autoLoadTarget || !hasNextPage) { finishAutoLoad(); } else { // Small delay between auto-loads to prevent overwhelming the server setTimeout(() => { loadNextPage(); }, 500); } } }) .catch(error => { console.error('Error loading next page:', error); loadingDiv.innerHTML = 'Error loading content. Scroll to try again.'; isLoading = false; if (autoLoadInProgress) { finishAutoLoad(); } }); } function handleScroll() { if (isLoading || !hasNextPage || autoLoadInProgress) return; const scrollPosition = window.innerHeight + window.scrollY; const documentHeight = document.documentElement.offsetHeight; // Load next page when user is 200px from bottom if (scrollPosition >= documentHeight - 200) { loadNextPage(); } } // Auto-load functionality function startAutoLoad() { const pagesToLoad = parseInt(document.getElementById('autoLoadPages').value); if (!pagesToLoad || pagesToLoad < 1) { alert('Please enter a valid number of pages (1-100)'); return; } if (pagesToLoad > 100) { alert('Maximum 100 pages allowed'); return; } autoLoadInProgress = true; autoLoadTarget = pagesToLoad; autoLoadCurrent = 0; const startButton = document.getElementById('autoLoadStart'); startButton.disabled = true; startButton.textContent = 'Loading...'; updateAutoLoadStatus(); loadNextPage(); } function updateAutoLoadStatus() { const statusEl = document.getElementById('autoLoadStatus'); if (statusEl) { statusEl.textContent = `Loading ${autoLoadCurrent}/${autoLoadTarget} pages...`; } } function finishAutoLoad() { autoLoadInProgress = false; const startButton = document.getElementById('autoLoadStart'); const statusEl = document.getElementById('autoLoadStatus'); if (startButton) { startButton.disabled = false; startButton.textContent = 'Load Pages'; } if (statusEl) { statusEl.textContent = `✓ Loaded ${autoLoadCurrent} pages. Manual scroll active.`; setTimeout(() => { statusEl.textContent = ''; }, 3000); } } // Initialize infinite scroll function initInfiniteScroll() { currentPageOffset = getCurrentPageFromURL(); // Check if next page exists on initial load const nextPageLink = document.querySelector('a[href*="page="]:has(i)'); if (!nextPageLink || nextPageLink.textContent.includes('Next Page')) { hasNextPage = !!nextPageLink; } // Add scroll listener window.addEventListener('scroll', handleScroll); // Hide existing pagination controls const paginationControls = document.querySelector('#center_control'); const nextPageDiv = document.querySelector('.next_page'); if (paginationControls) { paginationControls.style.display = 'none'; } if (nextPageDiv && nextPageDiv.parentElement) { nextPageDiv.parentElement.style.display = 'none'; } // Add infinite scroll indicator const scrollIndicator = document.createElement('div'); scrollIndicator.id = 'scroll-indicator'; scrollIndicator.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: rgba(0, 0, 0, 0.8); color: white; padding: 10px 15px; border-radius: 5px; font-size: 12px; z-index: 1000; `; const pageNumber = Math.floor(currentPageOffset / 30) + 1; scrollIndicator.innerHTML = `📜 Infinite Scroll Active<br>Page ${pageNumber}`; document.body.appendChild(scrollIndicator); // Update page indicator on scroll window.addEventListener('scroll', () => { const currentPageNumber = Math.floor(currentPageOffset / 30) + 1; const indicator = document.getElementById('scroll-indicator'); if (indicator) { indicator.innerHTML = `📜 Infinite Scroll Active<br>Page ${currentPageNumber}`; } }); } // Initialize infinite scroll when page loads if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initInfiniteScroll); } else { initInfiniteScroll(); } })();