// ==UserScript==
// @name SpankBang - Search and UI Enhancer
// @namespace brazenvoid
// @version 1.0.0
// @author brazenvoid
// @license GPL-3.0-only
// @description Various search filters and user experience enhancers
// @match https://spankbang.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=899286
// @require https://greasyfork.org/scripts/416104-brazen-ui-generator/code/Brazen%20UI%20Generator.js?version=899448
// @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=899428
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
GM_addStyle(`button.show-settings{height:60vh;top:20vh}#settings-wrapper{top:20vh;width:300px}.bg-brand{background-color:#69150d}.font-primary{color:white}.font-secondary{color:black}hr{background:white}`)
const PAGE_PATH_NAME = window.location.pathname
const PAGE_PATH_FRAGMENTS = PAGE_PATH_NAME.split('/')
const IS_HOME_PAGE = PAGE_PATH_NAME === '/'
const IS_VIDEO_PAGE = PAGE_PATH_FRAGMENTS[2] === 'video' || PAGE_PATH_FRAGMENTS[2] === 'playlist'
const ITEM_CLASSES = 'div.video-item'
const ITEM_LISTS_SELECTOR = 'div.video-list'
const ITEM_TEXT_NODE_SELECTOR = 'span.n'
const SCRIPT_PREFIX = 'sb-sui-'
// Configuration
const FILTER_VIDEOS_SD = 'Show Only HD Videos'
const FILTER_VIDEOS_DURATION = 'Duration'
const FILTER_VIDEOS_MAXIMUM_AGE = 'Maximum Age'
const FILTER_VIDEOS_MAXIMUM_AGE_UNIT = 'Maximum Age Unit'
const FILTER_VIDEOS_MINIMUM_AGE = 'Minimum Age'
const FILTER_VIDEOS_MINIMUM_AGE_UNIT = 'Minimum Age Unit'
const FILTER_VIDEOS_RATING = 'Rating'
const FILTER_VIDEOS_VIEWS = 'Views'
const UI_REMOVE_EMBED_VIDEO_SECTION = 'Remove Embed Video Section'
const UI_REMOVE_LIVE_MODEL_SECTIONS = 'Remove Live Model Sections'
const UI_REDIRECT_SUBSCRIPTIONS_LINK = 'Redirect Subscriptions Link'
const UI_REPOSITION_SCREENSHOTS = 'Reposition Video Screenshots'
const UI_REPOSITION_VIDEO_DETAILS = 'Reposition Video Details'
const UI_SWAP_RELATED_VIDEOS = 'Reposition Related Videos'
// Item attributes
const ITEM_AGE = 'scriptAge'
const ITEM_DURATION = 'scriptAge'
const ITEM_RATING = 'scriptRating'
const ITEM_VIEWS = 'scriptViews'
class SpankBangSearchAndUIEnhancer extends BrazenBaseSearchEnhancer
{
constructor ()
{
super(SCRIPT_PREFIX, ITEM_CLASSES)
this._configurationManager.
addFlagField(FILTER_VIDEOS_SD, 'Filter videos of less than 720p resolution.').
addFlagField(UI_REMOVE_EMBED_VIDEO_SECTION, 'Removes embed video section on video pages.').
addFlagField(UI_REMOVE_LIVE_MODEL_SECTIONS, 'Removes live model sections from the site.').
addFlagField(UI_REDIRECT_SUBSCRIPTIONS_LINK, 'Redirects subscription videos page shortcut on user bar to new subscribed videos view.').
addFlagField(UI_REPOSITION_SCREENSHOTS, 'Move video screenshots above video player on video pages.').
addFlagField(UI_REPOSITION_VIDEO_DETAILS, 'Move video details section to the top of the right pane.').
addFlagField(UI_SWAP_RELATED_VIDEOS, 'Swaps related videos section with comments section').
addNumberField(FILTER_VIDEOS_MAXIMUM_AGE, 0, 100, 'Maximum age filter value.').
addNumberField(FILTER_VIDEOS_MINIMUM_AGE, 0, 100, 'Minimum age filter value.').
addRangeField(FILTER_VIDEOS_DURATION, 0, 100000, 'Filter videos by duration in minutes.').
addRangeField(FILTER_VIDEOS_RATING, 0, 100, 'Filter videos by duration.').
addRangeField(FILTER_VIDEOS_VIEWS, 0, 9999999999, 'Filter videos by view count.')
// addSelectField(FILTER_VIDEOS_MAXIMUM_AGE_UNIT, [
// ['Minutes', 'minutes'],
// ['Hours', 'hours'],
// ['Days', 'days'],
// ['Weeks', 'weeks'],
// ['Months', 'months'],
// ['Years', 'years]'],
// ], 'Maximum age filter unit.').
// addSelectField(FILTER_VIDEOS_MINIMUM_AGE_UNIT, [
// ['Minutes', 'minutes'],
// ['Hours', 'hours'],
// ['Days', 'days'],
// ['Weeks', 'weeks'],
// ['Months', 'months'],
// ['Years', 'years]'],
// ], 'Minimum age filter unit.')
// this._addPaginationConfiguration()
this._setupUI()
this._setupCompliance()
this._setupComplianceFilters()
}
_analyseItem (item)
{
let stats = item.find('div.stats')
if (stats.length) {
stats = stats.text().split('%')
item[0][ITEM_AGE] = stats[1]
stats = stats[0].split(' ')
item[0][ITEM_VIEWS] = stats[0]
item[0][ITEM_RATING] = parseInt(stats[1].trim())
} else {
item[0][ITEM_AGE] = null
item[0][ITEM_RATING] = null
item[0][ITEM_VIEWS] = null
}
item[0][ITEM_DURATION] = parseInt(item.find('span.l').text().replace('m', ''))
}
/**
* @private
*/
_redirectSubscriptionsLink ()
{
if (this._configurationManager.getValue(UI_REDIRECT_SUBSCRIPTIONS_LINK)) {
$('a[href="/users/social"]').attr('href', '/users/social?sort=new')
}
}
/**
* @private
*/
_removeEmbedVideoSection ()
{
if (this._configurationManager.getValue(UI_REMOVE_EMBED_VIDEO_SECTION)) {
$('.embed').remove()
}
}
/**
* @private
*/
_removeLiveModelSections ()
{
if (this._configurationManager.getValue(UI_REMOVE_EMBED_VIDEO_SECTION)) {
let liveModelsSection = $('.lv_cm_cl_mx_why')
if (IS_HOME_PAGE) {
liveModelsSection.prev().remove()
liveModelsSection.next().remove()
}
liveModelsSection.remove()
}
}
/**
* @return {JQuery<HTMLElement> | jQuery | HTMLElement}
* @private
*/
_generatePseudoLeftSection ()
{
return $('<div class="left" style="padding: 23px 0 0 0"></div>')
}
/**
* @private
*/
_repositionVideoDetails ()
{
if (this._configurationManager.getValue(UI_REPOSITION_VIDEO_DETAILS)) {
let rightPane = $('div.right')
let pseudoLeftSection = this._generatePseudoLeftSection()
rightPane.prepend(pseudoLeftSection)
pseudoLeftSection.append($('div.left section.details'))
}
}
/**
* @private
*/
_repositionVideoScreenshots ()
{
if (this._configurationManager.getValue(UI_REPOSITION_SCREENSHOTS)) {
$('div.left section.timeline').insertBefore('#player_wrapper_outer')
}
}
/**
* @private
*/
_setupCompliance ()
{
this._onGetItemLists = () => $(ITEM_LISTS_SELECTOR)
this._onGetItemName = (videoItem) => videoItem.find(ITEM_TEXT_NODE_SELECTOR).text()
this._onFirstHitBeforeCompliance = (item) => this._analyseItem(item)
this._onFirstHitAfterCompliance = (item) => {
Validator.sanitizeTextNode(item.find(ITEM_TEXT_NODE_SELECTOR), 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._addItemComplianceFilter(FILTER_VIDEOS_RATING, (item, range) =>
item[0][ITEM_RATING] ? Validator.isInRange(item[0][ITEM_RATING], range.minimum, range.maximum) : true)
this._addItemComplianceFilter(FILTER_VIDEOS_DURATION, (item, range) =>
Validator.isInRange(item[0][ITEM_DURATION], range.minimum, range.maximum))
this._addItemComplianceFilter(FILTER_VIDEOS_VIEWS, (item, range) => {
let viewsString = item[0][ITEM_VIEWS]
if (!viewsString) {
return true
}
let viewsAmount = parseFloat(viewsString.replace('K', '').replace('M', '').replace('B', ''))
if (viewsString.endsWith('K')) {
viewsAmount *= 1000
} else if (viewsString.endsWith('M')) {
viewsAmount *= 1000000
} else if (viewsString.endsWith('B')) {
viewsAmount *= 1000000000
}
return Validator.isInRange(viewsAmount, range.minimum, range.maximum)
})
this._addItemComplianceFilter(FILTER_VIDEOS_SD, (item) => Validator.doesChildExist(item, 'span.h'))
this._addItemBlacklistFilter('Hide videos with specified phrases in their names. Separate the phrases with line breaks.')
}
/**
* @private
*/
_setupUI ()
{
this._onBeforeUIBuild = () => {
this._redirectSubscriptionsLink()
this._removeLiveModelSections()
if (IS_VIDEO_PAGE) {
this._removeEmbedVideoSection()
this._repositionVideoDetails()
this._repositionVideoScreenshots()
this._swapRelatedVideos()
Validator.sanitizeNodeOfSelector('div.video > div.left > h1', this._configurationManager.getFieldOrFail(FILTER_TEXT_SANITIZATION).optimized)
}
}
this._onUIBuild = () =>
this._uiGen.createSettingsSection().append([
this._uiGen.createTabsSection(['Filters', 'Text', 'UI', 'Global', 'Stats'], [
this._uiGen.createTabPanel('Filters', true).append([
this._configurationManager.createElement(FILTER_VIDEOS_DURATION),
this._configurationManager.createElement(FILTER_VIDEOS_RATING),
this._configurationManager.createElement(FILTER_VIDEOS_VIEWS),
// this._configurationManager.createElement(FILTER_VIDEOS_MINIMUM_AGE),
// this._configurationManager.createElement(FILTER_VIDEOS_MINIMUM_AGE_UNIT),
// this._configurationManager.createElement(FILTER_VIDEOS_MAXIMUM_AGE),
// this._configurationManager.createElement(FILTER_VIDEOS_MAXIMUM_AGE_UNIT),
this._configurationManager.createElement(FILTER_VIDEOS_SD),
this._uiGen.createSeparator(),
this._configurationManager.createElement(OPTION_DISABLE_COMPLIANCE_VALIDATION),
]),
this._uiGen.createTabPanel('Text').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('UI').append([
this._configurationManager.createElement(UI_REDIRECT_SUBSCRIPTIONS_LINK),
this._configurationManager.createElement(UI_REMOVE_EMBED_VIDEO_SECTION),
this._configurationManager.createElement(UI_REMOVE_LIVE_MODEL_SECTIONS),
this._configurationManager.createElement(UI_SWAP_RELATED_VIDEOS),
this._configurationManager.createElement(UI_REPOSITION_VIDEO_DETAILS),
this._configurationManager.createElement(UI_REPOSITION_SCREENSHOTS),
this._uiGen.createSeparator(),
this._configurationManager.createElement(OPTION_ALWAYS_SHOW_SETTINGS_PANE),
]),
this._uiGen.createTabPanel('Global').append([
this._createSettingsBackupRestoreFormActions(),
]),
this._uiGen.createTabPanel('Stats').append([
this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_DURATION),
this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_RATING),
this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_VIEWS),
this._uiGen.createStatisticsFormGroup(FILTER_VIDEOS_SD),
this._uiGen.createStatisticsFormGroup(FILTER_TEXT_SEARCH),
this._uiGen.createStatisticsFormGroup(FILTER_TEXT_BLACKLIST),
this._uiGen.createStatisticsFormGroup(FILTER_TEXT_WHITELIST),
this._uiGen.createSeparator(),
this._uiGen.createStatisticsTotalsGroup(),
]),
]),
this._createSettingsFormActions(),
this._uiGen.createSeparator(),
this._uiGen.createStatusSection(),
])
this._onAfterUIBuild = () => {
this._uiGen.getSelectedSection()[0].userScript = this
}
}
/**
* @private
*/
_swapRelatedVideos ()
{
if (this._configurationManager.getValue(UI_SWAP_RELATED_VIDEOS)) {
let newRelatedVideosSection = $('<section class="user_uploads"></section>')
newRelatedVideosSection.insertAfter('section.all_comments')
newRelatedVideosSection.append('<h2>Similar Videos</h2>')
newRelatedVideosSection.append($('.similar div.video-list'))
$('.similar').remove()
let pseudoLeftSection = this._configurationManager.getValue(UI_REPOSITION_VIDEO_DETAILS) ? $('div.right div.left') : this._generatePseudoLeftSection()
pseudoLeftSection.append($('section.all_comments'))
}
}
}
(new SpankBangSearchAndUIEnhancer).init()