pervert-monkey 1.0.13 → 1.0.15

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 (56) hide show
  1. package/dist/core/pervertmonkey.core.es.d.ts +82 -29
  2. package/dist/core/pervertmonkey.core.es.js +233 -115
  3. package/dist/core/pervertmonkey.core.es.js.map +1 -1
  4. package/dist/core/pervertmonkey.core.umd.js +233 -115
  5. package/dist/core/pervertmonkey.core.umd.js.map +1 -1
  6. package/dist/userscripts/3hentai.user.js +4 -11
  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 +3 -4
  10. package/dist/userscripts/ebalka.user.js +13 -5
  11. package/dist/userscripts/eporner.user.js +21 -41
  12. package/dist/userscripts/erome.user.js +9 -8
  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 +12 -5
  17. package/dist/userscripts/namethatporn.user.js +8 -14
  18. package/dist/userscripts/nhentai.user.js +5 -13
  19. package/dist/userscripts/obmenvsem.user.js +10 -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 +12 -30
  23. package/dist/userscripts/xhamster.user.js +9 -14
  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 +60 -0
  28. package/src/core/data-handler/data-filter.ts +22 -96
  29. package/src/core/data-handler/data-manager.ts +75 -26
  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 +4 -15
  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 -6
  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 +4 -6
  41. package/src/userscripts/scripts/ebalka.ts +11 -3
  42. package/src/userscripts/scripts/eporner.ts +20 -39
  43. package/src/userscripts/scripts/erome.ts +9 -7
  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 +11 -6
  48. package/src/userscripts/scripts/namethatporn.ts +8 -15
  49. package/src/userscripts/scripts/nhentai.ts +6 -13
  50. package/src/userscripts/scripts/obmenvsem.ts +9 -3
  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 +14 -29
  54. package/src/userscripts/scripts/xhamster.ts +9 -14
  55. package/src/userscripts/scripts/xvideos.ts +32 -3
  56. package/src/utils/parsers/index.ts +5 -2
@@ -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,10 +32,10 @@ export class DataManager {
29
32
  const iterator = this.data.values().drop(offset);
30
33
  let finished = false;
31
34
 
35
+ const updates: { e: HTMLElement; tag: 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;
@@ -44,14 +47,6 @@ export class DataManager {
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,6 +56,32 @@ export class DataManager {
61
56
 
62
57
  requestIdleCallback(runBatch);
63
58
  });
59
+
60
+ const parents = new Set(updates.map((u) => u.e.parentElement));
61
+
62
+ requestAnimationFrame(() => {
63
+ const revertDisplayStyle = [...parents].map((p) => {
64
+ const display = p?.style.display;
65
+ if (!display) return undefined;
66
+ p.style.display = 'none';
67
+ p.style.contain = 'layout style paint';
68
+ p.style.willChange = 'contents';
69
+ return () => {
70
+ p.style.display = display;
71
+ requestAnimationFrame(() => {
72
+ p.style.willChange = 'auto';
73
+ });
74
+ };
75
+ });
76
+
77
+ updates.forEach((u) => {
78
+ u.e.classList.toggle(u.tag, u.condition);
79
+ });
80
+
81
+ revertDisplayStyle.forEach((f) => {
82
+ f?.();
83
+ });
84
+ });
64
85
  };
65
86
 
66
87
  public filterAll = async (offset?: number): Promise<void> => {
@@ -82,7 +103,12 @@ export class DataManager {
82
103
  const dataOffset = this.data.size;
83
104
  const fragment = document.createDocumentFragment();
84
105
  const parent = container || this.rules.container;
85
- const homogenity = !!this.parentHomogenity;
106
+ const homogenity = !!this.containerHomogenity;
107
+
108
+ if (parent) {
109
+ parent.style.contain = 'layout style paint';
110
+ parent.style.willChange = 'contents';
111
+ }
86
112
 
87
113
  for (const thumbElement of thumbs) {
88
114
  const url = this.rules.thumbDataParser.getUrl(thumbElement);
@@ -94,7 +120,7 @@ export class DataManager {
94
120
  !checkHomogenity(
95
121
  parent,
96
122
  thumbElement.parentElement as HTMLElement,
97
- this.parentHomogenity as object,
123
+ this.containerHomogenity as object,
98
124
  ))
99
125
  ) {
100
126
  if (removeDuplicates) thumbElement.remove();
@@ -114,7 +140,7 @@ export class DataManager {
114
140
 
115
141
  this.filterAll(dataOffset).then(() => {
116
142
  requestAnimationFrame(() => {
117
- parent.appendChild(fragment);
143
+ parent?.appendChild(fragment);
118
144
  });
119
145
  });
120
146
  };
@@ -122,23 +148,46 @@ export class DataManager {
122
148
  public sortBy<K extends keyof DataElement>(key: K, direction = true): void {
123
149
  if (this.data.size < 2) return;
124
150
 
125
- let sorted: DataElement[] = this.data
151
+ const elements = this.data
126
152
  .values()
127
153
  .toArray()
128
- .sort((a: DataElement, b: DataElement) => {
129
- return (a[key] as number) - (b[key] as number);
130
- });
154
+ .filter((e) => e.element.parentElement !== null)
155
+ .map((e) => e);
131
156
 
132
- if (!direction) sorted = sorted.reverse();
133
-
134
- const container = (sorted[0].element as HTMLElement).parentElement as HTMLElement;
157
+ const containers = new Set(elements.map((e) => e.element.parentElement as HTMLElement));
158
+ containers.forEach((c) => {
159
+ c.style.contain = 'layout style paint';
160
+ c.style.willChange = 'contents';
161
+ });
135
162
 
136
- container.style.visibility = 'hidden';
163
+ const elementsByContainers = new Map<HTMLElement, DataElement[]>();
164
+ containers.forEach((c) => {
165
+ elementsByContainers.set(c, []);
166
+ });
137
167
 
138
- sorted.forEach((s) => {
139
- container.append(s.element as HTMLElement);
168
+ elements.forEach((e) => {
169
+ const parent = e.element.parentElement as HTMLElement;
170
+ const container = elementsByContainers.get(parent);
171
+ container?.push(e);
140
172
  });
141
173
 
142
- container.style.visibility = 'visible';
174
+ const dir = direction ? -1 : 1;
175
+
176
+ for (const [container, items] of elementsByContainers) {
177
+ items.sort((a, b) => ((a[key] as number) - (b[key] as number)) * dir);
178
+ const domNodes = items.map((e) => e.element);
179
+
180
+ const display = container.style.display;
181
+ container.style.display = 'none';
182
+
183
+ container.replaceChildren(...domNodes);
184
+
185
+ requestAnimationFrame(() => {
186
+ container.style.display = display;
187
+ requestAnimationFrame(() => {
188
+ container.style.willChange = 'auto';
189
+ });
190
+ });
191
+ }
143
192
  }
144
193
  }
@@ -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
+ }
@@ -56,6 +56,7 @@ export class ThumbDataParser {
56
56
  selector: '[class *= uploader], [class *= user], [class *= name]',
57
57
  },
58
58
  { name: 'duration', type: 'duration', selector: '[class *= duration]' },
59
+ // { name: 'views', type: 'float', selector: '[class *= view]' },
59
60
  ];
60
61
 
61
62
  private getThumbDataWith(
@@ -82,17 +83,14 @@ export class ThumbDataParser {
82
83
  public strategy: 'manual' | 'auto-select' | 'auto-text' = 'manual',
83
84
  public selectors: ThumbDataSelectorsRaw = {},
84
85
  public callback?: (thumb: HTMLElement, thumbData: ThumbData) => void,
85
- public stringsMeltInTitle = true,
86
86
  ) {
87
87
  this.preprocessCustomThumbDataSelectors();
88
88
  }
89
89
 
90
90
  public static create(
91
- o: Partial<
92
- Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback' | 'stringsMeltInTitle'>
93
- > = {},
91
+ o: Partial<Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback'>> = {},
94
92
  ) {
95
- return new ThumbDataParser(o.strategy, o.selectors, o.callback, o.stringsMeltInTitle);
93
+ return new ThumbDataParser(o.strategy, o.selectors, o.callback);
96
94
  }
97
95
 
98
96
  public getThumbData(thumb: HTMLElement): ThumbData {
@@ -101,22 +99,13 @@ export class ThumbDataParser {
101
99
  }
102
100
 
103
101
  if (this.strategy === 'auto-select') {
104
- this.thumbDataSelectors = this.defaultThumbDataSelectors;
102
+ this.thumbDataSelectors.push(...this.defaultThumbDataSelectors);
105
103
  }
106
104
 
107
105
  const thumbData = Object.fromEntries(
108
106
  this.thumbDataSelectors.map((s) => [s.name, this.getThumbDataWith(thumb, s)]),
109
107
  );
110
108
 
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
109
  this.callback?.(thumb, thumbData);
121
110
 
122
111
  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,7 +4,7 @@ import { circularShift, OnHover, Tick } from '../../utils';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: '3Hentai PervertMonkey',
7
- version: '1.0.4',
7
+ version: '1.0.7',
8
8
  description: 'Infinite scroll [optional], Filter by Title',
9
9
  match: 'https://*.3hentai.net/*',
10
10
  };
@@ -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
 
@@ -42,9 +41,7 @@ function animatePreview() {
42
41
  img.src = img.src.replace(/\w+\.\w+$/, '1t.jpg');
43
42
  img.onerror = (_) => tick.stop();
44
43
  tick.start(
45
- () => {
46
- img.src = rotate(img.src);
47
- },
44
+ () => img.setAttribute('src', img.src),
48
45
  () => {
49
46
  img.src = origin;
50
47
  },
@@ -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.5',
6
6
  description:
7
7
  'Adds model links for CamWhores, webcamrecordings, recu.me, camvideos, privat-zapisi',
8
8
  match: ['https://camgirlfinder.net/*'],
@@ -17,9 +17,9 @@ import {
17
17
 
18
18
  export const meta: MonkeyUserScript = {
19
19
  name: 'CamWhores PervertMonkey',
20
- version: '3.0.6',
20
+ version: '3.0.9',
21
21
  description:
22
- 'Infinite scroll [optional]. Filter by Title, Duration and Private/Public. Mass friend request button. Download button',
22
+ 'Infinite scroll [optional]. Filter by Title, Duration and Private/Public. Sort by Duration and Views. Mass friend request button. Download button',
23
23
  match: ['https://*.camwhores.tv', 'https://*.camwhores.*/*'],
24
24
  exclude: 'https://*.camwhores.tv/*mode=async*',
25
25
  };
@@ -54,27 +54,18 @@ const rules = new Rules({
54
54
  strategy: 'auto-select',
55
55
  selectors: {
56
56
  private: { type: 'boolean', selector: '[class*=private]' },
57
+ views: { selector: '.views', type: 'float' },
57
58
  },
58
59
  },
59
60
  thumbImg: {
60
61
  selector: 'data-original',
61
62
  },
62
63
  gropeStrategy: 'all-in-all',
63
- customDataSelectorFns: [
64
- 'filterInclude',
65
- 'filterExclude',
66
- 'filterDuration',
67
- {
68
- filterPrivate: (e, state) => (state.filterPrivate && e.private) as boolean,
69
- },
70
- {
71
- filterPublic: (e, state) => (state.filterPublic && !e.private) as boolean,
72
- },
73
- ],
74
64
  schemeOptions: [
75
- 'Text Filter',
65
+ 'Title Filter',
76
66
  'Duration Filter',
77
67
  'Privacy Filter',
68
+ 'Sort By',
78
69
  'Badge',
79
70
  {
80
71
  title: 'Advanced',
@@ -4,7 +4,7 @@ import { fetchHtml } from '../../utils';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'E-Hentai PervertMonkey',
7
- version: '1.0.2',
7
+ version: '1.0.5',
8
8
  description: 'Infinite scroll [optional], Filter by Title',
9
9
  match: ['https://*.e-hentai.org/*'],
10
10
  };
@@ -13,16 +13,15 @@ const rules = new Rules({
13
13
  thumbs: { selector: '.gl1t' },
14
14
  thumb: {
15
15
  selectors: {
16
- title: '.glname'
17
- }
16
+ title: '.glname',
17
+ },
18
18
  },
19
19
  thumbImg: {
20
20
  selector: 'data-lazy-load',
21
21
  },
22
22
  containerSelectorLast: '.itg.gld',
23
23
  paginationStrategyOptions: createPaginationStrategyOptions(),
24
- customDataSelectorFns: ['filterInclude', 'filterExclude'],
25
- schemeOptions: ['Text Filter', 'Badge', 'Advanced'],
24
+ schemeOptions: ['Title Filter', 'Badge', 'Advanced'],
26
25
  });
27
26
 
28
27
  function createPaginationStrategyOptions(): Rules['paginationStrategyOptions'] {
@@ -39,7 +38,6 @@ function createPaginationStrategyOptions(): Rules['paginationStrategyOptions'] {
39
38
  nextLink = getNextLink();
40
39
  return nextLink;
41
40
  }
42
- // need cache or reuse infinite scroller request
43
41
  const doc = await fetchHtml(nextLink);
44
42
  nextLink = getNextLink(doc);
45
43
  return nextLink;
@@ -4,8 +4,8 @@ import { exterminateVideo, OnHover, parseHtml } from '../../utils';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'Ebalka PervertMonkey',
7
- version: '3.0.3',
8
- description: 'Infinite scroll [optional], Filter by Title and Duration',
7
+ version: '3.0.6',
8
+ description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
9
9
  match: [
10
10
  'https://b.ebalka.zip/*',
11
11
  'https://a.ebalka.love/*',
@@ -27,10 +27,18 @@ const rules = new Rules({
27
27
  selectors: {
28
28
  title: '.card__title',
29
29
  duration: '.card__spot > span:last-child',
30
+ hd: { selector: '.card__icons > .card__icon', type: 'boolean' },
30
31
  },
31
32
  },
32
33
  animatePreview,
33
- schemeOptions: ['Text Filter', 'Badge', 'Duration Filter', 'Advanced'],
34
+ schemeOptions: [
35
+ 'Title Filter',
36
+ 'Duration Filter',
37
+ 'HD Filter',
38
+ 'Sort By Duration',
39
+ 'Badge',
40
+ 'Advanced',
41
+ ],
34
42
  });
35
43
 
36
44
  function animatePreview(container: HTMLElement) {
@@ -5,8 +5,9 @@ import { OnHover } from '../../utils';
5
5
 
6
6
  export const meta: MonkeyUserScript = {
7
7
  name: 'Eporner PervertMonkey',
8
- version: '2.0.4',
9
- description: 'Infinite scroll [optional], Filter by Title, Duration and HD',
8
+ version: '2.0.7',
9
+ description:
10
+ 'Infinite scroll [optional], Filter by Title, Uploader, Duration and HD, Sort by Views and Duration',
10
11
  match: ['https://*.eporner.com/*', 'https://*.eporner.*/*'],
11
12
  };
12
13
 
@@ -19,10 +20,11 @@ const rules = new Rules({
19
20
  thumbs: { selector: 'div[id^=vf][data-id]' },
20
21
  thumb: {
21
22
  selectors: {
22
- quality: { type: 'number', selector: '[title="Quality"]' },
23
23
  title: 'a',
24
24
  uploader: '[title="Uploader"]',
25
25
  duration: '[title="Duration"]',
26
+ views: { selector: '[title="Views"]', type: 'float' },
27
+ quality: { selector: '[title="Quality"]', type: 'number' },
26
28
  },
27
29
  },
28
30
  thumbImg: {
@@ -30,50 +32,29 @@ const rules = new Rules({
30
32
  remove: 'auto',
31
33
  },
32
34
  containerSelectorLast: '#vidresults',
33
- customDataSelectorFns: [
34
- 'filterInclude',
35
- 'filterExclude',
36
- 'filterDuration',
37
- {
38
- quality360: (el, state) => !!state.quality360 && el.quality !== 360,
39
- },
40
- {
41
- quality480: (el, state) => !!state.quality480 && el.quality !== 480,
42
- },
43
- {
44
- quality720: (el, state) => !!state.quality720 && el.quality !== 720,
45
- },
46
- {
47
- quality1080: (el, state) => !!state.quality1080 && el.quality !== 1080,
48
- },
49
- {
50
- quality4k: (el, state) => !!state.quality4k && el.quality !== 4,
51
- },
35
+ customDataFilterFns: [
36
+ { quality360: (el, state) => !!state.quality360 && el.quality !== 360 },
37
+ { quality480: (el, state) => !!state.quality480 && el.quality !== 480 },
38
+ { quality720: (el, state) => !!state.quality720 && el.quality !== 720 },
39
+ { quality1080: (el, state) => !!state.quality1080 && el.quality !== 1080 },
40
+ { quality4k: (el, state) => !!state.quality4k && el.quality !== 4 },
52
41
  ],
53
42
  schemeOptions: [
54
- 'Text Filter',
55
- 'Badge',
43
+ 'Title Filter',
44
+ 'Uploader Filter',
56
45
  'Duration Filter',
57
46
  {
58
47
  title: 'Quality Filter ',
59
48
  content: [
60
- {
61
- quality360: false,
62
- },
63
- {
64
- quality480: false,
65
- },
66
- {
67
- quality720: false,
68
- },
69
- {
70
- quality1080: false,
71
- },
72
- {
73
- quality4k: false,
74
- },
49
+ { quality360: false },
50
+ { quality480: false },
51
+ { quality720: false },
52
+ { quality1080: false },
53
+ { quality4k: false },
75
54
  ],
76
55
  },
56
+ 'Sort By',
57
+ 'Badge',
77
58
  'Advanced',
78
59
  ],
79
60
  animatePreview,