- // ==UserScript==
- // @name PH - Search & UI Tweaks
- // @namespace brazenvoid
- // @version 4.0.0
- // @author brazenvoid
- // @license GPL-3.0-only
- // @description Various search filters and user experience enhancers
- // @match https://*.pornhub.com/*
- // @match https://*.pornhub.org/*
- // @match https://*.pornhubpremium.com/*
- // @match https://*.pornhubpremium.org/*
- // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
- // @require https://update.greasyfork.org/scripts/375557/1244990/Base%20Brazen%20Resource.js
- // @require https://update.greasyfork.org/scripts/416104/1498249/Brazen%20UI%20Generator.js
- // @require https://update.greasyfork.org/scripts/418665/1481350/Brazen%20Configuration%20Manager.js
- // @require https://update.greasyfork.org/scripts/429587/1244644/Brazen%20Item%20Attributes%20Resolver.js
- // @require https://update.greasyfork.org/scripts/424516/1114774/Brazen%20Subscriptions%20Loader.js
- // @require https://update.greasyfork.org/scripts/416105/1478692/Brazen%20Base%20Search%20Enhancer.js
- // @grant GM_addStyle
- // @run-at document-end
- // ==/UserScript==
-
- GM_addStyle(`#settings-wrapper{min-width:390px;width:390px}`)
-
- // Environment
-
- const PAGE_PATH_NAME = window.location.pathname
-
- const IS_FEED_PAGE = PAGE_PATH_NAME.startsWith('/feeds')
- 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')
-
- // Filters and configuration
-
- 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_RECOMMENDED_VIDEOS = 'Hide Recommended Videos'
- const FILTER_VIDEOS_VIEWS = 'Views'
- const FILTER_USER = 'User Blacklist'
- const FILTER_WATCHED_VIDEOS = 'Watched Filters'
-
- const LINK_DISABLE_PLAYLIST_CONTROLS = 'Disable Playlist Controls'
- const LINK_USER_PUBLIC_VIDEOS = 'User Public Videos'
-
- const UI_AUTO_NEXT = 'Auto Next'
- const UI_LARGE_PLAYER_ALWAYS = 'Always Enlarge Player'
- 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({
- isUserLoggedIn: $('#topRightProfileMenu').length > 0,
- itemDeepAnalysisSelector: '.video-wrapper',
- itemLinkSelector: '.title > a',
- itemListSelectors: 'ul.videos',
- itemNameSelector: '.title > a',
- itemSelectors: '.videoblock',
- requestDelay: 0,
- scriptPrefix: 'ph-sui-',
- })
-
- this._playlistPageUsername = ''
- this._profilePageUsername = ''
-
- this._setupFeatures()
- this._setupComplianceFilters()
- this._setupUI()
- this._setupEvents()
- }
-
- /**
- * Automatic next search page
- * @private
- */
- _autoNext()
- {
- let allVideos = $('.nf-videos ' + this._config.itemSelectors)
- if (allVideos.length > 0 && !allVideos.is(':visible')) {
- let nextButton = $('.page_next:not(.disabled) > a')
- if (nextButton.length) {
- window.location = nextButton.attr('href')
- }
- }
- }
-
- /**
- * Changes profile links to directly point to public video listings
- * @private
- */
- _complyProfileLinks()
- {
- $('.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')
- }
- })
- }
-
- /**
- * @private
- */
- _enlargePlayer()
- {
- let player = $('#player')
- if (player.hasClass('original')) {
- player.removeClass('original').addClass('wide')
- }
- }
-
- /**
- * 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($('div.nf-videos .search-video-thumbs'))
- }
-
- _removeLoadMoreButtons()
- {
- $('.more_recommended_btn, #loadMoreRelatedVideosCenter').remove()
- }
-
- /**
- * @private
- */
- _removePremiumSectionFromSearchPage()
- {
- $('.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._getConfig(FILTER_PAID_VIDEOS), linkSuffix: 'paid'},
- {setting: this._getConfig(FILTER_PREMIUM_VIDEOS), linkSuffix: 'fanonly'},
- {setting: this._getConfig(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()
- }
- }
-
- /**
- * @private
- */
- _setupComplianceFilters()
- {
- this._addItemTextSanitizationFilter(
- 'Censor video names by substituting offensive phrases. Each rule in separate line with comma separated target phrases. ' +
- 'Requires page reload to apply. Example Rule: boyfriend=stepson,stepdad')
- this._addItemWhitelistFilter('Show videos with specified phrases in their names. Separate the phrases with line breaks.')
- this._addItemTextSearchFilter()
- this._addItemComplianceFilter(FILTER_WATCHED_VIDEOS, (item, value) => {
- if (value === '1') {
- return !this._get(item, FILTER_WATCHED_VIDEOS)
- } else if (value === '2') {
- return this._get(item, FILTER_WATCHED_VIDEOS)
- }
- return true
- })
- this._addItemPercentageRatingRangeFilter('.value')
- this._addItemDurationRangeFilter('.duration')
- this._addItemComplianceFilter(FILTER_VIDEOS_VIEWS)
- this._addItemComplianceFilter(FILTER_PRO_CHANNEL_VIDEOS)
- this._addItemComplianceFilter(FILTER_PAID_VIDEOS)
- this._addItemComplianceFilter(FILTER_PREMIUM_VIDEOS)
- this._addItemComplianceFilter(FILTER_PRIVATE_VIDEOS)
- this._addItemComplianceFilter(FILTER_RECOMMENDED_VIDEOS)
- this._addItemComplianceFilter(FILTER_USER, (item, users) => !users.includes(this._get(item, FILTER_USER)))
- this._addSubscriptionsFilter(() => !IS_FEED_PAGE, (item) => {
- let username = this._get(item, FILTER_USER)
- return (username === this._playlistPageUsername || username === this._profilePageUsername) ? false : username
- })
- this._addItemBlacklistFilter('Hide videos with specified phrases in their names.')
- }
-
- /**
- * @private
- */
- _setupEvents()
- {
- if (IS_FEED_PAGE) {
- this._onAfterInitialization.push(() => ChildObserver.create().
- onNodesAdded((itemsAdded) => {
- let itemsList
- for (let item of itemsAdded) {
- if (typeof item.querySelector === 'function') {
- itemsList = item.querySelector(this._config.itemListSelectors)
- if (itemsList) {
- this._complyItemsList($(itemsList))
- }
- }
- }
- }).
- observe($('#moreData')[0]))
-
- } else if (IS_VIDEO_SEARCH_PAGE) {
- this._onAfterInitialization.push(() => this._performOperation(UI_AUTO_NEXT, () => this._autoNext()))
- }
-
- this._onBeforeUIBuild.push(() => {
-
- if (IS_VIDEO_PAGE) {
- this._performOperation(FILTER_PAID_VIDEOS, () => $('#p2vVideosVPage').remove())
- this._performOperation(UI_LARGE_PLAYER_ALWAYS, () => this._enlargePlayer())
- this._removeLoadMoreButtons()
- Validator.sanitizeNodeOfSelector('.inlineFree', this._configurationManager.getFieldOrFail(FILTER_TEXT_SANITIZATION).optimized)
-
- } else if (IS_VIDEO_SEARCH_PAGE) {
- this._performOperation(UI_REMOVE_PORN_STAR_SECTIONS, () => $('#relatedPornstarSidebar').remove())
- this._performOperation(FILTER_PREMIUM_VIDEOS, () => this._removePremiumSectionFromSearchPage())
- this._fixLeftOverSpaceOnVideoSearchPage()
- this._fixPaginationNavOnVideoSearchPage()
-
- } else if (IS_PROFILE_PAGE) {
- this._removeVideoSectionsOnProfilePage()
- this._profilePageUsername = PAGE_PATH_NAME.split('/')[1]
-
- } else if (IS_PLAYLIST_PAGE) {
- this._playlistPageUsername = $('#js-aboutPlaylistTabView .usernameWrap a').text().trim()
-
- if (this._getConfig(LINK_DISABLE_PLAYLIST_CONTROLS)) {
- this._onFirstHitAfterCompliance.push((item) => this._validatePlaylistVideoLink(item))
- }
- }
-
- this._performOperation(
- UI_REMOVE_LIVE_MODELS_SECTIONS,
- () => $('.streamateContent').
- each((index, element) => {$(element).parents('.sectionWrapper:first').remove()}),
- )
- })
-
- this._onAfterUIBuild.push(() => {
- this._performOperation(LINK_USER_PUBLIC_VIDEOS, () => this._complyProfileLinks())
- this._uiGen.getSelectedSection()[0].userScript = this
- })
- }
-
- /**
- * @private
- */
- _setupFeatures()
- {
- this._configurationManager.
- 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(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_AUTO_NEXT, 'Automatically go to next search page if no videos match after first run.').
- addFlagField(UI_LARGE_PLAYER_ALWAYS, 'Enlarges player on all video pages.').
- 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.').
- addRadiosGroup(FILTER_WATCHED_VIDEOS, [
- ['No Filtering', 0],
- ['Hide Watched Videos', 1],
- ['Show Only Watched Videos', 2],
- ], 'Control fate of already watched videos.').
- addRangeField(FILTER_VIDEOS_VIEWS, 0, 10000000, 'Filter videos by view count.').
- addRulesetField(FILTER_USER, 6, 'Hides videos from specified users/channels.')
-
- this._itemAttributesResolver.
- addAttribute(FILTER_PAID_VIDEOS, (item) => Validator.isChildMissing(item, '.p2v-icon, .fanClubVideoWrapper')).
- addAttribute(FILTER_PREMIUM_VIDEOS, (item) => Validator.isChildMissing(item, '.marker-overlays > .premiumIcon')).
- addAttribute(FILTER_PRIVATE_VIDEOS, (item) => Validator.isChildMissing(item, '.privateOverlay')).
- addAttribute(FILTER_PRO_CHANNEL_VIDEOS, (item) => Validator.isChildMissing(item, '.channel-icon')).
- addAttribute(FILTER_RECOMMENDED_VIDEOS, (item) => Validator.isChildMissing(item, '.recommendedFor')).
- addAttribute(FILTER_USER, (item) => item.find('.usernameWrap a').attr('title')).
- addAttribute(FILTER_VIDEOS_VIEWS, (item) => {
- let viewsCountString = item.find('.views var').text()
- 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', '')
- }
- return parseFloat(viewsCountString) * viewsCountMultiplier
- }).
- addAttribute(FILTER_WATCHED_VIDEOS, (item) => Validator.doesChildExist(item, '.watchedVideoText') || Validator.doesChildExist(item, '.watchedVideo'))
-
- this._setupSubscriptionLoader().addConfig({
- url: window.location.origin + $('#profileMenuDropdown > li > span > a').first().attr('href') + '/subscriptions',
- getPageCount: (page) => parseInt(page.children().first().text().replace(REGEX_PRESERVE_NUMBERS, '')) / 100,
- getPageUrl: (baseUrl, pageNo) => baseUrl + '?page=' + pageNo + ' .userWidgetWrapperGrid',
- subscriptionsCountSelector: '.profileContentLeft .showingInfo',
- subscriptionNameSelector: 'a.usernameLink',
- })
- }
-
- /**
- * @private
- */
- _setupUI()
- {
- this._userInterface = [
- this._uiGen.createTabsSection(['Filters 1', 'Filters 2', 'Interface', 'Settings', 'Stats'], [
- this._uiGen.createTabPanel('Filters 1', true).append([
- this._configurationManager.createElement(FILTER_DURATION_RANGE),
- this._configurationManager.createElement(FILTER_PERCENTAGE_RATING_RANGE),
- this._configurationManager.createElement(FILTER_VIDEOS_VIEWS),
- this._uiGen.createBreakSeparator(),
- 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),
- this._uiGen.createSeparator(),
- this._configurationManager.createElement(FILTER_WATCHED_VIDEOS),
- this._uiGen.createSeparator(),
- this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
-
- ]),
- this._uiGen.createTabPanel('Filters 2').append([
- this._configurationManager.createElement(FILTER_TEXT_SEARCH),
- this._configurationManager.createElement(FILTER_TEXT_BLACKLIST),
- this._configurationManager.createElement(FILTER_TEXT_WHITELIST),
- this._configurationManager.createElement(FILTER_TEXT_SANITIZATION),
- this._configurationManager.createElement(FILTER_USER),
- ]),
- this._uiGen.createTabPanel('Interface').append([
- this._configurationManager.createElement(UI_LARGE_PLAYER_ALWAYS),
- this._configurationManager.createElement(LINK_DISABLE_PLAYLIST_CONTROLS),
- this._configurationManager.createElement(LINK_USER_PUBLIC_VIDEOS),
- this._configurationManager.createElement(UI_AUTO_NEXT),
- this._uiGen.createSeparator(),
- this._configurationManager.createElement(UI_REMOVE_LIVE_MODELS_SECTIONS),
- this._configurationManager.createElement(UI_REMOVE_PORN_STAR_SECTIONS),
- ]),
- this._uiGen.createTabPanel('Settings').append([
- this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
- this._uiGen.createSeparator(),
- this._uiGen.createFormSection('Account').append([
- this._createSubscriptionLoaderControls(),
- ]),
- this._uiGen.createSeparator(),
- this._createSettingsBackupRestoreFormActions(),
- ]),
- this._uiGen.createTabPanel('Stats').append([
- this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST),
- this._uiGen.createStatisticsFormGroup(FILTER_TEXT_WHITELIST),
- this._uiGen.createStatisticsFormGroup(FILTER_DURATION_RANGE),
- 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_PERCENTAGE_RATING_RANGE),
- this._uiGen.createStatisticsFormGroup(FILTER_RECOMMENDED_VIDEOS, 'Recommended'),
- this._uiGen.createStatisticsFormGroup(FILTER_SUBSCRIBED_VIDEOS, 'Subscribed'),
- this._uiGen.createStatisticsFormGroup(FILTER_UNRATED, '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(),
- ]
- }
-
- /**
- * Validate and change playlist video links
- * @param {JQuery} videoItem
- * @private
- */
- _validatePlaylistVideoLink(videoItem)
- {
- videoItem.find('a').each((_i, playlistLink) => {
- playlistLink = $(playlistLink)
- playlistLink.attr('href', playlistLink.attr('href').replace(/&pkey.*/, ''))
- })
- }
- }
-
- (new PHSearchAndUITweaks).init()