PH - Search & UI Tweaks

Various search filters and user experience enhancers

La data de 23-12-2020. Vezi ultima versiune.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         PH - Search & UI Tweaks
// @namespace    brazenvoid
// @version      3.0.4
// @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/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=880816
// @require      https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=880818
// @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', '', (event) => {
                                IS_LOGGED_IN ? this._loadSubscriptions($(event.currentTarget)) : 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 and records subscriptions by page
     * @param {JQuery} button
     * @param {JQuery} sandbox
     * @param {string} url
     * @param {number} pageNo
     * @param {number} maxPage
     * @param {ConfigurationField} configField
     * @private
     */
    _loadSubscriptionsPage (button, sandbox, url, pageNo, maxPage, configField)
    {
        sandbox.load(url + pageNo + ' .userWidgetWrapperGrid', () => {
            sandbox.find('a.usernameLink').each((index, element) => {
                configField.value.push($(element).text().trim())
            })
            sandbox.empty()

            this._uiGen.updateStatus('Loading subscriptions - ' + Math.ceil(((pageNo / maxPage) * 100)) + '% Complete')

            pageNo++
            if (pageNo > maxPage) {
                this._configurationManager.save()
                sandbox.remove()
                button.prop('disabled', false)
                this._uiGen.updateStatus('Account subscriptions loaded.')
            } else {
                this._loadSubscriptionsPage(button, sandbox, url, pageNo, maxPage, configField)
            }
        })
    }

    /**
     * Prepares and organizes loading of logged in account's subscriptions
     * @param {JQuery} button
     * @private
     */
    _loadSubscriptions (button)
    {
        button.prop('disabled', true)
        this._uiGen.updateStatus('Loading subscriptions - 0% Complete')

        let profileLink = $('#profileMenuDropdown > li.alpha > span > a').attr('href')
        let subscriptionsBaseUrl = window.location.origin + profileLink + '/subscriptions'
        let sandbox = $('<div>').appendTo('body')

        sandbox.load(subscriptionsBaseUrl + ' .profileContentLeft .showingInfo', () => {
            let subscriptionsCount = sandbox.children().first().text().replace(REGEX_PRESERVE_NUMBERS, '')
            let subscriptionPageIndex = 1
            let subscriptionPages = Math.ceil(subscriptionsCount / 100)
            let subscriptionsPageUrl = subscriptionsBaseUrl + '?page='
            let configField = this._configurationManager.getField(ACCOUNT_SUBSCRIPTIONS)

            configField.value = []
            sandbox.empty()

            this._loadSubscriptionsPage(button, sandbox, subscriptionsPageUrl, subscriptionPageIndex, subscriptionPages, configField)
        })
    }

    /**
     * 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_VIEWS)
        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()