XHamster Search and UI Improvements

Various search filters

Per 08-02-2021. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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