XHamster Search and UI Improvements

Various search filters

Ekde 2021/02/08. Vidu La ĝisdata versio.

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         XHamster Search and UI Improvements
// @namespace    brazenvoid
// @version      2.1.0
// @author       brazenvoid
// @license      GPL-3.0-only
// @description  Various search filters
// @include      https://xhamster.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=889107
// @require      https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=889085
// @require      https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=892799
// @require      https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=894780
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

GM_addStyle(
    `#settings-wrapper{top:5vh;width:270px}#settings-wrapper .check-radio-input{display:block}#settings-wrapper .form-button{border:revert}.font-primary{color:white}.tab-button:hover:not(.active){color:black}.tab-button:not(.active){color:white}.bg-brand{background-color:#3a3a3a}`)

const PAGE_PATH_NAME = window.location.pathname
const IS_PROFILE_PAGE = PAGE_PATH_NAME.startsWith('/users')
const IS_VIDEO_PAGE = PAGE_PATH_NAME.startsWith('/videos')

const FILTER_HD_VIDEOS = 'Show only HD Videos'
const FILTER_VIDEOS_VIEWS = 'Views'
const FILTER_WATCHED_VIDEOS = 'Hide Watched Videos'

const SCRIPT_PREFIX = 'xh-sui-'

const SELECTOR_ITEM = '.thumb-list__item'
const SELECTOR_ITEM_LIST = 'div.thumb-list'
const SELECTOR_ITEM_NAME = 'a.video-thumb-info__name'

class XHamsterSearchAndUITweaks extends BrazenBaseSearchEnhancer
{
    constructor ()
    {
        super(SCRIPT_PREFIX, SELECTOR_ITEM)

        this._configurationManager.
            addFlagField(FILTER_HD_VIDEOS, 'Hides videos of less than 720p resolution.').
            addFlagField(FILTER_WATCHED_VIDEOS, 'Hides names of seen videos to aid in ignoring them. Requires page reload to apply.').
            addRangeField(FILTER_VIDEOS_VIEWS, 0, 10000000, 'Filter videos by view count.')

        if (!IS_VIDEO_PAGE) {
            let paginationSection = $('.pager-container ul')
            let nextPageNode = paginationSection.find('li.next')
            let lastPageUrl = nextPageNode.length ? nextPageNode.prev().find('a').attr('href') : window.location.href

            this._paginator = BrazenPaginator.create(paginationSection, '.thumb-list:not(.thumb-list--related):first', SELECTOR_ITEM, lastPageUrl).
                onGetPageNoFromUrl((url) => {
                    let lastSegment = parseInt(url.split('/').pop())
                    return isNaN(lastSegment) ? 1 : lastSegment
                }).
                onGetPageUrlFromPageNo((newPageNo) => {
                    let currentUrl = window.location.href
                    let currentUrlFragments = currentUrl.split('/')

                    if (newPageNo === 1) {
                        if (!isNaN(parseInt(currentUrlFragments[currentUrl.length - 1]))) {
                            currentUrlFragments.pop()
                        }
                    } else {
                        if (isNaN(parseInt(currentUrlFragments[currentUrl.length - 1]))) {
                            currentUrlFragments.push(newPageNo)
                        } else {
                            currentUrlFragments[currentUrl.length - 1] = newPageNo
                        }
                    }
                    return currentUrlFragments.join('/')
                }).
                onGetPaginationElementForPageNo((pageNo, paginator) =>
                    paginator.getPaginationWrapper().
                        find(pageNo === paginator.getCurrentPageNo() ? 'a.xh-paginator-button.active' : 'a.xh-paginator-button[data-page=' + pageNo + ']').
                        parent(),
                )
        }

        this._addPaginationConfiguration()
        this._setupUI()
        this._setupCompliance()
        this._setupComplianceFilters()
    }

    /**
     * @private
     */
    _applyWatchedFilter ()
    {
        if (this._configurationManager.getValue(FILTER_WATCHED_VIDEOS)) {
            let color
            let isDarkThemeActive = $('.theme-section .dark.active').length
            if (IS_PROFILE_PAGE) {
                color = isDarkThemeActive ? 'rgb(16, 16, 16)' : 'rgb(234, 234, 234)'
            } else {
                color = isDarkThemeActive ? '#202020' : '#f5f5f5'
            }
            GM_addStyle('.video-thumb-info__name:visited{color:' + color + '}')
        }
    }

    /**
     * @private
     */
    _setupCompliance ()
    {
        this._onGetItemLists = () => $(SELECTOR_ITEM_LIST)

        this._onGetItemName = (item) => item.find(SELECTOR_ITEM_NAME).text()

        this._onFirstHitBeforeCompliance = (item) => {
            Validator.sanitizeTextNode(item.find(SELECTOR_ITEM_NAME), this._configurationManager.getFieldOrFail(FILTER_TEXT_SANITIZATION).optimized)
        }
    }

    /**
     * @private
     */
    _setupComplianceFilters ()
    {
        this._addItemTextSanitizationFilter(
            'Censor video names by substituting offensive phrases. Each rule in separate line with comma separated target phrases. Example Rule: boyfriend=stepson,stepdad')
        this._addItemWhitelistFilter('Show videos with specified phrases in their names. Separate the phrases with line breaks.')
        this._addItemTextSearchFilter()
        this._addItemPercentageRatingRangeFilter('div.rating span.metric-text')
        this._addItemDurationRangeFilter('div.thumb-image-container__duration')
        this._addItemComplianceFilter(FILTER_VIDEOS_VIEWS, (range) => range.minimum > 0 || range.maximum > 0, (item, range) => {
            let viewsCount = 0
            let viewsCountNode = item.find('div.views span.metric-text')
            if (viewsCountNode.length) {
                viewsCount = parseInt(viewsCountNode.text().replace(',', ''))
                if (isNaN(viewsCount)) {
                    viewsCount = 0
                }
            }
            return Validator.isInRange(viewsCount, range.minimum, range.maximum)
        })
        this._addItemComplianceFilter(
            FILTER_HD_VIDEOS,
            (value) => value,
            (item) => !Validator.isChildMissing(item, 'i.thumb-image-container__icon--hd,i.thumb-image-container__icon--uhd'),
        )
        this._addItemBlacklistFilter('Hide videos with specified phrases in their names. Separate the phrases with line breaks.')
    }

    /**
     * @private
     */
    _setupUI ()
    {
        this._onBeforeUIBuild = () => {
            if (IS_VIDEO_PAGE) {
                Validator.sanitizeNodeOfSelector(
                    'main > div.width-wrap.with-player-container > h1:first', this._configurationManager.getFieldOrFail(FILTER_TEXT_SANITIZATION).optimized)
            }
            this._applyWatchedFilter()
        }

        this._onUIBuild = () =>
            this._uiGen.createSettingsSection().append([
                this._uiGen.createTabsSection(['Filters', 'Text Based', 'Global'], [
                    this._uiGen.createTabPanel('Filters', 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_HD_VIDEOS),
                        this._configurationManager.createElement(FILTER_UNRATED),
                        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._configurationManager.createElement(FILTER_TEXT_SANITIZATION),
                    ]),
                    this._uiGen.createTabPanel('Global').append([
                        this._configurationManager.createElement(FILTER_WATCHED_VIDEOS),
                        this._uiGen.createSeparator(),
                        this._configurationManager.createElement(CONFIG_PAGINATOR_THRESHOLD),
                        this._configurationManager.createElement(CONFIG_PAGINATOR_LIMIT),
                        this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
                        this._uiGen.createSeparator(),
                        this._createSettingsBackupRestoreFormActions(),
                    ]),
                ]),
                this._createSettingsFormActions(),
                this._uiGen.createSeparator(),
                this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST),
                this._uiGen.createStatisticsFormGroup(FILTER_TEXT_WHITELIST),
                this._uiGen.createStatisticsFormGroup(FILTER_DURATION_RANGE),
                this._uiGen.createStatisticsFormGroup(FILTER_HD_VIDEOS, 'High Definition'),
                this._uiGen.createStatisticsFormGroup(FILTER_TEXT_SEARCH),
                this._uiGen.createStatisticsFormGroup(FILTER_PERCENTAGE_RATING_RANGE),
                this._uiGen.createStatisticsFormGroup(FILTER_DURATION_RANGE, 'Unrated'),
                this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_VIEWS),
                this._uiGen.createSeparator(),
                this._uiGen.createStatisticsTotalsGroup(),
                this._uiGen.createSeparator(),
                this._uiGen.createStatusSection(),
            ])

        this._onAfterUIBuild = () => {
            this._uiGen.getSelectedSection()[0].userScript = this
        }
    }
}

(new XHamsterSearchAndUITweaks).init()