pervert-monkey 1.0.13 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/core/pervertmonkey.core.es.d.ts +90 -36
  2. package/dist/core/pervertmonkey.core.es.js +258 -129
  3. package/dist/core/pervertmonkey.core.es.js.map +1 -1
  4. package/dist/core/pervertmonkey.core.umd.js +258 -129
  5. package/dist/core/pervertmonkey.core.umd.js.map +1 -1
  6. package/dist/userscripts/3hentai.user.js +4 -5
  7. package/dist/userscripts/camgirlfinder.user.js +2 -2
  8. package/dist/userscripts/camwhores.user.js +7 -16
  9. package/dist/userscripts/e-hentai.user.js +8 -8
  10. package/dist/userscripts/ebalka.user.js +18 -10
  11. package/dist/userscripts/eporner.user.js +24 -41
  12. package/dist/userscripts/erome.user.js +13 -16
  13. package/dist/userscripts/eroprofile.user.js +5 -14
  14. package/dist/userscripts/javhdporn.user.js +6 -5
  15. package/dist/userscripts/missav.user.js +10 -4
  16. package/dist/userscripts/motherless.user.js +13 -6
  17. package/dist/userscripts/namethatporn.user.js +10 -16
  18. package/dist/userscripts/nhentai.user.js +5 -13
  19. package/dist/userscripts/obmenvsem.user.js +11 -4
  20. package/dist/userscripts/pornhub.user.js +14 -6
  21. package/dist/userscripts/spankbang.user.js +28 -7
  22. package/dist/userscripts/thisvid.user.js +15 -33
  23. package/dist/userscripts/xhamster.user.js +13 -18
  24. package/dist/userscripts/xvideos.user.js +33 -5
  25. package/package.json +1 -1
  26. package/src/core/data-handler/data-filter-fn-defaults.ts +52 -0
  27. package/src/core/data-handler/data-filter-fn.ts +62 -0
  28. package/src/core/data-handler/data-filter.ts +31 -103
  29. package/src/core/data-handler/data-manager.ts +91 -28
  30. package/src/core/jabroni-config/default-scheme.ts +54 -5
  31. package/src/core/jabroni-config/index.ts +1 -0
  32. package/src/core/jabroni-config/jabroni-gui-controller.ts +1 -1
  33. package/src/core/jabroni-config/scheme-selectors-mapping.ts +12 -0
  34. package/src/core/parsers/thumb-data-parser.ts +15 -19
  35. package/src/core/rules/index.ts +15 -9
  36. package/src/userscripts/index.ts +1 -1
  37. package/src/userscripts/scripts/3hentai.ts +3 -4
  38. package/src/userscripts/scripts/camgirlfinder.ts +1 -1
  39. package/src/userscripts/scripts/camwhores.ts +5 -14
  40. package/src/userscripts/scripts/e-hentai.ts +12 -12
  41. package/src/userscripts/scripts/ebalka.ts +16 -8
  42. package/src/userscripts/scripts/eporner.ts +23 -39
  43. package/src/userscripts/scripts/erome.ts +13 -17
  44. package/src/userscripts/scripts/eroprofile.ts +4 -12
  45. package/src/userscripts/scripts/javhdporn.ts +7 -8
  46. package/src/userscripts/scripts/missav.ts +10 -4
  47. package/src/userscripts/scripts/motherless.ts +13 -7
  48. package/src/userscripts/scripts/namethatporn.ts +10 -17
  49. package/src/userscripts/scripts/nhentai.ts +6 -13
  50. package/src/userscripts/scripts/obmenvsem.ts +14 -4
  51. package/src/userscripts/scripts/pornhub.ts +13 -4
  52. package/src/userscripts/scripts/spankbang.ts +29 -5
  53. package/src/userscripts/scripts/thisvid.ts +16 -31
  54. package/src/userscripts/scripts/xhamster.ts +13 -18
  55. package/src/userscripts/scripts/xvideos.ts +32 -3
  56. package/src/utils/dom/dom-observers.ts +3 -3
  57. package/src/utils/dom/index.ts +1 -1
  58. package/src/utils/parsers/index.ts +5 -2
@@ -1,139 +1,67 @@
1
- import type { StoreState } from 'jabroni-outfit';
2
1
  import { GM_addStyle } from 'vite-plugin-monkey/dist/client';
3
- import { RegexFilter } from '../../utils';
4
2
  import type { Rules } from '../rules';
5
- import type { DataElement } from './data-manager';
6
-
7
- export type DataSelectorFnShort = (e: DataElement, state: StoreState) => boolean;
8
-
9
- export type DataSelectorFnAdvanced<R> = {
10
- handle: (el: DataElement, state: StoreState, $preDefineResult?: R) => boolean;
11
- $preDefine?: (state: StoreState) => R;
12
- deps?: string[];
13
- };
14
-
15
- export type DataSelectorFn<R> = DataSelectorFnAdvanced<R> | DataSelectorFnShort;
16
-
17
- interface DataFilterResult {
18
- tag: string;
19
- condition: boolean;
20
- }
21
-
22
- export type DataFilterFn = (v: DataElement) => DataFilterResult;
3
+ import {
4
+ DataFilterFn,
5
+ type DataFilterFnFrom,
6
+ type DataFilterFnRendered,
7
+ } from './data-filter-fn';
8
+ import { defaultDataFilterFns } from './data-filter-fn-defaults';
23
9
 
24
10
  export class DataFilter {
25
- public filters = new Map<string, () => DataFilterFn>();
11
+ public filters = new Map<string, () => DataFilterFnRendered>();
12
+ public filterMapping: Record<string, string> = {};
26
13
 
27
14
  constructor(private rules: Rules) {
28
- this.registerFilters(rules.customDataSelectorFns);
29
- this.applyCSSFilters();
15
+ this.registerFilters(rules.customDataFilterFns);
16
+ this.createCssFilters();
30
17
  }
31
18
 
32
- public static isFiltered(el: HTMLElement): boolean {
33
- return el.className.includes('filter-');
19
+ public static isFiltered(e: HTMLElement): boolean {
20
+ return e.className.includes(DataFilterFn.prefix);
34
21
  }
35
22
 
36
- public applyCSSFilters(wrapper?: (cssRule: string) => string) {
23
+ public createCssFilters(wrapper?: (cssRule: string) => string) {
37
24
  this.filters.forEach((_, name) => {
38
- const cssRule = `.filter-${name} { display: none !important; }`;
25
+ const className = DataFilterFn.setPrefix(name);
26
+ const cssRule = `.${className} { display: none !important; }`;
39
27
  GM_addStyle(wrapper ? wrapper(cssRule) : cssRule);
40
28
  });
41
29
  }
42
30
 
43
- public customDataSelectorFns: Record<string, DataSelectorFn<any>> = {};
31
+ public customDataFilterFns: Record<string, DataFilterFnFrom<any>> = {};
44
32
 
45
- public registerFilters(customFilters: (Record<string, DataSelectorFn<any>> | string)[]) {
33
+ public registerFilters(
34
+ customFilters: (Record<string, DataFilterFnFrom<any>> | string)[],
35
+ ) {
46
36
  customFilters.forEach((o) => {
47
- if (typeof o === 'string') {
48
- this.customDataSelectorFns[o] = DataFilter.customDataSelectorFnsDefault[o];
49
- this.registerFilter(o);
50
- } else {
51
- const k = Object.keys(o)[0];
52
- this.customDataSelectorFns[k] = o[k];
53
- this.registerFilter(k);
54
- }
55
- });
56
- }
37
+ const isStr = typeof o === 'string';
38
+ const k = isStr ? o : Object.keys(o)[0];
57
39
 
58
- private customSelectorParser<T>(
59
- name: string,
60
- selector: DataSelectorFn<T>,
61
- ): DataSelectorFnAdvanced<T> {
62
- if ('handle' in selector) {
63
- return selector as DataSelectorFnAdvanced<T>;
64
- } else {
65
- return { handle: selector, deps: [name] } as DataSelectorFnAdvanced<T>;
66
- }
40
+ this.customDataFilterFns[k] = isStr ? defaultDataFilterFns[o] : o[k];
41
+ this.registerFilter(k);
42
+ });
67
43
  }
68
44
 
69
45
  public registerFilter(customSelectorName: string) {
70
- const handler = this.customSelectorParser(
46
+ const dataFilterFn = DataFilterFn.from(
47
+ this.customDataFilterFns[customSelectorName],
71
48
  customSelectorName,
72
- this.customDataSelectorFns[customSelectorName],
73
49
  );
74
- const tag = `filter-${customSelectorName}`;
75
50
 
76
- [customSelectorName, ...(handler.deps || [])]?.forEach((name) => {
51
+ dataFilterFn.deps.push(customSelectorName);
52
+
53
+ dataFilterFn.deps.forEach((name) => {
77
54
  Object.assign(this.filterMapping, { [name]: customSelectorName });
78
55
  });
79
56
 
80
- const fn = (): DataFilterFn => {
81
- const preDefined = handler.$preDefine?.(this.rules.store.state);
82
-
83
- return (v: DataElement) => {
84
- const condition = handler.handle(v, this.rules.store.state, preDefined);
85
-
86
- return {
87
- condition,
88
- tag,
89
- };
90
- };
91
- };
92
-
93
- this.filters.set(customSelectorName, fn);
57
+ this.filters.set(customSelectorName, dataFilterFn.renderFn(this.rules.store.state));
94
58
  }
95
59
 
96
- public filterMapping: Record<string, string> = {};
97
-
98
60
  public selectFilters(filters: { [key: string]: boolean }) {
99
61
  const selectedFilters = Object.keys(filters)
100
62
  .filter((k) => k in this.filterMapping)
101
63
  .map((k) => this.filterMapping[k])
102
- .map((k) => this.filters.get(k) as () => DataFilterFn);
64
+ .map((k) => this.filters.get(k) as () => DataFilterFnRendered);
103
65
  return selectedFilters;
104
66
  }
105
-
106
- static customDataSelectorFnsDefault: Record<string, DataSelectorFn<any>> = {
107
- filterDuration: {
108
- handle(el, state, notInRange) {
109
- if (!state.filterDuration) return false;
110
- return notInRange(el.duration);
111
- },
112
- $preDefine: (state) => {
113
- const from = state.filterDurationFrom as number;
114
- const to = state.filterDurationTo as number;
115
- function notInRange(d: number): boolean {
116
- return d < from || d > to;
117
- }
118
- return notInRange;
119
- },
120
- deps: ['filterDurationFrom', 'filterDurationTo'],
121
- },
122
- filterExclude: {
123
- handle(el, state, searchFilter) {
124
- if (!state.filterExclude) return false;
125
- return !(searchFilter as RegexFilter).hasNone(el.title as string);
126
- },
127
- $preDefine: (state) => new RegexFilter(state.filterExcludeWords as string),
128
- deps: ['filterExcludeWords'],
129
- },
130
- filterInclude: {
131
- handle(el, state, searchFilter) {
132
- if (!state.filterInclude) return false;
133
- return !(searchFilter as RegexFilter).hasEvery(el.title as string);
134
- },
135
- $preDefine: (state) => new RegexFilter(state.filterIncludeWords as string),
136
- deps: ['filterIncludeWords'],
137
- },
138
- };
139
67
  }
@@ -3,7 +3,10 @@ import { checkHomogenity, LazyImgLoader } from '../../utils';
3
3
  import type { Rules } from '../rules';
4
4
  import { DataFilter } from './data-filter';
5
5
 
6
- export type DataElement = Record<string, string | number | boolean | HTMLElement>;
6
+ export type DataElement = {
7
+ element: HTMLElement;
8
+ [key: string]: HTMLElement | string | number | boolean;
9
+ };
7
10
 
8
11
  export class DataManager {
9
12
  public data = new Map<string, DataElement>();
@@ -14,7 +17,7 @@ export class DataManager {
14
17
 
15
18
  constructor(
16
19
  private rules: Rules,
17
- private parentHomogenity?: Parameters<typeof checkHomogenity>[2],
20
+ private containerHomogenity?: Parameters<typeof checkHomogenity>[2],
18
21
  ) {
19
22
  this.dataFilter = new DataFilter(this.rules);
20
23
  }
@@ -29,29 +32,21 @@ export class DataManager {
29
32
  const iterator = this.data.values().drop(offset);
30
33
  let finished = false;
31
34
 
35
+ const updates: { e: HTMLElement; name: string; condition: boolean }[] = [];
36
+
32
37
  await new Promise((resolve) => {
33
38
  function runBatch(deadline: IdleDeadline) {
34
- const updates: { e: HTMLElement; tag: string; condition: boolean }[] = [];
35
-
36
39
  while (deadline.timeRemaining() > 0) {
37
40
  const { value, done } = iterator.next();
38
41
  finished = !!done;
39
42
  if (done) break;
40
43
 
41
44
  for (const f of filtersToApply) {
42
- const { tag, condition } = f()(value);
43
- updates.push({ e: value.element as HTMLElement, tag, condition });
45
+ const { name, condition } = f()(value);
46
+ updates.push({ e: value.element as HTMLElement, name, condition });
44
47
  }
45
48
  }
46
49
 
47
- if (updates.length > 0) {
48
- requestAnimationFrame(() => {
49
- updates.forEach((u) => {
50
- u.e.classList.toggle(u.tag, u.condition);
51
- });
52
- });
53
- }
54
-
55
50
  if (!finished) {
56
51
  requestIdleCallback(runBatch);
57
52
  } else {
@@ -61,8 +56,42 @@ export class DataManager {
61
56
 
62
57
  requestIdleCallback(runBatch);
63
58
  });
59
+
60
+ const parents = [...new Set(updates.map((u) => u.e.parentElement))].filter(
61
+ (_) => _ !== null,
62
+ );
63
+
64
+ requestAnimationFrame(() => {
65
+ const revertDisplayStyle = parents.map((p) => {
66
+ const display = p.style.display;
67
+ p.style.display = 'none';
68
+ this.layoutStylePaint(p);
69
+ p.style.willChange = 'contents';
70
+ return () => {
71
+ p.style.display = display;
72
+ requestAnimationFrame(() => {
73
+ p.style.willChange = 'auto';
74
+ });
75
+ };
76
+ });
77
+
78
+ updates.forEach((u) => {
79
+ u.e.classList.toggle(u.name, u.condition);
80
+ });
81
+
82
+ revertDisplayStyle.forEach((f) => {
83
+ f?.();
84
+ });
85
+ });
64
86
  };
65
87
 
88
+ public layoutStylePaintEnabled = false;
89
+
90
+ private layoutStylePaint(e: HTMLElement) {
91
+ if (!this.layoutStylePaintEnabled) return;
92
+ e.style.contain = 'layout style paint';
93
+ }
94
+
66
95
  public filterAll = async (offset?: number): Promise<void> => {
67
96
  const keys = Array.from(this.dataFilter.filters.keys());
68
97
  const filters = Object.fromEntries(
@@ -82,7 +111,7 @@ export class DataManager {
82
111
  const dataOffset = this.data.size;
83
112
  const fragment = document.createDocumentFragment();
84
113
  const parent = container || this.rules.container;
85
- const homogenity = !!this.parentHomogenity;
114
+ const homogenity = !!this.containerHomogenity;
86
115
 
87
116
  for (const thumbElement of thumbs) {
88
117
  const url = this.rules.thumbDataParser.getUrl(thumbElement);
@@ -94,7 +123,7 @@ export class DataManager {
94
123
  !checkHomogenity(
95
124
  parent,
96
125
  thumbElement.parentElement as HTMLElement,
97
- this.parentHomogenity as object,
126
+ this.containerHomogenity as object,
98
127
  ))
99
128
  ) {
100
129
  if (removeDuplicates) thumbElement.remove();
@@ -113,8 +142,19 @@ export class DataManager {
113
142
  }
114
143
 
115
144
  this.filterAll(dataOffset).then(() => {
145
+ if (!parent) return;
146
+ //
147
+ // THIS LINE BRAKES RENDERING, EVERYTHING DISAPPEARS
148
+ //
149
+ // this.layoutStylePaint(parent)
150
+ //
151
+ parent.style.willChange = 'contents';
116
152
  requestAnimationFrame(() => {
117
- parent.appendChild(fragment);
153
+ // parent.style.contain = 'layout style paint';
154
+ parent?.appendChild(fragment);
155
+ requestAnimationFrame(() => {
156
+ parent.style.willChange = 'auto';
157
+ });
118
158
  });
119
159
  });
120
160
  };
@@ -122,23 +162,46 @@ export class DataManager {
122
162
  public sortBy<K extends keyof DataElement>(key: K, direction = true): void {
123
163
  if (this.data.size < 2) return;
124
164
 
125
- let sorted: DataElement[] = this.data
165
+ const elements = this.data
126
166
  .values()
127
167
  .toArray()
128
- .sort((a: DataElement, b: DataElement) => {
129
- return (a[key] as number) - (b[key] as number);
130
- });
168
+ .filter((e) => e.element.parentElement !== null)
169
+ .map((e) => e);
131
170
 
132
- if (!direction) sorted = sorted.reverse();
133
-
134
- const container = (sorted[0].element as HTMLElement).parentElement as HTMLElement;
171
+ const containers = new Set(elements.map((e) => e.element.parentElement as HTMLElement));
172
+ containers.forEach((c) => {
173
+ this.layoutStylePaint(c);
174
+ c.style.willChange = 'contents';
175
+ });
135
176
 
136
- container.style.visibility = 'hidden';
177
+ const elementsByContainers = new Map<HTMLElement, DataElement[]>();
178
+ containers.forEach((c) => {
179
+ elementsByContainers.set(c, []);
180
+ });
137
181
 
138
- sorted.forEach((s) => {
139
- container.append(s.element as HTMLElement);
182
+ elements.forEach((e) => {
183
+ const parent = e.element.parentElement as HTMLElement;
184
+ const container = elementsByContainers.get(parent);
185
+ container?.push(e);
140
186
  });
141
187
 
142
- container.style.visibility = 'visible';
188
+ const dir = direction ? -1 : 1;
189
+
190
+ for (const [container, items] of elementsByContainers) {
191
+ items.sort((a, b) => ((a[key] as number) - (b[key] as number)) * dir);
192
+ const domNodes = items.map((e) => e.element);
193
+
194
+ const display = container.style.display;
195
+ container.style.display = 'none';
196
+
197
+ container.replaceChildren(...domNodes);
198
+
199
+ requestAnimationFrame(() => {
200
+ container.style.display = display;
201
+ requestAnimationFrame(() => {
202
+ container.style.willChange = 'auto';
203
+ });
204
+ });
205
+ }
143
206
  }
144
207
  }
@@ -2,7 +2,7 @@ import type { JabroniTypes, SchemeInput, setupScheme } from 'jabroni-outfit';
2
2
 
3
3
  export const DefaultScheme = [
4
4
  {
5
- title: 'Text Filter',
5
+ title: 'Title Filter',
6
6
  collapsed: true,
7
7
  content: [
8
8
  { filterExclude: false, label: 'exclude' },
@@ -21,6 +21,26 @@ export const DefaultScheme = [
21
21
  },
22
22
  ],
23
23
  },
24
+ {
25
+ title: 'Uploader Filter',
26
+ collapsed: true,
27
+ content: [
28
+ { filterUploaderExclude: false, label: 'exclude' },
29
+ {
30
+ filterUploaderExcludeWords: '',
31
+ label: 'keywords',
32
+ watch: 'filterUploaderExclude',
33
+ placeholder: 'word, f:full_word, r:RegEx...',
34
+ },
35
+ { filterUploaderInclude: false, label: 'include' },
36
+ {
37
+ filterUploaderIncludeWords: '',
38
+ label: 'keywords',
39
+ watch: 'filterUploaderInclude',
40
+ placeholder: 'word, f:full_word, r:RegEx...',
41
+ },
42
+ ],
43
+ },
24
44
  {
25
45
  title: 'Duration Filter',
26
46
  collapsed: true,
@@ -42,6 +62,7 @@ export const DefaultScheme = [
42
62
  },
43
63
  {
44
64
  title: 'Sort By',
65
+ collapsed: true,
45
66
  content: [
46
67
  {
47
68
  'sort by views': () => {},
@@ -51,14 +72,40 @@ export const DefaultScheme = [
51
72
  },
52
73
  ],
53
74
  },
75
+ {
76
+ title: 'Sort By Duration',
77
+ collapsed: true,
78
+ content: [
79
+ {
80
+ 'sort by duration': () => {},
81
+ },
82
+ ],
83
+ },
84
+ {
85
+ title: 'Sort By Views',
86
+ collapsed: true,
87
+ content: [
88
+ {
89
+ 'sort by views': () => {},
90
+ },
91
+ ],
92
+ },
54
93
  {
55
94
  title: 'Privacy Filter',
95
+ collapsed: true,
56
96
  content: [
57
97
  { filterPrivate: false, label: 'private' },
58
98
  { filterPublic: false, label: 'public' },
59
99
  { 'check access 🔓': () => {} },
60
100
  ],
61
101
  },
102
+ {
103
+ title: 'HD Filter',
104
+ content: [
105
+ { filterHD: false, label: 'hd' },
106
+ { filterNonHD: false, label: 'non-hd' },
107
+ ],
108
+ },
62
109
  {
63
110
  title: 'Advanced',
64
111
  collapsed: true,
@@ -79,6 +126,9 @@ export const DefaultScheme = [
79
126
  writeHistory: false,
80
127
  label: 'write history',
81
128
  },
129
+ {
130
+ reset: () => { localStorage.removeItem('state_acephale'); }
131
+ }
82
132
  ],
83
133
  },
84
134
  {
@@ -92,7 +142,6 @@ export const DefaultScheme = [
92
142
  },
93
143
  ] as const satisfies SchemeInput;
94
144
 
95
- export type SchemeOptions = (
96
- | Parameters<typeof setupScheme>[0][0]
97
- | JabroniTypes.ExtractValuesByKey<typeof DefaultScheme, 'title'>
98
- )[];
145
+ export type SchemeKeys = JabroniTypes.ExtractValuesByKey<typeof DefaultScheme, 'title'>;
146
+
147
+ export type SchemeOptions = (Parameters<typeof setupScheme>[0][0] | SchemeKeys)[];
@@ -1,3 +1,4 @@
1
1
  export * from './default-scheme';
2
2
  export * from './default-store';
3
3
  export * from './jabroni-gui-controller';
4
+ export * from './scheme-selectors-mapping';
@@ -51,7 +51,7 @@ export class JabronioGuiController {
51
51
 
52
52
  private setupStoreListeners() {
53
53
  this.directionalEventObservable$?.subscribe((e) => {
54
- this.eventsMap[e.type](e.direction);
54
+ this.eventsMap[e.type]?.(e.direction);
55
55
  });
56
56
 
57
57
  this.store.stateSubject.pipe(takeUntil(this.destroy$)).subscribe((a) => {
@@ -0,0 +1,12 @@
1
+ import { defaultDataFilterFns } from '../data-handler/data-filter-fn-defaults';
2
+ import { DefaultScheme, type SchemeKeys } from './default-scheme';
3
+
4
+ export function getSelectorFnsFromScheme(xs: SchemeKeys[]) {
5
+ const keys = xs.flatMap((s) => {
6
+ const schemeBlock = DefaultScheme.find((e) => e.title === s);
7
+ if (!schemeBlock) return [];
8
+ return schemeBlock.content.flatMap((c) => Object.keys(c));
9
+ });
10
+
11
+ return keys.filter((k) => k in defaultDataFilterFns);
12
+ }
@@ -7,13 +7,15 @@ import {
7
7
  } from '../../utils';
8
8
 
9
9
  type Primitive = string | number | boolean;
10
- type PrimitiveString = 'boolean' | 'string' | 'number' | 'float' | 'duration';
10
+
11
11
  type ThumbData = Record<string, Primitive>;
12
+
12
13
  type ThumbDataSelector = {
13
14
  name: string;
14
15
  selector: string;
15
- type: PrimitiveString;
16
+ type: 'boolean' | 'string' | 'number' | 'float' | 'duration';
16
17
  };
18
+
17
19
  type ThumbDataSelectorsRaw = Record<
18
20
  string,
19
21
  string | Pick<ThumbDataSelector, 'selector' | 'type'>
@@ -21,8 +23,13 @@ type ThumbDataSelectorsRaw = Record<
21
23
 
22
24
  export class ThumbDataParser {
23
25
  private autoParseText(thumb: HTMLElement): ThumbData {
24
- const title = sanitizeStr(thumb.innerText);
25
- const duration = timeToSeconds(title.match(/\d+m|\d+:\d+/)?.[0] || '');
26
+ let title = sanitizeStr(thumb.innerText);
27
+
28
+ const durationStr = title.match(/(\d+:\d+:?\d+?)|\d+m/)?.[0] || '';
29
+ const duration = timeToSeconds(durationStr);
30
+
31
+ title = title.replaceAll(durationStr, '');
32
+
26
33
  return { title, duration };
27
34
  }
28
35
 
@@ -56,6 +63,7 @@ export class ThumbDataParser {
56
63
  selector: '[class *= uploader], [class *= user], [class *= name]',
57
64
  },
58
65
  { name: 'duration', type: 'duration', selector: '[class *= duration]' },
66
+ // { name: 'views', type: 'float', selector: '[class *= view]' },
59
67
  ];
60
68
 
61
69
  private getThumbDataWith(
@@ -82,17 +90,14 @@ export class ThumbDataParser {
82
90
  public strategy: 'manual' | 'auto-select' | 'auto-text' = 'manual',
83
91
  public selectors: ThumbDataSelectorsRaw = {},
84
92
  public callback?: (thumb: HTMLElement, thumbData: ThumbData) => void,
85
- public stringsMeltInTitle = true,
86
93
  ) {
87
94
  this.preprocessCustomThumbDataSelectors();
88
95
  }
89
96
 
90
97
  public static create(
91
- o: Partial<
92
- Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback' | 'stringsMeltInTitle'>
93
- > = {},
98
+ o: Partial<Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback'>> = {},
94
99
  ) {
95
- return new ThumbDataParser(o.strategy, o.selectors, o.callback, o.stringsMeltInTitle);
100
+ return new ThumbDataParser(o.strategy, o.selectors, o.callback);
96
101
  }
97
102
 
98
103
  public getThumbData(thumb: HTMLElement): ThumbData {
@@ -101,22 +106,13 @@ export class ThumbDataParser {
101
106
  }
102
107
 
103
108
  if (this.strategy === 'auto-select') {
104
- this.thumbDataSelectors = this.defaultThumbDataSelectors;
109
+ this.thumbDataSelectors.push(...this.defaultThumbDataSelectors);
105
110
  }
106
111
 
107
112
  const thumbData = Object.fromEntries(
108
113
  this.thumbDataSelectors.map((s) => [s.name, this.getThumbDataWith(thumb, s)]),
109
114
  );
110
115
 
111
- if (this.stringsMeltInTitle) {
112
- Object.entries(thumbData).forEach(([k, v]) => {
113
- if (typeof v === 'string' && k !== 'title') {
114
- thumbData.title = `${thumbData.title} ${k}:${v}`;
115
- delete thumbData[k];
116
- }
117
- });
118
- }
119
-
120
116
  this.callback?.(thumb, thumbData);
121
117
 
122
118
  return thumbData;
@@ -4,10 +4,12 @@ import {
4
4
  querySelectorLast,
5
5
  waitForElementToDisappear,
6
6
  } from '../../utils';
7
- import { DataManager, type DataSelectorFn } from '../data-handler';
7
+ import { DataManager } from '../data-handler';
8
+ import type { DataFilterFnFrom } from '../data-handler/data-filter-fn';
8
9
  import { InfiniteScroller, type OffsetGenerator } from '../infinite-scroll';
9
10
  import {
10
11
  DefaultScheme,
12
+ getSelectorFnsFromScheme,
11
13
  JabronioGuiController,
12
14
  type SchemeOptions,
13
15
  StoreStateDefault,
@@ -61,12 +63,15 @@ export class Rules {
61
63
  public paginationStrategy: PaginationStrategy;
62
64
 
63
65
  public dataManager: DataManager;
64
- public dataHomogenity: ConstructorParameters<typeof DataManager>[1];
65
- public customDataSelectorFns: (Record<string, DataSelectorFn<any>> | string)[] = [
66
- 'filterInclude',
67
- 'filterExclude',
68
- 'filterDuration',
69
- ];
66
+ public containerHomogenity: ConstructorParameters<typeof DataManager>[1];
67
+
68
+ public customDataFilterFns: (Record<string, DataFilterFnFrom<any>> | string)[] = [];
69
+ private hookDataFilterFns() {
70
+ const defaultFilterFns = getSelectorFnsFromScheme(
71
+ this.schemeOptions.filter((s) => typeof s === 'string'),
72
+ );
73
+ this.customDataFilterFns.push(...defaultFilterFns);
74
+ }
70
75
 
71
76
  public animatePreview?: (doc: HTMLElement) => void;
72
77
 
@@ -151,7 +156,7 @@ export class Rules {
151
156
 
152
157
  this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
153
158
 
154
- this.dataManager = new DataManager(this, this.dataHomogenity);
159
+ this.dataManager = new DataManager(this, this.containerHomogenity);
155
160
 
156
161
  this.inputController.dispose();
157
162
  this.inputController = new JabronioGuiController(this.store, this.dataManager);
@@ -181,7 +186,8 @@ export class Rules {
181
186
  this.store = this.createStore();
182
187
  this.gui = this.createGui();
183
188
 
184
- this.dataManager = new DataManager(this, this.dataHomogenity);
189
+ this.hookDataFilterFns();
190
+ this.dataManager = new DataManager(this, this.containerHomogenity);
185
191
  this.inputController = new JabronioGuiController(this.store, this.dataManager);
186
192
 
187
193
  this.reset();
@@ -1 +1 @@
1
- import './scripts/motherless'
1
+ import './scripts/ebalka';
@@ -4,8 +4,8 @@ import { circularShift, OnHover, Tick } from '../../utils';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: '3Hentai PervertMonkey',
7
- version: '1.0.4',
8
- description: 'Infinite scroll [optional], Filter by Title',
7
+ version: '1.0.7',
8
+ description: 'Infinite scroll [optional], Filter by Title, thumb preview',
9
9
  match: 'https://*.3hentai.net/*',
10
10
  };
11
11
 
@@ -23,8 +23,7 @@ const rules = new Rules({
23
23
  strategy: 'auto',
24
24
  },
25
25
  gropeStrategy: 'all-in-all',
26
- customDataSelectorFns: ['filterInclude', 'filterExclude'],
27
- schemeOptions: ['Text Filter', 'Badge', 'Advanced'],
26
+ schemeOptions: ['Title Filter', 'Badge', 'Advanced'],
28
27
  animatePreview,
29
28
  });
30
29
 
@@ -2,7 +2,7 @@ import type { MonkeyUserScript } from 'vite-plugin-monkey';
2
2
 
3
3
  export const meta: MonkeyUserScript = {
4
4
  name: 'CamGirlFinder PervertMonkey',
5
- version: '1.6.2',
5
+ version: '1.6.6',
6
6
  description:
7
7
  'Adds model links for CamWhores, webcamrecordings, recu.me, camvideos, privat-zapisi',
8
8
  match: ['https://camgirlfinder.net/*'],