// ==UserScript==
// @name XHamster - Search Filters
// @namespace brazenvoid
// @version 1.0.0
// @author brazenvoid
// @license GPL-3.0-only
// @description Various search filters
// @include https://xhamster.com/*
// @require https://greasyfork.org/scripts/375557-brazenvoid-s-base-resource/code/Brazenvoid's%20Base%20Resource.js?version=673754
// @run-at document-idle
// ==/UserScript==
// Settings & Defaults
let settings = {
blacklist: [ // case-insensitive
'urin',
'arab',
'muslim',
'desi',
'squirt',
'fake',
'pregnant',
'pee',
],
sanitize: { // Substitutes values with key (case-insensitive)
' ': ['neighbor', 'step'],
Boyfriend: ['brother', 'bro', 'daddy', 'dad'],
Girlfriend: ['daughter', 'mom', 'mother', 'sister', 'sis']
},
duration: { // In Seconds
minimum: 120,
maximum: 0
},
rating: {
minimum: 80,
maximum: 0
},
views: {
minimum: 0,
maximum: 0
},
hideSDVideos: true,
showUIAlways: false, // Desktop
debugLogging: false
}
// Base Resources Initialization
const scriptPrefix = 'xh-sui-'
let storage = new LocalStore(scriptPrefix + 'settings', settings)
settings = storage.retrieve().get()
let logger = new Logger(settings.debugLogging)
let selectorGenerator = new SelectorGenerator(scriptPrefix)
let statistics = new StatisticsRecorder(logger, selectorGenerator)
let uiGenerator = new UIGenerator(settings.showUIAlways, selectorGenerator)
uiGenerator.buttonBackroundColor = 'rgb(218, 218, 218)'
let validator = new Validator(statistics)
validator.addBlacklistFilter(settings.blacklist).addSanitizationFilter(settings.sanitize).optimize()
function getVideoLists () {
return document.querySelectorAll('div.thumb-list')
}
// Local Store Events
let refreshUI = function () {
let store = this.get()
uiGenerator.setSettingsInputValue('Min Duration', store.duration.minimum)
uiGenerator.setSettingsInputValue('Min Rating', store.rating.minimum)
uiGenerator.setSettingsInputValue('Min Views', store.views.minimum)
}
storage.onDefaultsLoaded = refreshUI
storage.onRetrieval = refreshUI
storage.onUpdated = refreshUI
// Validators
// -- Duration validation
let validateDuration = function (videoItem) {
if (settings.duration.minimum > 0 || settings.duration.maximum > 0) {
let duration = videoItem.querySelector('div.thumb-image-container__duration').textContent.split(':')
duration = (parseInt(duration[0]) * 60) + parseInt(duration[1])
return validator.validateRange('Duration', duration, [settings.duration.minimum, settings.duration.maximum])
}
return true
}
// -- High definition validation
let validateHD = function (videoItem) {
let validationCheck = true
if (settings.hideSDVideos) {
validationCheck = videoItem.querySelector('i.thumb-image-container__icon--hd') !== null
logger.logValidation('HD', validationCheck)
}
return validationCheck
}
// -- Rating validation
let validateRating = function (videoItem) {
let validationCheck = true
if (settings.rating.minimum > 0 || settings.rating.maximum > 0) {
let rating = videoItem.querySelector('i.video-thumb-info__rating')
if (rating !== null) {
rating = parseInt(rating.textContent.replace('%', ''))
validationCheck = validator.validateRange('Rating', rating, [settings.rating.minimum, settings.rating.maximum])
}
}
return validationCheck
}
// -- View count validation
let validateViews = function (videoItem) {
if (settings.views.minimum > 0 || settings.views.maximum > 0) {
return validator.validateRange('Views', videoItem.querySelector('i.video-thumb-info__views').textContent, [settings.views.minimum, settings.views.maximum])
}
return true
}
// -- Compliance logic
let complianceCallback = function (target) {
let videoItems = target.querySelectorAll('div.thumb-list__item')
let videoName, videoComplies
for (let videoItem of videoItems) {
videoName = videoItem.querySelector('a.video-thumb-info__name')
logger.logVideoCheck(videoName.textContent)
videoComplies =
validateHD(videoItem) &&
validateRating(videoItem) &&
validator.validateBlackList(videoName.textContent) &&
validateDuration(videoItem) &&
validateViews(videoItem)
if (videoComplies) {
videoItem.style.display = 'inline-block'
validator.sanitizeVideoItem(videoName)
} else {
videoItem.style.display = 'none'
}
logger.logSeparator()
}
statistics.updateUI()
}
// UI Composition
// -- Control Panel
let section = uiGenerator.createSection('settings', '#ffa31a', '150px', '220px', [
uiGenerator.createSettingsFormGroup('Min Duration', 'number', settings.duration.minimum),
uiGenerator.createSettingsFormGroup('Min Rating', 'number', settings.rating.minimum),
uiGenerator.createSettingsFormGroup('Min Views', 'number', settings.views.minimum),
uiGenerator.createSettingsFormActions(storage, function () {
settings.duration.minimum = uiGenerator.getSettingsInputValue('Min Duration')
settings.rating.minimum = uiGenerator.getSettingsInputValue('Min Rating')
settings.views.minimum = uiGenerator.getSettingsInputValue('Min Views')
statistics.reset()
for (let videoList of getVideoLists()) {
complianceCallback(videoList)
}
}, true),
uiGenerator.createSeparator(),
uiGenerator.createStoreControlPanel(storage),
uiGenerator.createSeparator(),
uiGenerator.createStatisticsFormGroup('Duration', 'Short'),
uiGenerator.createStatisticsFormGroup('Rating', 'Low Rated'),
uiGenerator.createStatisticsFormGroup('Views', 'By Views'),
uiGenerator.createStatisticsFormGroup('Blacklist', 'Blacklisted'),
uiGenerator.createStatisticsFormGroup('Total'),
uiGenerator.createSettingsHideButton('settings'),
])
uiGenerator.appendToBody(section)
uiGenerator.appendToBody(uiGenerator.createSettingsShowButton('Search & Tweaks', section))
logger.logTaskCompletion('Building UI')
// Script run
// -- Initial compliance run & observer attachment
ChildObserver.observe(getVideoLists(), complianceCallback, true)
validator.sanitizeVideoPage('h1.entity-info-container__title')
logger.logTaskCompletion('Initial run and observer attachment.')