grrrr

Base library for my scripts

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greatest.deepsurf.us/scripts/411993/851645/grrrr.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

  1. // ==UserScript==
  2. // @name grrrr
  3. // @namespace brazenvoid
  4. // @version 1.0
  5. // @author brazenvoid
  6. // @license GPL-3.0-only
  7. // @description Base library for my scripts
  8. // @grant GM_addStyle
  9. // @run-at document-end
  10. // ==/UserScript==
  11.  
  12. /**
  13. * @function GM_addStyle
  14. * @param {string} style
  15. */
  16. GM_addStyle(
  17. `@keyframes fadeEffect{from{opacity:0}to{opacity:1}}button.form-button{padding:0 5px;width:100%}button.show-settings{background-color:#000000;border:0;margin:2px 5px;padding:2px 5px;width:100%}button.show-settings.fixed{color:#ffffff;font-size:.7rem;left:0;height:90vh;margin:0;padding:0;position:fixed;top:5vh;width:.1vw;writing-mode:sideways-lr;z-index:999}button.tab-button{background-color:#808080;border:1px solid #000000;border-bottom:0;border-top-left-radius:3px;border-top-right-radius:3px;cursor:pointer;float:left;outline:none;padding:5px 10px;transition:.3s}button.tab-button:hover{background-color:#fff}button.tab-button.active{background-color:#fff;display:block}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:4px 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;solid #000000}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}div.tab-panel{animation:fadeEffect 1s;border:1px solid #000000;display:none;padding:5px 10px}div.tab-panel.active{display:block}div.tabs-nav{overflow:hidden}div.tabs-section{margin-bottom:5px}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{color:#ffffff,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:#ffffff;font-size:12px;font-weight:700;position:fixed;left:0;padding:5px 10px;z-index:1000000}select.form-dropdown{float:right;height:18px;text-align:center;width:100px}textarea.form-input{display:block;height:auto;position:relative;width:98%}`)
  18.  
  19. /**
  20. * @param milliseconds
  21. * @return {Promise<*>}
  22. */
  23. const sleep = (milliseconds) => {
  24. return new Promise(resolve => setTimeout(resolve, milliseconds))
  25. }
  26.  
  27. /**
  28. * @param {string} text
  29. * @return {string}
  30. */
  31. function toKebabCase (text)
  32. {
  33. return text.toLowerCase().replace(' ', '-')
  34. }
  35.  
  36. class ChildObserver
  37. {
  38. /**
  39. * @callback observerOnMutation
  40. * @param {NodeList} nodes
  41. */
  42.  
  43. /**
  44. * @return {ChildObserver}
  45. */
  46. static create ()
  47. {
  48. return new ChildObserver
  49. }
  50.  
  51. /**
  52. * ChildObserver constructor
  53. */
  54. constructor ()
  55. {
  56. this._node = null
  57. this._observer = null
  58. this._onNodesAdded = null
  59. this._onNodesRemoved = null
  60. }
  61.  
  62. /**
  63. * @return {ChildObserver}
  64. * @private
  65. */
  66. _observeNodes ()
  67. {
  68. this._observer.observe(this._node, {childList: true})
  69. return this
  70. }
  71.  
  72. /**
  73. * Attach an observer to the specified node(s)
  74. * @param {Node} node
  75. * @returns {ChildObserver}
  76. */
  77. observe (node)
  78. {
  79. this._node = node
  80. this._observer = new MutationObserver((mutations) => {
  81. for (let mutation of mutations) {
  82. if (mutation.addedNodes.length && this._onNodesAdded !== null) {
  83. this._onNodesAdded(
  84. mutation.addedNodes,
  85. mutation.previousSibling,
  86. mutation.nextSibling,
  87. mutation.target,
  88. )
  89. }
  90. if (mutation.removedNodes.length && this._onNodesRemoved !== null) {
  91. this._onNodesRemoved(
  92. mutation.removedNodes,
  93. mutation.previousSibling,
  94. mutation.nextSibling,
  95. mutation.target,
  96. )
  97. }
  98. }
  99. })
  100. return this._observeNodes()
  101. }
  102.  
  103. /**
  104. * @param {observerOnMutation} eventHandler
  105. * @returns {ChildObserver}
  106. */
  107. onNodesAdded (eventHandler)
  108. {
  109. this._onNodesAdded = eventHandler
  110. return this
  111. }
  112.  
  113. /**
  114. * @param {observerOnMutation} eventHandler
  115. * @returns {ChildObserver}
  116. */
  117. onNodesRemoved (eventHandler)
  118. {
  119. this._onNodesRemoved = eventHandler
  120. return this
  121. }
  122.  
  123. pauseObservation ()
  124. {
  125. this._observer.disconnect()
  126. }
  127.  
  128. resumeObservation ()
  129. {
  130. this._observeNodes()
  131. }
  132. }
  133.  
  134. class LocalStore
  135. {
  136. /**
  137. * @callback storeEventHandler
  138. * @param {Object} store
  139. */
  140.  
  141. /**
  142. * @param {string} scriptPrefix
  143. * @param {Object} defaults
  144. * @return {LocalStore}
  145. */
  146. static createGlobalConfigStore (scriptPrefix, defaults)
  147. {
  148. return new LocalStore(scriptPrefix + 'globals', defaults)
  149. }
  150.  
  151. static createPresetConfigStore (scriptPrefix, defaults)
  152. {
  153. return new LocalStore(scriptPrefix + 'presets', [
  154. {
  155. name: 'default',
  156. config: defaults,
  157. },
  158. ])
  159. }
  160.  
  161. /**
  162. * @param {string} key
  163. * @param {Object} defaults
  164. */
  165. constructor (key, defaults)
  166. {
  167. /**
  168. * @type {string}
  169. * @private
  170. */
  171. this._key = key
  172.  
  173. /**
  174. * @type {Object}
  175. * @private
  176. */
  177. this._store = {}
  178.  
  179. /**
  180. * @type {string}
  181. * @private
  182. */
  183. this._defaults = this._toJSON(defaults)
  184.  
  185. /**
  186. * @type {storeEventHandler}
  187. */
  188. this._onChange = null
  189. }
  190.  
  191. /**
  192. * @param {string} json
  193. * @return {Object}
  194. * @private
  195. */
  196. _fromJSON (json)
  197. {
  198. /** @type {{arrays: Object, objects: Object, properties: Object}} */
  199. let parsedJSON = JSON.parse(json)
  200. let arrayObject = {}
  201. let store = {}
  202.  
  203. for (let property in parsedJSON.arrays) {
  204. arrayObject = JSON.parse(parsedJSON.arrays[property])
  205. store[property] = []
  206.  
  207. for (let key in arrayObject) {
  208. store[property].push(arrayObject[key])
  209. }
  210. }
  211. for (let property in parsedJSON.objects) {
  212. store[property] = this._fromJSON(parsedJSON.objects[property])
  213. }
  214. for (let property in parsedJSON.properties) {
  215. store[property] = parsedJSON.properties[property]
  216. }
  217. return store
  218. }
  219.  
  220. /**
  221. * @return {string}
  222. * @private
  223. */
  224. _getStore ()
  225. {
  226. return window.localStorage.getItem(this._key)
  227. }
  228.  
  229. /**
  230. * @return {Object}
  231. * @private
  232. */
  233. _getDefaults ()
  234. {
  235. return this._fromJSON(this._defaults)
  236. }
  237.  
  238. /**
  239. * @param {Object} store
  240. * @return {string}
  241. * @private
  242. */
  243. _toJSON (store)
  244. {
  245. let arrayToObject = {}
  246. let json = {arrays: {}, objects: {}, properties: {}}
  247.  
  248. for (let property in store) {
  249. if (typeof store[property] === 'object') {
  250. if (Array.isArray(store[property])) {
  251. for (let key in store[property]) {
  252. arrayToObject[key] = store[property][key]
  253. }
  254. json.arrays[property] = JSON.stringify(arrayToObject)
  255. } else {
  256. json.objects[property] = this._toJSON(store[property])
  257. }
  258. } else {
  259. json.properties[property] = store[property]
  260. }
  261. }
  262. return JSON.stringify(json)
  263. }
  264.  
  265. _handleOnChange ()
  266. {
  267. if (this._onChange !== null) {
  268. this._onChange(this._store)
  269. }
  270. }
  271.  
  272. /**
  273. * @return {LocalStore}
  274. */
  275. delete ()
  276. {
  277. window.localStorage.removeItem(this._key)
  278. return this
  279. }
  280.  
  281. /**
  282. * @return {*}
  283. */
  284. get ()
  285. {
  286. return this._store
  287. }
  288.  
  289. /**
  290. * @return {boolean}
  291. */
  292. isPurged ()
  293. {
  294. return this._getStore() === null
  295. }
  296.  
  297. /**
  298. * @param {storeEventHandler} handler
  299. * @return {LocalStore}
  300. */
  301. onChange (handler)
  302. {
  303. this._onChange = handler
  304. return this
  305. }
  306.  
  307. /**
  308. * @return {LocalStore}
  309. */
  310. restoreDefaults ()
  311. {
  312. this._store = this._getDefaults()
  313. this._handleOnChange()
  314. return this
  315. }
  316.  
  317. /**
  318. * @return {LocalStore}
  319. */
  320. retrieve ()
  321. {
  322. let storedStore = this._getStore()
  323. if (storedStore === null) {
  324. this.restoreDefaults()
  325. } else {
  326. this._store = this._fromJSON(storedStore)
  327. }
  328. this._handleOnChange()
  329. return this
  330. }
  331.  
  332. /**
  333. * @return {LocalStore}
  334. */
  335. save ()
  336. {
  337. window.localStorage.setItem(this._key, this._toJSON(this._store))
  338. this._handleOnChange()
  339. return this
  340. }
  341.  
  342. /**
  343. * @param {*} data
  344. * @return {LocalStore}
  345. */
  346. update (data)
  347. {
  348. this._store = data
  349. return this.save()
  350. }
  351. }
  352.  
  353. class SelectorGenerator
  354. {
  355. /**
  356. * @param {string} selectorPrefix
  357. */
  358. constructor (selectorPrefix)
  359. {
  360. /**
  361. * @type {string}
  362. * @private
  363. */
  364. this._prefix = selectorPrefix
  365. }
  366.  
  367. /**
  368. * @param {string} selector
  369. * @return {string}
  370. */
  371. getSelector (selector)
  372. {
  373. return this._prefix + selector
  374. }
  375.  
  376. /**
  377. * @param {string} settingName
  378. * @return {string}
  379. */
  380. getSettingsInputSelector (settingName)
  381. {
  382. return this.getSelector(toKebabCase(settingName) + '-setting')
  383. }
  384.  
  385. /**
  386. * @param {string} settingName
  387. * @param {boolean} getMinInputSelector
  388. * @return {string}
  389. */
  390. getSettingsRangeInputSelector (settingName, getMinInputSelector)
  391. {
  392. return this.getSelector(toKebabCase(settingName) + (getMinInputSelector ? '-min' : '-max') + '-setting')
  393. }
  394.  
  395. /**
  396. * @param {string} statisticType
  397. * @return {string}
  398. */
  399. getStatLabelSelector (statisticType)
  400. {
  401. return this.getSelector(toKebabCase(statisticType) + '-stat')
  402. }
  403. }
  404.  
  405. class StatisticsRecorder
  406. {
  407. /**
  408. * @param {string} selectorPrefix
  409. */
  410. constructor (selectorPrefix)
  411. {
  412. /**
  413. * @type {SelectorGenerator}
  414. * @private
  415. */
  416. this._selectorGenerator = new SelectorGenerator(selectorPrefix)
  417.  
  418. /**
  419. * @type {{Total: number}}
  420. * @private
  421. */
  422. this._statistics = {Total: 0}
  423. }
  424.  
  425. /**
  426. * @param {string} statisticType
  427. * @param {boolean} validationResult
  428. * @param {number} value
  429. */
  430. record (statisticType, validationResult, value = 1)
  431. {
  432. if (!validationResult) {
  433. if (typeof this._statistics[statisticType] !== 'undefined') {
  434. this._statistics[statisticType] += value
  435. } else {
  436. this._statistics[statisticType] = value
  437. }
  438. this._statistics.Total += value
  439. }
  440. }
  441.  
  442. reset ()
  443. {
  444. for (const statisticType in this._statistics) {
  445. this._statistics[statisticType] = 0
  446. }
  447. }
  448.  
  449. updateUI ()
  450. {
  451. let label, labelSelector
  452.  
  453. for (const statisticType in this._statistics) {
  454. labelSelector = this._selectorGenerator.getStatLabelSelector(statisticType)
  455. label = document.getElementById(labelSelector)
  456. if (label !== null) {
  457. label.textContent = this._statistics[statisticType]
  458. }
  459. }
  460. }
  461. }
  462.  
  463. class UIGenerator
  464. {
  465. /**
  466. * @param {HTMLElement|Node} node
  467. */
  468. static appendToBody (node)
  469. {
  470. document.getElementsByTagName('body')[0].appendChild(node)
  471. }
  472.  
  473. /**
  474. * @param {HTMLElement} node
  475. * @param {HTMLElement[]} children
  476. * @return {HTMLElement}
  477. */
  478. static populateChildren (node, children)
  479. {
  480. for (let child of children) {
  481. node.appendChild(child)
  482. }
  483. return node
  484. }
  485.  
  486. /**
  487. * @param {boolean} showUI
  488. * @param {string} selectorPrefix
  489. */
  490. constructor (showUI, selectorPrefix)
  491. {
  492. /**
  493. * @type {*}
  494. * @private
  495. */
  496. this._buttonBackroundColor = null
  497.  
  498. /**
  499. * @type {HTMLElement}
  500. * @private
  501. */
  502. this._section = null
  503.  
  504. /**
  505. * @type {SelectorGenerator}
  506. * @private
  507. */
  508. this._selectorGenerator = new SelectorGenerator(selectorPrefix)
  509.  
  510. /**
  511. * @type {string}
  512. * @private
  513. */
  514. this._selectorPrefix = selectorPrefix
  515.  
  516. /**
  517. * @type {boolean}
  518. * @private
  519. */
  520. this._showUI = showUI
  521.  
  522. /**
  523. * @type {HTMLLabelElement}
  524. * @private
  525. */
  526. this._statusLine = null
  527.  
  528. /**
  529. * @type {string}
  530. * @private
  531. */
  532. this._statusText = ''
  533. }
  534.  
  535. /**
  536. * @param {HTMLElement} node
  537. * @param {string} text
  538. * @return {this}
  539. * @private
  540. */
  541. _addHelpTextOnHover (node, text)
  542. {
  543. node.addEventListener('mouseover', () => this.updateStatus(text, true))
  544. node.addEventListener('mouseout', () => this.resetStatus())
  545. }
  546.  
  547. /**
  548. * @param {HTMLElement[]} children
  549. * @return {HTMLElement}
  550. */
  551. addSectionChildren (children)
  552. {
  553. return UIGenerator.populateChildren(this._section, children)
  554. }
  555.  
  556. /**
  557. * @return {HTMLBRElement}
  558. */
  559. createBreakSeparator ()
  560. {
  561. return document.createElement('br')
  562. }
  563.  
  564. /**
  565. * @param {HTMLElement[]} children
  566. * @return {HTMLDivElement}
  567. */
  568. createFormActions (children)
  569. {
  570. let wrapperDiv = document.createElement('div')
  571. wrapperDiv.classList.add('form-actions-wrapper')
  572.  
  573. UIGenerator.populateChildren(wrapperDiv, children)
  574.  
  575. let formActionsDiv = document.createElement('div')
  576. formActionsDiv.classList.add('form-actions')
  577. formActionsDiv.appendChild(wrapperDiv)
  578.  
  579. return formActionsDiv
  580. }
  581.  
  582. /**
  583. * @param {string} caption
  584. * @param {EventListenerOrEventListenerObject} onClick
  585. * @param {string} hoverHelp
  586. * @return {HTMLButtonElement}
  587. */
  588. createFormButton (caption, onClick, hoverHelp = '')
  589. {
  590. console.log (caption);
  591.  
  592. let button = document.createElement('button')
  593. if (caption == 'Apply')
  594. {
  595. button.classList.add('grrapp')
  596. } else {
  597. button.classList.add('formapp')
  598. }
  599. if (caption == 'Update')
  600. {
  601. button.classList.add('fsdf')
  602. }
  603. button.textContent = caption
  604. button.addEventListener('click', onClick)
  605.  
  606. if (hoverHelp !== '') {
  607. this._addHelpTextOnHover(button, hoverHelp)
  608. }
  609. if (this._buttonBackroundColor !== null) {
  610. button.style.backgroundColor = this._buttonBackroundColor
  611. }
  612. return button
  613. }
  614.  
  615. /**
  616. * @param {HTMLElement[]} children
  617. * @return {HTMLElement}
  618. */
  619. createFormGroup (children)
  620. {
  621. let divFormGroup = document.createElement('div')
  622. divFormGroup.classList.add('form-group')
  623.  
  624. return UIGenerator.populateChildren(divFormGroup, children)
  625. }
  626.  
  627. /**
  628. * @param {string} id
  629. * @param {Array} keyValuePairs
  630. * @param {*} defaultValue
  631. * @return {HTMLSelectElement}
  632. */
  633. createFormGroupDropdown (id, keyValuePairs, defaultValue = null)
  634. {
  635. let dropdown = document.createElement('select'), item
  636. dropdown.id = id
  637. dropdown.classList.add('form-dropdown')
  638.  
  639. for (let [key, value] of keyValuePairs) {
  640. item = document.createElement('option')
  641. item.textContent = value
  642. item.value = key
  643. dropdown.appendChild(item)
  644. }
  645. dropdown.value = defaultValue === null ? keyValuePairs[0][0] : defaultValue
  646.  
  647. return dropdown
  648. }
  649.  
  650. /**
  651. * @param {string} id
  652. * @param {string} type
  653. * @param {*} defaultValue
  654. * @return {HTMLInputElement}
  655. */
  656. createFormGroupInput (id, type, defaultValue = null)
  657. {
  658. let inputFormGroup = document.createElement('input')
  659. inputFormGroup.id = id
  660. inputFormGroup.classList.add('form-input')
  661. inputFormGroup.type = type
  662.  
  663. switch (type) {
  664. case 'number':
  665. case 'text':
  666. inputFormGroup.classList.add('regular-input')
  667.  
  668. if (defaultValue !== null) {
  669. inputFormGroup.value = defaultValue
  670. }
  671. break
  672. case 'radio':
  673. case 'checkbox':
  674. inputFormGroup.classList.add('check-radio-input')
  675.  
  676. if (defaultValue !== null) {
  677. inputFormGroup.checked = defaultValue
  678. }
  679. break
  680. }
  681. return inputFormGroup
  682. }
  683.  
  684. /**
  685. * @param {string} label
  686. * @param {string} inputID
  687. * @param {string} inputType
  688. * @return {HTMLLabelElement}
  689. */
  690. createFormGroupLabel (label, inputID = '', inputType = '')
  691. {
  692. let labelFormGroup = document.createElement('label')
  693. labelFormGroup.classList.add('form-label')
  694. labelFormGroup.textContent = label
  695.  
  696. if (inputID !== '') {
  697. labelFormGroup.setAttribute('for', inputID)
  698. }
  699. if (inputType !== '') {
  700. switch (inputType) {
  701. case 'number':
  702. case 'text':
  703. labelFormGroup.classList.add('regular-input')
  704. labelFormGroup.textContent += ': '
  705. break
  706. case 'radio':
  707. case 'checkbox':
  708. labelFormGroup.classList.add('check-radio-input')
  709. break
  710. }
  711. }
  712. return labelFormGroup
  713. }
  714.  
  715. /**
  716. * @param {string} statisticType
  717. * @return {HTMLLabelElement}
  718. */
  719. createFormGroupStatLabel (statisticType)
  720. {
  721. let labelFormGroup = document.createElement('label')
  722. labelFormGroup.id = this._selectorGenerator.getStatLabelSelector(statisticType)
  723. labelFormGroup.classList.add('form-stat-label')
  724. labelFormGroup.textContent = '0'
  725.  
  726. return labelFormGroup
  727. }
  728.  
  729. /**
  730. * @param {string} label
  731. * @param {string} inputType
  732. * @param {string} hoverHelp
  733. * @param {*} defaultValue
  734. * @return {HTMLElement}
  735. */
  736. createFormInputGroup (label, inputType = 'text', hoverHelp = '', defaultValue = null)
  737. {
  738. let divFormInputGroup
  739. let inputID = this._selectorGenerator.getSettingsInputSelector(label)
  740. let labelFormGroup = this.createFormGroupLabel(label, inputID, inputType)
  741. let inputFormGroup = this.createFormGroupInput(inputID, inputType, defaultValue)
  742.  
  743. switch (inputType) {
  744. case 'number':
  745. case 'text':
  746. divFormInputGroup = this.createFormGroup([labelFormGroup, inputFormGroup])
  747. break
  748. case 'radio':
  749. case 'checkbox':
  750. divFormInputGroup = this.createFormGroup([inputFormGroup, labelFormGroup])
  751. break
  752. }
  753. if (hoverHelp !== '') {
  754. this._addHelpTextOnHover(divFormInputGroup, hoverHelp)
  755. }
  756. return divFormInputGroup
  757. }
  758.  
  759. /**
  760. * @param {string} label
  761. * @param {string} inputsType
  762. * @param {int[]|string[]} defaultValues
  763. * @return {HTMLElement}
  764. */
  765. createFormRangeInputGroup (label, inputsType = 'text', defaultValues = [])
  766. {
  767. let maxInputSelector = this._selectorGenerator.getSettingsRangeInputSelector(label, false)
  768. let minInputSelector = this._selectorGenerator.getSettingsRangeInputSelector(label, true)
  769.  
  770. let divFormInputGroup = this.createFormGroup([
  771. this.createFormGroupLabel(label, '', inputsType),
  772. this.createFormGroupInput(maxInputSelector, inputsType, defaultValues.length ? defaultValues[1] : null),
  773. this.createFormGroupInput(minInputSelector, inputsType, defaultValues.length ? defaultValues[0] : null),
  774. ])
  775. divFormInputGroup.classList.add('form-range-input-group')
  776.  
  777. return divFormInputGroup
  778. }
  779.  
  780. /**
  781. * @param {string} title
  782. * @param {HTMLElement[]} children
  783. * @return {HTMLElement|HTMLDivElement}
  784. */
  785. createFormSection (title, children)
  786. {
  787. let sectionDiv = document.createElement('div')
  788. sectionDiv.classList.add('form-section')
  789.  
  790. if (title !== '') {
  791. let sectionTitle = document.createElement('label')
  792. sectionTitle.textContent = title
  793. sectionTitle.classList.add('title')
  794. UIGenerator.populateChildren(sectionDiv, [sectionTitle])
  795. }
  796. return UIGenerator.populateChildren(sectionDiv, children)
  797. }
  798.  
  799. /**
  800. * @param {string} caption
  801. * @param {string} tooltip
  802. * @param {EventListenerOrEventListenerObject} onClick
  803. * @param {string} hoverHelp
  804. * @return {HTMLButtonElement}
  805. */
  806. createFormSectionButton (caption, tooltip, onClick, hoverHelp = '', grr= '')
  807. {
  808. let button = this.createFormButton(caption, onClick, hoverHelp, grr)
  809. button.title = tooltip
  810.  
  811. return button
  812. }
  813.  
  814. /**
  815. * @param {string} label
  816. * @param {int} rows
  817. * @param {string} hoverHelp
  818. * @param {string} defaultValue
  819. * @return {HTMLElement}
  820. */
  821. createFormTextAreaGroup (label, rows, hoverHelp = '', defaultValue = '')
  822. {
  823. let labelElement = this.createFormGroupLabel(label)
  824. labelElement.style.textAlign = 'center'
  825.  
  826. let textAreaElement = document.createElement('textarea')
  827. textAreaElement.id = this._selectorGenerator.getSettingsInputSelector(label)
  828. textAreaElement.classList.add('form-input')
  829. textAreaElement.value = defaultValue
  830. textAreaElement.setAttribute('rows', rows.toString())
  831.  
  832. let group = this.createFormGroup([labelElement, textAreaElement])
  833.  
  834. if (hoverHelp !== '') {
  835. this._addHelpTextOnHover(group, hoverHelp)
  836. }
  837. return group
  838. }
  839.  
  840. /**
  841. * @param {string} IDSuffix
  842. * @param {*} backgroundColor
  843. * @param {*} top
  844. * @param {*} width
  845. * @return {this}
  846. */
  847. createSection (IDSuffix, backgroundColor, top, width)
  848. {
  849. this._section = document.createElement('section')
  850. this._section.id = this._selectorGenerator.getSelector(IDSuffix)
  851. this._section.classList.add('form-section')
  852. this._section.style.display = this._showUI ? 'block' : 'none'
  853. this._section.style.top = top
  854. this._section.style.width = width
  855. this._section.style.backgroundColor = null
  856.  
  857. return this
  858. }
  859.  
  860. /**
  861. * @return {HTMLHRElement}
  862. */
  863. createSeparator ()
  864. {
  865. return document.createElement('hr')
  866. }
  867.  
  868. /**
  869. * @param {LocalStore} localStore
  870. * @param {EventListenerOrEventListenerObject|Function} onClick
  871. * @param {boolean} addTopPadding
  872. * @return {HTMLDivElement}
  873. */
  874. createSettingsFormActions (localStore, onClick, addTopPadding = false)
  875. {
  876. let divFormActions = this.createFormSection('', [
  877. this.createFormActions([
  878. this.createFormButton('Apply', onClick, 'Filter items as per the settings in the dialog.', 'derpapply'),
  879. this.createFormButton('Reset', () => {
  880. localStore.retrieve()
  881. onClick()
  882. }, 'Restore and apply saved configuration.', 'derpreset'),
  883. ]),
  884. ])
  885. if (addTopPadding) {
  886. divFormActions.style.paddingTop = '10px'
  887. }
  888. return divFormActions
  889. }
  890.  
  891. /**
  892. * @param {string} label
  893. * @param {Array} keyValuePairs
  894. * @param {*} defaultValue
  895. * @return {HTMLElement}
  896. */
  897. createSettingsDropDownFormGroup (label, keyValuePairs, defaultValue = null)
  898. {
  899. let dropdownID = this._selectorGenerator.getSettingsInputSelector(label)
  900.  
  901. return this.createFormGroup([
  902. this.createFormGroupLabel(label, dropdownID, 'text'),
  903. this.createFormGroupDropdown(dropdownID, keyValuePairs, defaultValue),
  904. ])
  905. }
  906.  
  907. /**
  908. * @return {HTMLButtonElement}
  909. */
  910. createSettingsHideButton ()
  911. {
  912. let section = this._section
  913. return this.createFormButton('<< Hide', () => section.style.display = 'none')
  914. }
  915.  
  916. /**
  917. * @param {string} caption
  918. * @param {HTMLElement} settingsSection
  919. * @param {boolean} fixed
  920. * @param {EventListenerOrEventListenerObject|Function|null} onMouseLeave
  921. * @return {HTMLButtonElement}
  922. */
  923. createSettingsShowButton (caption, settingsSection, fixed = true, onMouseLeave = null)
  924. {
  925. let controlButton = document.createElement('button')
  926. controlButton.textContent = caption
  927. controlButton.classList.add('show-settings')
  928.  
  929. if (fixed) {
  930. controlButton.classList.add('fixed')
  931. }
  932. controlButton.addEventListener('click', () => {
  933. let settingsUI = document.getElementById(settingsSection.id)
  934. settingsUI.style.display = settingsUI.style.display === 'none' ? 'block' : 'none'
  935. })
  936. settingsSection.addEventListener('mouseleave', onMouseLeave ? () => onMouseLeave() : () => settingsSection.style.display = 'none')
  937.  
  938. return controlButton
  939. }
  940.  
  941. /**
  942. * @param {string} statisticsType
  943. * @param {string} label
  944. * @return {HTMLElement}
  945. */
  946. createStatisticsFormGroup (statisticsType, label = '')
  947. {
  948. if (label === '') {
  949. label = statisticsType
  950. }
  951. return this.createFormGroup([
  952. this.createFormGroupLabel(label + ' Filter'),
  953. this.createFormGroupStatLabel(statisticsType),
  954. ])
  955. }
  956.  
  957. /**
  958. * @return {HTMLElement}
  959. */
  960. createStatisticsTotalsGroup ()
  961. {
  962. return this.createFormGroup([
  963. this.createFormGroupLabel('Total'),
  964. this.createFormGroupStatLabel('Total'),
  965. ])
  966. }
  967.  
  968. /**
  969. * @return {HTMLElement|HTMLDivElement}
  970. */
  971. createStatusSection ()
  972. {
  973. this._statusLine = this.createFormGroupLabel('')
  974. this._statusLine.id = this._selectorGenerator.getSelector('status')
  975.  
  976. return this.createFormSection('', [this._statusLine])
  977. }
  978.  
  979. /**
  980. * @param {LocalStore} localStore
  981. * @return {HTMLElement}
  982. */
  983. createStoreFormSection (localStore)
  984. {
  985. return this.createFormSection('Cached Configuration', [
  986. this.createFormActions([
  987. this.createFormSectionButton(
  988. 'Update', 'Save UI settings in store', () => localStore.save(), 'Saves applied settings.'),
  989. this.createFormSectionButton(
  990. 'Purge', 'Purge store', () => localStore.delete(), 'Removes saved settings. Settings will then be sourced from the defaults defined in the script.'),
  991. ]),
  992. ])
  993. }
  994.  
  995. /**
  996. * @param {string} tabName
  997. * @return {HTMLButtonElement}
  998. */
  999. createTabButton (tabName)
  1000. {
  1001. let button = document.createElement('button')
  1002. button.classList.add('tab-button')
  1003. button.textContent = tabName
  1004. button.addEventListener('click', (event) => {
  1005.  
  1006. let button = event.currentTarget
  1007. let tabsSection = button.closest('.tabs-section')
  1008. let tabToOpen = tabsSection.querySelector('#' + toKebabCase(tabName))
  1009.  
  1010. for (let tabButton of tabsSection.querySelectorAll('.tab-button')) {
  1011. tabButton.classList.remove('active')
  1012. }
  1013. for (let tabPanel of tabsSection.querySelectorAll('.tab-panel')) {
  1014. tabPanel.classList.remove('active')
  1015. }
  1016.  
  1017. button.classList.add('active')
  1018. tabToOpen.classList.add('active')
  1019. })
  1020. return button
  1021. }
  1022.  
  1023. /**
  1024. * @param {string} tabName
  1025. * @param {HTMLElement[]} children
  1026. * @return {HTMLElement|HTMLDivElement}
  1027. */
  1028. createTabPanel (tabName, children)
  1029. {
  1030. let panel = document.createElement('div')
  1031. panel.id = toKebabCase(tabName)
  1032. panel.classList.add('tab-panel')
  1033.  
  1034. return UIGenerator.populateChildren(panel, children)
  1035. }
  1036.  
  1037. /**
  1038. * @param {string[]} tabNames
  1039. * @param {HTMLElement[]} tabPanels
  1040. * @return {HTMLElement|HTMLDivElement}
  1041. */
  1042. createTabsSection (tabNames, tabPanels)
  1043. {
  1044. let wrapper = document.createElement('div')
  1045. wrapper.classList.add('tabs-section')
  1046.  
  1047. let tabsDiv = document.createElement('div')
  1048. tabsDiv.classList.add('tabs-nav')
  1049.  
  1050. let tabButtons = []
  1051. for (let tabName of tabNames) {
  1052. tabButtons.push(this.createTabButton(tabName))
  1053. }
  1054.  
  1055. UIGenerator.populateChildren(tabsDiv, tabButtons)
  1056. UIGenerator.populateChildren(wrapper, [tabsDiv, ...tabPanels])
  1057. tabButtons[0].click()
  1058.  
  1059. return wrapper
  1060. }
  1061.  
  1062. /**
  1063. * @param {string} label
  1064. * @return {HTMLElement}
  1065. */
  1066. getSettingsInput (label)
  1067. {
  1068. return document.getElementById(this._selectorGenerator.getSettingsInputSelector(label))
  1069. }
  1070.  
  1071. /**
  1072. * @param {string} label
  1073. * @return {boolean}
  1074. */
  1075. getSettingsInputCheckedStatus (label)
  1076. {
  1077. return this.getSettingsInput(label).checked
  1078. }
  1079.  
  1080. /**
  1081. * @param {string} label
  1082. * @return {*}
  1083. */
  1084. getSettingsInputValue (label)
  1085. {
  1086. return this.getSettingsInput(label).value
  1087. }
  1088.  
  1089. /**
  1090. * @param {string} label
  1091. * @param {boolean} getMinInput
  1092. * @return {HTMLElement}
  1093. */
  1094. getSettingsRangeInput (label, getMinInput)
  1095. {
  1096. return document.getElementById(this._selectorGenerator.getSettingsRangeInputSelector(label, getMinInput))
  1097. }
  1098.  
  1099. /**
  1100. * @param {string} label
  1101. * @param {boolean} getMinInputValue
  1102. * @return {*}
  1103. */
  1104. getSettingsRangeInputValue (label, getMinInputValue)
  1105. {
  1106. return this.getSettingsRangeInput(label, getMinInputValue).value
  1107. }
  1108.  
  1109. resetStatus ()
  1110. {
  1111. this._statusLine.textContent = this._statusText
  1112. }
  1113.  
  1114. /**
  1115. * @param {string} label
  1116. * @param {boolean} bool
  1117. */
  1118. setSettingsInputCheckedStatus (label, bool)
  1119. {
  1120. this.getSettingsInput(label).checked = bool
  1121. }
  1122.  
  1123. /**
  1124. * @param {string} label
  1125. * @param {*} value
  1126. */
  1127. setSettingsInputValue (label, value)
  1128. {
  1129. this.getSettingsInput(label).value = value
  1130. }
  1131.  
  1132. /**
  1133. * @param {string} label
  1134. * @param {number} lowerBound
  1135. * @param {number} upperBound
  1136. */
  1137. setSettingsRangeInputValue (label, lowerBound, upperBound)
  1138. {
  1139. this.getSettingsRangeInput(label, true).value = lowerBound
  1140. this.getSettingsRangeInput(label, false).value = upperBound
  1141. }
  1142.  
  1143. /**
  1144. * @param {string} status
  1145. * @param {boolean} transient
  1146. */
  1147. updateStatus (status, transient = false)
  1148. {
  1149. if (!transient) {
  1150. this._statusText = status
  1151. }
  1152. this._statusLine.textContent = status
  1153. }
  1154. }
  1155.  
  1156. class Validator
  1157. {
  1158. static iFramesRemover ()
  1159. {
  1160. GM_addStyle(' iframe { display: none !important; } ')
  1161. }
  1162.  
  1163. /**
  1164. * @param {StatisticsRecorder} statisticsRecorder
  1165. */
  1166. constructor (statisticsRecorder)
  1167. {
  1168. /**
  1169. * @type {Array}
  1170. * @private
  1171. */
  1172. this._filters = []
  1173.  
  1174. /**
  1175. * @type {RegExp|null}
  1176. * @private
  1177. */
  1178. this._optimizedBlacklist = null
  1179.  
  1180. /**
  1181. * @type {Object}
  1182. * @private
  1183. */
  1184. this._optimizedSanitizationRules = {}
  1185.  
  1186. /**
  1187. * @type {StatisticsRecorder}
  1188. * @private
  1189. */
  1190. this._statisticsRecorder = statisticsRecorder
  1191. }
  1192.  
  1193. _buildWholeWordMatchingRegex (words)
  1194. {
  1195. let patternedWords = []
  1196. for (let i = 0; i < words.length; i++) {
  1197. patternedWords.push('\\b' + words[i] + '\\b')
  1198. }
  1199. return new RegExp('(' + patternedWords.join('|') + ')', 'gi')
  1200. }
  1201.  
  1202. /**
  1203. * @param {string} text
  1204. * @return {string}
  1205. */
  1206. sanitize (text)
  1207. {
  1208. for (const substitute in this._optimizedSanitizationRules) {
  1209. text = text.replace(this._optimizedSanitizationRules[substitute], substitute)
  1210. }
  1211. return text.trim()
  1212. }
  1213.  
  1214. /**
  1215. * @param {HTMLElement} textNode
  1216. * @return {Validator}
  1217. */
  1218. sanitizeTextNode (textNode)
  1219. {
  1220. textNode.textContent = this.sanitize(textNode.textContent)
  1221. return this
  1222. }
  1223.  
  1224. /**
  1225. * @param {string} selector
  1226. * @return {Validator}
  1227. */
  1228. sanitizeNodeOfSelector (selector)
  1229. {
  1230. let node = document.querySelector(selector)
  1231. if (node) {
  1232. let sanitizedText = this.sanitize(node.textContent)
  1233. node.textContent = sanitizedText
  1234. document.title = sanitizedText
  1235. }
  1236. return this
  1237. }
  1238.  
  1239. /**
  1240. * @param {string[]} blacklistedWords
  1241. * @return {Validator}
  1242. */
  1243. setBlacklist (blacklistedWords)
  1244. {
  1245. this._optimizedBlacklist = blacklistedWords.length ? this._buildWholeWordMatchingRegex(blacklistedWords) : null
  1246. return this
  1247. }
  1248.  
  1249. /**
  1250. * @param {Object} sanitizationRules
  1251. * @return {Validator}
  1252. */
  1253. setSanitizationRules (sanitizationRules)
  1254. {
  1255. for (const substitute in sanitizationRules) {
  1256. this._optimizedSanitizationRules[substitute] = this._buildWholeWordMatchingRegex(sanitizationRules[substitute])
  1257. }
  1258. return this
  1259. }
  1260.  
  1261. /**
  1262. * @param {string} text
  1263. * @return {boolean}
  1264. */
  1265. validateBlackList (text)
  1266. {
  1267. let validationCheck = true
  1268.  
  1269. if (this._optimizedBlacklist) {
  1270. validationCheck = text.match(this._optimizedBlacklist) === null
  1271. this._statisticsRecorder.record('Blacklist', validationCheck)
  1272. }
  1273. return validationCheck
  1274. }
  1275.  
  1276. /**
  1277. * @param {string} name
  1278. * @param {Node|HTMLElement} item
  1279. * @param {string} selector
  1280. * @return {boolean}
  1281. */
  1282. validateNodeExistence (name, item, selector)
  1283. {
  1284. let validationCheck = item.querySelector(selector) !== null
  1285. this._statisticsRecorder.record(name, validationCheck)
  1286.  
  1287. return validationCheck
  1288. }
  1289.  
  1290. /**
  1291. * @param {string} name
  1292. * @param {Node|HTMLElement} item
  1293. * @param {string} selector
  1294. * @return {boolean}
  1295. */
  1296. validateNodeNonExistence (name, item, selector)
  1297. {
  1298. let validationCheck = item.querySelector(selector) === null
  1299. this._statisticsRecorder.record(name, validationCheck)
  1300.  
  1301. return validationCheck
  1302. }
  1303.  
  1304. /**
  1305. * @param {string} name
  1306. * @param {number} value
  1307. * @param {number[]} bounds
  1308. * @return {boolean}
  1309. */
  1310. validateRange (name, value, bounds)
  1311. {
  1312. let validationCheck = true
  1313.  
  1314. if (bounds[0] > 0 && bounds[1] > 0) {
  1315. validationCheck = value >= bounds[0] && value <= bounds[1]
  1316. } else {
  1317. if (bounds[0] > 0) {
  1318. validationCheck = value >= bounds[0]
  1319. }
  1320. if (bounds[1] > 0) {
  1321. validationCheck = value <= bounds[1]
  1322. }
  1323. }
  1324. this._statisticsRecorder.record(name, validationCheck)
  1325.  
  1326. return validationCheck
  1327. }
  1328.  
  1329. /**
  1330. * @param {string} name
  1331. * @param {number} lowerBound
  1332. * @param {number} upperBound
  1333. * @param getValueCallback
  1334. * @return {boolean}
  1335. */
  1336. validateRangeFilter (name, lowerBound, upperBound, getValueCallback)
  1337. {
  1338. if (lowerBound > 0 || upperBound > 0) {
  1339. return this.validateRange(name, getValueCallback(), [lowerBound, upperBound])
  1340. }
  1341. return true
  1342. }
  1343. }
  1344.  
  1345. class PresetSwitcher
  1346. {
  1347. /**
  1348. * @param {string} scriptPrefix
  1349. * @param {Object} defaultPreset
  1350. * @param {Object} globalConfiguration
  1351. */
  1352. static create (scriptPrefix, defaultPreset, globalConfiguration)
  1353. {
  1354. return new PresetSwitcher(scriptPrefix, defaultPreset, globalConfiguration)
  1355. }
  1356.  
  1357. /**
  1358. * @param {string} scriptPrefix
  1359. * @param {Object} defaultPreset
  1360. * @param {Object} globalConfiguration
  1361. */
  1362. constructor (scriptPrefix, defaultPreset, globalConfiguration)
  1363. {
  1364. /**
  1365. * @type {Object}
  1366. * @private
  1367. */
  1368. this._appliedPreset = null
  1369.  
  1370. /**
  1371. * @type {Object}
  1372. * @private
  1373. */
  1374. this._defaultPreset = defaultPreset
  1375.  
  1376. /**
  1377. * {LocalStore}
  1378. */
  1379. this._globalConfigurationStore = LocalStore.createGlobalConfigStore(scriptPrefix, globalConfiguration)
  1380.  
  1381. /**
  1382. * {Object}
  1383. */
  1384. this._globalConfiguration = this._globalConfigurationStore.retrieve().get()
  1385.  
  1386. /**
  1387. * @type {LocalStore}
  1388. * @private
  1389. */
  1390. this._presetsStore = LocalStore.createPresetConfigStore(scriptPrefix, defaultPreset)
  1391.  
  1392. /**
  1393. * @type {{name: string, config: Object}[]}
  1394. * @private
  1395. */
  1396. this._presets = this._presetsStore.retrieve().get()
  1397.  
  1398. /**
  1399. * @type {string}
  1400. * @private
  1401. */
  1402. this._scriptPrefix = scriptPrefix
  1403. }
  1404.  
  1405. /**
  1406. * @param {string} name
  1407. * @param {Object} config
  1408. * @return {this}
  1409. */
  1410. createPreset (name, config)
  1411. {
  1412. this._presets.push({
  1413. name: name,
  1414. config: config,
  1415. })
  1416. this._presetsStore.update(this._presets)
  1417. return this
  1418. }
  1419.  
  1420. /**
  1421. * @param {string} name
  1422. * @return {this}
  1423. */
  1424. deletePreset (name)
  1425. {
  1426. for (let i = 0; i < this._presets.length; i++) {
  1427. if (this._presets[i].name === name) {
  1428. this._presets.splice(i, 1)
  1429. this._presetsStore.update(this._presets)
  1430. break
  1431. }
  1432. }
  1433. return this
  1434. }
  1435.  
  1436. /**
  1437. * @param name
  1438. * @return {{name: string, config: Object}|null}
  1439. */
  1440. findPreset (name)
  1441. {
  1442. for (let preset of this._presets) {
  1443. if (preset.name === name) {
  1444. return preset
  1445. }
  1446. }
  1447. return null
  1448. }
  1449.  
  1450. /**
  1451. * @return {{name: string, config: Object}}
  1452. */
  1453. getAppliedPreset ()
  1454. {
  1455. return this._appliedPreset
  1456. }
  1457. }
  1458.  
  1459. class BaseHandler
  1460. {
  1461. static initialize ()
  1462. {
  1463. BaseHandler.throwOverrideError()
  1464. //return (new XNXXSearchFilters).init()
  1465. }
  1466.  
  1467. static throwOverrideError ()
  1468. {
  1469. throw new Error('override this method')
  1470. }
  1471.  
  1472. /**
  1473. * @param {string} scriptPrefix
  1474. * @param {string} itemClass
  1475. * @param {Object} settingsDefaults
  1476. */
  1477. constructor (scriptPrefix, itemClass, settingsDefaults)
  1478. {
  1479. settingsDefaults.disableItemComplianceValidation = false
  1480. settingsDefaults.showUIAlways = false
  1481.  
  1482. /**
  1483. * Array of item compliance filters ordered in intended sequence of execution
  1484. * @type {Function[]}
  1485. * @protected
  1486. */
  1487. this._complianceFilters = []
  1488.  
  1489. /**
  1490. * @type {string}
  1491. * @protected
  1492. */
  1493. this._itemClass = itemClass
  1494.  
  1495. /**
  1496. * Operations to perform after script initialization
  1497. * @type {Function}
  1498. * @protected
  1499. */
  1500. this._onAfterInitialization = null
  1501.  
  1502. /**
  1503. * Operations to perform after UI generation
  1504. * @type {Function}
  1505. * @protected
  1506. */
  1507. this._onAfterUIBuild = null
  1508.  
  1509. /**
  1510. * Operations to perform before UI generation
  1511. * @type {Function}
  1512. * @protected
  1513. */
  1514. this._onBeforeUIBuild = null
  1515.  
  1516. /**
  1517. * Operations to perform after compliance checks, the first time a item is retrieved
  1518. * @type {Function}
  1519. * @protected
  1520. */
  1521. this._onFirstHitAfterCompliance = null
  1522.  
  1523. /**
  1524. * Operations to perform before compliance checks, the first time a item is retrieved
  1525. * @type {Function}
  1526. * @protected
  1527. */
  1528. this._onFirstHitBeforeCompliance = null
  1529.  
  1530. /**
  1531. * Get item lists from the page
  1532. * @type {Function}
  1533. * @protected
  1534. */
  1535. this._onGetItemLists = null
  1536.  
  1537. /**
  1538. * Logic to hide a non-compliant item
  1539. * @type {Function}
  1540. * @protected
  1541. */
  1542. this._onItemHide = (item) => {item.style.display = 'none'}
  1543.  
  1544. /**
  1545. * Logic to show compliant item
  1546. * @type {Function}
  1547. * @protected
  1548. */
  1549. this._onItemShow = (item) => {item.style.display = 'inline-block'}
  1550.  
  1551. /**
  1552. * Retrieve settings from UI and update settings object
  1553. * @type {Function}
  1554. * @private
  1555. */
  1556. this._onSettingsApply = null
  1557.  
  1558. /**
  1559. * Settings to update in the UI or elsewhere when settings store is updated
  1560. * @type {Function}
  1561. * @protected
  1562. */
  1563. this._onSettingsStoreUpdate = null
  1564.  
  1565. /**
  1566. * Must return the generated settings section node
  1567. * @type {Function}
  1568. * @protected
  1569. */
  1570. this._onUIBuild = null
  1571.  
  1572. /**
  1573. * Validate initiating initialization.
  1574. * Can be used to stop script init on specific pages or vice versa
  1575. * @type {Function}
  1576. * @protected
  1577. */
  1578. this._onValidateInit = () => true
  1579.  
  1580. /**
  1581. * @type {string}
  1582. * @private
  1583. */
  1584. this._scriptPrefix = scriptPrefix
  1585.  
  1586. /**
  1587. * Local storage store with defaults
  1588. * @type {LocalStore}
  1589. * @protected
  1590. */
  1591. this._settingsStore = new LocalStore(this._scriptPrefix + 'settings', settingsDefaults)
  1592.  
  1593. /**
  1594. * @type {Object}
  1595. * @protected
  1596. */
  1597. this._settings = this._settingsStore.retrieve().get()
  1598.  
  1599. /**
  1600. * @type {StatisticsRecorder}
  1601. * @protected
  1602. */
  1603. this._statistics = new StatisticsRecorder(this._scriptPrefix)
  1604.  
  1605. /**
  1606. * @type {UIGenerator}
  1607. * @protected
  1608. */
  1609. this._uiGen = new UIGenerator(this._settings.showUIAlways, this._scriptPrefix)
  1610.  
  1611. /**
  1612. * @type {Validator}
  1613. * @protected
  1614. */
  1615. this._validator = (new Validator(this._statistics))
  1616. }
  1617.  
  1618. /**
  1619. * @param {Function} eventHandler
  1620. * @param {*} parameters
  1621. * @return {null|NodeListOf<HTMLElement>|*}
  1622. * @private
  1623. */
  1624. _callEventHandler (eventHandler, ...parameters)
  1625. {
  1626. if (eventHandler) {
  1627. return eventHandler(...parameters)
  1628. }
  1629. return null
  1630. }
  1631.  
  1632. /**
  1633. * Filters items as per settings
  1634. * @param {HTMLElement|NodeList<HTMLElement>} itemsList
  1635. * @protected
  1636. */
  1637. _complyItemsList (itemsList)
  1638. {
  1639. for (let item of this._getItemsFromItemsList(itemsList)) {
  1640.  
  1641. if (typeof item.scriptProcessedOnce === 'undefined') {
  1642. item.scriptProcessedOnce = false
  1643. this._callEventHandler(this._onFirstHitBeforeCompliance, item)
  1644. }
  1645.  
  1646. this._validateItemCompliance(item)
  1647.  
  1648. if (!item.scriptProcessedOnce) {
  1649. this._callEventHandler(this._onFirstHitAfterCompliance, item)
  1650. item.scriptProcessedOnce = true
  1651. }
  1652.  
  1653. this._statistics.updateUI()
  1654. }
  1655. }
  1656.  
  1657. /**
  1658. * @protected
  1659. */
  1660. _createSettingsFormActions ()
  1661. {
  1662. return this._uiGen.createSettingsFormActions(this._settingsStore, () => {
  1663. this._callEventHandler(this._onSettingsApply)
  1664. this._statistics.reset()
  1665. for (let itemsList of this._callEventHandler(this._onGetItemLists)) {
  1666. this._complyItemsList(itemsList)
  1667. }
  1668. })
  1669. }
  1670.  
  1671. /**
  1672. * @param {HTMLElement|null} UISection
  1673. * @private
  1674. */
  1675. _embedUI (UISection)
  1676. {
  1677. if (UISection) {
  1678. this._uiGen.constructor.appendToBody(UISection)
  1679. this._uiGen.constructor.appendToBody(this._uiGen.createSettingsShowButton('', UISection, true, () => {
  1680. if (!this._settings.showUIAlways) {
  1681. UISection.style.display = 'none'
  1682. }
  1683. }))
  1684. this._callEventHandler(this._onSettingsStoreUpdate)
  1685. }
  1686. }
  1687.  
  1688. /**
  1689. * @param {HTMLElement|NodeList<HTMLElement>} itemsList
  1690. * @return {NodeListOf<HTMLElement>|HTMLElement[]}
  1691. * @protected
  1692. */
  1693. _getItemsFromItemsList (itemsList)
  1694. {
  1695. let items = []
  1696. if (itemsList instanceof NodeList) {
  1697. itemsList.forEach((node) => {
  1698. if (typeof node.classList !== 'undefined' && node.classList.contains(this._itemClass)) {
  1699. items.push(node)
  1700. }
  1701. })
  1702. } else {
  1703. items = itemsList.querySelectorAll('.' + this._itemClass)
  1704. }
  1705. return items
  1706. }
  1707.  
  1708. /**
  1709. * @param {Object} sanitizationRules
  1710. * @return {string}
  1711. * @protected
  1712. */
  1713. _transformSanitizationRulesToText (sanitizationRules)
  1714. {
  1715. let sanitizationRulesText = []
  1716. for (let substitute in sanitizationRules) {
  1717. sanitizationRulesText.push(substitute + '=' + sanitizationRules[substitute].join(','))
  1718. }
  1719. return sanitizationRulesText.join('\n')
  1720. }
  1721.  
  1722. /**
  1723. * @param {string[]} strings
  1724. * @protected
  1725. */
  1726. _trimAndKeepNonEmptyStrings (strings)
  1727. {
  1728. let nonEmptyStrings = []
  1729. for (let string of strings) {
  1730. string = string.trim()
  1731. if (string !== '') {
  1732. nonEmptyStrings.push(string)
  1733. }
  1734. }
  1735. return nonEmptyStrings
  1736. }
  1737.  
  1738. /**
  1739. * @param {string[]} blacklistedWords
  1740. * @protected
  1741. */
  1742. _validateAndSetBlacklistedWords (blacklistedWords)
  1743. {
  1744. this._settings.blacklist = this._trimAndKeepNonEmptyStrings(blacklistedWords)
  1745. this._validator.setBlacklist(this._settings.blacklist)
  1746. }
  1747.  
  1748. /**
  1749. * @param {string[]} sanitizationRules
  1750. * @protected
  1751. */
  1752. _validateAndSetSanitizationRules (sanitizationRules)
  1753. {
  1754. let fragments, validatedTargetWords
  1755. this._settings.sanitize = {}
  1756.  
  1757. for (let sanitizationRule of sanitizationRules) {
  1758. if (sanitizationRule.includes('=')) {
  1759.  
  1760. fragments = sanitizationRule.split('=')
  1761. if (fragments[0] === '') {
  1762. fragments[0] = ' '
  1763. }
  1764.  
  1765. validatedTargetWords = this._trimAndKeepNonEmptyStrings(fragments[1].split(','))
  1766. if (validatedTargetWords.length) {
  1767. this._settings.sanitize[fragments[0]] = validatedTargetWords
  1768. }
  1769. }
  1770. }
  1771. this._validator.setSanitizationRules(this._settings.sanitize)
  1772. }
  1773.  
  1774. /**
  1775. * @param {HTMLElement|Node} item
  1776. * @protected
  1777. */
  1778. _validateItemCompliance (item)
  1779. {
  1780. let itemComplies = true
  1781.  
  1782. if (!this._settings.disableItemComplianceValidation) {
  1783. for (let complianceFilter of this._complianceFilters) {
  1784. if (!complianceFilter(item)) {
  1785. itemComplies = false
  1786. break
  1787. }
  1788. }
  1789. }
  1790. itemComplies ? this._callEventHandler(this._onItemShow, item) : this._callEventHandler(this._onItemHide, item)
  1791. }
  1792.  
  1793. /**
  1794. * Initialize the script and do basic UI removals
  1795. */
  1796. init ()
  1797. {
  1798. try {
  1799. if (this._callEventHandler(this._onValidateInit)) {
  1800.  
  1801. this._callEventHandler(this._onBeforeUIBuild)
  1802. this._embedUI(this._callEventHandler(this._onUIBuild))
  1803. this._callEventHandler(this._onAfterUIBuild)
  1804.  
  1805. for (let itemsList of this._callEventHandler(this._onGetItemLists)) {
  1806. ChildObserver.create().onNodesAdded((itemsAdded) => this._complyItemsList(itemsAdded)).observe(itemsList)
  1807. this._complyItemsList(itemsList)
  1808. }
  1809.  
  1810.  
  1811.  
  1812. this._callEventHandler(this._onAfterInitialization)
  1813.  
  1814. this._settingsStore.onChange(() => this._callEventHandler(this._onSettingsStoreUpdate))
  1815. }
  1816. } catch (error) {
  1817. console.error(this._scriptPrefix + 'script encountered an error: ' + error)
  1818. }
  1819. }
  1820. }