您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Base class for search enhancement scripts
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.sleazyfork.org/scripts/416105/869333/Brazen%20Base%20Search%20Enhancer.js
// ==UserScript== // @name Brazen Base Search Enhancer // @namespace brazen // @version 1.0.0 // @author brazenvoid // @license GPL-3.0-only // @description Base class for search enhancement scripts // @require https://greasyfork.org/scripts/375557-base-resource/code/Base%20Resource.js?version=869326 // ==/UserScript== class BrazenBaseSearchEnhancer { static initialize () { BrazenBaseSearchEnhancer.throwOverrideError() //return (new XNXXSearchFilters).init() } static throwOverrideError () { throw new Error('override this method') } /** * @param {Object} options * @param {string} options.scriptPrefix * @param {string[]} options.itemClasses * @param {Object} options.paginator * @param {boolean} options.paginator.enable * @param {string} options.paginator.listSelector * @param {string} options.paginator.lastPageUrl * @param {Function} options.paginator.getPageNo * @param {Function} options.paginator.getPageUrl * @param {Function} options.paginator.afterPagination * @param {Object} options.configDefaults * @param {boolean} options.configDefaults.disableItemComplianceValidation * @param {number} options.configDefaults.paginationLimit * @param {number} options.configDefaults.paginationThreshold * @param {boolean} options.configDefaults.showUIAlways */ constructor (options) { options.configDefaults.disableItemComplianceValidation = false options.configDefaults.paginationLimit = 0 options.configDefaults.paginationThreshold = 0 options.configDefaults.showUIAlways = false /** * Array of item compliance filters ordered in intended sequence of execution * @type {Function[]} * @protected */ this._complianceFilters = [] /** * @type {string[]} * @protected */ this._itemClasses = Array.isArray(options.itemClasses) ? options.itemClasses : [options.itemClasses] /** * @type {string} * @protected */ this._itemClassesSelector = '.' + this._itemClasses.join(',.') /** * 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) /** * Retrieve settings from UI and update settings object * @type {Function} * @private */ this._onSettingsApply = null /** * Settings to update in the UI or elsewhere when settings store is updated * @type {Function} * @protected */ this._onSettingsStoreUpdate = null /** * 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 /** * Pagination manager * @type {{lastPageUrl: string, listSelector: string, paginatedPageNo: number, enable: boolean, lastPageNo: number, getPageNo: Function, getPageUrl: Function, * currentPageNo: number, afterPagination: Function, targetElement: null|JQuery}} * @private */ this._paginator = { enable: options.paginator.enable, targetElement: null, currentPageNo: 0, lastPageNo: 0, lastPageUrl: options.paginator.lastPageUrl, listSelector: options.paginator.listSelector, paginatedPageNo: 0, afterPagination: options.paginator.afterPagination, getPageNo: options.paginator.getPageNo, getPageUrl: options.paginator.getPageUrl, } /** * @type {string} * @private */ this._scriptPrefix = options.scriptPrefix /** * Local storage store with defaults * @type {LocalStore} * @protected */ this._settingsStore = new LocalStore(this._scriptPrefix + 'settings', options.configDefaults) /** * @type {Object} * @protected */ this._settings = this._settingsStore.retrieve().get() /** * @type {StatisticsRecorder} * @protected */ this._statistics = new StatisticsRecorder(this._scriptPrefix) /** * @type {BrazenUIGenerator} * @protected */ this._uiGen = new BrazenUIGenerator(this._settings.showUIAlways, this._scriptPrefix) /** * @type {Validator} * @protected */ this._validator = (new Validator(this._statistics)) } /** * @param {Function} eventHandler * @param {*} parameters * @return {*} * @private */ _callEventHandler (eventHandler, ...parameters) { if (eventHandler) { return eventHandler(...parameters) } return null } /** * 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 this._callEventHandler(this._onFirstHitBeforeCompliance, item) } this._validateItemCompliance(item) if (!element['scriptProcessedOnce']) { this._callEventHandler(this._onFirstHitAfterCompliance, item) element.scriptProcessedOnce = true } this._statistics.updateUI() }) this._validatePaginationThreshold() } /** * @protected */ _createSettingsFormActions () { return this._uiGen.createSettingsFormActions(this._settingsStore, () => { this._callEventHandler(this._onSettingsApply) this._statistics.reset() for (let itemsList of this._callEventHandler(this._onGetItemLists)) { this._complyItemsList(itemsList) } }) } /** * @param {JQuery|null} UISection * @private */ _embedUI (UISection) { if (UISection) { this._uiGen.constructor.appendToBody(UISection) this._uiGen.constructor.appendToBody(this._uiGen.createSettingsShowButton('', UISection, () => { if (!this._settings.showUIAlways) { UISection.hide() } })) this._callEventHandler(this._onSettingsStoreUpdate) } } /** * @param {Object} sanitizationRules * @return {string} * @protected */ _transformSanitizationRulesToText (sanitizationRules) { let sanitizationRulesText = [] for (let substitute in sanitizationRules) { sanitizationRulesText.push(substitute + '=' + sanitizationRules[substitute].join(',')) } return sanitizationRulesText.join('\n') } /** * @param {string[]} strings * @protected */ _trimAndKeepNonEmptyStrings (strings) { let nonEmptyStrings = [] for (let string of strings) { string = string.trim() if (string !== '') { nonEmptyStrings.push(string) } } return nonEmptyStrings } /** * @param {string[]} sanitizationRules * @protected */ _validateAndSetSanitizationRules (sanitizationRules) { if (sanitizationRules.length) { let fragments, validatedTargetWords this._settings.sanitize = {} for (let sanitizationRule of sanitizationRules) { if (sanitizationRule.includes('=')) { fragments = sanitizationRule.split('=') if (fragments[0] === '') { fragments[0] = ' ' } validatedTargetWords = this._trimAndKeepNonEmptyStrings(fragments[1].split(',')) if (validatedTargetWords.length) { this._settings.sanitize[fragments[0]] = validatedTargetWords } } } } } /** * @param {JQuery} item * @protected */ _validateItemCompliance (item) { let itemComplies = true if (!this._settings.disableItemComplianceValidation && this._callEventHandler(this._onBeforeCompliance, item) !== false) { for (let complianceFilter of this._complianceFilters) { if (!complianceFilter(item)) { itemComplies = false break } } } itemComplies ? this._callEventHandler(this._onItemShow, item) : this._callEventHandler(this._onItemHide, item) } _validatePaginationThreshold () { if (this._paginator.enable && this._settings.paginationThreshold && (this._settings.paginationLimit <= 0 || (this._paginator.paginatedPageNo - this._paginator.currentPageNo <= this._settings.paginationLimit)) && this._paginator.paginatedPageNo < this._paginator.lastPageNo ) { let compliantItems = this._paginator.targetElement.find(this._itemClassesSelector + ':visible') if (compliantItems.length < this._settings.paginationThreshold) { let nextPageUrl = this._paginator.getPageUrl(++this._paginator.paginatedPageNo) let sandbox = $('<div id="brazen-sandbox" hidden/>') sandbox.appendTo('body') sandbox.load(nextPageUrl + ' ' + this._paginator.listSelector, '', () => { sandbox.find(this._itemClassesSelector).insertAfter(this._paginator.targetElement.find(this._itemClassesSelector + ':last')) sandbox.remove() this._paginator.afterPagination(this._paginator) }) } } } /** * Initialize the script and do basic UI removals */ init () { try { if (this._callEventHandler(this._onValidateInit)) { if (this._paginator.enable) { this._paginator.targetElement = $(this._paginator.listSelector + ':first') this._paginator.currentPageNo = this._paginator.getPageNo(window.location.href) this._paginator.lastPageNo = this._paginator.getPageNo(this._paginator.lastPageUrl) this._paginator.paginatedPageNo = this._paginator.currentPageNo } this._callEventHandler(this._onBeforeUIBuild) this._embedUI(this._callEventHandler(this._onUIBuild)) this._callEventHandler(this._onAfterUIBuild) for (let itemsList of this._callEventHandler(this._onGetItemLists)) { ChildObserver.create().onNodesAdded((itemsAdded) => this._complyItemsList($(itemsAdded), true).observe(itemsList)) this._complyItemsList(itemsList) } this._uiGen.updateStatus('Initial run completed.') this._callEventHandler(this._onAfterInitialization) this._settingsStore.onChange(() => this._callEventHandler(this._onSettingsStoreUpdate)) } } catch (error) { console.error(this._scriptPrefix + 'script encountered an error: ' + error) } } }