// ==UserScript==
// @name XNXX - Search Filters
// @namespace brazenvoid
// @version 3.0.2
// @author brazenvoid
// @license GPL-3.0-only
// @description Various search filters
// @include https://www.xnxx.com/*
// @require https://greasyfork.org/scripts/375557-base-resource/code/Base%20Resource.js?version=847480
// @grant GM_addStyle
// @run-at document-idle
// ==/UserScript==
GM_addStyle(`
section input, section textarea { font-size: 12px !important } section div.form-group { margin-bottom: 0 } section hr { margin: 0 3px } section #stats div.form-group { margin-bottom: 0 } section div.tab-panel.active { display: grid } section label.form-label { padding: 0 } div.form-section label.title { height: auto; margin-top: 5px }
`)
const PAGE_PATH_NAME = window.location.pathname
const IS_VIDEO_PAGE = PAGE_PATH_NAME.startsWith('/video-')
const FILTER_BLACKLIST_KEY = 'Blacklist'
const FILTER_VIDEO_RESOLUTION_KEY = 'Resolution'
const FILTER_RATING_VIDEOS_KEY = 'Rating'
const FILTER_VIDEO_DURATION_KEY = 'Duration'
const FILTER_VIDEO_VIEWS_KEY = 'Views'
const OPTION_ALWAYS_SHOW_UI = 'Always Show This Settings Pane'
const OPTION_DISABLE_VIDEO_FILTERS = 'Disable All Video Filters'
const OPTION_SANITIZATION_KEY = 'Video Names Sanitization Rules'
const VIDEO_DURATION_KEY = 'XNSSSFDuration'
const VIDEO_NAME_KEY = 'XNSSSFName'
const VIDEO_RATING_KEY = 'XNSSSFRating'
const VIDEO_RESOLUTION_KEY = 'XNSSSFResolution'
const VIDEO_VIEWS_KEY = 'XNSSSFViews'
class XNXXSearchFilters extends BaseHandler
{
/**
* @typedef {{duration: {maximum: number, minimum: number}, disableItemComplianceValidation: boolean, rating: {maximum: number, minimum: number}, blacklist: [], showUIAlways: boolean,
* resolution: string, views: {maximum: number, minimum: number}, sanitize: {}}} XNXXSFSettings
*/
static initialize ()
{
return (new XNXXSearchFilters).init()
}
constructor ()
{
super('xnxx-sf-', 'thumb-block', {
blacklist: [],
sanitize: {},
duration: {
minimum: 0,
maximum: 0,
},
rating: {
minimum: 0,
maximum: 0,
},
resolution: 'All',
views: {
minimum: 0,
maximum: 0,
},
})
// UI Events
this._onBeforeUIBuild = () => {
if (IS_VIDEO_PAGE) {
this._validator.sanitizeNodeOfSelector('.clear-infobar > strong:nth-child(1)')
}
}
this._onUIBuild = () =>
this._uiGen.createSection('settings', '#ffa31a', '20vh', '300px').addSectionChildren([
this._uiGen.createTabsSection(['Videos', 'Global', 'Stats'], [
this._uiGen.createTabPanel('Videos', [
this._uiGen.createFormRangeInputGroup(FILTER_VIDEO_DURATION_KEY, 'number'),
this._uiGen.createFormRangeInputGroup(FILTER_RATING_VIDEOS_KEY, 'number'),
this._uiGen.createFormRangeInputGroup(FILTER_VIDEO_VIEWS_KEY, 'number'),
this._uiGen.createSettingsDropDownFormGroup(FILTER_VIDEO_RESOLUTION_KEY,
[['All', 'Show All'], ['360', 'SD 360p'], ['480', 'SD 480p'], ['720', 'HD 720p'], ['1080', 'HD 1080p']]),
this._uiGen.createFormTextAreaGroup(FILTER_BLACKLIST_KEY, 2, 'Hide videos with these comma separated words in their names.'),
this._uiGen.createSeparator(),
this._uiGen.createFormInputGroup(OPTION_DISABLE_VIDEO_FILTERS, 'checkbox', 'Disables all video filters.'),
this._uiGen.createSeparator(),
this._createSettingsFormActions(),
this._uiGen.createSeparator(),
this._uiGen.createStoreFormSection(this._settingsStore),
]),
this._uiGen.createTabPanel('Global', [
this._uiGen.createFormTextAreaGroup(OPTION_SANITIZATION_KEY, 2, 'Censor video names by substituting offensive words. ' +
'Each rule in separate line and target words must be comma separated. ' +
'Example Rule: boyfriend=stepson,stepdad'),
this._uiGen.createSeparator(),
this._uiGen.createFormInputGroup(OPTION_ALWAYS_SHOW_UI, 'checkbox', 'Always show this interface.'),
this._uiGen.createSeparator(),
this._createSettingsFormActions(),
this._uiGen.createSeparator(),
this._uiGen.createStoreFormSection(this._settingsStore),
]),
this._uiGen.createTabPanel('Stats', [
this._uiGen.createStatisticsFormGroup(FILTER_BLACKLIST_KEY),
this._uiGen.createStatisticsFormGroup(FILTER_VIDEO_DURATION_KEY),
this._uiGen.createStatisticsFormGroup(FILTER_VIDEO_RESOLUTION_KEY),
this._uiGen.createStatisticsFormGroup(FILTER_RATING_VIDEOS_KEY),
this._uiGen.createStatisticsFormGroup(FILTER_VIDEO_VIEWS_KEY),
this._uiGen.createSeparator(),
this._uiGen.createStatisticsTotalsGroup(),
]),
]),
this._uiGen.createStatusSection(),
])
this._onAfterUIBuild = () => this._validator.setBlacklist(this._settings.blacklist).setSanitizationRules(this._settings.sanitize)
// Compliance Events
this._onGetItemLists = () => document.querySelectorAll('.mozaique')
this._onFirstHitBeforeCompliance = (item) => this._analyzeVideoItem(item)
this._complianceFilters = [
(videoItem) => this._validateRating(videoItem),
(videoItem) => this._validateDuration(videoItem),
(videoItem) => this._validateViews(videoItem),
(videoItem) => this._validateResolution(videoItem),
(videoItem) => this._validator.validateBlackList(videoItem[VIDEO_NAME_KEY].textContent),
]
this._onFirstHitAfterCompliance = (item) => this._validator.sanitizeTextNode(item[VIDEO_NAME_KEY])
// Store Events
this._onSettingsStoreUpdate = () => {
/** @type {XNXXSFSettings} */
let store = this._settingsStore.get()
this._uiGen.setSettingsInputCheckedStatus(OPTION_ALWAYS_SHOW_UI, store.showUIAlways)
this._uiGen.setSettingsInputCheckedStatus(OPTION_DISABLE_VIDEO_FILTERS, store.disableItemComplianceValidation)
this._uiGen.setSettingsInputValue(FILTER_BLACKLIST_KEY, store.blacklist.join(','))
this._uiGen.setSettingsInputValue(FILTER_VIDEO_RESOLUTION_KEY, store.resolution)
this._uiGen.setSettingsInputValue(OPTION_SANITIZATION_KEY, this._transformSanitizationRulesToText(store.sanitize))
this._uiGen.setSettingsRangeInputValue(FILTER_VIDEO_DURATION_KEY, store.duration.minimum, store.duration.maximum)
this._uiGen.setSettingsRangeInputValue(FILTER_RATING_VIDEOS_KEY, store.rating.minimum, store.rating.maximum)
this._uiGen.setSettingsRangeInputValue(FILTER_VIDEO_VIEWS_KEY, store.views.minimum, store.views.maximum)
}
this._onSettingsApply = () => {
/** @type {XNXXSFSettings} */
let settings = this._settings
settings.disableItemComplianceValidation = this._uiGen.getSettingsInputCheckedStatus(OPTION_DISABLE_VIDEO_FILTERS)
settings.duration.minimum = this._uiGen.getSettingsRangeInputValue(FILTER_VIDEO_DURATION_KEY, true)
settings.duration.maximum = this._uiGen.getSettingsRangeInputValue(FILTER_VIDEO_DURATION_KEY, false)
settings.rating.minimum = this._uiGen.getSettingsRangeInputValue(FILTER_RATING_VIDEOS_KEY, true)
settings.rating.maximum = this._uiGen.getSettingsRangeInputValue(FILTER_RATING_VIDEOS_KEY, false)
settings.resolution = this._uiGen.getSettingsInputValue(FILTER_VIDEO_RESOLUTION_KEY)
settings.showUIAlways = this._uiGen.getSettingsInputCheckedStatus(OPTION_ALWAYS_SHOW_UI)
settings.views.minimum = this._uiGen.getSettingsRangeInputValue(FILTER_VIDEO_VIEWS_KEY, true)
settings.views.maximum = this._uiGen.getSettingsRangeInputValue(FILTER_VIDEO_VIEWS_KEY, false)
this._validateAndSetBlacklistedWords(this._uiGen.getSettingsInputValue(FILTER_BLACKLIST_KEY).split(','))
this._validateAndSetSanitizationRules(this._uiGen.getSettingsInputValue(OPTION_SANITIZATION_KEY).split(/\r?\n/g))
}
}
/**
* @param {Node|HTMLElement} videoItem
* @private
*/
_analyzeVideoItem (videoItem)
{
let videoMetadata = videoItem.querySelector('.metadata')
if (videoMetadata) {
if (IS_VIDEO_PAGE) {
videoMetadata = videoMetadata.textContent.split(' ')
videoItem[VIDEO_DURATION_KEY] = this._analyzeVideItemDuration(videoMetadata[1])
videoItem[VIDEO_RATING_KEY] = 100
videoItem[VIDEO_RESOLUTION_KEY] = parseInt(videoMetadata[5].replace('p', ''))
videoItem[VIDEO_VIEWS_KEY] = videoMetadata[0]
} else {
videoMetadata = videoMetadata.textContent.split('\n')
if (videoMetadata.length > 3) {
videoMetadata[1] = videoMetadata[1].split(' ')
videoItem[VIDEO_DURATION_KEY] = this._analyzeVideItemDuration(videoMetadata[2])
videoItem[VIDEO_RATING_KEY] = videoMetadata[1][1].replace('%', '')
videoItem[VIDEO_RESOLUTION_KEY] = parseInt(videoMetadata[3].replace(' - ', '').replace('p', ''))
videoItem[VIDEO_VIEWS_KEY] = videoMetadata[1][0]
} else {
videoItem[VIDEO_DURATION_KEY] = this._analyzeVideItemDuration(videoMetadata[1])
videoItem[VIDEO_RATING_KEY] = 100
videoItem[VIDEO_RESOLUTION_KEY] = parseInt(videoMetadata[2].replace(' - ', '').replace('p', ''))
videoItem[VIDEO_VIEWS_KEY] = 0
}
}
videoItem[VIDEO_NAME_KEY] = videoItem.querySelector('.thumb-under > p:nth-child(1) > a:nth-child(1)')
}
}
/**
* @param {string} durationString
* @return {number}
* @private
*/
_analyzeVideItemDuration (durationString)
{
let duration = 0, splitArray
if (IS_VIDEO_PAGE) {
splitArray = durationString.split(' ')
for (let i = 0; i < splitArray.length; i++) {
if (splitArray[i].endsWith('min')) {
duration += 60 * splitArray[i].replace('min', '')
} else {
if (splitArray[i].endsWith('sec')) {
duration += splitArray[i].replace('sec', '')
}
}
}
} else {
splitArray = durationString.split('min')
if (splitArray.length === 2) {
duration = 60 * splitArray[0]
} else {
splitArray = durationString.split('sec')
if (splitArray.length === 2) {
duration = splitArray[0]
}
}
}
return duration
}
/**
* Validates video duration
* @param {Node|HTMLElement} videoItem
* @return {boolean}
* @private
*/
_validateDuration (videoItem)
{
if (this._settings.duration.minimum > 0 || this._settings.duration.maximum > 0) {
return this._validator.validateRange(
FILTER_VIDEO_DURATION_KEY, videoItem[VIDEO_DURATION_KEY], [this._settings.duration.minimum, this._settings.duration.maximum])
}
return true
}
/**
* Validate video rating
* @param {Node|HTMLElement} videoItem
* @return {boolean}
* @private
*/
_validateRating (videoItem)
{
if (this._settings.rating.minimum > 0 || this._settings.rating.maximum > 0) {
return this._validator.validateRange(FILTER_RATING_VIDEOS_KEY, videoItem[VIDEO_RATING_KEY], [this._settings.rating.minimum, this._settings.rating.maximum])
}
return true
}
/**
* Validate video quality
* @param {Node|HTMLElement} videoItem
* @return {boolean}
* @private
*/
_validateResolution (videoItem)
{
let validationCheck = true
if (this._settings.resolution !== 'All') {
validationCheck = videoItem[VIDEO_RESOLUTION_KEY] >= this._settings.resolution
this._statistics.record(FILTER_VIDEO_RESOLUTION_KEY, validationCheck)
}
return validationCheck
}
/**
* Validate video view count
* @param {Node|HTMLElement} videoItem
* @return {boolean}
* @private
*/
_validateViews (videoItem)
{
if (this._settings.views.minimum > 0 || this._settings.views.maximum > 0) {
return this._validator.validateRange(FILTER_VIDEO_VIEWS_KEY, videoItem[VIDEO_VIEWS_KEY], [this._settings.views.minimum, this._settings.views.maximum])
}
return true
}
}
XNXXSearchFilters.initialize()