PH - Search & UI Tweaks

Various search filters and user experience enhancers

As of 2020-12-23. See the latest version.

// ==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()