您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Various search filters and user experience enhancers
当前为
// ==UserScript== // @name PH - Search & UI Tweaks // @namespace brazenvoid // @version 1.16.0 // @author brazenvoid // @license GPL-3.0-only // @description Various search filters and user experience enhancers // @include https://www.pornhub.com/* // @require https://greasyfork.org/scripts/375557-base-resource/code/Base%20Resource.js?version=835825 // @grant GM_addStyle // @run-at document-end // ==/UserScript== // Settings & Defaults let settings = { blacklist: [ // case-insensitive 'asmr', 'urine', 'arab', 'mmd', 'muslim', 'desi', 'fake', 'pee', 'granny', ], sanitize: { // Substitutes values with key (case-insensitive) '': ['neighbor', 'step'], boyfriend: ['brother', 'bro', 'daddy', 'dad', 'son', 'father', 'stepbro', 'stepbrother', 'stepson'], girlfriend: ['daughter', 'mom', 'mother', 'sister', 'sis', 'stepsis', 'stepsister', 'stepdaughter'], }, duration: { // In Seconds minimum: 60, maximum: 0, }, rating: { minimum: 70, maximum: 0, }, views: { minimum: 0, maximum: 0, }, hideSDVideos: false, hidePaidVideos: true, hidePremiumVideos: true, hidePrivateVideos: true, hideProChannelVideos: true, hideUnratedVideos: false, hideWatchedVideos: false, linkDisablePlaylistControls: false, linkUserPublicVideos: false, removeIFrames: true, removeLiveModelsSection: true, removePornStarsListingInSidebar: true, showUIAlways: false, debugLogging: false, } const IS_PLAYLIST_PAGE = window.location.pathname.startsWith('/playlist') const FILTER_DURATION_KEY = 'Duration' const FILTER_HD_VIDEOS_KEY = 'Only HD Videos' const FILTER_PAID_VIDEOS_KEY = 'Paid' const FILTER_PREMIUM_VIDEOS_KEY = 'Premium' const FILTER_PRO_CHANNEL_VIDEOS_KEY = 'Pro Channel' const FILTER_PRIVATE_VIDEOS_KEY = 'Private' const FILTER_RATING_KEY = 'Rating' const FILTER_UNRATED_VIDEOS_KEY = 'Unrated' const FILTER_VIEWS_KEY = 'Views' const FILTER_WATCHED_VIDEOS_KEY = 'Watched' const LINK_DISABLE_PLAYLIST_CONTROLS_KEY = 'Disable Playlist Controls' const LINK_USER_PUBLIC_VIDEOS_KEY = 'User Public Videos' const OPTION_ALWAYS_SHOW_UI = 'Always Show UI' // Base Resources Initialization const scriptPrefix = 'ph-sui-' let storage = new LocalStore(scriptPrefix + 'settings', settings) settings = storage.retrieve().get() let logger = new Logger(settings.debugLogging) let selectorGenerator = new SelectorGenerator(scriptPrefix) let statistics = new StatisticsRecorder(logger, selectorGenerator) let UI = new UIGenerator(settings.showUIAlways, selectorGenerator) let validator = new Validator(statistics) validator.addBlacklistFilter(settings.blacklist).addSanitizationFilter(settings.sanitize).optimize() function getVideoLists () { return document.querySelectorAll('ul.videos') } // Local Store Events let refreshUI = function () { let store = this.get() UI.setSettingsRangeInputValue(FILTER_DURATION_KEY, store.duration.minimum, store.duration.maximum) UI.setSettingsRangeInputValue(FILTER_RATING_KEY, store.rating.minimum, store.rating.maximum) UI.setSettingsRangeInputValue(FILTER_VIEWS_KEY, store.views.minimum, store.views.maximum) UI.setSettingsInputCheckedStatus(OPTION_ALWAYS_SHOW_UI, store.showUIAlways) UI.setSettingsInputCheckedStatus(FILTER_HD_VIDEOS_KEY, store.hideSDVideos) UI.setSettingsInputCheckedStatus(FILTER_PAID_VIDEOS_KEY, store.hidePaidVideos) UI.setSettingsInputCheckedStatus(FILTER_PREMIUM_VIDEOS_KEY, store.hidePremiumVideos) UI.setSettingsInputCheckedStatus(FILTER_PRO_CHANNEL_VIDEOS_KEY, store.hideProChannelVideos) UI.setSettingsInputCheckedStatus(FILTER_PRIVATE_VIDEOS_KEY, store.hidePrivateVideos) UI.setSettingsInputCheckedStatus(FILTER_WATCHED_VIDEOS_KEY, store.hideWatchedVideos) UI.setSettingsInputCheckedStatus(FILTER_UNRATED_VIDEOS_KEY, store.hideUnratedVideos) UI.setSettingsInputCheckedStatus(LINK_DISABLE_PLAYLIST_CONTROLS_KEY, store.linkDisablePlaylistControls) UI.setSettingsInputCheckedStatus(LINK_USER_PUBLIC_VIDEOS_KEY, store.linkUserPublicVideos) } storage.onDefaultsLoaded = refreshUI storage.onRetrieval = refreshUI storage.onUpdated = refreshUI // Validators // -- Duration validation let validateDuration = (videoItem) => { if (settings.duration.minimum > 0 || settings.duration.maximum > 0) { let durationNode = videoItem.querySelector('.duration') if (durationNode !== null) { let duration = durationNode.textContent.split(':') duration = (parseInt(duration[0]) * 60) + parseInt(duration[1]) return validator.validateRange(FILTER_DURATION_KEY, duration, [settings.duration.minimum, settings.duration.maximum]) } } return true } // -- High definition validation let validateHD = (videoItem) => { let validationCheck = true if (settings.hideSDVideos) { validationCheck = videoItem.querySelector('.hd-thumbnail') !== null statistics.record('SD', validationCheck) } return validationCheck } // -- Paid video validation let validatePaidVideo = (videoItem) => { let validationCheck = true if (settings.hidePaidVideos) { validationCheck = videoItem.querySelector('.p2v-icon, .fanClubVideoWrapper') === null logger.logValidation('Paid Video', validationCheck) } return validationCheck } // -- Premium video validation let validatePremiumVideo = (videoItem) => { let validationCheck = true if (settings.hidePremiumVideos) { validationCheck = videoItem.querySelector('.premiumIcon') === null logger.logValidation('Premium Video', validationCheck) } return validationCheck } // -- Private video validation let validatePrivateVideo = (videoItem) => { let validationCheck = true if (settings.hidePrivateVideos) { validationCheck = videoItem.querySelector('.privateOverlay') === null logger.logValidation('Private Video', validationCheck) } return validationCheck } // -- Professional channel video validation let validateProfessionalChannelVideo = (videoItem) => { let validationCheck = true if (settings.hideProChannelVideos) { validationCheck = videoItem.querySelector('.channel-icon') === null statistics.record(FILTER_PRO_CHANNEL_VIDEOS_KEY, validationCheck) } return validationCheck } // -- Playlist link manipulation let validateVideoLink = (videoItem) => { if (settings.linkDisablePlaylistControls) { let playlistLinks = videoItem.querySelectorAll('a.linkVideoThumb, span.title a') for (let playlistLink of playlistLinks) { playlistLink.setAttribute('href', playlistLink.getAttribute('href').replace(/&pkey.*/, '')) } } } // -- Rating validation let validateRating = (videoItem) => { let validationCheck = true if (settings.rating.minimum > 0 || settings.rating.maximum > 0) { let rating = videoItem.querySelector('.value') if (rating === null) { validationCheck = !settings.hideUnratedVideos statistics.record(FILTER_RATING_KEY, validationCheck) } else { rating = parseInt(rating.textContent.replace('%', '')) if (rating === 0) { validationCheck = !settings.hideUnratedVideos } else { validationCheck = validator.validateRange(FILTER_RATING_KEY, rating, [ settings.rating.minimum, settings.rating.maximum, ]) } } } return validationCheck } // -- View count validation let validateViews = (videoItem) => { if (settings.views.minimum > 0 || settings.views.maximum > 0) { let viewsCountString = videoItem.querySelector('.views').textContent.replace(' views', '') let viewsCountMultiplier = 1 let viewsCountStringLength = viewsCountString.length if (viewsCountString[viewsCountStringLength - 1] === 'K') { viewsCountMultiplier = 1000 viewsCountString = viewsCountString.replace('K', '') } else { if (viewsCountString[viewsCountStringLength - 1] === 'M') { viewsCountMultiplier = 1000000 viewsCountString = viewsCountString.replace('M', '') } } let viewsCount = parseFloat(viewsCountString) * viewsCountMultiplier return validator.validateRange(FILTER_VIEWS_KEY, viewsCount, [ settings.views.minimum, settings.views.maximum, ]) } return true } // -- Watched video validation let validateWatchStatus = (videoItem) => { let validationCheck = true if (settings.hideWatchedVideos) { validationCheck = videoItem.querySelector('.watchedVideoText') === null statistics.record(FILTER_WATCHED_VIDEOS_KEY, validationCheck) } return validationCheck } // -- Compliance logic let complianceCallback = (target) => { complyVideoSections() let videoItems, videoName, videoComplies if (target instanceof NodeList) { videoItems = [] target.forEach((node) => { if (typeof node.classList !== 'undefined' && node.classList.contains('videoblock')) { videoItems.push(node) } }) } else { videoItems = target.querySelectorAll('.videoblock') } for (let videoItem of videoItems) { videoName = videoItem.querySelector('.title > a') logger.logVideoCheck(videoName.textContent) videoComplies = validatePaidVideo(videoItem) && validatePremiumVideo(videoItem) && validatePrivateVideo(videoItem) && validateProfessionalChannelVideo(videoItem) && validateWatchStatus(videoItem) && validateHD(videoItem) && validateRating(videoItem) && validator.validateBlackList(videoName.textContent) && validateDuration(videoItem) && validateViews(videoItem) if (videoComplies) { videoItem.style.display = 'inline-block' validator.sanitizeVideoItem(videoName) if (IS_PLAYLIST_PAGE) { validateVideoLink(videoItem) } } else { videoItem.style.display = 'none' } logger.logSeparator() } statistics.updateUI() } // UI Composition // -- Control Panel let section = UI.createSection('settings', '#ffa31a', '5vh', '240px'). addSectionChildren([ UI.createFormRangeInputGroup(FILTER_DURATION_KEY, 'number', [ settings.duration.minimum, settings.duration.maximum, ]), UI.createFormRangeInputGroup(FILTER_RATING_KEY, 'number', [ settings.rating.minimum, settings.rating.maximum, ]), UI.createFormRangeInputGroup(FILTER_VIEWS_KEY, 'number', [ settings.views.minimum, settings.views.maximum, ]), UI.createFormInputGroup( OPTION_ALWAYS_SHOW_UI, 'checkbox', settings.showUIAlways, 'Always show this interface.'), UI.createFormInputGroup( FILTER_HD_VIDEOS_KEY, 'checkbox', settings.hideSDVideos, 'Show only HD videos.'), UI.createSeparator(), UI.createFormSection('Hide Videos', [ UI.createFormActions([ UI.createFormInputGroup( FILTER_PAID_VIDEOS_KEY, 'checkbox', settings.hidePaidVideos, 'Hide paid videos.'), UI.createFormInputGroup( FILTER_PREMIUM_VIDEOS_KEY, 'checkbox', settings.hidePremiumVideos, 'Hide Premium Only Videos.'), UI.createFormInputGroup( FILTER_PRIVATE_VIDEOS_KEY, 'checkbox', settings.hidePrivateVideos, 'Hide videos needing befriended status.'), ]), UI.createFormActions([ UI.createFormInputGroup( FILTER_PRO_CHANNEL_VIDEOS_KEY, 'checkbox', settings.hideProChannelVideos, 'Hide videos from professional channels.'), UI.createFormInputGroup( FILTER_WATCHED_VIDEOS_KEY, 'checkbox', settings.hideWatchedVideos, 'Hide already watched videos.'), ]), UI.createFormActions([ UI.createFormInputGroup( FILTER_UNRATED_VIDEOS_KEY, 'checkbox', settings.hideUnratedVideos, 'Hide videos with 0% rating.'), ]), ]), UI.createSeparator(), UI.createFormSection('Link Changes', [ UI.createFormInputGroup( LINK_DISABLE_PLAYLIST_CONTROLS_KEY, 'checkbox', settings.linkDisablePlaylistControls, 'Disable playlist controls on video pages.'), UI.createFormInputGroup( LINK_USER_PUBLIC_VIDEOS_KEY, 'checkbox', settings.linkUserPublicVideos, 'Jump directly to user public videos on any profile link click.'), ]), UI.createSeparator(), UI.createSettingsFormActions(storage, () => { settings.duration.minimum = UI.getSettingsRangeInputValue(FILTER_DURATION_KEY, true) settings.duration.maximum = UI.getSettingsRangeInputValue(FILTER_DURATION_KEY, false) settings.rating.minimum = UI.getSettingsRangeInputValue(FILTER_RATING_KEY, true) settings.rating.maximum = UI.getSettingsRangeInputValue(FILTER_RATING_KEY, false) settings.views.minimum = UI.getSettingsRangeInputValue(FILTER_VIEWS_KEY, true) settings.views.maximum = UI.getSettingsRangeInputValue(FILTER_VIEWS_KEY, false) settings.showUIAlways = UI.getSettingsInputCheckedStatus(OPTION_ALWAYS_SHOW_UI) settings.hideSDVideos = UI.getSettingsInputCheckedStatus(FILTER_HD_VIDEOS_KEY) settings.hidePaidVideos = UI.getSettingsInputCheckedStatus(FILTER_PAID_VIDEOS_KEY) settings.hidePremiumVideos = UI.getSettingsInputCheckedStatus(FILTER_PREMIUM_VIDEOS_KEY) settings.hidePrivateVideos = UI.getSettingsInputCheckedStatus(FILTER_PRIVATE_VIDEOS_KEY) settings.hideProChannelVideos = UI.getSettingsInputCheckedStatus(FILTER_PRO_CHANNEL_VIDEOS_KEY) settings.hideWatchedVideos = UI.getSettingsInputCheckedStatus(FILTER_WATCHED_VIDEOS_KEY) settings.hideUnratedVideos = UI.getSettingsInputCheckedStatus(FILTER_UNRATED_VIDEOS_KEY) settings.linkDisablePlaylistControls = UI.getSettingsInputCheckedStatus(LINK_DISABLE_PLAYLIST_CONTROLS_KEY) settings.linkUserPublicVideos = UI.getSettingsInputCheckedStatus(LINK_USER_PUBLIC_VIDEOS_KEY) statistics.reset() for (let videoList of getVideoLists()) { complianceCallback(videoList) } }), UI.createSeparator(), UI.createStoreFormSection(storage), UI.createSeparator(), UI.createStatisticsFormGroup('SD', 'Low Res'), UI.createStatisticsFormGroup(FILTER_DURATION_KEY, 'Short'), UI.createStatisticsFormGroup(FILTER_RATING_KEY, 'Low Rated'), UI.createStatisticsFormGroup(FILTER_VIEWS_KEY, 'By Views'), UI.createStatisticsFormGroup('Blacklist', 'Blacklisted'), UI.createStatisticsFormGroup(FILTER_PRO_CHANNEL_VIDEOS_KEY, FILTER_PRO_CHANNEL_VIDEOS_KEY), UI.createStatisticsFormGroup(FILTER_WATCHED_VIDEOS_KEY, FILTER_WATCHED_VIDEOS_KEY), UI.createStatisticsFormGroup('Total'), UI.createSeparator(), UI.createStatusSection(), ]) UI.constructor.appendToBody(section) UI.constructor.appendToBody(UI.createSettingsShowButton('', section)) logger.logTaskCompletion('Building UI') // -- Move pagination section and other fixes let videosSection = document.querySelector('.nf-videos') if (videosSection !== null) { let paginationBlock = document.querySelector('.pagination3') videosSection.appendChild(paginationBlock) } // -- Fix search section div heights for (let div of document.querySelectorAll('.showingCounter, .tagsForWomen')) { div.style.height = 'auto' } // Script run // -- Videos sections removal function complyVideoSections () { function complyVideoSection (setting, linkSuffix) { let videoSectionLink = document.querySelector('.videoSection > div > div > h2 > a[href$="/' + linkSuffix + '"]') if (videoSectionLink !== null) { videoSectionLink.closest('.videoSection').style.display = setting ? 'none' : 'block' } } complyVideoSection(settings.hidePaidVideos, FILTER_PAID_VIDEOS_KEY) complyVideoSection(settings.hidePremiumVideos, 'fanonly') complyVideoSection(settings.hidePrivateVideos, FILTER_PRIVATE_VIDEOS_KEY) } // -- Initial compliance run & observer attachment for (let list of getVideoLists()) { ChildObserver.create().onNodesAdded((nodes) => complianceCallback(nodes)).observe(list) complianceCallback(list) } let recommendedVideosHandler = (waitIteration = 1) => { let button = document.querySelector('.more_related_btn') button.click() button.click() if (button.style.display !== 'none') { waitIteration += 1 if (waitIteration < 5) { sleep(1000).then(() => recommendedVideosHandler(waitIteration)) } } else { complianceCallback(document.querySelector('#relateRecommendedItems')) } } if (document.querySelector('#relateRecommendedItems')) { sleep(2000).then(() => recommendedVideosHandler()) } validator.sanitizeVideoPage('.inlineFree') UI.updateStatus('Initial run completed.') logger.logTaskCompletion('Initial run and observer attachment.') // -- IFrames removal let removePHIframes = () => { let iframes = document.getElementsByTagName('milktruck') for (let iframe of iframes) { iframe.remove() } return iframes.length } if (settings.removeIFrames) { Validator.iFramesRemover() let iframesCount do { iframesCount = removePHIframes() } while (iframesCount) } // -- Link Manipulations if (settings.linkUserPublicVideos) { let userProfileLinks = document.querySelectorAll('.usernameBadgesWrapper a, a.usernameLink, .usernameWrap a'), href for (let userProfileLink of userProfileLinks) { href = userProfileLink.getAttribute('href') if (href.startsWith('/channels') || href.startsWith('/model')) { userProfileLink.setAttribute('href', href + '/videos') } else { if (href.startsWith('/user')) { userProfileLink.setAttribute('href', href + '/videos/public') } } } } // -- Live models section removal if (settings.removeLiveModelsSection) { let liveModelStreamsSection = document.querySelector('.streamateContent') if (liveModelStreamsSection !== null) { liveModelStreamsSection.closest('.sectionWrapper').remove() } logger.logTaskCompletion('Live model section removal.') } // -- Porn stars listing in sidebar removal if (settings.removePornStarsListingInSidebar) { let pornStarsSection = document.getElementById('relatedPornstarSidebar') if (pornStarsSection !== null) { pornStarsSection.remove() } logger.logTaskCompletion('Sidebar porn start listing removal.') }