Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.sleazyfork.org/scripts/416105/879317/Brazen%20Base%20Search%20Enhancer.js
// ==UserScript==
// @name Brazen Base Search Enhancer
// @namespace brazen
// @version 2.1.3
// @author brazenvoid
// @license GPL-3.0-only
// @description Base class for search enhancement scripts
// ==/UserScript==
const CONFIG_PAGINATOR_LIMIT = 'Pagination Limit'
const CONFIG_PAGINATOR_THRESHOLD = 'Pagination Threshold'
const OPTION_ALWAYS_SHOW_SETTINGS_PANE = 'Always Show Settings Pane'
const OPTION_DISABLE_COMPLIANCE_VALIDATION = 'Disable Item Compliance Validation'
const FILTER_TEXT_BLACKLIST = 'Blacklist'
const FILTER_TEXT_SEARCH = 'Search'
const FILTER_TEXT_SANITIZATION = 'Text Sanitization Rules'
const FILTER_TEXT_WHITELIST = 'Whitelist'
class BrazenBaseSearchEnhancer
{
/**
* @return BrazenBaseSearchEnhancer
*/
static initialize ()
{
BrazenBaseSearchEnhancer.throwOverrideError()
}
static throwOverrideError ()
{
throw new Error('Method must be overridden.')
}
/**
* @param {string} scriptPrefix
* @param {string|string[]} itemClasses
* @param {boolean} enablePaginator
*/
constructor (scriptPrefix, itemClasses, enablePaginator = false)
{
/**
* Array of item compliance filters ordered in intended sequence of execution
* @type {Function[]}
* @protected
*/
this._complianceFilters = []
/**
* @type {string[]}
* @protected
*/
this._itemClasses = Array.isArray(itemClasses) ? itemClasses : [itemClasses]
/**
* @type {string}
* @protected
*/
this._itemClassesSelector = '.' + this._itemClasses.join(',.')
/**
* Pagination manager
* @type Paginator|null
* @protected
*/
this._paginator = null
/**
* @type {string}
* @protected
*/
this._scriptPrefix = scriptPrefix
/**
* @type {StatisticsRecorder}
* @protected
*/
this._statistics = new StatisticsRecorder(this._scriptPrefix)
/**
* @type {BrazenUIGenerator}
* @protected
*/
this._uiGen = new BrazenUIGenerator(this._scriptPrefix)
/**
* @type {Validator}
* @protected
*/
this._validator = (new Validator(this._statistics))
/**
* Local storage store with defaults
* @type {ConfigurationManager}
* @protected
*/
this._configurationManager = ConfigurationManager.create(this._uiGen).
addFlagField(OPTION_DISABLE_COMPLIANCE_VALIDATION, 'Disables all search filters.').
addFlagField(OPTION_ALWAYS_SHOW_SETTINGS_PANE, 'Always show configuration interface.').
addNumberField(CONFIG_PAGINATOR_LIMIT, 0, 50, 'Limit paginator to concatenate the specified number of maximum pages. Zero equates to infinite.').
addNumberField(CONFIG_PAGINATOR_THRESHOLD, 10, 1000, 'Make paginator ensure the specified number of minimum results.').
addRulesetField(FILTER_TEXT_BLACKLIST, '', null, null, (rules) => Utilities.buildWholeWordMatchingRegex(rules)).
addRulesetField(FILTER_TEXT_SANITIZATION, '', (rules) => {
let sanitizationRulesText = []
for (let substitute in rules) {
sanitizationRulesText.push(substitute + '=' + rules[substitute].join(','))
}
return sanitizationRulesText
}, (rules) => {
let sanitizationRules = {}, fragments, validatedTargetWords
for (let sanitizationRule of rules) {
if (sanitizationRule.includes('=')) {
fragments = sanitizationRule.split('=')
if (fragments[0] === '') {
fragments[0] = ' '
}
validatedTargetWords = Utilities.trimAndKeepNonEmptyStrings(fragments[1].split(','))
if (validatedTargetWords.length) {
sanitizationRules[fragments[0]] = validatedTargetWords
}
}
}
return sanitizationRules
}, (rules) => {
let optimizedRules = {}
for (const substitute in rules) {
optimizedRules[substitute] = Utilities.buildWholeWordMatchingRegex(rules[substitute])
}
return optimizedRules
}).
addRulesetField(FILTER_TEXT_WHITELIST, '', null, null, (rules) => Utilities.buildWholeWordMatchingRegex(rules)).
addTextField(FILTER_TEXT_SEARCH, 'Show videos with these comma separated words in their names.')
// Events
/**
* Operations to perform after script initialization
* @type {Function}
* @protected
*/
this._onAfterInitialization = null
/**
* Operations to perform after UI generation
* @type {Function}
* @protected
*/
this._onAfterUIBuild = null
/**
* Operations to perform before compliance validation. This callback can also be used to skip compliance validation by returning false.
* @type {null}
* @protected
*/
this._onBeforeCompliance = null
/**
* Operations to perform before UI generation
* @type {Function}
* @protected
*/
this._onBeforeUIBuild = null
/**
* Operations to perform after compliance checks, the first time a item is retrieved
* @type {Function}
* @protected
*/
this._onFirstHitAfterCompliance = null
/**
* Operations to perform before compliance checks, the first time a item is retrieved
* @type {Function}
* @protected
*/
this._onFirstHitBeforeCompliance = null
/**
* Get item lists from the page
* @type {Function}
* @protected
*/
this._onGetItemLists = null
/**
* Logic to hide a non-compliant item
* @type {Function}
* @protected
*/
this._onItemHide = (item) => item.hide(100)
/**
* Logic to show compliant item
* @type {Function}
* @protected
*/
this._onItemShow = (item) => item.show(100)
/**
* Must return the generated settings section node
* @type {Function}
* @protected
*/
this._onUIBuild = null
/**
* Validate initiating initialization.
* Can be used to stop script init on specific pages or vice versa
* @type {Function}
* @protected
*/
this._onValidateInit = () => true
}
/**
* Filters items as per settings
* @param {JQuery} itemsList
* @param {boolean} fromObserver
* @protected
*/
_complyItemsList (itemsList, fromObserver = false)
{
let items = fromObserver ? itemsList.filter(this._itemClassesSelector) : itemsList.find(this._itemClassesSelector)
items.each((index, element) => {
let item = $(element)
if (typeof element['scriptProcessedOnce'] === 'undefined') {
element.scriptProcessedOnce = false
Utilities.callEventHandler(this._onFirstHitBeforeCompliance, [item])
}
this._validateItemCompliance(item)
if (!element['scriptProcessedOnce']) {
Utilities.callEventHandler(this._onFirstHitAfterCompliance, [item])
element.scriptProcessedOnce = true
}
this._statistics.updateUI()
})
}
/**
* @protected
*/
_createSettingsFormActions ()
{
return this._uiGen.createFormSection().append([
this._uiGen.createFormActions([
this._uiGen.createFormButton('Apply', () => this._onApplyNewSettings(), 'Apply settings.'),
this._uiGen.createFormButton('Save', () => this._onSaveSettings(), 'Apply and update saved configuration.'),
this._uiGen.createFormButton('Reset', () => this._onResetSettings(), 'Revert to saved configuration.'),
]),
])
}
/**
* @param {JQuery} UISection
* @private
*/
_embedUI (UISection)
{
UISection.on('mouseleave', (event) => {
if (!this._configurationManager.getValue(OPTION_ALWAYS_SHOW_SETTINGS_PANE)) {
$(event.currentTarget).hide(300)
}
})
if (this._configurationManager.getValue(OPTION_ALWAYS_SHOW_SETTINGS_PANE)) {
UISection.show()
}
this._uiGen.constructor.appendToBody(UISection)
this._uiGen.constructor.appendToBody(this._uiGen.createSettingsShowButton('', UISection))
}
_onApplyNewSettings ()
{
this._configurationManager.update()
this._validateCompliance()
}
_onResetSettings ()
{
this._configurationManager.revertChanges()
this._validateCompliance()
}
_onSaveSettings ()
{
this._onApplyNewSettings()
this._configurationManager.save()
}
/**
* @param {boolean} firstRun
* @protected
*/
_validateCompliance (firstRun = false)
{
let itemLists = Utilities.callEventHandler(this._onGetItemLists)
if (!firstRun) {
this._statistics.reset()
itemLists.each((index, itemsList) => {
this._complyItemsList($(itemsList))
})
} else {
itemLists.each((index, itemList) => {
ChildObserver.create().onNodesAdded((itemsAdded) => this._complyItemsList($(itemsAdded), true)).observe(itemList)
this._complyItemsList($(itemList))
})
}
if (this._paginator) {
this._paginator.run(this._configurationManager.getValue(CONFIG_PAGINATOR_THRESHOLD), this._configurationManager.getValue(CONFIG_PAGINATOR_LIMIT))
}
}
/**
* @param {JQuery} item
* @protected
*/
_validateItemCompliance (item)
{
let itemComplies = true
if (!this._configurationManager.getValue(OPTION_DISABLE_COMPLIANCE_VALIDATION) && Utilities.callEventHandler(this._onBeforeCompliance, [item]) !== false) {
for (let complianceFilter of this._complianceFilters) {
if (!complianceFilter(item)) {
itemComplies = false
break
}
}
}
itemComplies ? Utilities.callEventHandler(this._onItemShow, [item]) : Utilities.callEventHandler(this._onItemHide, [item])
}
/**
* Initialize the script and do basic UI removals
*/
init ()
{
if (Utilities.callEventHandler(this._onValidateInit)) {
this._configurationManager.initialize(this._scriptPrefix)
if (this._paginator) {
this._paginator.initialize()
}
Utilities.callEventHandler(this._onBeforeUIBuild)
this._embedUI(Utilities.callEventHandler(this._onUIBuild))
Utilities.callEventHandler(this._onAfterUIBuild)
this._configurationManager.updateInterface()
this._validateCompliance(true)
this._uiGen.updateStatus('Initial run completed.')
Utilities.callEventHandler(this._onAfterInitialization)
}
}
}