Brazenvoid's Base Resource

Base library for my scripts

目前為 2019-02-23 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/375557/673187/Brazenvoid%27s%20Base%20Resource.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          Brazenvoid's Base Resource
// @namespace     brazenvoid
// @version       1.4.1
// @author        brazenvoid
// @license       GPL-3.0-only
// @description   Base library for my scripts
// @run-at	      document-end
// ==/UserScript==

const sleep = (milliseconds) => {
  return new Promise(resolve => setTimeout(resolve, milliseconds))
}

class CaseConverters {

  toCamel (text) {
    return text.replace(/(?:^\w|[A-Z]|\b\w)/g, function (letter, index) {
      return index === 0 ? letter.toLowerCase() : letter.toUpperCase()
    }).replace(/\s+/g, '')
  }

  toKebab (text) {
    return text.toLowerCase().replace(' ', '-')
  }

  toKebabFromSnake (text) {
    return text.replace('_', '-')
  }

  toNormalFromKebab (text) {
    return text.replace('-', ' ')
  }

  toNormalFromSnake (text) {
    return text.replace('_', ' ')
  }

  toSnake (text) {
    return text.toLowerCase().replace(' ', '_')
  }
}

class ChildObserver {

  constructor (handler) {
    this.config = {
      attributes: false,
      childList: true,
      subtree: false
    }
    this.handler = handler
    this.observer = new MutationObserver(function (mutations) {
      for (let mutation of mutations) {
        handler(mutation.target)
      }
    })
  }

  observe (node, doInitialRun = false) {

    if (doInitialRun) {
      this.handler(node)
    }
    this.observer.observe(node, this.config)
    return this
  }
}

class Filters {

  constructor (statisticsRecorder) {
    this.blacklist = []
    this.sanitizationRules = []

    this._optimizedBlacklist = []
    this._optimizedSanitizationRules = {}
    this._statisticsRecorder = statisticsRecorder
  }

  init () {

    for (let i = 0; i < this.blacklist.length; i++) {
      this._optimizedBlacklist[i] = new RegExp(this.blacklist[i], 'ig')
    }

    for (const substitute in this.sanitizationRules) {
      this._optimizedSanitizationRules[substitute] = []

      for (let i = 0; i < this.sanitizationRules[substitute].length; i++) {
        this._optimizedSanitizationRules[substitute][i] = new RegExp(this.sanitizationRules[substitute][i], 'ig')
      }
    }
  }

  iFramesRemover () {

    GM_addStyle(`
      iframe {
          display: none !important;
      }
    `)
  }

  sanitize (text) {

    for (const substitute in this._optimizedSanitizationRules) {
      for (const subject of this._optimizedSanitizationRules[substitute]) {
        text = text.replace(subject, substitute)
      }
    }
    return text
  }

  validateBlackList (text) {

    let validationCheck = true

    if (this._optimizedBlacklist.length > 0) {

      for (const blacklistedWord of this._optimizedBlacklist) {

        validationCheck = text.match(blacklistedWord) === null
        if (!validationCheck) {
          break
        }
      }
      this._statisticsRecorder.record('Blacklist', validationCheck)
    }
    return validationCheck
  }

  validateRange (type, value, bounds) {

    let validationCheck = true

    if (bounds[0] > 0) {
      validationCheck = value >= bounds[0]
    }
    if (bounds[1] > 0) {
      validationCheck = value <= bounds[1]
    }
    this._statisticsRecorder.record(type, validationCheck)

    return validationCheck
  }
}

class LocalStore {

  constructor (key, defaultStore) {
    this.onDefaultsLoaded = null
    this.onRetrieval = null
    this.onUpdated = null

    this._key = key
    this._store = {}
    this._defaultStore = this._toJSON(defaultStore)
  }

  _fromJSON (json) {
    let arrayObject = {}
    let parsedJSON = JSON.parse(json)
    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
  }

  _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)
  }

  delete () {
    window.localStorage.removeItem(this._key)
    return this
  }

  get () {
    return this._store
  }

  restoreDefaults () {
    this._store = this._fromJSON(this._defaultStore)

    if (this.onDefaultsLoaded !== null) {
      this.onDefaultsLoaded(this._store)
    }
    return this
  }

  retrieve () {
    let storedStore = window.localStorage.getItem(this._key)
    if (storedStore === null) {
      this.restoreDefaults()
    } else {
      this._store = this._fromJSON(storedStore)
    }
    if (this.onRetrieval !== null) {
      this.onRetrieval(this._store)
    }
    return this
  }

  save () {
    window.localStorage.setItem(this._key, this._toJSON(this._store))

    if (this.onUpdated !== null) {
      this.onUpdated(this._store)
    }
    return this
  }
}

class Logger {

  constructor (enableDebugging) {
    this.enableDebugging = enableDebugging
  }

  _log (message) {
    if (this.enableDebugging) {
      console.log(message)
    }
  }

  logTaskCompletion (task) {
    this._log('Completed: ' + task)
    this.logSeparator()
  }

  logSeparator () {
    this._log('------------------------------------------------------------------------------------')
  }

  logValidation (filterName, validationResult = null) {
    this._log('Satisfies ' + filterName + ' Filter: ' + (validationResult ? 'true' : 'false'))
  }

  logVideoCheck (videoName) {
    this._log('Checking Video: ' + videoName)
  }
}

class SelectorGenerator {

  constructor (selectorPrefix) {
    this.prefix = selectorPrefix
  }

  getSelector (selector) {
    return this.prefix + selector
  };

  getSelectorFromName (name) {
    return CaseConverters.prototype.toKebab(name)
  }

  getSettingsInputSelector (settingName) {
    return this.getSelector(this.getSelectorFromName(settingName) + '-setting')
  }

  getStatLabelSelector (statisticType) {
    return this.getSelector(this.getSelectorFromName(statisticType) + '-stat')
  };
}

class StatisticsRecorder {

  constructor (logger, selectorGenerator) {
    this.logger = logger
    this.selectorGenerator = selectorGenerator
    this.statistics = {Total: 0}
  }

  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 {

  constructor (showUI, selectorGenerator) {
    this.selectorGenerator = selectorGenerator
    this.showUI = showUI
  }

  appendToBody (node) {

    let bodyTag = document.getElementsByTagName('body')[0]
    bodyTag.appendChild(node)
  }

  createFormButton (caption, onClick) {

    let button = document.createElement('button')
    button.textContent = caption
    button.style.height = '30px'
    button.style.width = '100%'
    button.addEventListener('click', onClick)

    return button
  }

  createFormGroup () {

    let divFormGroup = document.createElement('div')
    divFormGroup.style.display = 'block'
    divFormGroup.style.height = '18px'
    divFormGroup.style.marginBottom = '2px'
    divFormGroup.style.padding = '5px 0'

    return divFormGroup
  }

  createFormGroupLabel (label, inputID = null, inputType = null) {

    let labelFormGroup = document.createElement('label')
    labelFormGroup.style.padding = '2px 0'
    labelFormGroup.textContent = label

    if (inputID !== null) {
      labelFormGroup.setAttribute('for', inputID)

      switch (inputType) {
        case 'number':
        case 'text':
          labelFormGroup.textContent += ': '
          labelFormGroup.style.float = 'left'
          break
        case 'radio':
        case 'checkbox':
          labelFormGroup.style.float = 'right'
          break
      }
    }

    return labelFormGroup
  }

  createFormGroupStatLabel (statisticType) {

    let labelFormGroup = document.createElement('label')
    labelFormGroup.id = this.selectorGenerator.getStatLabelSelector(statisticType)
    labelFormGroup.style.float = 'right'
    labelFormGroup.style.padding = '2px 0'
    labelFormGroup.textContent = '0'

    return labelFormGroup
  }

  createFormGroupInput (id, type, defaultValue = null) {

    let inputFormGroup = document.createElement('input')
    inputFormGroup.id = id
    inputFormGroup.type = type

    inputFormGroup.style.textAlign = 'center'
    switch (type) {
      case 'number':
      case 'text':
        inputFormGroup.style.float = 'right'
        inputFormGroup.style.width = '100px'

        if (defaultValue !== null) {
          inputFormGroup.value = defaultValue
        }
        break
      case 'radio':
      case 'checkbox':
        inputFormGroup.style.float = 'left'
        inputFormGroup.style.marginRight = '5px'

        if (defaultValue !== null) {
          inputFormGroup.checked = defaultValue
        }
        break
    }

    return inputFormGroup
  }

  createSection (id, backgroundColor, top, width, children) {

    let section = document.createElement('section')
    section.id = this.selectorGenerator.getSelector(id)
    section.style.color = 'black'
    section.style.display = this.showUI ? 'block' : 'none'
    section.style.fontSize = '12px'
    section.style.fontWeight = 'bold'
    section.style.position = 'fixed'
    section.style.top = top
    section.style.left = '0'
    section.style.width = width
    section.style.padding = '5px 10px'
    section.style.backgroundColor = backgroundColor
    section.style.zIndex = '1000'

    for (let child of children) {
      section.appendChild(child)
    }
    return section
  }

  createSeparator () {

    let separator = document.createElement('hr')
    separator.style.margin = '3px'

    return separator
  }

  createSettingsFormGroup (label, inputType = 'text', defaultValue = null) {

    let divFormGroup = this.createFormGroup()
    let inputID = this.selectorGenerator.getSettingsInputSelector(label)
    let labelFormGroup = this.createFormGroupLabel(label, inputType, inputID)
    let inputFormGroup = this.createFormGroupInput(inputID, inputType, defaultValue)

    switch (inputType) {
      case 'number':
      case 'text':
        divFormGroup.appendChild(labelFormGroup)
        divFormGroup.appendChild(inputFormGroup)
        break
      case 'radio':
      case 'checkbox':
        divFormGroup.appendChild(inputFormGroup)
        divFormGroup.appendChild(labelFormGroup)
        break
    }
    return divFormGroup
  }

  createSettingShowButton (caption, settingSection, fixed = true) {

    let controlButton = document.createElement('button')
    controlButton.textContent = caption

    if (fixed) {
      controlButton.style.backgroundColor = '#ffa31a'
      controlButton.style.border = '0'
      controlButton.style.color = 'black'
      controlButton.style.fontSize = '14px'
      controlButton.style.left = '0'
      controlButton.style.padding = '15px 0px'
      controlButton.style.position = 'fixed'
      controlButton.style.writingMode = 'sideways-lr'
      controlButton.style.top = '250px'
      controlButton.style.width = '30px'
      controlButton.style.zIndex = '999'
    } else {
      controlButton.style.width = '100%'
      controlButton.style.margin = '2px 5px'
      controlButton.style.padding = '2px 5px'
      controlButton.style.backgroundColor = '#ffa31a'
      controlButton.style.border = '0'
    }
    controlButton.addEventListener('click', function () {
      let settingsUI = document.getElementById(settingSection.id)
      settingsUI.style.display = settingsUI.style.display === 'none' ? 'block' : 'none'
    })
    return controlButton
  }

  createStatisticsFormGroup (statisticsType, label = null) {

    if (label === null) {
      label = statisticsType
    }

    let divFormGroup = this.createFormGroup()
    let labelFormGroup = this.createFormGroupLabel('Filtered ' + label + ' Videos')
    let statLabelFormGroup = this.createFormGroupStatLabel(statisticsType)

    divFormGroup.appendChild(labelFormGroup)
    divFormGroup.appendChild(statLabelFormGroup)

    return divFormGroup
  }

  createStoreUpdateButton (localStore) {
    return this.createFormButton('Update Store', function () {
      localStore.save()
    })
  }

  createStoreReloadButton (localStore) {
    return this.createFormButton('Reload Store', function () {
      localStore.retrieve()
    })
  }

  createStoreResetButton (localStore) {
    return this.createFormButton('Load Defaults', function () {
      localStore.restoreDefaults()
    })
  }

  createStoreDeleteButton (localStore) {
    return this.createFormButton('Delete Store', function () {
      localStore.delete()
    })
  }

  getSettingsInput (label) {
    return document.getElementById(this.selectorGenerator.getSettingsInputSelector(label))
  }

  getSettingsInputCheckedStatus (label) {
    return this.getSettingsInput(label).checked
  }

  getSettingsInputValue (label) {
    return this.getSettingsInput(label).value;
  }

  setSettingsInputCheckedStatus (label, bool) {
    this.getSettingsLabel(label).checked = bool
  }

  setSettingsInputValue (label, value) {
    this.getSettingsLabel(label).value = value
  }
}