// ==UserScript==
// @name CelebrityMovieArchive - Search Enhancements
// @namespace brazenvoid
// @version 1.3.0
// @author brazenvoid
// @license GPL-3.0-only
// @description Various search filters and user experience enhancers
// @match https://www.celebritymoviearchive.com/*
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js
// @require https://greasyfork.org/scripts/375557-base-brazen-resource/code/Base%20Brazen%20Resource.js?version=1115796
// @require https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=1115813
// @require https://greasyfork.org/scripts/418665-brazen-configuration-manager/code/Brazen%20Configuration%20Manager.js?version=1163542
// @require https://greasyfork.org/scripts/429587-brazen-item-attributes-resolver/code/Brazen%20Item%20Attributes%20Resolver.js?version=1139392
// @require https://greasyfork.org/scripts/416105-brazen-base-search-enhancer/code/Brazen%20Base%20Search%20Enhancer.js?version=1163543
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
GM_addStyle(`#settings-wrapper{top:5vh;width:310px;font-size:11px}.bg-brand{background-color:#1774ab}.font-primary{color:#feff76}.font-secondary{color:black}`)
// Environment
const PAGE_PATH_NAME = window.location.pathname
const IS_ACTRESS_PAGE = PAGE_PATH_NAME.startsWith('/members/name.php')
const IS_LIBRARY_PAGE = PAGE_PATH_NAME.startsWith('/members/mydownloads.php')
const IS_TITLE_PAGE = PAGE_PATH_NAME.startsWith('/members/source.php')
const IS_VIDEO_PAGE = PAGE_PATH_NAME.startsWith('/members/movie.php')
const IS_PAGINATED_VIDEO_LIST_PAGE = !(IS_ACTRESS_PAGE || IS_TITLE_PAGE || IS_VIDEO_PAGE)
const RESOLUTION_BREAKPOINTS_IN_PIXELS = [6635520, 2949120, 1658880, 737280, 0]
const ITEM_SELECTOR = '.info'
// Config
const DISABLE_ON_ACTRESS_PAGE = 'Disable on actress pages'
const DISABLE_ON_LIBRARY_PAGE = 'Disable on library pages'
const DISABLE_ON_TITLE_PAGE = 'Disable on source pages'
// Filters
const FILTER_ACTRESS_BLACKLIST = 'Actress Blacklist'
const FILTER_CONTENT = 'Content Rating'
const FILTER_DOWNLOADS = 'Downloads'
const FILTER_DURATION = 'Duration'
const FILTER_RESOLUTION = 'Resolution'
const FILTER_UNWATCHED = 'Unwatched'
const FILTER_YEAR = 'Year'
// UI
const UI_AUTO_NEXT = 'Automatic Next'
class CMASearchEnhancements extends BrazenBaseSearchEnhancer
{
constructor()
{
super({
isUserLoggedIn: $('.logout').length > 0,
itemDeepAnalysisSelector: '.articleInfo',
itemLinkSelector: '.btmInfo > em > .filename',
itemListSelectors: '.demo',
itemNameSelector: 'p',
itemSelectors: ITEM_SELECTOR,
requestDelay: 0,
scriptPrefix: 'cma-se-',
})
/**
* @type {JQuery|string|null}
* @private
*/
this._navNextPage = $('.pagi > .next')
this._navNextPage = $(ITEM_SELECTOR).length === 0 || this._navNextPage.attr('href') === PAGE_PATH_NAME
? null
: this._navNextPage.attr('href')
this._configurationManager
.addCheckboxesGroup(FILTER_RESOLUTION, [
['4k', RESOLUTION_BREAKPOINTS_IN_PIXELS[0]],
['1440p', RESOLUTION_BREAKPOINTS_IN_PIXELS[1]],
['1080p', RESOLUTION_BREAKPOINTS_IN_PIXELS[2]],
['720p', RESOLUTION_BREAKPOINTS_IN_PIXELS[3]],
['SD', RESOLUTION_BREAKPOINTS_IN_PIXELS[4]],
], 'Show videos of resolutions selected.')
.addFlagField(DISABLE_ON_ACTRESS_PAGE, 'Disable script on actress pages.')
.addFlagField(DISABLE_ON_LIBRARY_PAGE, 'Disable script on your library pages.')
.addFlagField(DISABLE_ON_TITLE_PAGE, 'Disable script on series/movie pages.')
.addFlagField(FILTER_UNWATCHED, 'Hide videos that are in your library. Has major performance and data impact.')
.addFlagField(UI_AUTO_NEXT, 'Automatically go to next page when all results get filtered.')
.addRadiosGroup(FILTER_CONTENT, [
['Nude', 'Nude'],
['Sexy', 'Sexy'],
['All', 'All'],
], 'Show content rated as per the choices selected')
.addRangeField(FILTER_DURATION, 0, 10000000, 'Filter videos by duration in seconds.')
.addRangeField(FILTER_DOWNLOADS, 0, 10000000, 'Filter videos by the number of downloads. Has major performance and data impact.')
.addRangeField(FILTER_YEAR, 0, 10000000, 'Filter videos by the years of release.')
.addRulesetField(FILTER_ACTRESS_BLACKLIST, 5, 'Hide all videos of specified actresses.')
this._itemAttributesResolver
.addAttribute(FILTER_ACTRESS_BLACKLIST, (item) => item.find('h2 > a').text().trim())
.addAttribute(FILTER_CONTENT, (item) => item.find('img').length ? 'Nude' : 'Sexy')
.addAttribute(FILTER_RESOLUTION, (item) => {
let resolution = item.find('span > em')
.eq(this._get(item, FILTER_CONTENT) === 'Sexy' ? 1 : 0)
.text()
.trim()
.split(' ')[5]
.split('x')
return parseInt(resolution[0]) * parseInt(resolution[1])
})
.addAttribute(FILTER_YEAR, (item) => {
let title = item.find('h3 > a')
if (title.length) {
let year = title.text().trim().split('(').pop().replace(REGEX_PRESERVE_NUMBERS, '')
if (year !== '') {
return parseInt(year)
}
}
return null
})
.addDeepAttribute(FILTER_DOWNLOADS, (page) => parseInt(page.find('.cols > .col > dl').last().find('dd').text().trim().replace(' times', '')))
.addDeepAttribute(FILTER_UNWATCHED, (page) => page.find('.movieMessage').length === 1)
this._onItemHide = (item) => item.parent().addClass('noncompliant-item').hide()
this._onItemShow = (item) => item.parent().removeClass('noncompliant-item').show()
this._onBeforeCompliance = () => !(
IS_VIDEO_PAGE ||
(IS_ACTRESS_PAGE && this._getConfig(DISABLE_ON_ACTRESS_PAGE)) ||
(IS_LIBRARY_PAGE && this._getConfig(DISABLE_ON_LIBRARY_PAGE)) ||
(IS_TITLE_PAGE && this._getConfig(DISABLE_ON_TITLE_PAGE))
)
this._onAfterComplianceRun = () => this._performComplexFlaggedOperation(
UI_AUTO_NEXT,
() => !(IS_ACTRESS_PAGE || IS_TITLE_PAGE || IS_VIDEO_PAGE) && this._navNextPage &&
$('li:not(.noncompliant-item) > ' + ITEM_SELECTOR).length === 0,
() => window.location = this._navNextPage
)
this._setupUI()
this._setupComplianceFilters()
}
/**
* @private
*/
_setupComplianceFilters()
{
this._addItemWhitelistFilter('Show videos with specified phrases in their description')
this._addItemComplianceFilter(FILTER_CONTENT, (item, value) => {
let contentRating = this._get(item, FILTER_CONTENT)
return value === 'All' ? true : value === contentRating
})
this._addItemDurationRangeFilter(
(item) => item.find('span > em').eq(this._get(item, FILTER_CONTENT) === 'Sexy' ? 1 : 0).text().trim().split(' ')[0])
this._addItemComplianceFilter(FILTER_RESOLUTION, (item, values) => {
let resolution = this._get(item, FILTER_RESOLUTION)
for (let breakpoint of RESOLUTION_BREAKPOINTS_IN_PIXELS) {
if (resolution > breakpoint) {
return values.includes(breakpoint.toString())
}
}
return false
})
this._addItemComplianceFilter(FILTER_YEAR)
this._addItemComplianceFilter(FILTER_ACTRESS_BLACKLIST, (item, values) => {
let attribute = this._get(item, FILTER_ACTRESS_BLACKLIST)
return attribute && values.length ? !values.includes(attribute) : true
})
this._addItemBlacklistFilter('Hide videos with specified phrases in their description.')
this._addItemComplianceFilter(FILTER_UNWATCHED, FILTER_UNWATCHED, (value) => !IS_LIBRARY_PAGE && value)
this._addItemComplianceFilter(FILTER_DOWNLOADS)
}
/**
* @private
*/
_setupUI()
{
this._onUIBuild = () =>
this._uiGen.createSettingsSection().append([
this._uiGen.createTabsSection(['Filters', 'Text', 'Global', 'Stats'], [
this._uiGen.createTabPanel('Filters', true).append([
this._configurationManager.createElement(FILTER_DOWNLOADS),
this._configurationManager.createElement(FILTER_DURATION_RANGE),
this._configurationManager.createElement(FILTER_YEAR),
this._configurationManager.createElement(FILTER_UNWATCHED),
this._uiGen.createSeparator(),
this._configurationManager.createElement(FILTER_RESOLUTION),
this._uiGen.createSeparator(),
this._configurationManager.createElement(FILTER_CONTENT),
this._uiGen.createSeparator(),
this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
]),
this._uiGen.createTabPanel('Text').append([
this._configurationManager.createElement(FILTER_ACTRESS_BLACKLIST),
this._configurationManager.createElement(FILTER_TEXT_BLACKLIST),
this._configurationManager.createElement(FILTER_TEXT_WHITELIST),
]),
this._uiGen.createTabPanel('Global').append([
this._configurationManager.createElement(UI_AUTO_NEXT),
this._uiGen.createSeparator(),
this._configurationManager.createElement(DISABLE_ON_ACTRESS_PAGE),
this._configurationManager.createElement(DISABLE_ON_LIBRARY_PAGE),
this._configurationManager.createElement(DISABLE_ON_TITLE_PAGE),
this._uiGen.createSeparator(),
this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
this._uiGen.createSeparator(),
this._createSettingsBackupRestoreFormActions(),
]),
this._uiGen.createTabPanel('Stats').append([
this._uiGen.createStatisticsFormGroup(FILTER_ACTRESS_BLACKLIST),
this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST),
this._uiGen.createStatisticsFormGroup(FILTER_TEXT_WHITELIST),
this._uiGen.createStatisticsFormGroup(FILTER_CONTENT),
this._uiGen.createStatisticsFormGroup(FILTER_DOWNLOADS),
this._uiGen.createStatisticsFormGroup(FILTER_DURATION),
this._uiGen.createStatisticsFormGroup(FILTER_RESOLUTION),
this._uiGen.createStatisticsFormGroup(FILTER_UNWATCHED),
this._uiGen.createStatisticsFormGroup(FILTER_YEAR),
this._uiGen.createSeparator(),
this._uiGen.createStatisticsTotalsGroup(),
]),
]),
this._createSettingsFormActions(),
this._uiGen.createSeparator(),
this._uiGen.createStatusSection(),
])
this._onAfterUIBuild = () => {
this._uiGen.getSelectedSection()[0].userScript = this
}
}
}
(new CMASearchEnhancements).init()