Base Brazen Resource

Base library for my scripts

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/375557/1244990/Base%20Brazen%20Resource.js

  1. // ==UserScript==
  2. // @name Base Brazen Resource
  3. // @namespace brazenvoid
  4. // @version 3.9.0
  5. // @author brazenvoid
  6. // @license GPL-3.0-only
  7. // @description Base library for my scripts
  8. // @run-at document-end
  9. // ==/UserScript==
  10.  
  11. const REGEX_LINE_BREAK = /\r?\n/g
  12. const REGEX_PRESERVE_NUMBERS = /\D/g
  13.  
  14. class ChildObserver
  15. {
  16. /**
  17. * @callback observerOnMutation
  18. * @param {NodeList} nodes
  19. * @param {Element} previousSibling
  20. * @param {Element} nextSibling
  21. * @param {Element} target
  22. */
  23.  
  24. /**
  25. * @return {ChildObserver}
  26. */
  27. static create ()
  28. {
  29. return new ChildObserver
  30. }
  31.  
  32. /**
  33. * ChildObserver constructor
  34. */
  35. constructor ()
  36. {
  37. this._node = null
  38. this._observer = null
  39. this._onNodesAdded = null
  40. this._onNodesRemoved = null
  41. }
  42.  
  43. /**
  44. * @return {ChildObserver}
  45. * @private
  46. */
  47. _observeNodes ()
  48. {
  49. this._observer.observe(this._node, {childList: true})
  50. return this
  51. }
  52.  
  53. /**
  54. * Attach an observer to the specified node(s)
  55. * @param {Node} node
  56. * @returns {ChildObserver}
  57. */
  58. observe (node)
  59. {
  60. this._node = node
  61. this._observer = new MutationObserver((mutations) => {
  62. for (let mutation of mutations) {
  63. if (mutation.addedNodes.length && this._onNodesAdded !== null) {
  64. this._onNodesAdded(
  65. mutation.addedNodes,
  66. mutation.previousSibling,
  67. mutation.nextSibling,
  68. mutation.target,
  69. )
  70. }
  71. if (mutation.removedNodes.length && this._onNodesRemoved !== null) {
  72. this._onNodesRemoved(
  73. mutation.removedNodes,
  74. mutation.previousSibling,
  75. mutation.nextSibling,
  76. mutation.target,
  77. )
  78. }
  79. }
  80. })
  81. return this._observeNodes()
  82. }
  83.  
  84. /**
  85. * @param {observerOnMutation} eventHandler
  86. * @returns {ChildObserver}
  87. */
  88. onNodesAdded (eventHandler)
  89. {
  90. this._onNodesAdded = eventHandler
  91. return this
  92. }
  93.  
  94. /**
  95. * @param {observerOnMutation} eventHandler
  96. * @returns {ChildObserver}
  97. */
  98. onNodesRemoved (eventHandler)
  99. {
  100. this._onNodesRemoved = eventHandler
  101. return this
  102. }
  103.  
  104. pauseObservation ()
  105. {
  106. this._observer.disconnect()
  107. }
  108.  
  109. resumeObservation ()
  110. {
  111. this._observeNodes()
  112. }
  113. }
  114.  
  115. class LocalStore
  116. {
  117. /**
  118. * @callback storeEventHandler
  119. * @param {Object} store
  120. */
  121.  
  122. /**
  123. * @param {string} key
  124. * @param {Object} defaults
  125. */
  126. constructor (key, defaults)
  127. {
  128. /**
  129. * @type {Object}
  130. * @private
  131. */
  132. this._defaults = defaults
  133.  
  134. /**
  135. * @type {boolean}
  136. * @private
  137. */
  138. this._defaultsSet = false
  139.  
  140. /**
  141. * @type {string}
  142. * @private
  143. */
  144. this._key = key
  145.  
  146. // Events
  147.  
  148. /**
  149. * @type {storeEventHandler}
  150. */
  151. this._onChange = null
  152. }
  153.  
  154. _handleOnChange ()
  155. {
  156. if (this._onChange !== null) {
  157. this._onChange(this.get())
  158. }
  159. }
  160.  
  161. /**
  162. * @return {LocalStore}
  163. */
  164. delete ()
  165. {
  166. window.localStorage.removeItem(this._key)
  167. return this
  168. }
  169.  
  170. /**
  171. * @return {*}
  172. */
  173. get ()
  174. {
  175. this._defaultsSet = false
  176. let storedStore = window.localStorage.getItem(this._key)
  177. return storedStore === null ? this.restoreDefaults() : Utilities.objectFromJSON(storedStore)
  178. }
  179.  
  180. /**
  181. * @param {storeEventHandler} handler
  182. * @return {LocalStore}
  183. */
  184. onChange (handler)
  185. {
  186. this._onChange = handler
  187. return this
  188. }
  189.  
  190. /**
  191. * @return {Object}
  192. */
  193. restoreDefaults ()
  194. {
  195. this._defaultsSet = true
  196. this.save(this._defaults)
  197. return this._defaults
  198. }
  199.  
  200. /**
  201. * @param {Object} data
  202. * @return {LocalStore}
  203. */
  204. save (data)
  205. {
  206. window.localStorage.setItem(this._key, Utilities.objectToJSON(data))
  207. this._handleOnChange()
  208. return this
  209. }
  210.  
  211. /**
  212. * @return {boolean}
  213. */
  214. wereDefaultsSet ()
  215. {
  216. return this._defaultsSet
  217. }
  218. }
  219.  
  220. class SelectorGenerator
  221. {
  222. /**
  223. * @param {string} selectorPrefix
  224. */
  225. constructor (selectorPrefix)
  226. {
  227. /**
  228. * @type {string}
  229. * @private
  230. */
  231. this._prefix = selectorPrefix
  232. }
  233.  
  234. /**
  235. * @param {string} selector
  236. * @return {string}
  237. */
  238. getSelector (selector)
  239. {
  240. return this._prefix + selector
  241. }
  242.  
  243. /**
  244. * @param {string} settingName
  245. * @return {string}
  246. */
  247. getSettingsInputSelector (settingName)
  248. {
  249. return this.getSelector(Utilities.toKebabCase(settingName) + '-setting')
  250. }
  251.  
  252. /**
  253. * @param {string} settingName
  254. * @param {boolean} getMinInputSelector
  255. * @return {string}
  256. */
  257. getSettingsRangeInputSelector (settingName, getMinInputSelector)
  258. {
  259. return this.getSelector(Utilities.toKebabCase(settingName) + (getMinInputSelector ? '-min' : '-max') + '-setting')
  260. }
  261.  
  262. /**
  263. * @param {string} statisticType
  264. * @return {string}
  265. */
  266. getStatLabelSelector (statisticType)
  267. {
  268. return this.getSelector(Utilities.toKebabCase(statisticType) + '-stat')
  269. }
  270. }
  271.  
  272. class StatisticsRecorder
  273. {
  274. /**
  275. * @param {string} selectorPrefix
  276. */
  277. constructor (selectorPrefix)
  278. {
  279. /**
  280. * @type {SelectorGenerator}
  281. * @private
  282. */
  283. this._selectorGenerator = new SelectorGenerator(selectorPrefix)
  284.  
  285. /**
  286. * @type {{Total: number}}
  287. * @private
  288. */
  289. this._statistics = {Total: 0}
  290. }
  291. getTotal()
  292. {
  293. return this._statistics.Total
  294. }
  295.  
  296. /**
  297. * @param {string} statisticType
  298. * @param {boolean} validationResult
  299. * @param {number} value
  300. */
  301. record (statisticType, validationResult, value = 1)
  302. {
  303. if (!validationResult) {
  304. if (typeof this._statistics[statisticType] !== 'undefined') {
  305. this._statistics[statisticType] += value
  306. } else {
  307. this._statistics[statisticType] = value
  308. }
  309. this._statistics.Total += value
  310. }
  311. }
  312.  
  313. reset ()
  314. {
  315. for (const statisticType in this._statistics) {
  316. this._statistics[statisticType] = 0
  317. }
  318. }
  319.  
  320. updateUI ()
  321. {
  322. let label, labelSelector
  323.  
  324. for (const statisticType in this._statistics) {
  325. labelSelector = this._selectorGenerator.getStatLabelSelector(statisticType)
  326. label = document.getElementById(labelSelector)
  327. if (label !== null) {
  328. label.textContent = this._statistics[statisticType]
  329. }
  330. }
  331. }
  332. }
  333.  
  334. class Utilities
  335. {
  336. /**
  337. * @param {string[]} words
  338. * @return {RegExp|null}
  339. */
  340. static buildWholeWordMatchingRegex (words)
  341. {
  342. if (words.length) {
  343. let patternedWords = []
  344. for (const element of words) {
  345. patternedWords.push('\\b' + element + '\\b')
  346. }
  347. return new RegExp('(' + patternedWords.join('|') + ')', 'gi')
  348. }
  349. return null
  350. }
  351.  
  352. static callEventHandler (handler, parameters = [], defaultValue = null)
  353. {
  354. return handler ? handler(...parameters) : defaultValue
  355. }
  356.  
  357. static callEventHandlerOrFail (name, handler, parameters = [])
  358. {
  359. if (handler) {
  360. return handler(...parameters)
  361. }
  362. throw new Error('Callback "' + name + '" must be defined.')
  363. }
  364.  
  365. /**
  366. * @return {number|string}
  367. */
  368. static generateId (prefix = null)
  369. {
  370. let id = Math.trunc(Math.random() * 1000000000)
  371. return prefix ? prefix + id.toString() : id
  372. }
  373.  
  374. /**
  375. * @param {string} json
  376. * @return {Object}
  377. */
  378. static objectFromJSON (json)
  379. {
  380. /** @type {{arrays: Object, objects: Object, properties: Object}} */
  381. let parsedJSON = JSON.parse(json)
  382. let arrayObject = {}
  383. let result = {}
  384.  
  385. for (let property in parsedJSON.arrays) {
  386. arrayObject = JSON.parse(parsedJSON.arrays[property])
  387. result[property] = []
  388.  
  389. for (let key in arrayObject) {
  390. result[property].push(arrayObject[key])
  391. }
  392. }
  393. for (let property in parsedJSON.objects) {
  394. result[property] = Utilities.objectFromJSON(parsedJSON.objects[property])
  395. }
  396. for (let property in parsedJSON.properties) {
  397. result[property] = parsedJSON.properties[property]
  398. }
  399. return result
  400. }
  401.  
  402. /**
  403. * @param {Object} object
  404. * @return {string}
  405. */
  406. static objectToJSON (object)
  407. {
  408. let arrayToObject
  409. let json = {arrays: {}, objects: {}, properties: {}}
  410. for (let property in object) {
  411. if (typeof object[property] === 'object') {
  412. if (Array.isArray(object[property])) {
  413. arrayToObject = {}
  414. for (let key in object[property]) {
  415. arrayToObject[key] = object[property][key]
  416. }
  417. json.arrays[property] = JSON.stringify(arrayToObject)
  418. } else {
  419. json.objects[property] = Utilities.objectToJSON(object[property])
  420. }
  421. } else {
  422. json.properties[property] = object[property]
  423. }
  424. }
  425. return JSON.stringify(json)
  426. }
  427. static processEventHandlerQueue (handlers, parameters = [], defaultValue = null)
  428. {
  429. if (handlers.length) {
  430. for (let handler of handlers) {
  431. handler(...parameters)
  432. }
  433. }
  434. return defaultValue
  435. }
  436.  
  437. /**
  438. * @param milliseconds
  439. * @return {Promise<*>}
  440. */
  441. static sleep (milliseconds)
  442. {
  443. return new Promise(resolve => setTimeout(resolve, milliseconds))
  444. }
  445.  
  446. /**
  447. * @param {string} text
  448. * @return {string}
  449. */
  450. static toKebabCase (text)
  451. {
  452. return text.toLowerCase().replaceAll(' ', '-')
  453. }
  454.  
  455. /**
  456. * @param {string[]} strings
  457. */
  458. static trimAndKeepNonEmptyStrings (strings)
  459. {
  460. let nonEmptyStrings = [], trimmedString
  461. for (let string of strings) {
  462. trimmedString = string.trim()
  463. if (trimmedString !== '') {
  464. nonEmptyStrings.push(trimmedString)
  465. }
  466. }
  467. return nonEmptyStrings
  468. }
  469. }
  470.  
  471. class Validator
  472. {
  473. /**
  474. * @param {JQuery} item
  475. * @param {string} selector
  476. * @return {boolean}
  477. */
  478. static doesChildExist (item, selector)
  479. {
  480. return item.find(selector).length > 0
  481. }
  482.  
  483. static iFramesRemover ()
  484. {
  485. GM_addStyle('iframe { display: none !important; }')
  486. }
  487.  
  488. /**
  489. * @param {JQuery} item
  490. * @param {JQuery.Selector} selector
  491. * @return {boolean}
  492. */
  493. static isChildMissing (item, selector)
  494. {
  495. return item.find(selector).length === 0
  496. }
  497.  
  498. /**
  499. * @param {number} value
  500. * @param {number} lowerBound
  501. * @param {number} upperBound
  502. * @return {boolean}
  503. */
  504. static isInRange (value, lowerBound, upperBound)
  505. {
  506. let validationCheck = true
  507.  
  508. if (lowerBound > 0 && upperBound > 0) {
  509. validationCheck = value >= lowerBound && value <= upperBound
  510. } else {
  511. if (lowerBound > 0) {
  512. validationCheck = value >= lowerBound
  513. }
  514. if (upperBound > 0) {
  515. validationCheck = value <= upperBound
  516. }
  517. }
  518. return validationCheck
  519. }
  520.  
  521. /**
  522. * @param {string} text
  523. * @param {Object} rules
  524. * @return {string}
  525. */
  526. static sanitize (text, rules)
  527. {
  528. if (rules) {
  529. for (const substitute in rules) {
  530. text = text.replace(rules[substitute], substitute)
  531. }
  532. }
  533. return text.trim()
  534. }
  535.  
  536. /**
  537. * @param {JQuery} textNode
  538. * @param {Object} rules
  539. * @return {Validator}
  540. */
  541. static sanitizeTextNode (textNode, rules)
  542. {
  543. textNode.text(Validator.sanitize(textNode.text(), rules))
  544. return this
  545. }
  546.  
  547. /**
  548. * @param {string} selector
  549. * @param {Object} rules
  550. * @return {Validator}
  551. */
  552. static sanitizeNodeOfSelector (selector, rules)
  553. {
  554. let node = $(selector)
  555. if (node.length) {
  556. let sanitizedText = Validator.sanitize(node.text(), rules)
  557. node.text(sanitizedText)
  558. document.title = sanitizedText
  559. }
  560. return this
  561. }
  562.  
  563. /**
  564. * @param {string} text
  565. * @param {Object} rules
  566. * @return {boolean}
  567. */
  568. static regexMatches (text, rules)
  569. {
  570. return rules ? text.match(rules) !== null : true
  571. }
  572.  
  573. /**
  574. * @param {string} text
  575. * @param {Object} rules
  576. * @return {boolean}
  577. */
  578. static validateTextDoesNotContain (text, rules)
  579. {
  580. return rules ? text.match(rules) === null : true
  581. }
  582. }