grrrr

Base library for my scripts

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/411993/851645/grrrr.js

  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. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址