您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Разбивает плейлист на страницы с поддержкой динамической подгрузки, URL-параметрами и сохранением настроек
// ==UserScript== // @name Playlist PH Pagination Enhanced // @namespace https://www.ph.com/ // @version 1.4 // @description Разбивает плейлист на страницы с поддержкой динамической подгрузки, URL-параметрами и сохранением настроек // @match *://*.pornhub.com/playlist/* // @match *://*.pornhubpremium.com/playlist/* // @run-at document-end // @grant GM.getValue // @grant GM.setValue // @license MIT // ==/UserScript== (async function() { 'use strict'; const DEFAULT_ITEMS_PER_PAGE = 20; const MAX_PAGE_BUTTONS = 7; const REFRESH_INTERVAL = 1000; const SESSION_STORAGE_PREFIX = 'ph_pagination_'; async function getStoredValue(key, defaultValue) { const sessionValue = sessionStorage.getItem(SESSION_STORAGE_PREFIX + key); if (sessionValue !== null) { return JSON.parse(sessionValue); } return await GM.getValue(key, defaultValue); } async function storeValue(key, value) { sessionStorage.setItem(SESSION_STORAGE_PREFIX + key, JSON.stringify(value)); await GM.setValue(key, value); } let itemsPerPage = await getStoredValue('ph_items_per_page', DEFAULT_ITEMS_PER_PAGE); let currentPage = 1; let items = []; let totalPages = 1; let refreshTimer = null; let lastItemCount = 0; const playlistId = window.location.pathname.split('/').pop(); const itemsCountKey = 'ph_items_count_' + playlistId; function getUrlParams() { const urlParams = new URLSearchParams(window.location.search); return { page: parseInt(urlParams.get('page')) || 1 }; } function updateUrlWithPage(page) { const url = new URL(window.location); url.searchParams.set('page', page); history.replaceState({}, '', url); } const urlParams = getUrlParams(); if (urlParams.page > 1) { currentPage = urlParams.page; } else if (playlistId) { currentPage = await getStoredValue('ph_current_page_' + playlistId, 1); } function getContainer() { return document.querySelector('.js-playlistWrapper'); } function waitForContainer() { if (document.getElementById('ph-pager')) return; const container = getContainer(); if (!container) return setTimeout(waitForContainer, 300); setupPagination(container); } async function refreshItemsList() { const container = getContainer(); if (!container) return; const newItems = Array.from(container.querySelectorAll('.pcVideoListItem, .js-playlistWrapper')); lastItemCount = newItems.length; items = newItems; totalPages = Math.ceil(items.length / itemsPerPage); if (playlistId) { await storeValue(itemsCountKey, items.length); } createPagination(); const counter = document.getElementById('ph-counter'); if (counter) { const start = (currentPage - 1) * itemsPerPage; const end = start + itemsPerPage; counter.textContent = `Видео ${start + 1}-${Math.min(end, items.length)} из ${items.length}`; } applyCurrentPage(); } function setupScrollHandler() { window.addEventListener('scroll', function() { if (refreshTimer) clearTimeout(refreshTimer); refreshTimer = setTimeout(refreshItemsList, REFRESH_INTERVAL); }); } async function setupPagination(container) { items = Array.from(container.querySelectorAll('.pcVideoListItem, .js-playlistWrapper')); const savedItemCount = await getStoredValue(itemsCountKey, 0); if (!items.length && savedItemCount > 0) { lastItemCount = savedItemCount; setTimeout(refreshItemsList, 1000); } else { if (!items.length) return; lastItemCount = items.length; if (playlistId) { await storeValue(itemsCountKey, items.length); } } totalPages = Math.ceil(lastItemCount / itemsPerPage); const controls = document.createElement('div'); controls.id = 'ph-controls'; controls.style = 'display: flex; justify-content: space-between; margin: 10px 0; padding: 10px; background: #1b1b1b; border-radius: 5px; position: sticky; top: 0; z-index: 100;'; const settingsDiv = document.createElement('div'); settingsDiv.style = 'display: flex; align-items: center;'; const label = document.createElement('label'); label.textContent = 'Видео на странице: '; label.style = 'margin-right: 10px; color: #fff;'; const select = document.createElement('select'); select.style = 'padding: 3px; background: #333; color: #fff; border: 1px solid #555;'; [10, 20, 30, 50, 100].forEach(num => { const option = document.createElement('option'); option.value = num; option.textContent = num; option.selected = (num === itemsPerPage); select.appendChild(option); }); select.onchange = async function() { itemsPerPage = parseInt(this.value); await storeValue('ph_items_per_page', itemsPerPage); totalPages = Math.ceil(items.length / itemsPerPage); createPagination(); showPage(1); }; settingsDiv.appendChild(label); settingsDiv.appendChild(select); const counter = document.createElement('div'); counter.id = 'ph-counter'; counter.style = 'color: #fff;'; counter.textContent = `Всего видео: ${items.length}`; const refreshBtn = document.createElement('button'); refreshBtn.textContent = '⟳ Обновить'; refreshBtn.style = 'margin-left: 10px; padding: 3px 8px; background: #333; color: #fff; border: 1px solid #555; cursor: pointer;'; refreshBtn.onclick = refreshItemsList; settingsDiv.appendChild(refreshBtn); controls.appendChild(settingsDiv); controls.appendChild(counter); container.parentNode.insertBefore(controls, container); const pager = document.createElement('div'); pager.id = 'ph-pager'; pager.style = 'text-align: center; margin: 10px 0; padding: 10px;'; container.parentNode.insertBefore(pager, container); const bottomPager = document.createElement('div'); bottomPager.id = 'ph-pager-bottom'; bottomPager.style = 'text-align: center; margin: 10px 0; padding: 10px;'; if (container.nextSibling) { container.parentNode.insertBefore(bottomPager, container.nextSibling); } else { container.parentNode.appendChild(bottomPager); } createPagination(); const urlParams = getUrlParams(); if (urlParams.page > 1 && urlParams.page <= totalPages) { showPage(urlParams.page); } else { showPage(currentPage); } setupScrollHandler(); } function createPagination() { updatePaginationPanel(document.getElementById('ph-pager')); updatePaginationPanel(document.getElementById('ph-pager-bottom')); } function updatePaginationPanel(pager) { if (!pager) return; pager.innerHTML = ''; const buttonStyle = 'margin: 0 3px; padding: 5px 10px; background: #ff9000; color: #000; border: none; border-radius: 3px; cursor: pointer; transition: background 0.3s;'; const disabledStyle = 'background: #444; color: #aaa; cursor: not-allowed;'; const activeStyle = 'background: #ff5400; font-weight: bold;'; const btnFirst = document.createElement('button'); btnFirst.textContent = '«'; btnFirst.style = buttonStyle; btnFirst.onclick = () => showPage(1); pager.appendChild(btnFirst); const btnPrev = document.createElement('button'); btnPrev.textContent = '‹'; btnPrev.style = buttonStyle; btnPrev.onclick = () => showPage(currentPage - 1); pager.appendChild(btnPrev); let startPage = Math.max(1, currentPage - Math.floor(MAX_PAGE_BUTTONS / 2)); let endPage = Math.min(totalPages, startPage + MAX_PAGE_BUTTONS - 1); if (endPage - startPage + 1 < MAX_PAGE_BUTTONS) { startPage = Math.max(1, endPage - MAX_PAGE_BUTTONS + 1); } if (startPage > 1) { const ellipsis = document.createElement('span'); ellipsis.textContent = '...'; ellipsis.style = 'margin: 0 10px; color: #fff;'; pager.appendChild(ellipsis); } for (let i = startPage; i <= endPage; i++) { const btn = document.createElement('button'); btn.textContent = i; btn.style = buttonStyle + (i === currentPage ? activeStyle : ''); btn.onclick = () => showPage(i); pager.appendChild(btn); } if (endPage < totalPages) { const ellipsis = document.createElement('span'); ellipsis.textContent = '...'; ellipsis.style = 'margin: 0 10px; color: #fff;'; pager.appendChild(ellipsis); } const btnNext = document.createElement('button'); btnNext.textContent = '›'; btnNext.style = buttonStyle + (currentPage === totalPages ? disabledStyle : ''); btnNext.onclick = () => showPage(currentPage + 1); btnNext.disabled = (currentPage === totalPages); pager.appendChild(btnNext); const btnLast = document.createElement('button'); btnLast.textContent = '»'; btnLast.style = buttonStyle + (currentPage === totalPages ? disabledStyle : ''); btnLast.onclick = () => showPage(totalPages); btnLast.disabled = (currentPage === totalPages); pager.appendChild(btnLast); const pageInfo = document.createElement('span'); pageInfo.style = 'margin-left: 15px; color: #fff;'; pageInfo.textContent = `Страница ${currentPage} из ${totalPages}`; pager.appendChild(pageInfo); } function showPage(page) { if (page < 1 || page > totalPages) return; currentPage = page; updateUrlWithPage(currentPage); if (playlistId) storeValue('ph_current_page_' + playlistId, currentPage); applyCurrentPage(); createPagination(); const pageJumpInputs = document.querySelectorAll('input[type="number"]'); pageJumpInputs.forEach(input => { input.value = currentPage; input.max = totalPages; }); window.scrollTo(0, 0); } function applyCurrentPage() { const start = (currentPage - 1) * itemsPerPage; const end = start + itemsPerPage; items.forEach((el, idx) => { el.style.display = (idx >= start && idx < end) ? '' : 'none'; }); const counter = document.getElementById('ph-counter'); if (counter) { counter.textContent = `Видео ${start + 1}-${Math.min(end, items.length)} из ${items.length}`; } } function setupMutationObserver() { const container = getContainer(); if (!container) return; const observer = new MutationObserver(() => refreshItemsList()); observer.observe(container, { childList: true, subtree: true }); } window.addEventListener('popstate', function() { const urlParams = getUrlParams(); if (urlParams.page && urlParams.page !== currentPage && urlParams.page <= totalPages) { showPage(urlParams.page); } }); function restoreStoredData() { if (playlistId) { getStoredValue(itemsCountKey, 0).then(savedItemCount => { if (savedItemCount > 0) { lastItemCount = savedItemCount; totalPages = Math.ceil(savedItemCount / itemsPerPage); console.log(`[Playlist Pagination] Восстановлены данные: ${savedItemCount} видео, ${totalPages} страниц`); } }); } } window.addEventListener('load', function() { restoreStoredData(); setTimeout(() => { waitForContainer(); setupMutationObserver(); }, 1000); }); window.addEventListener('beforeunload', function() { console.log('[Playlist Pagination] Страница закрывается, данные сессии сохранены'); }); let lastUrl = location.href; const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(() => { waitForContainer(); setupMutationObserver(); }, 1000); } }); urlObserver.observe(document, { subtree: true, childList: true }); setTimeout(() => { waitForContainer(); setupMutationObserver(); }, 1000); const style = document.createElement('style'); style.textContent = `#ph-controls { display: flex; justify-content: space-between; margin: 10px 0; padding: 10px; background: #1b1b1b; border-radius: 5px; position: sticky; top: 0; z-index: 100; } #ph-pager, #ph-pager-bottom { text-align: center; margin: 10px 0; padding: 10px; } #ph-pager button, #ph-pager-bottom button { margin: 0 3px; padding: 5px 10px; background: #ff9000; color: #000; border: none; border-radius: 3px; cursor: pointer; transition: background 0.3s; } #ph-pager button:hover, #ph-pager-bottom button:hover { background: #ffb244; } #ph-pager button:disabled, #ph-pager-bottom button:disabled { background: #444; color: #aaa; cursor: not-allowed; } #ph-pager button.active, #ph-pager-bottom button.active { background: #ff5400; font-weight: bold; } input[type="number"] { width: 60px; padding: 3px; background: #333; color: #fff; border: 1px solid #555; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { opacity: 1; }`; document.head.appendChild(style); })();