pervert-monkey 1.0.24 → 1.0.26

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/README.md +5 -2
  2. package/dist/core/pervertmonkey.core.es.d.ts +2 -540
  3. package/dist/core/pervertmonkey.core.es.js +8001 -9280
  4. package/dist/core/pervertmonkey.core.es.js.map +1 -1
  5. package/dist/core/pervertmonkey.core.umd.js +8829 -10050
  6. package/dist/core/pervertmonkey.core.umd.js.map +1 -1
  7. package/dist/userscripts/3hentai.user.js +40 -46
  8. package/dist/userscripts/camgirlfinder.user.js +56 -49
  9. package/dist/userscripts/camwhores.user.js +286 -343
  10. package/dist/userscripts/e-hentai.user.js +54 -57
  11. package/dist/userscripts/ebalka.user.js +52 -55
  12. package/dist/userscripts/eporner.user.js +76 -68
  13. package/dist/userscripts/erome.user.js +79 -84
  14. package/dist/userscripts/eroprofile.user.js +28 -29
  15. package/dist/userscripts/javhdporn.user.js +28 -22
  16. package/dist/userscripts/missav.user.js +22 -23
  17. package/dist/userscripts/motherless.user.js +141 -147
  18. package/dist/userscripts/namethatporn.user.js +49 -58
  19. package/dist/userscripts/nhentai.user.js +104 -99
  20. package/dist/userscripts/obmenvsem.user.js +61 -69
  21. package/dist/userscripts/pornhub.user.js +50 -47
  22. package/dist/userscripts/simpcity.user.js +44 -0
  23. package/dist/userscripts/socialmediagirls.user.js +26 -22
  24. package/dist/userscripts/spankbang.user.js +62 -44
  25. package/dist/userscripts/thisvid.user.js +1269 -1366
  26. package/dist/userscripts/xhamster.user.js +119 -132
  27. package/dist/userscripts/xvideos.user.js +115 -91
  28. package/package.json +10 -6
  29. package/src/core/data-handler/data-filter.ts +4 -3
  30. package/src/core/parsers/pagination-parser/index.ts +0 -6
  31. package/src/core/parsers/pagination-parser/pagination-strategies/PaginationStrategyPathnameParams.ts +36 -1
  32. package/src/core/parsers/thumb-data-parser.ts +7 -3
  33. package/src/userscripts/index.ts +1 -1
  34. package/src/userscripts/meta.json +1 -1
  35. package/src/userscripts/scripts/3hentai.ts +1 -1
  36. package/src/userscripts/scripts/camgirlfinder.ts +1 -1
  37. package/src/userscripts/scripts/camwhores.ts +2 -2
  38. package/src/userscripts/scripts/e-hentai.ts +1 -1
  39. package/src/userscripts/scripts/ebalka.ts +1 -1
  40. package/src/userscripts/scripts/eporner.ts +41 -15
  41. package/src/userscripts/scripts/erome.ts +1 -1
  42. package/src/userscripts/scripts/eroprofile.ts +6 -6
  43. package/src/userscripts/scripts/javhdporn.ts +1 -1
  44. package/src/userscripts/scripts/missav.ts +1 -1
  45. package/src/userscripts/scripts/motherless.ts +42 -37
  46. package/src/userscripts/scripts/namethatporn.ts +1 -1
  47. package/src/userscripts/scripts/nhentai.ts +1 -1
  48. package/src/userscripts/scripts/obmenvsem.ts +1 -1
  49. package/src/userscripts/scripts/pornhub.ts +1 -1
  50. package/src/userscripts/scripts/simpcity.ts +27 -0
  51. package/src/userscripts/scripts/socialmediagirls.ts +4 -3
  52. package/src/userscripts/scripts/spankbang.ts +20 -4
  53. package/src/userscripts/scripts/thisvid.ts +8 -21
  54. package/src/userscripts/scripts/xhamster.ts +11 -3
  55. package/src/userscripts/scripts/xvideos.ts +26 -7
  56. package/src/utils/fetch/index.ts +2 -0
  57. package/src/utils/objects/memoize.ts +1 -9
  58. package/dist/test/test.user.js +0 -10301
@@ -7,7 +7,8 @@ export class PaginationStrategyPathnameParams extends PaginationStrategy {
7
7
  const href = typeof a === 'string' ? a : a.href;
8
8
  const { pathname } = new URL(href, this.doc.baseURI || this.url.origin);
9
9
  return parseInt(
10
- pathname.match(this.pathnameSelector)?.pop() || this.offsetMin.toString(),
10
+ pathname.match(this.pathnameSelector)?.filter(Boolean)?.pop() ||
11
+ this.offsetMin.toString(),
11
12
  );
12
13
  };
13
14
 
@@ -55,19 +56,53 @@ export class PaginationStrategyPathnameParams extends PaginationStrategy {
55
56
  getPaginationUrlGenerator(url_: URL = this.url) {
56
57
  const url = new URL(url_.href);
57
58
 
59
+ // console.log('this.pathnameSelector', this.pathnameSelector);
60
+
58
61
  const pathnameSelectorPlaceholder = this.pathnameSelector
59
62
  .toString()
60
63
  .replace(/\\{1,}/g, '')
61
64
  .replace(/[$?()]+/g, '')
62
65
  .replace(/\/{1,}/g, '/');
63
66
 
67
+ // console.log({ pathnameSelectorPlaceholder, 'url.pathname': url.pathname });
68
+
64
69
  if (!this.pathnameSelector.test(url.pathname)) {
65
70
  url.pathname = url.pathname
66
71
  .concat(pathnameSelectorPlaceholder.replace(/d\+/, this.offsetMin.toString()))
67
72
  .replace(/\/{1,}/g, '/');
68
73
  }
69
74
 
75
+ // console.log({ pathnameSelectorPlaceholder, 'url.pathname': url.pathname });
76
+
70
77
  const paginationUrlGenerator = (offset: number) => {
78
+ // console.log({
79
+ // pathname: url.pathname,
80
+ // replace: '',
81
+ // pathnameSelector: this.pathnameSelector,
82
+ // pathnameSelectorPlaceholder: pathnameSelectorPlaceholder.replace(
83
+ // /d\+/,
84
+ // offset.toString(),
85
+ // ),
86
+ // });
87
+
88
+ // pathnameSelectorPlaceholder.replace()
89
+ // need to create tests
90
+
91
+ // url.pathname = url.pathname.replace(
92
+ // this.pathnameSelector,
93
+ // (a, b, c, d) => {
94
+ // console.log({ a, b, c, d, pathnameSelectorPlaceholder });
95
+ // if (a.length + 1 < pathnameSelectorPlaceholder.length) {
96
+ // return a.replace(/\d+/, offset.toString());
97
+ // }
98
+ // // return a;
99
+ // return pathnameSelectorPlaceholder.replace(/d\+/, offset.toString());
100
+ // },
101
+ // // pathnameSelectorPlaceholder.replace(/d\+/, offset.toString()),
102
+ // );
103
+ // return url.href;
104
+ // };
105
+
71
106
  url.pathname = url.pathname.replace(
72
107
  this.pathnameSelector,
73
108
  pathnameSelectorPlaceholder.replace(/d\+/, offset.toString()),
@@ -35,7 +35,7 @@ export class ThumbDataParser {
35
35
  }
36
36
 
37
37
  public getUrl(thumb: HTMLElement | HTMLAnchorElement) {
38
- return (querySelectorOrSelf(thumb, 'a[href]') as HTMLAnchorElement).href;
38
+ return (querySelectorOrSelf(thumb, this.getUrlSelector) as HTMLAnchorElement).href;
39
39
  }
40
40
 
41
41
  private preprocessCustomThumbDataSelectors() {
@@ -56,6 +56,7 @@ export class ThumbDataParser {
56
56
  }
57
57
 
58
58
  private thumbDataSelectors: ThumbDataSelector[] = [];
59
+
59
60
  private readonly defaultThumbDataSelectors: ThumbDataSelector[] = [
60
61
  { name: 'title', type: 'string', selector: '[class *= title],[title]' },
61
62
  {
@@ -91,14 +92,17 @@ export class ThumbDataParser {
91
92
  public strategy: 'manual' | 'auto-select' | 'auto-text' = 'manual',
92
93
  public selectors: ThumbDataSelectorsRaw = {},
93
94
  public callback?: (thumb: HTMLElement, thumbData: ThumbData) => void,
95
+ public getUrlSelector = 'a[href]',
94
96
  ) {
95
97
  this.preprocessCustomThumbDataSelectors();
96
98
  }
97
99
 
98
100
  public static create(
99
- o: Partial<Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback'>> = {},
101
+ o: Partial<
102
+ Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback' | 'getUrlSelector'>
103
+ > = {},
100
104
  ) {
101
- return new ThumbDataParser(o.strategy, o.selectors, o.callback);
105
+ return new ThumbDataParser(o.strategy, o.selectors, o.callback, o.getUrlSelector);
102
106
  }
103
107
 
104
108
  public getThumbData(thumb: HTMLElement): ThumbData {
@@ -1 +1 @@
1
- import './scripts/erome';
1
+ import './scripts/eporner';
@@ -6,5 +6,5 @@
6
6
  "grant": ["unsafeWindow", "GM_addStyle"],
7
7
  "homepageURL": "https://sleazyfork.org/en/users/1253342-smartacephale",
8
8
  "supportURL": "https://github.com/smartacephale/sleazy-fork/issues",
9
- "source": "https://github.com/smartacephale"
9
+ "source": "https://github.com/smartacephale/sleazy-fork"
10
10
  }
@@ -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.18',
7
+ version: '1.0.20',
8
8
  description: 'Infinite scroll [optional], Filter by Title, thumb preview',
9
9
  match: 'https://*.3hentai.net/*',
10
10
  };
@@ -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.17',
5
+ version: '1.6.19',
6
6
  description:
7
7
  'Adds model links for CamWhores, webcamrecordings, recu.me, camvideos, privat-zapisi',
8
8
  match: ['https://camgirlfinder.net/*'],
@@ -17,10 +17,10 @@ import {
17
17
 
18
18
  export const meta: MonkeyUserScript = {
19
19
  name: 'CamWhores PervertMonkey',
20
- version: '3.0.21',
20
+ version: '3.0.24',
21
21
  description:
22
22
  'Infinite scroll [optional]. Filter by Title, Duration and Private/Public. Sort by Duration and Views. Mass friend request button. Download button',
23
- match: ['https://*.camwhores.tv', 'https://*.camwhores.*/*'],
23
+ match: ['https://*.camwhores.tv/*', 'https://*.camwhores.*/*'],
24
24
  exclude: 'https://*.camwhores.tv/*mode=async*',
25
25
  };
26
26
 
@@ -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.18',
7
+ version: '1.0.20',
8
8
  description: 'Infinite scroll [optional], Filter by Title',
9
9
  match: ['https://*.e-hentai.org/*'],
10
10
  };
@@ -4,7 +4,7 @@ import { downloader, exterminateVideo, OnHover, parseHtml } from '../../utils';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'Ebalka PervertMonkey',
7
- version: '3.0.19',
7
+ version: '3.0.21',
8
8
  description:
9
9
  'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration, Download button.',
10
10
  match: [
@@ -5,22 +5,24 @@ import { OnHover } from '../../utils';
5
5
 
6
6
  export const meta: MonkeyUserScript = {
7
7
  name: 'Eporner PervertMonkey',
8
- version: '2.0.20',
8
+ version: '2.1.3',
9
9
  description:
10
10
  'Infinite scroll [optional], Filter by Title, Uploader, Duration and HD, Sort by Views and Duration',
11
11
  match: ['https://*.eporner.com/*', 'https://*.eporner.*/*'],
12
+ supportURL: 'https://sleazyfork.org/en/scripts/552559-eporner-pervertmonkey/feedback',
12
13
  };
13
14
 
14
- const show_video_prev = (unsafeWindow as any).show_video_prev;
15
-
16
15
  const rules = new Rules({
17
16
  paginationStrategyOptions: {
18
17
  paginationSelector: '.numlist2',
18
+ // pathnameSelector: /\/(\d+)\/?$/,
19
+ // pathnameSelector: /\/(\d+)\/([\w-]+\/)?$/
20
+ overwritePaginationLast: (n, _) => (n === 9 ? 9999 : n),
19
21
  },
20
22
  thumbs: { selector: 'div[id^=vf][data-id]' },
21
23
  thumb: {
22
24
  selectors: {
23
- title: 'a',
25
+ title: 'a[href*=video-]',
24
26
  uploader: '[title="Uploader"]',
25
27
  duration: '[title="Duration"]',
26
28
  views: { selector: '[title="Views"]', type: 'float' },
@@ -33,12 +35,38 @@ const rules = new Rules({
33
35
  },
34
36
  containerSelectorLast: '#vidresults',
35
37
  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
- { quality2k: (el, state) => !!state.quality2k && el.quality !== 2 },
41
- { quality4k: (el, state) => !!state.quality4k && el.quality !== 4 },
38
+ {
39
+ qualityFilter: {
40
+ handle(el, state) {
41
+ const hasAnyQualitySelected =
42
+ state.quality360 ||
43
+ state.quality480 ||
44
+ state.quality720 ||
45
+ state.quality1080 ||
46
+ state.quality2k ||
47
+ state.quality4k;
48
+
49
+ if (!hasAnyQualitySelected) return false;
50
+
51
+ return !(
52
+ (state.quality360 && el.quality === 360) ||
53
+ (state.quality480 && el.quality === 480) ||
54
+ (state.quality720 && el.quality === 720) ||
55
+ (state.quality1080 && el.quality === 1080) ||
56
+ (state.quality2k && el.quality === 2) ||
57
+ (state.quality4k && el.quality === 4)
58
+ );
59
+ },
60
+ deps: [
61
+ 'quality360',
62
+ 'quality480',
63
+ 'quality720',
64
+ 'quality1080',
65
+ 'quality2k',
66
+ 'quality4k',
67
+ ],
68
+ },
69
+ },
42
70
  ],
43
71
  schemeOptions: [
44
72
  'Title Filter',
@@ -59,15 +87,13 @@ const rules = new Rules({
59
87
  'Badge',
60
88
  'Advanced',
61
89
  ],
62
- gropeStrategy: 'all-in-all',
90
+ gropeStrategy: 'all-in-one',
63
91
  animatePreview,
64
92
  });
65
93
 
66
94
  function animatePreview(doc: HTMLElement) {
67
95
  OnHover.create(doc, 'div[id^=vf][data-id]', (e) => {
68
- const target = e as HTMLImageElement;
69
- const thumb = target.closest('[data-id]');
70
- const id = thumb?.getAttribute('data-id');
71
- show_video_prev(id);
96
+ const thumb = e.closest('[data-id]');
97
+ (unsafeWindow as any).EP.thumbs.preview.start(thumb);
72
98
  });
73
99
  }
@@ -4,7 +4,7 @@ import { Rules } from '../../core';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'Erome PervertMonkey',
7
- version: '5.0.19',
7
+ version: '5.0.21',
8
8
  description:
9
9
  'Infinite scroll [optional], Filter by Title, Uploader and Video/Photo albums, Sort by Views. Show/Hide Photos in album. Remove disclaimer. Restore Search Bar',
10
10
  match: ['*://*.erome.com/*'],
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Eroprofile PervertMonkey',
6
- version: '2.0.18',
6
+ version: '2.0.21',
7
7
  description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
8
8
  match: ['https://*.eroprofile.com/*'],
9
9
  };
@@ -12,17 +12,17 @@ document.querySelector('.videoGrid')?.after(document.querySelector('.clB') as HT
12
12
 
13
13
  const rules = new Rules({
14
14
  paginationStrategyOptions: {
15
- paginationSelector: '.boxNav2',
15
+ paginationSelector: '.page-nav',
16
16
  searchParamSelector: 'pnum',
17
17
  },
18
- thumbs: { selector: '.video' },
18
+ thumbs: { selector: '.grid-tile-video' },
19
19
  thumb: {
20
20
  selectors: {
21
- title: '[title]',
22
- duration: '.videoDur',
21
+ title: '.title',
22
+ duration: '.duration',
23
23
  },
24
24
  },
25
- containerSelector: '.videoGrid',
25
+ containerSelector: 'div:has(>.grid-tile-video)',
26
26
  schemeOptions: [
27
27
  'Title Filter',
28
28
  'Duration Filter',
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Javhdporn PervertMonkey',
6
- version: '3.0.18',
6
+ version: '3.0.20',
7
7
  description:
8
8
  'Infinite scroll [optional], Filter by Title and Duration, Sort By Duration and Views',
9
9
  match: ['https://*.javhdporn.net/*', 'https://*.javhdporn.*/*'],
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Missav PervertMonkey',
6
- version: '3.0.17',
6
+ version: '3.0.19',
7
7
  description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
8
8
  match: [
9
9
  'https://*.missav123.com/*',
@@ -1,14 +1,20 @@
1
1
  import type { MonkeyUserScript } from 'vite-plugin-monkey';
2
2
  import { GM_addElement, GM_addStyle, unsafeWindow } from '$';
3
3
  import { Rules } from '../../core';
4
- import { fetchWith, OnHover, replaceElementTag, Tick } from '../../utils';
4
+ import {
5
+ fetchWith,
6
+ OnHover,
7
+ replaceElementTag,
8
+ Tick,
9
+ waitForElementToAppear,
10
+ } from '../../utils';
5
11
 
6
12
  export const meta: MonkeyUserScript = {
7
13
  name: 'Motherless PervertMonkey',
8
- version: '5.0.19',
14
+ version: '5.0.25',
9
15
  description:
10
16
  'Infinite scroll [optional], Filter by Title, Uploader and Duration, Sort by Duration and Views',
11
- match: ['https://motherless.com/*'],
17
+ match: ['https://motherless.xxx/*'],
12
18
  grant: ['GM_addElement', 'GM_addStyle', 'unsafeWindow'],
13
19
  };
14
20
 
@@ -16,7 +22,7 @@ export const meta: MonkeyUserScript = {
16
22
  const $ = (unsafeWindow as any).$;
17
23
 
18
24
  const rules = new Rules({
19
- containerSelectorLast: '.content-inner',
25
+ containerSelector: '.content-inner',
20
26
  thumbs: { selector: '.thumb-container, .mobile-thumb' },
21
27
  thumb: {
22
28
  selectors: {
@@ -38,7 +44,12 @@ const rules = new Rules({
38
44
  'Duration Filter',
39
45
  'Sort By',
40
46
  'Badge',
41
- 'Advanced',
47
+ {
48
+ title: 'Advanced',
49
+ content: [
50
+ { applyTitleFiltersToSearch: false, label: 'apply title filters to search' },
51
+ ],
52
+ },
42
53
  ],
43
54
  });
44
55
 
@@ -47,7 +58,8 @@ function animatePreview(_: HTMLElement) {
47
58
 
48
59
  function onOver(target: HTMLElement) {
49
60
  $('.video').off();
50
- const container = target.querySelector('.desktop-thumb.video') as HTMLElement;
61
+ const container = target.querySelector('.video') as HTMLElement;
62
+ if (!container) return;
51
63
  const img = container.querySelector('img.static') as HTMLImageElement;
52
64
  const stripSrc = img.getAttribute('data-strip-src');
53
65
 
@@ -93,8 +105,6 @@ function animatePreview(_: HTMLElement) {
93
105
  OnHover.create(document.body, '.thumb-container, .mobile-thumb', onOver);
94
106
  }
95
107
 
96
- //====================================================================================================
97
-
98
108
  function fixURLs() {
99
109
  document.querySelectorAll<HTMLElement>('.gallery-container').forEach((g) => {
100
110
  const x = (g.innerText as string).match(/([\d|.]+)k? videos/gi)?.[0];
@@ -108,12 +118,11 @@ function fixURLs() {
108
118
  });
109
119
 
110
120
  document
111
- .querySelectorAll<HTMLAnchorElement>('a[href^="/term/"]:not([href^="/term/videos/"])')
121
+ .querySelectorAll<HTMLAnchorElement>(
122
+ 'a[href^="/term/"]:not([href^="/term/videos/"]):not(.pop):not([rel])',
123
+ )
112
124
  .forEach((a) => {
113
- a.href = a.href.replace(
114
- /[\w|+]+$/,
115
- (v) => `videos/${v}?term=${v}&range=0&size=0&sort=date`,
116
- );
125
+ a.href = a.href.replace(/[\w|+]+$/, (v) => `videos/${v}?term=${v}`);
117
126
  });
118
127
 
119
128
  document
@@ -123,8 +132,6 @@ function fixURLs() {
123
132
  });
124
133
  }
125
134
 
126
- //====================================================================================================
127
-
128
135
  function mobileGalleryToDesktop(e: HTMLElement) {
129
136
  e.querySelector('.clear-left')?.remove();
130
137
  const container = e.firstElementChild as HTMLElement;
@@ -141,26 +148,27 @@ async function desktopAddMobGalleries() {
141
148
  const galleries = document.querySelector('.media-related-galleries');
142
149
  if (!galleries) return;
143
150
 
144
- const galleriesContainer = galleries.querySelector('.content-inner') as HTMLElement;
145
- const galleriesCount = galleries.querySelectorAll('.gallery-container').length;
146
- const mobDom = await fetchWith(window.location.href, { type: 'html', mobile: true });
147
- const mobGalleries = (mobDom as HTMLElement).querySelectorAll<HTMLElement>(
148
- '.ml-gallery-thumb',
149
- );
150
-
151
- for (const [i, x] of mobGalleries.entries()) {
152
- if (i > galleriesCount - 1) {
153
- galleriesContainer.append(mobileGalleryToDesktop(x));
154
- }
155
- }
156
- }
151
+ await new Promise((resolve) => {
152
+ waitForElementToAppear(document, '.thumb-container.gallery-container', resolve);
153
+ });
157
154
 
158
- //====================================================================================================
155
+ const mobDom = await fetchWith<HTMLElement>(window.location.href, {
156
+ type: 'html',
157
+ mobile: true,
158
+ });
159
+ const mobGalleries = mobDom.querySelectorAll<HTMLElement>('.ml-gallery-thumb');
159
160
 
160
- const overwrite1 = (x: string) => `@media only screen and (max-width: 1280px) {
161
- #categories-page.inner ${x} }`;
161
+ const galleriesContainer = galleries.querySelector('.content-inner') as HTMLElement;
162
162
 
163
- rules.dataManager.dataFilter.createCssFilters(overwrite1);
163
+ for (const x of mobGalleries.values()) {
164
+ const mobGallery = mobileGalleryToDesktop(x);
165
+ const mobGalleryId = (mobGallery.querySelector('a') as HTMLAnchorElement).href
166
+ .split('/')
167
+ .at(-1);
168
+ if (galleriesContainer.querySelector(`a[href*=${mobGalleryId}]`)) continue;
169
+ galleriesContainer.append(mobGallery);
170
+ }
171
+ }
164
172
 
165
173
  GM_addStyle(`
166
174
  .img-container, .desktop-thumb { min-height: 150px; max-height: 150px; }
@@ -176,8 +184,6 @@ document
176
184
  .querySelector('.ml-pagination')
177
185
  ?.before(GM_addElement('div', { class: 'clear-left' }));
178
186
 
179
- //====================================================================================================
180
-
181
187
  function applySearchFilters() {
182
188
  let pathname = window.location.pathname;
183
189
 
@@ -195,11 +201,10 @@ function applySearchFilters() {
195
201
  }
196
202
  }
197
203
 
198
- //====================================================================================================
199
-
200
204
  desktopAddMobGalleries().then(() => fixURLs());
201
205
 
202
206
  const IS_SEARCH = /^\/term\//.test(location.pathname);
203
- if (IS_SEARCH) {
207
+
208
+ if (IS_SEARCH && rules.store.state.applyTitleFiltersToSearch) {
204
209
  applySearchFilters();
205
210
  }
@@ -4,7 +4,7 @@ import { Rules } from '../../core';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'NameThatPorn PervertMonkey',
7
- version: '3.0.18',
7
+ version: '3.0.20',
8
8
  description: 'Infinite scroll [optional], Filter by Title, Uploader and Solved/Unsolved',
9
9
  match: ['https://namethatporn.com/*'],
10
10
  };
@@ -4,7 +4,7 @@ import { parseHtml } from '../../utils';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'NHentai PervertMonkey',
7
- version: '4.0.18',
7
+ version: '4.0.20',
8
8
  description: 'Infinite scroll [optional], Filter by Title',
9
9
  match: ['https://*.nhentai.net/*', 'https://*.nhentai.*/*'],
10
10
  };
@@ -5,7 +5,7 @@ import { fetchHtml, parseUrl } from '../../utils';
5
5
 
6
6
  export const meta: MonkeyUserScript = {
7
7
  name: 'Obmenvsem PervertMonkey',
8
- version: '1.0.20',
8
+ version: '1.0.22',
9
9
  description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
10
10
  match: [
11
11
  'https://*.obmenvsem.com/*',
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'PornHub PervertMonkey',
6
- version: '4.0.18',
6
+ version: '4.0.20',
7
7
  description:
8
8
  'Infinite scroll [optional]. Filter by Title, Uploader and Duration. Sort by Duration and Views',
9
9
  match: ['https://*.pornhub.com/*'],
@@ -0,0 +1,27 @@
1
+ import type { MonkeyUserScript } from 'vite-plugin-monkey';
2
+ import { Rules } from '../../core';
3
+
4
+ export const meta: MonkeyUserScript = {
5
+ name: 'Simpcity PervertMonkey',
6
+ version: '1.0.2',
7
+ description: 'Infinite scroll [optional], Filter by Title',
8
+ match: 'https://simpcity.cr/threads/*',
9
+ 'run-at': 'document-end',
10
+ };
11
+
12
+ const rules = new Rules({
13
+ containerSelector: '.js-replyNewMessageContainer',
14
+ paginationStrategyOptions: {
15
+ paginationSelector: '.block-container + * .pageNav',
16
+ pathnameSelector: /\/page-(\d+)\/?$/,
17
+ },
18
+ thumbs: {
19
+ selector: 'article.message',
20
+ },
21
+ thumb: {
22
+ strategy: 'auto-text',
23
+ getUrlSelector: 'a[href*=threads]',
24
+ },
25
+ gropeStrategy: 'all-in-all',
26
+ schemeOptions: ['Title Filter', 'Badge', 'Advanced'],
27
+ });
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Socialmediagirls PervertMonkey',
6
- version: '1.0.2',
6
+ version: '1.0.5',
7
7
  description: 'Infinite scroll [optional], Filter by Title',
8
8
  match: 'https://forums.socialmediagirls.com/threads/*',
9
9
  'run-at': 'document-end',
@@ -12,14 +12,15 @@ export const meta: MonkeyUserScript = {
12
12
  const rules = new Rules({
13
13
  containerSelector: '.js-replyNewMessageContainer',
14
14
  paginationStrategyOptions: {
15
- paginationSelector: '.pageNavWrapper',
16
- pathnameSelector: /\d+/,
15
+ paginationSelector: '.block-container + * .pageNav',
16
+ pathnameSelector: /\/page-(\d+)\/?$/,
17
17
  },
18
18
  thumbs: {
19
19
  selector: 'article.message',
20
20
  },
21
21
  thumb: {
22
22
  strategy: 'auto-text',
23
+ getUrlSelector: 'a[href*=threads]',
23
24
  },
24
25
  gropeStrategy: 'all-in-all',
25
26
  schemeOptions: ['Title Filter', 'Badge', 'Advanced'],
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'SpankBang.com PervertMonkey',
6
- version: '4.0.18',
6
+ version: '4.1.0',
7
7
  description:
8
8
  'Infinite scroll [optional]. Filter by Title and Duration. Sort by Duration and Views',
9
9
  match: ['https://*.spankbang.com/*', 'https://*.spankbang.*/*'],
@@ -27,9 +27,25 @@ const rules = new Rules({
27
27
  thumbImg: { strategy: 'auto' },
28
28
  gropeStrategy: 'all-in-all',
29
29
  customDataFilterFns: [
30
- { qualityLow: (e, state) => !!state.qualityLow && e.quality !== '' },
31
- { qualityHD: (e, state) => !!state.qualityHD && e.quality !== 'HD' },
32
- { quality4k: (e, state) => !!state.quality4k && e.quality !== '4K' },
30
+ {
31
+ qualityFilter: {
32
+ handle(el, state) {
33
+ const hasAnyQualitySelected =
34
+ state.qualityLow ||
35
+ state.qualityHD ||
36
+ state.quality4k;
37
+
38
+ if (!hasAnyQualitySelected) return false;
39
+
40
+ return !(
41
+ (state.qualityLow && el.quality === '') ||
42
+ (state.qualityHD && el.quality === 'HD') ||
43
+ (state.quality4k && el.quality === '4K')
44
+ );
45
+ },
46
+ deps: ['qualityLow', 'qualityHD', 'quality4k'],
47
+ },
48
+ },
33
49
  ],
34
50
  schemeOptions: [
35
51
  'Title Filter',