data-manager

process html, store and filter data

  1. // ==UserScript==
  2. // @name data-manager
  3. // @namespace Violentmonkey Scripts
  4. // @version 1.5
  5. // @license MIT
  6. // @description process html, store and filter data
  7. // @author smartacephale
  8. // @match *://*/*
  9. // @grant unsafeWindow
  10. // @grant GM_addStyle
  11. // ==/UserScript==
  12.  
  13. class DataFilter {
  14. constructor(rules, state) {
  15. this.state = state;
  16. this.rules = rules;
  17.  
  18. const methods = Object.getOwnPropertyNames(this);
  19. this.filters = methods.reduce((acc, k) => {
  20. if (k in this.state) {
  21. acc[k] = this[k];
  22. GM_addStyle(`.filter-${k.toLowerCase().slice(6)} { display: none !important; }`);
  23. }
  24. return acc;
  25. }, {});
  26. }
  27.  
  28. filterPublic = () => {
  29. return (v) => {
  30. const isPublic = !this.rules.IS_PRIVATE(v.element);
  31. return {
  32. tag: 'filter-public',
  33. condition: this.state.filterPublic && isPublic
  34. };
  35. }
  36. }
  37.  
  38. filterPrivate = () => {
  39. return (v) => {
  40. const isPrivate = this.rules.IS_PRIVATE(v.element);
  41. return {
  42. tag: 'filter-private',
  43. condition: this.state.filterPrivate && isPrivate
  44. };
  45. }
  46. }
  47.  
  48. filterDuration = () => {
  49. return (v) => {
  50. const notInRange = v.duration < this.state.filterDurationFrom || v.duration > this.state.filterDurationTo;
  51. return {
  52. tag: 'filter-duration',
  53. condition: this.state.filterDuration && notInRange
  54. };
  55. }
  56. }
  57.  
  58. filterExclude = () => {
  59. const tags = DataManager.filterDSLToRegex(this.state.filterExcludeWords);
  60. return (v) => {
  61. const containTags = tags.some(tag => tag.test(v.title));
  62. return {
  63. tag: 'filter-exclude',
  64. condition: this.state.filterExclude && containTags
  65. };
  66. }
  67. }
  68.  
  69. filterInclude = () => {
  70. const tags = DataManager.filterDSLToRegex(this.state.filterIncludeWords);
  71. return (v) => {
  72. const containTagsNot = tags.some(tag => !tag.test(v.title));
  73. return {
  74. tag: 'filter-include',
  75. condition: this.state.filterInclude && containTagsNot
  76. };
  77. }
  78. }
  79. }
  80.  
  81. class DataManager {
  82. constructor(rules, state) {
  83. this.rules = rules;
  84. this.state = state;
  85. this.data = new Map();
  86. this.lazyImgLoader = new unsafeWindow.bhutils.LazyImgLoader((target) => !this.isFiltered(target));
  87. this.dataFilters = new DataFilter(rules, state).filters;
  88. }
  89.  
  90. static filterDSLToRegex(str) {
  91. const toFullWord = w => `(^|\ )${w}($|\ )`;
  92. const str_ = str.replace(/f\:(\w+)/g, (_, w) => toFullWord(w));
  93. return unsafeWindow.bhutils.stringToWords(str_).map(expr => new RegExp(expr, 'i'));
  94. }
  95.  
  96. isFiltered(el) {
  97. return el.className.includes('filtered');
  98. }
  99.  
  100. applyFilters = (filters, offset = 0) => {
  101. const filtersToApply = Object.keys(filters)
  102. .filter(k => Object.hasOwn(this.dataFilters, k))
  103. .map(k => this.dataFilters[k]());
  104.  
  105. if (filtersToApply.length === 0) return;
  106.  
  107. let updates = [];
  108. let offset_counter = 1;
  109. for (const v of this.data.values()) {
  110. if (++offset_counter > offset) {
  111. for (const f of filtersToApply) {
  112. const {tag, condition} = f(v);
  113. updates.push(() => v.element.classList.toggle(tag, condition));
  114. }
  115. }
  116. }
  117.  
  118. requestAnimationFrame(() => {
  119. updates.forEach(update => update());
  120. });
  121. }
  122.  
  123. filterAll = (offset) => {
  124. const filters = Object.assign({}, ...Object.keys(this.dataFilters).map(f => ({ [f]: this.state[f] })));
  125. this.applyFilters(filters, offset);
  126. }
  127.  
  128. handleLoadedHTML = (html, container, removeDuplicates = false, shouldLazify = true) => {
  129. const thumbs = this.rules.GET_THUMBS(html);
  130. const data_offset = this.data.size;
  131.  
  132. for (const thumbElement of thumbs) {
  133. const url = this.rules.THUMB_URL(thumbElement);
  134. if (!url || this.data.has(url)) {
  135. if (removeDuplicates) thumbElement.remove();
  136. continue;
  137. }
  138.  
  139. const { title, duration } = this.rules.THUMB_DATA(thumbElement);
  140. this.data.set(url, { element: thumbElement, duration, title });
  141.  
  142. if (shouldLazify) {
  143. const { img, imgSrc } = this.rules.THUMB_IMG_DATA(thumbElement);
  144. this.lazyImgLoader.lazify(thumbElement, img, imgSrc);
  145. }
  146.  
  147. const parent = container || this.rules.CONTAINER;
  148. if (!parent.contains(thumbElement)) parent.appendChild(thumbElement);
  149. }
  150.  
  151. this.filterAll(data_offset);
  152. };
  153. }