Base library for my scripts
Version vom
Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/375557/790976/Base%20Resource.js
// ==UserScript==
// @name Base Resource
// @namespace brazenvoid
// @version 1.8.0
// @author brazenvoid
// @license GPL-3.0-only
// @description Base library for my scripts
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
GM_addStyle(
'button.form-button{padding:0 5px;width:100%}button.show-settings{background-color:#ffa31a;border:0;margin:2px 5px;padding:2px 5px;width:100%}button.show-settings.fixed{color:#000;font-size:14px;left:0;margin:0;padding:15px 0;position:fixed;top:250px;width:30px;writing-mode:sideways-lr;z-index:999}div.form-actions{text-align:center}div.form-actions button.form-button{padding:0 15px;width:auto}div.form-actions-wrapper{display:inline-flex}div.form-actions-wrapper > div.form-group + *{margin-left:15px}div.form-group{min-height:15px;padding:5px 0}div.form-group.form-range-input-group > input{padding:0 5px;width:70px}div.form-group.form-range-input-group > input + input{margin-right:5px}div.form-section{text-align:center}div.form-section button + button{margin-left:5px}div.form-section label.title{display:block;height:20px;width:100%}div.form-section button.form-button{width:auto}hr{margin:3px}input.form-input{height:18px;text-align:center}input.form-input.check-radio-input{float:left;margin-right:5px}input.form-input.regular-input{float:right;width:100px}label.form-label{padding:2px 0}label.form-label.regular-input{float:left}label.form-label.check-radio-input{float:left}label.form-stat-label{float:right;padding:2px 0}section.form-section{color:#000;font-size:12px;font-weight:700;position:fixed;left:0;padding:5px 10px;z-index:1000}select.form-dropdown{float:right;height:18px;text-align:center;width:100px}')
/**
* @param milliseconds
* @return {Promise<*>}
*/
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
/**
* @param {string} classes
* @return {RegExp}
* @private
*/
function _generateClassesIdentificationRegex (classes)
{
return new RegExp('(\\s|^)' + classes + '(\\s|$)')
}
/**
* @param {Element} node
* @param {string} classes
* @return {Element}
*/
function addClasses (node, classes)
{
if (!hasClasses(node, classes)) {
if (node.className !== '') {
node.className += ' '
}
node.className += classes
}
return node
}
/**
* @param {Element} node
* @param {string} classes
* @return {boolean}
*/
function hasClasses (node, classes)
{
return !!node.className.match(_generateClassesIdentificationRegex(classes))
}
/**
* @param {Element} node
* @param {string} classes
* @return {Element}
*/
function removeClasses (node, classes)
{
if (hasClasses(node, classes)) {
node.className = node.className.replace(_generateClassesIdentificationRegex(classes), ' ')
}
return node
}
/**
* @param {string} text
* @return {string}
*/
function toKebabCase (text)
{
return text.toLowerCase().replace(' ', '-')
}
class ChildObserver
{
/**
* @callback observerMutationHandler
* @param {Node} target
*/
/**
* @param {Element|Element[]} nodes
* @param {observerMutationHandler} handler
* @param {boolean} doInitialRun
* @return {ChildObserver}
*/
static observe (nodes, handler, doInitialRun = false)
{
let instance = new ChildObserver(handler)
instance.observeNodes(nodes, doInitialRun)
return instance
}
/**
* @param {observerMutationHandler} handler
*/
constructor (handler)
{
/**
* @type {{subtree: boolean, attributes: boolean, childList: boolean}}
* @private
*/
this._config = {
attributes: false,
childList: true,
subtree: false,
}
/**
* @type {observerMutationHandler}
* @private
*/
this._handler = handler
/**
* @type {MutationObserver}
* @private
*/
this._observer = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
handler(mutation.target)
}
})
}
/**
* @param {Element|Element[]} nodes
* @param {boolean} doInitialRun
*/
observeNodes (nodes, doInitialRun = false)
{
nodes = (Array.isArray(nodes) || nodes instanceof NodeList) ? nodes : [nodes]
for (let node of nodes) {
if (doInitialRun) {
this._handler(node)
}
this._observer.observe(node, this._config)
}
}
}
class LocalStore
{
/**
* @callback storeEventHandler
* @param {Object} store
*/
/**
* @param {string} scriptPrefix
* @param {Object} defaults
* @return {LocalStore}
*/
static createGlobalConfigStore (scriptPrefix, defaults)
{
return new LocalStore(scriptPrefix + 'globals', defaults)
}
static createPresetConfigStore (scriptPrefix, defaults)
{
return new LocalStore(scriptPrefix + 'presets', [
{
name: 'default',
config: defaults,
},
])
}
/**
* @param {string} key
* @param {Object} defaults
*/
constructor (key, defaults)
{
/**
* @type {string}
* @private
*/
this._key = key
/**
* @type {Object}
* @private
*/
this._store = {}
/**
* @type {string}
* @private
*/
this._defaults = this._toJSON(defaults)
/**
* @type {storeEventHandler}
*/
this._onChange = null
}
/**
* @param {string} json
* @return {Object}
* @private
*/
_fromJSON (json)
{
/** @type {{arrays: Object, objects: Object, properties: Object}} */
let parsedJSON = JSON.parse(json)
let arrayObject = {}
let store = {}
for (let property in parsedJSON.arrays) {
arrayObject = JSON.parse(parsedJSON.arrays[property])
store[property] = []
for (let key in arrayObject) {
store[property].push(arrayObject[key])
}
}
for (let property in parsedJSON.objects) {
store[property] = this._fromJSON(parsedJSON.objects[property])
}
for (let property in parsedJSON.properties) {
store[property] = parsedJSON.properties[property]
}
return store
}
/**
* @return {string}
* @private
*/
_getStore ()
{
return window.localStorage.getItem(this._key)
}
/**
* @return {Object}
* @private
*/
_getDefaults ()
{
return this._fromJSON(this._defaults)
}
/**
* @param {Object} store
* @return {string}
* @private
*/
_toJSON (store)
{
let arrayToObject = {}
let json = {arrays: {}, objects: {}, properties: {}}
for (let property in store) {
if (typeof store[property] === 'object') {
if (Array.isArray(store[property])) {
for (let key in store[property]) {
arrayToObject[key] = store[property][key]
}
json.arrays[property] = JSON.stringify(arrayToObject)
} else {
json.objects[property] = this._toJSON(store[property])
}
} else {
json.properties[property] = store[property]
}
}
return JSON.stringify(json)
}
_handleOnChange ()
{
if (this._onChange !== null) {
this._onChange(this._store)
}
}
/**
* @return {LocalStore}
*/
delete ()
{
window.localStorage.removeItem(this._key)
return this
}
/**
* @return {Object}
*/
get ()
{
return this._store
}
/**
* @return {boolean}
*/
isPurged ()
{
return this._getStore() === null
}
/**
* @param {storeEventHandler} handler
* @return {LocalStore}
*/
onChange (handler)
{
this._onChange = handler
return this
}
/**
* @return {LocalStore}
*/
restoreDefaults ()
{
this._store = this._getDefaults()
this._handleOnChange()
return this
}
/**
* @return {LocalStore}
*/
retrieve ()
{
let storedStore = this._getStore()
if (storedStore === null) {
this.restoreDefaults()
} else {
this._store = this._fromJSON(storedStore)
}
this._handleOnChange()
return this
}
/**
* @return {LocalStore}
*/
save ()
{
window.localStorage.setItem(this._key, this._toJSON(this._store))
this._handleOnChange()
return this
}
/**
* @param {*} data
* @return {LocalStore}
*/
update (data)
{
this._store = data
return this.save()
}
}
class Logger
{
/**
* @param {boolean} enableDebugging
*/
constructor (enableDebugging)
{
/**
* @type {boolean}
* @private
*/
this._enableDebugging = enableDebugging
}
/**
* @param {string} message
* @private
*/
_log (message)
{
if (this._enableDebugging) {
console.log(message)
}
}
/**
* @param {string} task
*/
logTaskCompletion (task)
{
this._log('Completed: ' + task)
this.logSeparator()
}
logSeparator ()
{
this._log('------------------------------------------------------------------------------------')
}
/**
* @param {string} filterName
* @param {boolean} validationResult
*/
logValidation (filterName, validationResult)
{
this._log('Satisfies ' + filterName + ' Filter: ' + (validationResult ? 'true' : 'false'))
}
/**
* @param {string} videoName
*/
logVideoCheck (videoName)
{
this._log('Checking Video: ' + videoName)
}
}
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(toKebabCase(settingName) + '-setting')
}
/**
* @param {string} settingName
* @param {boolean} getMinInputSelector
* @return {string}
*/
getSettingsRangeInputSelector (settingName, getMinInputSelector)
{
return this.getSelector(toKebabCase(settingName) + (getMinInputSelector ? '-min' : '-max') + '-setting')
}
/**
* @param {string} statisticType
* @return {string}
*/
getStatLabelSelector (statisticType)
{
return this.getSelector(toKebabCase(statisticType) + '-stat')
}
}
class StatisticsRecorder
{
/**
* @param {Logger} logger
* @param {SelectorGenerator} selectorGenerator
*/
constructor (logger, selectorGenerator)
{
/**
* @type {Logger}
* @private
*/
this._logger = logger
/**
* @type {SelectorGenerator}
* @private
*/
this._selectorGenerator = selectorGenerator
/**
* @type {{Total: number}}
* @private
*/
this._statistics = {Total: 0}
}
/**
* @param {string} statisticType
* @param {boolean} validationResult
* @param {number} value
* @param {boolean} log
*/
record (statisticType, validationResult, value = 1, log = true)
{
if (!validationResult) {
if (typeof this._statistics[statisticType] !== 'undefined') {
this._statistics[statisticType] += value
} else {
this._statistics[statisticType] = value
}
this._statistics.Total += value
}
if (log) {
this._logger.logValidation(statisticType, validationResult)
}
}
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 UIGenerator
{
/**
* @param {Element|Node} node
*/
static appendToBody (node)
{
document.getElementsByTagName('body')[0].appendChild(node)
}
/**
* @param {Element} node
* @param {Element[]} children
* @return {Element}
*/
static populateChildren (node, children)
{
for (let child of children) {
node.appendChild(child)
}
return node
}
/**
* @param {boolean} showUI
* @param {SelectorGenerator} selectorGenerator
*/
constructor (showUI, selectorGenerator)
{
/**
* @type {*}
* @private
*/
this._buttonBackroundColor = null
/**
* @type {HTMLElement}
* @private
*/
this._section = null
/**
* @type {SelectorGenerator}
* @private
*/
this._selectorGenerator = selectorGenerator
/**
* @type {boolean}
* @private
*/
this._showUI = showUI
/**
* @type {HTMLLabelElement}
* @private
*/
this._statusLine = null
/**
* @type {string}
* @private
*/
this._statusText = ''
}
/**
* @param {Element} node
* @param {string} text
* @return {this}
* @private
*/
_addHelpTextOnHover (node, text)
{
let instance = this
node.addEventListener('mouseover', function () {
instance.updateStatus(text, true)
})
node.addEventListener('mouseout', function () {
instance.resetStatus()
})
}
/**
* @param {Element[]} children
* @return {Element}
*/
addSectionChildren (children)
{
return UIGenerator.populateChildren(this._section, children)
}
/**
* @param {Element[]} children
* @return {HTMLDivElement}
*/
createFormActions (children)
{
let wrapperDiv = document.createElement('div')
wrapperDiv.classList.add('form-actions-wrapper')
UIGenerator.populateChildren(wrapperDiv, children)
let formActionsDiv = document.createElement('div')
formActionsDiv.classList.add('form-actions')
formActionsDiv.appendChild(wrapperDiv)
return formActionsDiv
}
/**
* @param {string} caption
* @param {EventListenerOrEventListenerObject} onClick
* @param {string} hoverHelp
* @return {HTMLButtonElement}
*/
createFormButton (caption, onClick, hoverHelp = '')
{
let button = document.createElement('button')
button.classList.add('form-button')
button.textContent = caption
button.addEventListener('click', onClick)
if (hoverHelp !== '') {
this._addHelpTextOnHover(button, hoverHelp)
}
if (this._buttonBackroundColor !== null) {
button.style.backgroundColor = this._buttonBackroundColor
}
return button
}
/**
* @param {Element[]} children
* @return {Element}
*/
createFormGroup (children)
{
let divFormGroup = document.createElement('div')
divFormGroup.classList.add('form-group')
return UIGenerator.populateChildren(divFormGroup, children)
}
/**
* @param {string} id
* @param {Array} keyValuePairs
* @param {*} defaultValue
* @return {HTMLSelectElement}
*/
createFormGroupDropdown (id, keyValuePairs, defaultValue = null)
{
let dropdown = document.createElement('select'), item
dropdown.id = id
dropdown.classList.add('form-dropdown')
for (let [key, value] of keyValuePairs) {
item = document.createElement('option')
item.textContent = value
item.value = key
dropdown.appendChild(item)
}
dropdown.value = defaultValue === null ? keyValuePairs[0][0] : defaultValue
return dropdown
}
/**
* @param {string} id
* @param {string} type
* @param {*} defaultValue
* @return {HTMLInputElement}
*/
createFormGroupInput (id, type, defaultValue = null)
{
let inputFormGroup = document.createElement('input')
inputFormGroup.id = id
inputFormGroup.classList.add('form-input')
inputFormGroup.type = type
switch (type) {
case 'number':
case 'text':
inputFormGroup.classList.add('regular-input')
if (defaultValue !== null) {
inputFormGroup.value = defaultValue
}
break
case 'radio':
case 'checkbox':
inputFormGroup.classList.add('check-radio-input')
if (defaultValue !== null) {
inputFormGroup.checked = defaultValue
}
break
}
return inputFormGroup
}
/**
* @param {string} label
* @param {string} inputID
* @param {string} inputType
* @return {HTMLLabelElement}
*/
createFormGroupLabel (label, inputID = '', inputType = '')
{
let labelFormGroup = document.createElement('label')
labelFormGroup.classList.add('form-label')
labelFormGroup.textContent = label
if (inputID !== '') {
labelFormGroup.setAttribute('for', inputID)
}
if (inputType !== '') {
switch (inputType) {
case 'number':
case 'text':
labelFormGroup.classList.add('regular-input')
labelFormGroup.textContent += ': '
break
case 'radio':
case 'checkbox':
labelFormGroup.classList.add('check-radio-input')
break
}
}
return labelFormGroup
}
/**
* @param {string} statisticType
* @return {HTMLLabelElement}
*/
createFormGroupStatLabel (statisticType)
{
let labelFormGroup = document.createElement('label')
labelFormGroup.id = this._selectorGenerator.getStatLabelSelector(statisticType)
labelFormGroup.classList.add('form-stat-label')
labelFormGroup.textContent = '0'
return labelFormGroup
}
/**
* @param {string} label
* @param {string} inputType
* @param {*} defaultValue
* @param {string|null} hoverHelp
* @return {Element}
*/
createFormInputGroup (label, inputType = 'text', defaultValue = null, hoverHelp = null)
{
let divFormInputGroup
let inputID = this._selectorGenerator.getSettingsInputSelector(label)
let labelFormGroup = this.createFormGroupLabel(label, inputID, inputType)
let inputFormGroup = this.createFormGroupInput(inputID, inputType, defaultValue)
switch (inputType) {
case 'number':
case 'text':
divFormInputGroup = this.createFormGroup([labelFormGroup, inputFormGroup])
break
case 'radio':
case 'checkbox':
divFormInputGroup = this.createFormGroup([inputFormGroup, labelFormGroup])
break
}
if (hoverHelp !== null) {
this._addHelpTextOnHover(divFormInputGroup, hoverHelp)
}
return divFormInputGroup
}
/**
* @param {string} label
* @param {string} inputsType
* @param {*} defaultValues
* @return {Element}
*/
createFormRangeInputGroup (label, inputsType = 'text', defaultValues = null)
{
let divFormInputGroup = this.createFormGroup([
this.createFormGroupLabel(label, '', inputsType),
this.createFormGroupInput(
this._selectorGenerator.getSettingsRangeInputSelector(label, false),
inputsType,
defaultValues === null ? null : defaultValues[1],
),
this.createFormGroupInput(
this._selectorGenerator.getSettingsRangeInputSelector(label, true),
inputsType,
defaultValues === null ? null : defaultValues[0],
),
])
divFormInputGroup.classList.add('form-range-input-group')
return divFormInputGroup
}
/**
* @param {string} title
* @param {Element[]} children
* @return {Element|HTMLDivElement}
*/
createFormSection (title, children)
{
let sectionDiv = document.createElement('div')
sectionDiv.classList.add('form-section')
if (title !== '') {
let sectionTitle = document.createElement('label')
sectionTitle.textContent = title
sectionTitle.classList.add('title')
UIGenerator.populateChildren(sectionDiv, [sectionTitle])
}
return UIGenerator.populateChildren(sectionDiv, children)
}
/**
* @param {string} caption
* @param {string} tooltip
* @param {EventListenerOrEventListenerObject} onClick
* @param {string} hoverHelp
* @return {HTMLButtonElement}
*/
createFormSectionButton (caption, tooltip, onClick, hoverHelp = '')
{
let button = this.createFormButton(caption, onClick, hoverHelp)
button.title = tooltip
return button
}
/**
* @param {string} label
* @param {*} defaultValue
* @return {Element}
*/
createFormTextAreaGroup (label, defaultValue = null)
{
let labelElement = this.createFormGroupLabel(label)
labelElement.style.textAlign = 'center'
let textAreaElement = document.createElement('textarea')
textAreaElement.id = this._selectorGenerator.getSettingsInputSelector(label)
textAreaElement.classList.add('form-input')
textAreaElement.value = defaultValue
return this.createFormGroup([labelElement, textAreaElement])
}
/**
* @param {string} IDSuffix
* @param {*} backgroundColor
* @param {*} top
* @param {*} width
* @return {this}
*/
createSection (IDSuffix, backgroundColor, top, width)
{
this._section = document.createElement('section')
this._section.id = this._selectorGenerator.getSelector(IDSuffix)
this._section.classList.add('form-section')
this._section.style.display = this._showUI ? 'block' : 'none'
this._section.style.top = top
this._section.style.width = width
this._section.style.backgroundColor = backgroundColor
return this
}
/**
* @return {HTMLHRElement}
*/
createSeparator ()
{
return document.createElement('hr')
}
/**
* @param {LocalStore} localStore
* @param {EventListenerOrEventListenerObject} onClick
* @param {boolean} addTopPadding
* @return {HTMLDivElement}
*/
createSettingsFormActions (localStore, onClick, addTopPadding = false)
{
let divFormActions = this.createFormSection('', [
this.createFormActions([
this.createFormButton('Apply', onClick,
'Filter the videos as per the settings in the dialog.'),
this.createFormButton('Reset', function () {
localStore.retrieve()
onClick()
}, 'Restore and apply saved configuration.'),
]),
])
if (addTopPadding) {
divFormActions.style.paddingTop = '10px'
}
return divFormActions
}
/**
* @param {string} label
* @param {Array} keyValuePairs
* @param {*} defaultValue
* @return {Element}
*/
createSettingsDropDownFormGroup (label, keyValuePairs, defaultValue = null)
{
let dropdownID = this._selectorGenerator.getSettingsInputSelector(label)
return this.createFormGroup([
this.createFormGroupLabel(label, dropdownID, 'text'),
this.createFormGroupDropdown(dropdownID, keyValuePairs, defaultValue),
])
}
/**
* @return {HTMLButtonElement}
*/
createSettingsHideButton ()
{
let section = this._section
return this.createFormButton('<< Hide', function () {
section.style.display = 'none'
})
}
/**
* @param {string} caption
* @param {Element} settingsSection
* @param {boolean} fixed
* @return {HTMLButtonElement}
*/
createSettingsShowButton (caption, settingsSection, fixed = true)
{
let controlButton = document.createElement('button')
controlButton.textContent = caption
controlButton.classList.add('show-settings')
if (fixed) {
controlButton.classList.add('fixed')
}
controlButton.addEventListener('click', function () {
let settingsUI = document.getElementById(settingsSection.id)
settingsUI.style.display = settingsUI.style.display === 'none' ? 'block' : 'none'
})
return controlButton
}
/**
* @param {string} statisticsType
* @param {string} label
* @return {Element}
*/
createStatisticsFormGroup (statisticsType, label = '')
{
if (label === '') {
label = statisticsType
}
return this.createFormGroup([
this.createFormGroupLabel('Filtered ' + label + ' Videos'),
this.createFormGroupStatLabel(statisticsType),
])
}
/**
* @return {Element|HTMLDivElement}
*/
createStatusSection ()
{
this._statusLine = UI.createFormGroupLabel('Status')
this._statusLine.id = this._selectorGenerator.getSelector('status')
return UI.createFormSection('', [this._statusLine])
}
/**
* @param {LocalStore} localStore
* @return {Element}
*/
createStoreFormSection (localStore)
{
return this.createFormSection('Saved Configuration', [
this.createFormActions([
this.createFormSectionButton('Update', 'Save UI settings in store', function () {
localStore.save()
}, 'Saves applied settings.'),
this.createFormSectionButton('Purge', 'Purge store', function () {
localStore.delete()
}, 'Removes saved settings. Settings will then be sourced from the defaults defined in the script.'),
]),
])
}
createTextualSettingsButtonAndSection ()
{
let UI = new UIGenerator(this._showUI, this._selectorGenerator)
let section = UI.createSection(
'textual-settings',
this._section.style.backgroundColor,
this._section.style.top,
this._section.style.width,
).addSectionChildren([
UI.createFormTextAreaGroup('Blacklisted Words'),
UI.createSeparator(),
UI.createFormButton('Update', function () {
}, 'Videos having name containing any blacklisted words will be filtered out.')
])
UI.constructor.appendToBody(section)
return this.createFormButton('More Settings >>', function () {
section.style.display = section.style.display === 'none' ? 'block' : 'none'
})
}
/**
* @param {string} label
* @return {HTMLElement}
*/
getSettingsInput (label)
{
return document.getElementById(this._selectorGenerator.getSettingsInputSelector(label))
}
/**
* @param {string} label
* @return {boolean}
*/
getSettingsInputCheckedStatus (label)
{
return this.getSettingsInput(label).checked
}
/**
* @param {string} label
* @return {*}
*/
getSettingsInputValue (label)
{
return this.getSettingsInput(label).value
}
/**
* @param {string} label
* @param {boolean} getMinInput
* @return {HTMLElement}
*/
getSettingsRangeInput (label, getMinInput)
{
return document.getElementById(this._selectorGenerator.getSettingsRangeInputSelector(label, getMinInput))
}
/**
* @param {string} label
* @param {boolean} getMinInputValue
* @return {*}
*/
getSettingsRangeInputValue (label, getMinInputValue)
{
return this.getSettingsRangeInput(label, getMinInputValue).value
}
resetStatus ()
{
this._statusLine.textContent = this._statusText
}
/**
* @param {string} label
* @param {boolean} bool
*/
setSettingsInputCheckedStatus (label, bool)
{
this.getSettingsInput(label).checked = bool
}
/**
* @param {string} label
* @param {*} value
*/
setSettingsInputValue (label, value)
{
this.getSettingsInput(label).value = value
}
/**
* @param {string} label
* @param {number} lowerBound
* @param {number} upperBound
*/
setSettingsRangeInputValue (label, lowerBound, upperBound)
{
this.getSettingsRangeInput(label, true).value = lowerBound
this.getSettingsRangeInput(label, false).value = upperBound
}
/**
* @param {string} status
* @param {boolean} transient
*/
updateStatus (status, transient = false)
{
if (!transient) {
this._statusText = status
}
this._statusLine.textContent = status
}
}
class Validator
{
static iFramesRemover ()
{
GM_addStyle(' iframe { display: none !important; } ')
}
/**
* @param {StatisticsRecorder} statisticsRecorder
*/
constructor (statisticsRecorder)
{
/**
* @type {Array}
* @private
*/
this._blacklist = []
/**
* @type {Array}
* @private
*/
this._filters = []
/**
* @type {RegExp}
* @private
*/
this._optimizedBlacklist = null
/**
* @type {Object}
* @private
*/
this._optimizedSanitizationRules = {}
/**
* @type {Object}
* @private
*/
this._sanitizationRules = []
/**
* @type {StatisticsRecorder}
* @private
*/
this._statisticsRecorder = statisticsRecorder
}
_buildWholeWordMatchingRegex (words)
{
let patternedWords = []
for (let i = 0; i < words.length; i++) {
patternedWords.push('\\b' + words[i] + '\\b')
}
return new RegExp('(' + patternedWords.join('|') + ')', 'gi')
}
/**
* @param {string[]} blacklistedWords
* @return {Validator}
*/
addBlacklistFilter (blacklistedWords)
{
this._blacklist = blacklistedWords
return this
}
/**
* @param {Object} sanitizationRules
* @return {Validator}
*/
addSanitizationFilter (sanitizationRules)
{
this._sanitizationRules = sanitizationRules
return this
}
/**
* @return {Validator}
*/
optimize ()
{
this._optimizedBlacklist = this._buildWholeWordMatchingRegex(this._blacklist)
for (const substitute in this._sanitizationRules) {
this._optimizedSanitizationRules[substitute] =
this._buildWholeWordMatchingRegex(this._sanitizationRules[substitute])
}
return this
}
/**
* @param {string} text
* @return {string}
*/
sanitize (text)
{
for (const substitute in this._optimizedSanitizationRules) {
text = text.replace(this._optimizedSanitizationRules[substitute], substitute)
}
return text.trim()
}
/**
* @param {Element} videoNameNode
* @return {Validator}
*/
sanitizeVideoItem (videoNameNode)
{
videoNameNode.textContent = this.sanitize(videoNameNode.textContent)
return this
}
/**
* @param {string} videoNameNodeSelector
* @return {Validator}
*/
sanitizeVideoPage (videoNameNodeSelector)
{
let videoNameNode = document.querySelector(videoNameNodeSelector)
if (videoNameNode !== null) {
let sanitizedVideoName = this.sanitize(videoNameNode.textContent)
videoNameNode.textContent = sanitizedVideoName
document.title = sanitizedVideoName
}
return this
}
/**
* @param {string} text
* @return {boolean}
*/
validateBlackList (text)
{
let validationCheck = true
if (this._optimizedBlacklist !== null) {
validationCheck = text.match(this._optimizedBlacklist) === null
this._statisticsRecorder.record('Blacklist', 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
}
}
class TubeSiteSearchAndTweaks
{
/**
* @param {string} scriptPrefix
* @param {Object} defaultPreset
* @param {Object} globalConfiguration
*/
static create (scriptPrefix, defaultPreset, globalConfiguration)
{
return new TubeSiteSearchAndTweaks(scriptPrefix, defaultPreset, globalConfiguration)
}
/**
* @param {string} scriptPrefix
* @param {Object} defaultPreset
* @param {Object} globalConfiguration
*/
constructor (scriptPrefix, defaultPreset, globalConfiguration)
{
/**
* @type {Object}
* @private
*/
this._appliedPreset = null
/**
* @type {Object}
* @private
*/
this._defaultPreset = defaultPreset
/**
* {LocalStore}
*/
this._globalConfigurationStore = LocalStore.createGlobalConfigStore(scriptPrefix, globalConfiguration)
/**
* {Object}
*/
this._globalConfiguration = this._globalConfigurationStore.retrieve().get()
/**
* @type {LocalStore}
* @private
*/
this._presetsStore = LocalStore.createPresetConfigStore(scriptPrefix, defaultPreset)
/**
* @type {{name: string, config: Object}[]}
* @private
*/
this._presets = this._presetsStore.retrieve().get()
/**
* @type {string}
* @private
*/
this._scriptPrefix = scriptPrefix
}
/**
* @param {string} name
* @param {Object} config
* @return {this}
*/
createPreset (name, config)
{
this._presets.push({
name: name,
config: config,
})
this._presetsStore.update(this._presets)
return this
}
/**
* @param {string} name
* @return {this}
*/
deletePreset (name)
{
for (let i = 0; i < this._presets.length; i++) {
if (this._presets[i].name === name) {
this._presets.splice(i, 1)
this._presetsStore.update(this._presets)
break
}
}
return this
}
/**
* @param name
* @return {{name: string, config: Object}|null}
*/
findPreset (name)
{
for (let preset of this._presets) {
if (preset.name === name) {
return preset
}
}
return null
}
/**
* @return {{name: string, config: Object}}
*/
getAppliedPreset ()
{
return this._appliedPreset
}
}