Sxyprn Sorter Plus

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();
    }

})();