您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Various search filters and user experience enhancers
当前为
// ==UserScript== // @name PH - Search & UI Tweaks // @namespace brazenvoid // @version 3.0.0 // @author brazenvoid // @license GPL-3.0-only // @description Various search filters and user experience enhancers // @match https://*.pornhub.com/* // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js // @require https://greasyfork.org/scripts/375557-base-resource/code/Base%20Resource.js?version=882642 // @require https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=880818 // @require https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=880816 // @require https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=880821 // @grant GM_addStyle // @run-at document-end // ==/UserScript== GM_addStyle(`#settings-wrapper{background-color:#ffa31a;top:5vh;width:270px}`) const PAGE_PATH_NAME = window.location.pathname const IS_FEED_PAGE = PAGE_PATH_NAME.startsWith('/feeds') const IS_LOGGED_IN = $('#topRightProfileMenu').length > 0 const IS_PLAYLIST_PAGE = PAGE_PATH_NAME.startsWith('/playlist') const IS_PROFILE_PAGE = PAGE_PATH_NAME.startsWith('/model') || PAGE_PATH_NAME.startsWith('/channels') || PAGE_PATH_NAME.startsWith('/user') || PAGE_PATH_NAME.startsWith('/pornstar') const IS_VIDEO_PAGE = PAGE_PATH_NAME.startsWith('/view_video') const IS_VIDEO_SEARCH_PAGE = PAGE_PATH_NAME.startsWith('/video') || PAGE_PATH_NAME.startsWith('/categories') const FILTER_HD_VIDEOS = 'Show Only HD Videos' const FILTER_PAID_VIDEOS = 'Hide Paid Videos' const FILTER_PREMIUM_VIDEOS = 'Hide Premium Videos' const FILTER_PRO_CHANNEL_VIDEOS = 'Hide Pro Channel Videos' const FILTER_PRIVATE_VIDEOS = 'Hide Private Videos' const FILTER_VIDEOS_RATING = 'Rating' const FILTER_RECOMMENDED_VIDEOS = 'Hide Recommended Videos' const FILTER_UNRATED_VIDEOS = 'Hide Unrated Videos' const FILTER_SUBSCRIBED_VIDEOS = 'Hide Subscribed Videos' const FILTER_VIDEOS_DURATION = 'Duration' const FILTER_VIDEOS_VIEWS = 'Views' const FILTER_WATCHED_VIDEOS = 'Hide Watched Videos' const LINK_DISABLE_PLAYLIST_CONTROLS = 'Disable Playlist Controls' const LINK_USER_PUBLIC_VIDEOS = 'User Public Videos' const ACCOUNT_SUBSCRIPTIONS = 'Account Subscriptions' const UI_REMOVE_IFRAMES = 'Remove Ad IFrames' const UI_REMOVE_LIVE_MODELS_SECTIONS = 'Remove Live Models Sections' const UI_REMOVE_PORN_STAR_SECTIONS = 'Remove Porn Star Sections' class PHSearchAndUITweaks extends BrazenBaseSearchEnhancer { constructor () { super('ph-sui-', 'videoblock') this._configurationManager. addFlagField(FILTER_HD_VIDEOS, 'Hides videos of less than 720p resolution.'). addFlagField(FILTER_PAID_VIDEOS, 'Hide paid videos.'). addFlagField(FILTER_PREMIUM_VIDEOS, 'Hide premium videos.'). addFlagField(FILTER_PRIVATE_VIDEOS, 'Hide private Videos.'). addFlagField(FILTER_PRO_CHANNEL_VIDEOS, 'Hide videos from professional channels.'). addFlagField(FILTER_RECOMMENDED_VIDEOS, 'Hide recommended videos.'). addFlagField(FILTER_SUBSCRIBED_VIDEOS, 'Hide videos from subscribed channels.'). addFlagField(FILTER_UNRATED_VIDEOS, 'Hide videos with 0% rating.'). addFlagField(FILTER_WATCHED_VIDEOS, 'Hide already watched videos.'). addFlagField(LINK_DISABLE_PLAYLIST_CONTROLS, 'Disable playlist controls on video pages.'). addFlagField(LINK_USER_PUBLIC_VIDEOS, 'Jump directly to public videos on any profile link click.'). addFlagField(UI_REMOVE_IFRAMES, 'Removes all ad iframes.'). addFlagField(UI_REMOVE_LIVE_MODELS_SECTIONS, 'Remove live model stream sections from search.'). addFlagField(UI_REMOVE_PORN_STAR_SECTIONS, 'Remove porn star listings from search.'). addRangeField(FILTER_VIDEOS_DURATION, 0, 100000, 'Filter videos by duration.'). addRangeField(FILTER_VIDEOS_RATING, 0, 100, 'Filter videos by ratings.'). addRangeField(FILTER_VIDEOS_VIEWS, 0, 10000000, 'Filter videos by view count.'). addRulesetField(ACCOUNT_SUBSCRIPTIONS, 'Recorded subscription accounts.'). addRulesetField(FILTER_TEXT_BLACKLIST, 'Hide videos with specified phrases in their names. Separate the phrases with line breaks.'). addRulesetField(FILTER_TEXT_SANITIZATION, 'Censor video names by substituting offensive phrases. Each rule in separate line with comma separated target phrases. Example Rule: boyfriend=stepson,stepdad'). addRulesetField(FILTER_TEXT_WHITELIST, 'Show videos with specified phrases in their names. Separate the phrases with line breaks.') // UI Events this._onBeforeUIBuild = () => { this._removeIframes() if (IS_VIDEO_PAGE) { this._complyPaidVideosSectionOnVideoPage() this._removeLoadMoreButtons() this._validator.sanitizeNodeOfSelector('.inlineFree', this._configurationManager.getField(FILTER_TEXT_SANITIZATION).optimized) } else { if (IS_VIDEO_SEARCH_PAGE) { this._removePornStarSectionsFromSearchPage() this._removePremiumSectionFromSearchPage() this._fixLeftOverSpaceOnVideoSearchPage() this._fixPaginationNavOnVideoSearchPage() } else { if (IS_PROFILE_PAGE) { this._removeVideoSectionsOnProfilePage() } } } this._removeLiveModelsSections() } this._onUIBuild = () => this._uiGen.createSettingsSection().append([ this._uiGen.createTabsSection(['Filters', 'Text Based', 'Global', 'Stats'], [ this._uiGen.createTabPanel('Filters', true).append([ this._configurationManager.createElement(FILTER_VIDEOS_DURATION), this._configurationManager.createElement(FILTER_VIDEOS_RATING), this._configurationManager.createElement(FILTER_VIDEOS_VIEWS), this._uiGen.createBreakSeparator(), this._configurationManager.createElement(FILTER_HD_VIDEOS), this._configurationManager.createElement(FILTER_PAID_VIDEOS), this._configurationManager.createElement(FILTER_PREMIUM_VIDEOS), this._configurationManager.createElement(FILTER_PRIVATE_VIDEOS), this._configurationManager.createElement(FILTER_PRO_CHANNEL_VIDEOS), this._configurationManager.createElement(FILTER_RECOMMENDED_VIDEOS), this._configurationManager.createElement(FILTER_SUBSCRIBED_VIDEOS), this._configurationManager.createElement(FILTER_UNRATED_VIDEOS), this._configurationManager.createElement(FILTER_WATCHED_VIDEOS), this._uiGen.createSeparator(), this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION), ]), this._uiGen.createTabPanel('Text Based').append([ this._configurationManager.createElement(FILTER_TEXT_SEARCH), this._configurationManager.createElement(FILTER_TEXT_BLACKLIST), this._configurationManager.createElement(FILTER_TEXT_WHITELIST), ]), this._uiGen.createTabPanel('Global').append([ this._configurationManager.createElement(FILTER_TEXT_SANITIZATION), this._uiGen.createSeparator(), this._uiGen.createFormSection('Link Manipulations').append([ this._configurationManager.createElement(LINK_DISABLE_PLAYLIST_CONTROLS), this._configurationManager.createElement(LINK_USER_PUBLIC_VIDEOS), ]), this._uiGen.createSeparator(), this._uiGen.createFormSection('UI Manipulations').append([ this._configurationManager.createElement(UI_REMOVE_IFRAMES), this._configurationManager.createElement(UI_REMOVE_LIVE_MODELS_SECTIONS), this._configurationManager.createElement(UI_REMOVE_PORN_STAR_SECTIONS), ]), this._uiGen.createSeparator(), this._uiGen.createFormSection('Account').append([ this._uiGen.createFormSectionButton('Load Subscriptions', '', () => { IS_LOGGED_IN ? this._loadSubscriptions() : this._showNotLoggedInAlert() }, 'Makes a copy of your subscribed channels and users in cache for related filters.'), ]), this._uiGen.createSeparator(), this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE), ]), this._uiGen.createTabPanel('Stats').append([ this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST), this._uiGen.createStatisticsFormGroup(FILTER_TEXT_WHITELIST), this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_DURATION), this._uiGen.createStatisticsFormGroup(FILTER_HD_VIDEOS, 'High Definition'), this._uiGen.createStatisticsFormGroup(FILTER_TEXT_SEARCH), this._uiGen.createStatisticsFormGroup(FILTER_PAID_VIDEOS, 'Paid Videos'), this._uiGen.createStatisticsFormGroup(FILTER_PREMIUM_VIDEOS, 'Premium Videos'), this._uiGen.createStatisticsFormGroup(FILTER_PRIVATE_VIDEOS, 'Private Videos'), this._uiGen.createStatisticsFormGroup(FILTER_PRO_CHANNEL_VIDEOS, 'Pro Channel Videos'), this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_RATING), this._uiGen.createStatisticsFormGroup(FILTER_RECOMMENDED_VIDEOS, 'Recommended'), this._uiGen.createStatisticsFormGroup(FILTER_SUBSCRIBED_VIDEOS, 'Subscribed'), this._uiGen.createStatisticsFormGroup(FILTER_UNRATED_VIDEOS, 'Unrated'), this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_VIEWS), this._uiGen.createStatisticsFormGroup(FILTER_WATCHED_VIDEOS, 'Watched'), this._uiGen.createSeparator(), this._uiGen.createStatisticsTotalsGroup(), ]), ]), this._createSettingsFormActions(), this._uiGen.createSeparator(), this._uiGen.createStatusSection(), ]) this._onAfterUIBuild = () => { this._complyProfileLinks() this._uiGen.getSelectedSection()[0].userScript = this } // Compliance Events this._onGetItemLists = () => $('ul.videos') this._onGetItemName = (videoItem) => videoItem.find('.title > a').text() this._complianceFilters = [ (videoItem) => this._validateSearch(videoItem), (videoItem) => this._validateWatchStatus(videoItem), (videoItem) => this._validateRating(videoItem), (videoItem) => this._validateDuration(videoItem), (videoItem) => this._validateViews(videoItem), (videoItem) => this._validateHD(videoItem), (videoItem) => this._validateProfessionalChannelVideo(videoItem), (videoItem) => this._validatePaidVideo(videoItem), (videoItem) => this._validatePremiumVideo(videoItem), (videoItem) => this._validatePrivateVideo(videoItem), (videoItem) => this._validateRecommendedState(videoItem), (videoItem) => this._validateSubscribedVideo(videoItem), (videoItem) => this._validateItemBlacklist(videoItem), ] this._onFirstHitAfterCompliance = (item) => { if (IS_PLAYLIST_PAGE) { this._validatePlaylistVideoLink(item) } this._validator.sanitizeTextNode(item.find('.title > a'), this._configurationManager.getField(FILTER_TEXT_SANITIZATION).optimized) } if (IS_FEED_PAGE) { this._onAfterInitialization = () => ChildObserver.create(). onNodesAdded((itemsAdded) => { let itemsList for (let item of itemsAdded) { if (typeof item.querySelector === 'function') { itemsList = item.querySelector('ul.videos') if (itemsList) { this._complyItemsList($(itemsList)) } } } }). observe($('#moreData')[0]) } } /** * Remove paid videos listing * @private */ _complyPaidVideosSectionOnVideoPage () { if (this._configurationManager.getValue(FILTER_PAID_VIDEOS)) { $('#p2vVideosVPage').remove() } } /** * Changes profile links to directly point to public video listings * @private */ _complyProfileLinks () { if (this._configurationManager.getValue(LINK_USER_PUBLIC_VIDEOS)) { $('.usernameBadgesWrapper a, a.usernameLink, .usernameWrap a').each((index, profileLink) => { profileLink = $(profileLink) let href = profileLink.attr('href') if (href.startsWith('/channels') || href.startsWith('/model')) { profileLink.attr('href', href + '/videos') } else { if (href.startsWith('/user')) { profileLink.attr('href', href + '/videos/public') } } }) } } /** * Fixes left over space after ads removal * @private */ _fixLeftOverSpaceOnVideoSearchPage () { $('.showingCounter, .tagsForWomen').each((index, div) => { div.style.height = 'auto' }) } /** * Fixes pagination nav by moving it under video items list * @private */ _fixPaginationNavOnVideoSearchPage () { $('.pagination3').insertAfter($('#videoSearchResult')) } /** * Loads logged in account's subscriptions * @private */ _loadSubscriptions () { let profileLink = $('#profileMenuDropdown > li.alpha > span > a').attr('href') let sandbox = $('<div>').appendTo('body') sandbox.load(window.location.origin + profileLink + '/subscriptions .userWidgetWrapperGrid', () => { let configField = this._configurationManager.getField(ACCOUNT_SUBSCRIPTIONS) console.log(configField.value) sandbox.find('a.usernameLink').each((index, element) => { configField.value.push($(element).text().trim()) }) this._configurationManager.save() sandbox.remove() }) } /** * Removes any IFrames being displayed by going over the page repeatedly till none exist * @private */ _removeIframes () { let removeMilkTruckIframes = () => { let iframes = $('milktruck') let count = iframes.length iframes.remove() return count } if (this._configurationManager.getValue(UI_REMOVE_IFRAMES)) { Validator.iFramesRemover() let iframesCount do { iframesCount = removeMilkTruckIframes() } while (iframesCount) } } _removeLoadMoreButtons () { $('.more_recommended_btn, #loadMoreRelatedVideosCenter').remove() } /** * @private */ _removeLiveModelsSections () { if (this._configurationManager.getValue(UI_REMOVE_LIVE_MODELS_SECTIONS)) { $('.streamateContent').each((index, element) => {$(element).parents('.sectionWrapper:first').remove()}) } } /** * @private */ _removePornStarSectionsFromSearchPage () { if (this._configurationManager.getValue(UI_REMOVE_PORN_STAR_SECTIONS)) { $('#relatedPornstarSidebar').remove() } } /** * @private */ _removePremiumSectionFromSearchPage () { if (this._configurationManager.getValue(FILTER_PREMIUM_VIDEOS)) { $('.nf-videos .sectionWrapper .sectionTitle h2').each((index, element) => { let sectionTitle = $(element) if (sectionTitle.text().trim() === 'Premium Videos') { sectionTitle.parents('.sectionWrapper:first').remove() return false } }) } } /** * Removes premium video sections from profiles * @private */ _removeVideoSectionsOnProfilePage () { const videoSections = [ {setting: this._configurationManager.getValue(FILTER_PAID_VIDEOS), linkSuffix: 'paid'}, {setting: this._configurationManager.getValue(FILTER_PREMIUM_VIDEOS), linkSuffix: 'fanonly'}, {setting: this._configurationManager.getValue(FILTER_PRIVATE_VIDEOS), linkSuffix: 'private'}, ] for (let videoSection of videoSections) { let videoSectionWrapper = $('.videoSection > div > div > h2 > a[href$="/' + videoSection.linkSuffix + '"]').parents('.videoSection:first') videoSection.setting ? videoSectionWrapper.show() : videoSectionWrapper.hide() } } /** * Validates video duration * @param {JQuery} videoItem * @return {boolean} * @private */ _validateDuration (videoItem) { let range = this._configurationManager.getValue(FILTER_VIDEOS_DURATION) if (range.minimum > 0 || range.maximum > 0) { let durationNode = videoItem.find('.duration') if (durationNode.length) { let duration = durationNode.text().split(':') duration = (parseInt(duration[0]) * 60) + parseInt(duration[1]) return this._validator.validateRange(FILTER_VIDEOS_DURATION, duration, [range.minimum, range.maximum]) } } return true } /** * Validate video quality * @param {JQuery} videoItem * @return {boolean} * @private */ _validateHD (videoItem) { return this._configurationManager.getValue(FILTER_HD_VIDEOS) ? this._validator.validateNodeExistence(FILTER_HD_VIDEOS, videoItem, '.hd-thumbnail') : true } /** * Validate paid video status * @param {JQuery} videoItem * @return {boolean} * @private */ _validatePaidVideo (videoItem) { return this._configurationManager.getValue(FILTER_PAID_VIDEOS) ? this._validator.validateNodeNonExistence(FILTER_PAID_VIDEOS, videoItem, '.p2v-icon, .fanClubVideoWrapper') : true } /** * Validate and change playlist video links * @param {JQuery} videoItem * @private */ _validatePlaylistVideoLink (videoItem) { if (this._configurationManager.getValue(LINK_DISABLE_PLAYLIST_CONTROLS)) { videoItem.find('a.linkVideoThumb, span.title a').each((index, playlistLink) => { playlistLink = $(playlistLink) playlistLink.attr('href', playlistLink.attr('href').replace(/&pkey.*/, '')) }) } } /** * Validate premium video status * @param {JQuery} videoItem * @return {boolean} * @private */ _validatePremiumVideo (videoItem) { return this._configurationManager.getValue(FILTER_PREMIUM_VIDEOS) ? this._validator.validateNodeNonExistence(FILTER_PREMIUM_VIDEOS, videoItem, '.premiumIcon') : true } /** * Validate private video status * @param {JQuery} videoItem * @return {boolean} * @private */ _validatePrivateVideo (videoItem) { return this._configurationManager.getValue(FILTER_PRIVATE_VIDEOS) ? this._validator.validateNodeNonExistence(FILTER_PRIVATE_VIDEOS, videoItem, '.privateOverlay') : true } /** * Validate whether video is provided by a professional porn channel * @param {JQuery} videoItem * @return {boolean} * @private */ _validateProfessionalChannelVideo (videoItem) { return this._configurationManager.getValue(FILTER_PRO_CHANNEL_VIDEOS) ? this._validator.validateNodeNonExistence(FILTER_PRO_CHANNEL_VIDEOS, videoItem, '.channel-icon') : true } /** * Validate video rating * @param {JQuery} videoItem * @return {boolean} * @private */ _validateRating (videoItem) { let validationCheck = true let range = this._configurationManager.getValue(FILTER_VIDEOS_RATING) if (range.minimum > 0 || range.maximum > 0) { let rating = videoItem.find('.value') let isUnratedVideo = false if (rating.length === 0) { isUnratedVideo = true } else { rating = parseInt(rating.text().replace('%', '')) if (rating === 0) { isUnratedVideo = true } else { validationCheck = this._validator.validateRange(FILTER_VIDEOS_RATING, rating, [range.minimum, range.maximum]) } } if (isUnratedVideo && this._configurationManager.getValue(FILTER_UNRATED_VIDEOS)) { validationCheck = false this._statistics.record(FILTER_UNRATED_VIDEOS, validationCheck) } } return validationCheck } /** * Validate recommended video status * @param {JQuery} videoItem * @return {boolean} * @private */ _validateRecommendedState (videoItem) { return this._configurationManager.getValue(FILTER_RECOMMENDED_VIDEOS) ? this._validator.validateNodeNonExistence(FILTER_RECOMMENDED_VIDEOS, videoItem, '.recommendedFor') : true } /** * Validates existence of searched words in the video name * @param {JQuery} videoItem * @return {boolean} * @private */ _validateSearch (videoItem) { let validationResult = true let searchText = this._configurationManager.getValue(FILTER_TEXT_SEARCH) if (searchText !== '') { validationResult = videoItem.find('.title > a').text().includes(searchText) this._statistics.record(FILTER_TEXT_SEARCH, validationResult) } return validationResult } /** * Validates whether the video is not from a subscribed user or channel * @param {JQuery} videoItem * @return {boolean} * @private */ _validateSubscribedVideo (videoItem) { let validationResult = true if (IS_LOGGED_IN && !IS_FEED_PAGE && this._configurationManager.getValue(FILTER_SUBSCRIBED_VIDEOS)) { let subscribedUsers = this._configurationManager.getValue(ACCOUNT_SUBSCRIPTIONS) if (subscribedUsers.length) { if (IS_PLAYLIST_PAGE) { subscribedUsers = $(subscribedUsers).not($('#js-aboutPlaylistTabView .usernameWrap a').text()).get() } if (IS_PROFILE_PAGE) { subscribedUsers = $(subscribedUsers).not(PAGE_PATH_NAME.split('/')[1]).get() } validationResult = !subscribedUsers.includes(videoItem.find('.usernameWrap').text().trim()) this._statistics.record(FILTER_SUBSCRIBED_VIDEOS, validationResult) } } return validationResult } /** * Validate video view count * @param {JQuery} videoItem * @return {boolean} * @private */ _validateViews (videoItem) { let range = this._configurationManager.getValue(FILTER_VIDEOS_RATING) if (range.minimum > 0 || range.maximum > 0) { let viewsCountString = videoItem.find('.views').text().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 this._validator.validateRange(FILTER_VIDEOS_VIEWS, viewsCount, [range.minimum, range.maximum]) } return true } /** * Validate watched video status * @param {JQuery} videoItem * @return {boolean} * @private */ _validateWatchStatus (videoItem) { return this._configurationManager.getValue(FILTER_WATCHED_VIDEOS) ? this._validator.validateNodeNonExistence(FILTER_WATCHED_VIDEOS, videoItem, '.watchedVideoText') : true } } (new PHSearchAndUITweaks).init()