您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Base library for my scripts
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/375557/878636/Base%20Brazen%20Resource.js
// ==UserScript== // @name Base Brazen Resource // @namespace brazen // @version 3.3.2 // @author brazenvoid // @license GPL-3.0-only // @description Base library for my scripts // @run-at document-end // ==/UserScript== const CONFIG_TYPE_CHECKBOXES_GROUP = 'checkboxes' const CONFIG_TYPE_FLAG = 'flag' const CONFIG_TYPE_NUMBER = 'number' const CONFIG_TYPE_RADIOS_GROUP = 'radios' const CONFIG_TYPE_RANGE = 'range' const CONFIG_TYPE_RULESET = 'ruleset' const CONFIG_TYPE_TEXT = 'text' const REGEX_LINE_BREAK = /\r?\n/g const REGEX_PRESERVE_NUMBERS = /[^0-9]/g class ChildObserver { /** * @callback observerOnMutation * @param {NodeList} nodes */ /** * @return {ChildObserver} */ static create () { return new ChildObserver } /** * ChildObserver constructor */ constructor () { this._node = null this._observer = null this._onNodesAdded = null this._onNodesRemoved = null } /** * @return {ChildObserver} * @private */ _observeNodes () { this._observer.observe(this._node, {childList: true}) return this } /** * Attach an observer to the specified node(s) * @param {Node} node * @returns {ChildObserver} */ observe (node) { this._node = node this._observer = new MutationObserver((mutations) => { for (let mutation of mutations) { if (mutation.addedNodes.length && this._onNodesAdded !== null) { this._onNodesAdded( mutation.addedNodes, mutation.previousSibling, mutation.nextSibling, mutation.target, ) } if (mutation.removedNodes.length && this._onNodesRemoved !== null) { this._onNodesRemoved( mutation.removedNodes, mutation.previousSibling, mutation.nextSibling, mutation.target, ) } } }) return this._observeNodes() } /** * @param {observerOnMutation} eventHandler * @returns {ChildObserver} */ onNodesAdded (eventHandler) { this._onNodesAdded = eventHandler return this } /** * @param {observerOnMutation} eventHandler * @returns {ChildObserver} */ onNodesRemoved (eventHandler) { this._onNodesRemoved = eventHandler return this } pauseObservation () { this._observer.disconnect() } resumeObservation () { this._observeNodes() } } class ConfigurationManager { /** * @typedef {{title: string, type: string, element: null|JQuery, value: *, maximum: int, minimum: int, options: string[], helpText: string, onEncode: null|Function, onDecode: * null|Function}} ConfigurationField */ /** * @callback ConfigurationManagerRulesetCallback * @param {*} value */ /** * @param {BrazenUIGenerator} uiGenerator * @return {ConfigurationManager} */ static create (uiGenerator) { return new ConfigurationManager(uiGenerator) } constructor (uiGenerator) { /** * @type {{}} * @private */ this._config = {} /** * @type {LocalStore} * @private */ this._localStore = null /** * @type BrazenUIGenerator * @private */ this._uiGen = uiGenerator } /** * @param {string} type * @param {string} name * @param {*} value * @param {string} helpText * @return ConfigurationField * @private */ _createField (type, name, value, helpText) { let fieldKey = this._formatFieldKey(name) let field = this._config[fieldKey] if (!field) { field = { element: null, helpText: helpText, title: name, type: type, value: value, } this._config[fieldKey] = field } else { field.helpText = helpText field.value = value } return field } _formatFieldKey (name) { return Utilities.toKebabCase(name) } /** * @param {boolean} ignoreIfDefaultsSet * @private */ _syncLocalStore (ignoreIfDefaultsSet) { let field let storeObject = this._localStore.get() if (!ignoreIfDefaultsSet || !this._localStore.wereDefaultsSet()) { for (let key in this._config) { field = this._config[key] field.value = storeObject[key] if (field.type === CONFIG_TYPE_RULESET) { field.optimized = Utilities.callEventHandler(field.onOptimize, [field.value]) } } this.updateInterface() } return this } /** * @return {{}} * @private */ _toStoreObject () { let storeObject = {} for (let key in this._config) { storeObject[key] = this._config[key].value } return storeObject } createElement (name) { let field = this.getField(name) let inputGroup switch (field.type) { case CONFIG_TYPE_CHECKBOXES_GROUP: inputGroup = this._uiGen.createFormCheckBoxesGroupSection(field.title, field.options, field.helpText) field.element = inputGroup break case CONFIG_TYPE_FLAG: inputGroup = this._uiGen.createFormInputGroup(field.title, 'checkbox', field.helpText) field.element = inputGroup.find('input') break case CONFIG_TYPE_NUMBER: inputGroup = this._uiGen.createFormInputGroup(field.title, 'number', field.helpText).attr('min', field.minimum).attr('max', field.maximum) field.element = inputGroup.find('input') break case CONFIG_TYPE_RADIOS_GROUP: inputGroup = this._uiGen.createFormRadiosGroupSection(field.title, field.options, field.helpText) field.element = inputGroup break case CONFIG_TYPE_RANGE: inputGroup = this._uiGen.createFormRangeInputGroup(field.title, 'number', field.minimum, field.maximum, field.helpText) field.element = inputGroup.find('input') break case CONFIG_TYPE_RULESET: inputGroup = this._uiGen.createFormTextAreaGroup(field.title, 2, field.helpText) field.element = inputGroup.find('textarea') break case CONFIG_TYPE_TEXT: inputGroup = this._uiGen.createFormInputGroup(field.title, 'text', field.helpText) field.element = inputGroup.find('input') } return inputGroup } initialize (scriptPrefix) { this._localStore = new LocalStore(scriptPrefix + 'settings', this._toStoreObject()) this._localStore.onChange(() => this.updateInterface()) return this._syncLocalStore(true) } addCheckboxesGroup (name, keyValuePairs, helpText) { let field = this._createField(CONFIG_TYPE_CHECKBOXES_GROUP, name, [], helpText) field.options = keyValuePairs return this } addFlagField (name, helpText) { this._createField(CONFIG_TYPE_FLAG, name, false, helpText) return this } addNumberField (name, minimum, maximum, helpText) { let field = this._createField(CONFIG_TYPE_NUMBER, name, minimum, helpText) field.minimum = minimum field.maximum = maximum return this } addRadiosGroup (name, keyValuePairs, helpText) { let field = this._createField(CONFIG_TYPE_RADIOS_GROUP, name, keyValuePairs[0][1], helpText) field.options = keyValuePairs return this } addRangeField (name, minimum, maximum, helpText) { let field = this._createField(CONFIG_TYPE_RANGE, name, {minimum: minimum, maximum: minimum}, helpText) field.minimum = minimum field.maximum = maximum return this } /** * @param {string} name * @param {string} helpText * @param {ConfigurationManagerRulesetCallback} onEncode * @param {ConfigurationManagerRulesetCallback} onDecode * @param {ConfigurationManagerRulesetCallback} onOptimize * @return {ConfigurationManager} */ addRulesetField (name, helpText, onEncode = null, onDecode = null, onOptimize = null) { let field = this._createField(CONFIG_TYPE_RULESET, name, [], helpText) field.onDecode = onDecode ?? field.onDecode field.onEncode = onEncode ?? field.onEncode field.onOptimize = onOptimize ?? field.onOptimize field.optimized = null return this } addTextField (name, helpText) { this._createField(CONFIG_TYPE_TEXT, name, '', helpText) return this } /** * @param {string} name * @return {ConfigurationField} */ getField (name) { let field = this._config[this._formatFieldKey(name)] if (field) { return field } throw new Error('Field named "' + name + '" could not be found') } getValue (name) { return this.getField(name).value } revertChanges () { return this._syncLocalStore(false) } save () { this.update()._localStore.save(this._toStoreObject()) return this } update () { let field, value for (let fieldName in this._config) { field = this._config[fieldName] if (field.element) { switch (field.type) { case CONFIG_TYPE_CHECKBOXES_GROUP: field.value = [] field.element.find('input:checked').each((index, element) => { field.value.push($(element).attr('data-value')) }) break case CONFIG_TYPE_FLAG: field.value = field.element.prop('checked') break case CONFIG_TYPE_NUMBER: field.value = parseInt(field.element.val()) break case CONFIG_TYPE_RADIOS_GROUP: field.value = field.element.find('input:checked').attr('data-value') break case CONFIG_TYPE_RANGE: field.value = { minimum: field.element.first().val(), maximum: field.element.last().val(), } break case CONFIG_TYPE_RULESET: value = Utilities.trimAndKeepNonEmptyStrings(field.element.val().split(REGEX_LINE_BREAK)) field.value = Utilities.callEventHandler(field.onDecode, [value], value) field.optimized = Utilities.callEventHandler(field.onOptimize, [field.value]) break default: field.value = field.element.val() } } } return this } updateInterface () { let elements, field, value for (let fieldName in this._config) { field = this._config[fieldName] if (field.element) { switch (field.type) { case CONFIG_TYPE_CHECKBOXES_GROUP: elements = field.element.find('input') for (let key of field.value) { elements.filter('[data-value="' + key + '"]').prop('checked', true) } break case CONFIG_TYPE_FLAG: field.element.prop('checked', field.value) break case CONFIG_TYPE_NUMBER: field.element.val(field.value) break case CONFIG_TYPE_RADIOS_GROUP: field.element.find('input[data-value="' + field.value + '"]').prop('checked', true).trigger('change') break case CONFIG_TYPE_RANGE: field.element.first().val(field.value.minimum) field.element.last().val(field.value.maximum) break case CONFIG_TYPE_RULESET: value = Utilities.callEventHandler(field.onEncode, [field.value], field.value) field.element.val(value.join('\n')) break default: field.element.val(field.value) } } } return this } } class LocalStore { /** * @callback storeEventHandler * @param {Object} store */ /** * @param {string} key * @param {Object} defaults */ constructor (key, defaults) { /** * @type {Object} * @private */ this._defaults = defaults /** * @type {boolean} * @private */ this._defaultsSet = false /** * @type {string} * @private */ this._key = key // Events /** * @type {storeEventHandler} */ this._onChange = null } _handleOnChange () { if (this._onChange !== null) { this._onChange(this.get()) } } /** * @return {LocalStore} */ delete () { window.localStorage.removeItem(this._key) return this } /** * @param {string} filename */ exportToFile (filename) { let linkElement = document.createElement('a') let file = new Blob([Utilities.objectToJSON(this.get())], {type: 'application/json'}) linkElement.href = URL.createObjectURL(file) linkElement.download = filename linkElement.click() URL.revokeObjectURL(linkElement.href) linkElement.remove() } /** * @return {*} */ get () { this._defaultsSet = false let storedStore = window.localStorage.getItem(this._key) return storedStore === null ? this.restoreDefaults() : Utilities.objectFromJSON(storedStore) } importFromFile (file) { } /** * @param {storeEventHandler} handler * @return {LocalStore} */ onChange (handler) { this._onChange = handler return this } /** * @return {Object} */ restoreDefaults () { this._defaultsSet = true this.save(this._defaults) return this._defaults } /** * @param {Object} data * @return {LocalStore} */ save (data) { window.localStorage.setItem(this._key, Utilities.objectToJSON(data)) this._handleOnChange() return this } /** * @return {boolean} */ wereDefaultsSet () { return this._defaultsSet } } class Paginator { /** * @callback PaginatorAfterPaginationEventHandler * @param {Paginator} paginator */ /** * @callback PaginatorGetPageNoFromUrlHandler * @param {string} pageUrl * @param {Paginator} paginator */ /** * @callback PaginatorGetPageUrlFromPageNoHandler * @param {number} pageNo * @param {Paginator} paginator */ /** * @callback PaginatorGetPaginationElementForPageNoHandler * @param {number} pageNo * @param {Paginator} paginator */ /** * @param {JQuery} paginationWrapper * @param {JQuery.Selector} listSelector * @param {JQuery.Selector} itemClassesSelector * @param {string } lastPageUrl * @return {Paginator} */ static create (paginationWrapper, listSelector, itemClassesSelector, lastPageUrl) { return (new Paginator).configure(paginationWrapper, listSelector, itemClassesSelector, lastPageUrl) } /** * */ constructor () { /** * @type {number} * @private */ this._currentPageNo = 0 /** * @type {JQuery.Selector} * @private */ this._itemClassesSelector = '' /** * @type {number} * @private */ this._lastPageNo = 0 /** * @type {string} * @private */ this._lastPageUrl = '' /** * @type {JQuery.Selector} * @private */ this._listSelector = '' /** * @type {number} * @private */ this._paginatedPageNo = 0 /** * @type {JQuery} * @private */ this._paginationWrapper = null /** * @type {JQuery} * @private */ this._targetElement = null // Events and callbacks /** * @type {PaginatorAfterPaginationEventHandler} * @private */ this._onAfterPagination = null /** * @type {PaginatorGetPageNoFromUrlHandler} * @private */ this._onGetPageNoFromUrl = null /** * @type {PaginatorGetPageUrlFromPageNoHandler} * @private */ this._onGetPageUrlFromPageNo = null /** * @type {PaginatorGetPaginationElementForPageNoHandler} * @private */ this._onGetPaginationElementForPageNo = null } _conformUIToNewPaginatedState () { let currentPageElement = this.getPaginationElementForPageNo(this._currentPageNo) // Determine whether next page's pagination element exists let nextPageElement = this.getPaginationElementForPageNo(this._currentPageNo + 1) // Delete pagination element of paginated page // Mutate current page no element to show paginated page numbers currentPageElement.text(this._currentPageNo + '-' + this._paginatedPageNo) } /** * @param {number} threshold * @param {number} limit * @param {number} iteration * @return {number} * @private */ _loadAndParseNextPage (threshold, limit, iteration = 0) { let lastPageHasNotBeenReached = this._paginatedPageNo < this._lastPageNo let paginationLimitHasNotBeenMet = (limit <= 0 || (this._paginatedPageNo - this._currentPageNo <= limit)) let compliantItemsAreLessThanTheThreshold = this._targetElement.find(this._itemClassesSelector + ':visible').length < threshold if (lastPageHasNotBeenReached && paginationLimitHasNotBeenMet && compliantItemsAreLessThanTheThreshold) { this._sandbox.load(this.getPageUrlFromPageNo(++this._paginatedPageNo) + ' ' + this._listSelector, '', () => { this._sandbox.find(this._itemClassesSelector).insertAfter(this._targetElement.find(this._itemClassesSelector + ':last')) this._sandbox.empty() Utilities.callEventHandler(this.onAfterPagination, [this]) }) iteration = this._loadAndParseNextPage(threshold, limit, iteration + 1) } return iteration } /** * @param {JQuery} paginationWrapper * @param {JQuery.Selector} listSelector * @param {JQuery.Selector} itemClassesSelector * @param {string } lastPageUrl * @return {Paginator} */ configure (paginationWrapper, listSelector, itemClassesSelector, lastPageUrl) { this._lastPageUrl = lastPageUrl this._listSelector = listSelector this._itemClassesSelector = itemClassesSelector this._paginationWrapper = paginationWrapper return this } getCurrentPageNo () { return this._currentPageNo } getLastPageNo () { return this._lastPageNo } /** * @param {string} pageUrl * @return {number} */ getPageNoFromUrl (pageUrl) { return Utilities.callEventHandlerOrFail('onGetPageNoFromUrl', this._onGetPageNoFromUrl, [pageUrl, this]) } /** * @param {number} pageNo * @return {string} */ getPageUrlFromPageNo (pageNo) { return Utilities.callEventHandlerOrFail('onGetPageUrlFromPageNo', this._onGetPageUrlFromPageNo, [pageNo, this]) } /** * @param {number} pageNo * @return {JQuery} */ getPaginationElementForPageNo (pageNo) { return Utilities.callEventHandlerOrFail('onGetPaginationElementForPageNo', this._onGetPaginationElementForPageNo, [pageNo, this]) } getPaginatedPageNo () { return this._paginatedPageNo } getPaginationWrapper () { return this._paginationWrapper } initialize () { this._currentPageNo = this.getPageNoFromUrl(window.location.href) this._lastPageNo = this.getPageNoFromUrl(this._lastPageUrl) this._paginatedPageNo = this._currentPageNo this._sandbox = $('<div id="brazen-paginator-sandbox" hidden/>').appendTo('body') this._targetElement = $(this._listSelector + ':first') return this } /** * @param {PaginatorAfterPaginationEventHandler} handler * @return {this} */ onAfterPagination (handler) { this._onAfterPagination = handler return this } /** * @param {PaginatorGetPageNoFromUrlHandler} handler * @return {this} */ onGetPageNoFromUrl (handler) { this._onGetPageNoFromUrl = handler return this } /** * @param {PaginatorGetPageUrlFromPageNoHandler} handler * @return {this} */ onGetPageUrlFromPageNo (handler) { this._onGetPageUrlFromPageNo = handler return this } /** * @param {PaginatorGetPaginationElementForPageNoHandler} handler * @return {this} */ onGetPaginationElementForPageNo (handler) { this._onGetPaginationElementForPageNo = handler return this } run (threshold, limit) { if (this._paginationWrapper.length && threshold) { if (this._loadAndParseNextPage(threshold, limit)) { this._conformUIToNewPaginatedState() } } return this } } class SelectorGenerator { /** * @param {string} selectorPrefix */ constructor (selectorPrefix) { /** * @type {string} * @private */ this._prefix = selectorPrefix } /** * @param {string} selector * @return {string} */ getSelector (selector) { return this._prefix + selector } /** * @param {string} settingName * @return {string} */ getSettingsInputSelector (settingName) { return this.getSelector(Utilities.toKebabCase(settingName) + '-setting') } /** * @param {string} settingName * @param {boolean} getMinInputSelector * @return {string} */ getSettingsRangeInputSelector (settingName, getMinInputSelector) { return this.getSelector(Utilities.toKebabCase(settingName) + (getMinInputSelector ? '-min' : '-max') + '-setting') } /** * @param {string} statisticType * @return {string} */ getStatLabelSelector (statisticType) { return this.getSelector(Utilities.toKebabCase(statisticType) + '-stat') } } class StatisticsRecorder { /** * @param {string} selectorPrefix */ constructor (selectorPrefix) { /** * @type {SelectorGenerator} * @private */ this._selectorGenerator = new SelectorGenerator(selectorPrefix) /** * @type {{Total: number}} * @private */ this._statistics = {Total: 0} } /** * @param {string} statisticType * @param {boolean} validationResult * @param {number} value */ record (statisticType, validationResult, value = 1) { if (!validationResult) { if (typeof this._statistics[statisticType] !== 'undefined') { this._statistics[statisticType] += value } else { this._statistics[statisticType] = value } this._statistics.Total += value } } reset () { for (const statisticType in this._statistics) { this._statistics[statisticType] = 0 } } updateUI () { let label, labelSelector for (const statisticType in this._statistics) { labelSelector = this._selectorGenerator.getStatLabelSelector(statisticType) label = document.getElementById(labelSelector) if (label !== null) { label.textContent = this._statistics[statisticType] } } } } class Utilities { /** * @param {string[]} words * @return {RegExp} */ static buildWholeWordMatchingRegex (words) { let patternedWords = [] for (let i = 0; i < words.length; i++) { patternedWords.push('\\b' + words[i] + '\\b') } return new RegExp('(' + patternedWords.join('|') + ')', 'gi') } static callEventHandler (handler, parameters = [], defaultValue = null) { return handler ? handler(...parameters) : defaultValue } static callEventHandlerOrFail (name, handler, parameters = []) { if (handler) { return handler(...parameters) } throw new Error('Callback "' + name + '" must be defined.') } /** * @param {string} json * @return {Object} */ static objectFromJSON (json) { /** @type {{arrays: Object, objects: Object, properties: Object}} */ let parsedJSON = JSON.parse(json) let arrayObject = {} let result = {} for (let property in parsedJSON.arrays) { arrayObject = JSON.parse(parsedJSON.arrays[property]) result[property] = [] for (let key in arrayObject) { result[property].push(arrayObject[key]) } } for (let property in parsedJSON.objects) { result[property] = Utilities.objectFromJSON(parsedJSON.objects[property]) } for (let property in parsedJSON.properties) { result[property] = parsedJSON.properties[property] } return result } /** * @param {Object} object * @return {string} */ static objectToJSON (object) { let arrayToObject let json = {arrays: {}, objects: {}, properties: {}} for (let property in object) { if (typeof object[property] === 'object') { if (Array.isArray(object[property])) { arrayToObject = {} for (let key in object[property]) { arrayToObject[key] = object[property][key] } json.arrays[property] = JSON.stringify(arrayToObject) } else { json.objects[property] = Utilities.objectToJSON(object[property]) } } else { json.properties[property] = object[property] } } return JSON.stringify(json) } /** * @param milliseconds * @return {Promise<*>} */ static sleep (milliseconds) { return new Promise(resolve => setTimeout(resolve, milliseconds)) } /** * @param {string} text * @return {string} */ static toKebabCase (text) { return text.toLowerCase().replaceAll(' ', '-') } /** * @param {string[]} strings */ static trimAndKeepNonEmptyStrings (strings) { let nonEmptyStrings = [] for (let string of strings) { string = string.trim() if (string !== '') { nonEmptyStrings.push(string) } } return nonEmptyStrings } } class Validator { static iFramesRemover () { GM_addStyle(' iframe { display: none !important; } ') } /** * @param {StatisticsRecorder} statisticsRecorder */ constructor (statisticsRecorder) { /** * @type {StatisticsRecorder} * @private */ this._statisticsRecorder = statisticsRecorder } /** * @param {string} text * @param {Object} rules * @return {string} */ sanitize (text, rules) { if (rules) { for (const substitute in rules) { text = text.replace(rules[substitute], substitute) } } return text.trim() } /** * @param {JQuery} textNode * @param {Object} rules * @return {Validator} */ sanitizeTextNode (textNode, rules) { textNode.text(this.sanitize(textNode.text(), rules)) return this } /** * @param {string} selector * @param {Object} rules * @return {Validator} */ sanitizeNodeOfSelector (selector, rules) { let node = $(selector) if (node.length) { let sanitizedText = this.sanitize(node.text(), rules) node.text(sanitizedText) document.title = sanitizedText } return this } /** * @param {string} name * @param {JQuery} item * @param {string} selector * @return {boolean} */ validateNodeExistence (name, item, selector) { let validationCheck = item.find(selector).length > 0 this._statisticsRecorder.record(name, validationCheck) return validationCheck } /** * @param {string} name * @param {JQuery} item * @param {string} selector * @return {boolean} */ validateNodeNonExistence (name, item, selector) { let validationCheck = item.find(selector).length === 0 this._statisticsRecorder.record(name, validationCheck) return validationCheck } /** * @param {string} name * @param {number} value * @param {number[]} bounds * @return {boolean} */ validateRange (name, value, bounds) { let validationCheck = true if (bounds[0] > 0 && bounds[1] > 0) { validationCheck = value >= bounds[0] && value <= bounds[1] } else { if (bounds[0] > 0) { validationCheck = value >= bounds[0] } if (bounds[1] > 0) { validationCheck = value <= bounds[1] } } this._statisticsRecorder.record(name, validationCheck) return validationCheck } /** * @param {string} name * @param {number} lowerBound * @param {number} upperBound * @param getValueCallback * @return {boolean} */ validateRangeFilter (name, lowerBound, upperBound, getValueCallback) { if (lowerBound > 0 || upperBound > 0) { return this.validateRange(name, getValueCallback(), [lowerBound, upperBound]) } return true } /** * @param {string} text * @param {Object} rules * @param {string} key * @return {boolean} */ validateTextContains (text, rules, key) { let validationCheck = true if (rules) { this._statisticsRecorder.record(key, validationCheck = text.match(rules) !== null) } return validationCheck } /** * @param {string} text * @param {Object} rules * @param {string} key * @return {boolean} */ validateTextDoesNotContain (text, rules, key) { let validationCheck = true if (rules) { this._statisticsRecorder.record(key, validationCheck = text.match(rules) === null) } return validationCheck } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址