data-manager

handles loaded html, takes care of data, applying filters

当前为 2024-07-21 提交的版本,查看 最新版本

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

QingJ © 2025

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