pervert-monkey 1.0.25 → 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 -541
  3. package/dist/core/pervertmonkey.core.es.js +8001 -9281
  4. package/dist/core/pervertmonkey.core.es.js.map +1 -1
  5. package/dist/core/pervertmonkey.core.umd.js +8829 -10051
  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 -152
  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 +26 -23
  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 -4
  31. package/src/core/parsers/pagination-parser/pagination-strategies/PaginationStrategyPathnameParams.ts +36 -1
  32. package/src/userscripts/index.ts +1 -1
  33. package/src/userscripts/meta.json +1 -1
  34. package/src/userscripts/scripts/3hentai.ts +1 -1
  35. package/src/userscripts/scripts/camgirlfinder.ts +1 -1
  36. package/src/userscripts/scripts/camwhores.ts +2 -2
  37. package/src/userscripts/scripts/e-hentai.ts +1 -1
  38. package/src/userscripts/scripts/ebalka.ts +1 -1
  39. package/src/userscripts/scripts/eporner.ts +41 -15
  40. package/src/userscripts/scripts/erome.ts +1 -1
  41. package/src/userscripts/scripts/eroprofile.ts +6 -6
  42. package/src/userscripts/scripts/javhdporn.ts +1 -1
  43. package/src/userscripts/scripts/missav.ts +1 -1
  44. package/src/userscripts/scripts/motherless.ts +28 -19
  45. package/src/userscripts/scripts/namethatporn.ts +1 -1
  46. package/src/userscripts/scripts/nhentai.ts +1 -1
  47. package/src/userscripts/scripts/obmenvsem.ts +1 -1
  48. package/src/userscripts/scripts/pornhub.ts +1 -1
  49. package/src/userscripts/scripts/simpcity.ts +1 -1
  50. package/src/userscripts/scripts/{socialmediagirls copy.ts → socialmediagirls.ts} +1 -1
  51. package/src/userscripts/scripts/spankbang.ts +20 -4
  52. package/src/userscripts/scripts/thisvid.ts +8 -21
  53. package/src/userscripts/scripts/xhamster.ts +11 -3
  54. package/src/userscripts/scripts/xvideos.ts +26 -7
  55. package/src/utils/fetch/index.ts +2 -0
  56. package/src/utils/objects/memoize.ts +1 -9
  57. package/dist/test/test.user.js +0 -10301
  58. package/dist/userscripts/socialmediagirls copy.user.js +0 -41
@@ -1 +1 @@
1
- import './scripts/motherless';
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.19',
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.18',
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.22',
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.19',
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.20',
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.21',
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.20',
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.19',
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.19',
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.18',
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.20',
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: {
@@ -142,25 +148,28 @@ async function desktopAddMobGalleries() {
142
148
  const galleries = document.querySelector('.media-related-galleries');
143
149
  if (!galleries) return;
144
150
 
151
+ await new Promise((resolve) => {
152
+ waitForElementToAppear(document, '.thumb-container.gallery-container', resolve);
153
+ });
154
+
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');
160
+
145
161
  const galleriesContainer = galleries.querySelector('.content-inner') as HTMLElement;
146
- const galleriesCount = galleries.querySelectorAll('.gallery-container').length;
147
- const mobDom = await fetchWith(window.location.href, { type: 'html', mobile: true });
148
- const mobGalleries = (mobDom as HTMLElement).querySelectorAll<HTMLElement>(
149
- '.ml-gallery-thumb',
150
- );
151
-
152
- for (const [i, x] of mobGalleries.entries()) {
153
- if (i > galleriesCount - 1) {
154
- galleriesContainer.append(mobileGalleryToDesktop(x));
155
- }
162
+
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);
156
170
  }
157
171
  }
158
172
 
159
- const overwrite1 = (x: string) => `@media only screen and (max-width: 1280px) {
160
- #categories-page.inner ${x} }`;
161
-
162
- rules.dataManager.dataFilter.createCssFilters(overwrite1);
163
-
164
173
  GM_addStyle(`
165
174
  .img-container, .desktop-thumb { min-height: 150px; max-height: 150px; }
166
175
 
@@ -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.19',
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.19',
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.21',
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.19',
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/*'],
@@ -3,7 +3,7 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Simpcity PervertMonkey',
6
- version: '1.0.1',
6
+ version: '1.0.2',
7
7
  description: 'Infinite scroll [optional], Filter by Title',
8
8
  match: 'https://simpcity.cr/threads/*',
9
9
  'run-at': 'document-end',
@@ -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.4',
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',
@@ -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.19',
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',
@@ -21,7 +21,7 @@ import {
21
21
 
22
22
  export const meta: MonkeyUserScript = {
23
23
  name: 'ThisVid.com PervertMonkey',
24
- version: '8.0.20',
24
+ version: '8.0.23',
25
25
  description:
26
26
  'Infinite scroll [optional]. Preview for private videos. Filter by Title, Duration, Quality and Public/Private. Sort by Duration and Views. Private/Public feed of friends uploads. Check access to private vids. Mass friend request button. Sorts messages. Download button 📼',
27
27
  match: ['https://*.thisvid.com/*'],
@@ -138,8 +138,6 @@ GM_addStyle(`
138
138
  .friendProfile { background: radial-gradient(circle, rgb(28, 42, 50) 48%, rgb(0, 0, 0) 100%) !important; }
139
139
  `);
140
140
 
141
- //====================================================================================================
142
-
143
141
  function friend(id: string, message = '') {
144
142
  return fetch(
145
143
  `https://thisvid.com/members/${id}/?action=add_to_friends_complete&function=get_block&block_id=member_profile_view_view_profile&format=json&mode=async&message=${message}`,
@@ -209,7 +207,7 @@ async function friendMemberFriends(orientationFilter?: string) {
209
207
  await friend(fid);
210
208
  }
211
209
  }
212
- }, 60),
210
+ }, 5),
213
211
  )
214
212
  .forEach(() => {});
215
213
  }
@@ -247,8 +245,6 @@ function initFriendship() {
247
245
  }
248
246
  }
249
247
 
250
- //====================================================================================================
251
-
252
248
  type MemberData = {
253
249
  uploadedPublic: number;
254
250
  uploadedPrivate: number;
@@ -284,8 +280,6 @@ async function getMemberData(id: string) {
284
280
  return data as MemberData;
285
281
  }
286
282
 
287
- //====================================================================================================
288
-
289
283
  function requestAccessVideoPage() {
290
284
  const holder = document.querySelector('.video-holder > p');
291
285
  if (holder) {
@@ -308,6 +302,8 @@ const requestPrivateAccess = (e: PointerEvent, memberid: string) => {
308
302
  );
309
303
  };
310
304
 
305
+ Object.assign(unsafeWindow, { requestPrivateAccess });
306
+
311
307
  async function checkPrivateVideoAccess(url: string) {
312
308
  const html = await fetchHtml(url);
313
309
  const holder = html.querySelector('.video-holder > p');
@@ -317,6 +313,9 @@ async function checkPrivateVideoAccess(url: string) {
317
313
  const uploaderEl = (
318
314
  holder ? holder.querySelector('a') : html.querySelector('a.author')
319
315
  ) as HTMLAnchorElement;
316
+
317
+ if (!uploaderEl) return { access: false, uploaderURL: '' };
318
+
320
319
  const uploaderURL = uploaderEl.href.match(/\d+/)?.at(-1) as string;
321
320
  const uploaderName = uploaderEl.innerText;
322
321
 
@@ -344,7 +343,7 @@ async function requestAccess() {
344
343
  const { access, uploaderURL } = await checkPrivateVideoAccess(url);
345
344
 
346
345
  thumb.classList.add(access ? 'haveAccess' : 'haveNoAccess');
347
- if (access) return;
346
+ if (access || uploaderURL === '') return;
348
347
 
349
348
  if (rules.store.state.autoRequestAccess && !uploadersChecked.has(uploaderURL)) {
350
349
  acceptFriendship(uploaderURL);
@@ -357,8 +356,6 @@ async function requestAccess() {
357
356
  }
358
357
  }
359
358
 
360
- //====================================================================================================
361
-
362
359
  const createDownloadButton = () =>
363
360
  downloader({
364
361
  after: '.share_btn',
@@ -367,8 +364,6 @@ const createDownloadButton = () =>
367
364
  doBefore: () => $('.fp-ui').click(),
368
365
  });
369
366
 
370
- //====================================================================================================
371
-
372
367
  function animatePreview(_: HTMLElement) {
373
368
  const tick = new Tick(750);
374
369
  $('img[alt!="Private"]').off();
@@ -402,8 +397,6 @@ function animatePreview(_: HTMLElement) {
402
397
  );
403
398
  }
404
399
 
405
- //====================================================================================================
406
-
407
400
  async function getMemberVideos(id: string, type = 'private') {
408
401
  const { uploadedPrivate, uploadedPublic, name } = await getMemberData(id);
409
402
  const videosCount = type === 'private' ? uploadedPrivate : uploadedPublic;
@@ -625,8 +618,6 @@ async function createPrivateFeed() {
625
618
  return rulesConfig;
626
619
  }
627
620
 
628
- //====================================================================================================
629
-
630
621
  function deleteMsg(id: string) {
631
622
  fetch(
632
623
  `https://thisvid.com/my_messages/inbox/?mode=async&format=json&action=delete&function=get_block&block_id=list_messages_my_conversation_messages&delete[]=${id}`,
@@ -673,8 +664,6 @@ function highlightMessages() {
673
664
  });
674
665
  }
675
666
 
676
- //====================================================================================================
677
-
678
667
  if (LOGGED_IN) {
679
668
  rules.store.eventSubject.subscribe((x) => {
680
669
  if (x.includes('check access')) {
@@ -697,8 +686,6 @@ if (IS_OTHER_MEMBER_PAGE) {
697
686
  initFriendship();
698
687
  }
699
688
 
700
- Object.assign(unsafeWindow, { requestPrivateAccess });
701
-
702
689
  if (IS_MEMBER_FRIEND) {
703
690
  document.querySelector('.profile')?.classList.add('friendProfile');
704
691
  }
@@ -14,7 +14,7 @@ import {
14
14
 
15
15
  export const meta: MonkeyUserScript = {
16
16
  name: 'Xhamster PervertMonkey',
17
- version: '5.0.21',
17
+ version: '5.0.23',
18
18
  description:
19
19
  'Infinite scroll [optional], Filter by Title, Duration and Watched/Unwatched. Sort by Duration and Views',
20
20
  match: ['https://*.xhamster.com/*', 'https://*.xhamster.*/*'],
@@ -88,7 +88,15 @@ const rules = new Rules({
88
88
  paginationStrategyOptions,
89
89
  getPaginationData,
90
90
  containerSelectorLast: '.thumb-list',
91
- thumbs: { selector: '.video-thumb' },
91
+ thumbs: {
92
+ selector: '.video-thumb',
93
+ transform: (thumb: HTMLElement) => {
94
+ if (thumb.querySelector('[class*=placeholder]')) {
95
+ console.log('has placeholder', thumb);
96
+ }
97
+ return thumb;
98
+ },
99
+ },
92
100
  thumb: {
93
101
  selectors: {
94
102
  title: '.video-thumb-info__name,.video-thumb-info>a',
@@ -99,7 +107,7 @@ const rules = new Rules({
99
107
  },
100
108
  thumbImg: {
101
109
  strategy: 'auto',
102
- remove: '[loading]',
110
+ // remove: '[loading]',
103
111
  },
104
112
  gropeStrategy: 'all-in-all',
105
113
  customDataFilterFns: [
@@ -5,7 +5,7 @@ import { exterminateVideo, OnHover, parseHtml } from '../../utils';
5
5
 
6
6
  export const meta: MonkeyUserScript = {
7
7
  name: 'XVideos PervertMonkey',
8
- version: '4.0.21',
8
+ version: '4.1.0',
9
9
  description:
10
10
  'Infinite scroll [optional], Filter by Title, Uploader and Duration. Sort by Duration and Views.',
11
11
  match: 'https://*.xvideos.com/*',
@@ -36,12 +36,31 @@ const rules = new Rules({
36
36
  },
37
37
  },
38
38
  customDataFilterFns: [
39
- { qualityLow: (e, state) => !!state.qualityLow && e.quality !== '' },
40
- { quality360: (e, state) => !!state.quality360 && e.quality !== '360p' },
41
- { quality720: (e, state) => !!state.quality720 && e.quality !== '720p' },
42
- { quality1080: (e, state) => !!state.quality1080 && e.quality !== '1080p' },
43
- { quality1440: (e, state) => !!state.quality1440 && e.quality !== '1440p' },
44
- { quality4k: (e, state) => !!state.quality4k && e.quality !== '4k' },
39
+ {
40
+ qualityFilter: {
41
+ handle(el, state) {
42
+ const hasAnyQualitySelected =
43
+ state.qualityLow ||
44
+ state.quality360 ||
45
+ state.quality720 ||
46
+ state.quality1080 ||
47
+ state.quality1440 ||
48
+ state.quality4k;
49
+
50
+ if (!hasAnyQualitySelected) return false;
51
+
52
+ return !(
53
+ (state.qualityLow && el.quality === '') ||
54
+ (state.quality360 && el.quality === '360p') ||
55
+ (state.quality720 && el.quality === '720p') ||
56
+ (state.quality1080 && el.quality === '1080p') ||
57
+ (state.quality1440 && el.quality === '1440p') ||
58
+ (state.quality4k && el.quality === '4k')
59
+ );
60
+ },
61
+ deps: ['qualityLow', 'quality360', 'quality720', 'quality1080', 'quality1440', 'quality4k'],
62
+ },
63
+ },
45
64
  ],
46
65
  schemeOptions: [
47
66
  'Title Filter',
@@ -31,7 +31,9 @@ export async function fetchWith<T extends JSON | string | HTMLElement>(
31
31
 
32
32
  export const fetchJson = (input: RequestInfo | URL) =>
33
33
  fetchWith<JSON>(input, { type: 'json' });
34
+
34
35
  export const fetchHtml = (input: RequestInfo | URL) =>
35
36
  fetchWith<HTMLElement>(input, { type: 'html' });
37
+
36
38
  export const fetchText = (input: RequestInfo | URL) =>
37
39
  fetchWith<string>(input, { type: 'text' });
@@ -10,15 +10,7 @@ export function memoize<T extends AnyFunction>(fn: T): MemoizedFunction<T> {
10
10
 
11
11
  const memoizedFunction = ((...args: Parameters<T>): ReturnType<T> => {
12
12
  const key = JSON.stringify(args);
13
-
14
- if (cache.has(key)) {
15
- return cache.get(key) as ReturnType<T>;
16
- }
17
-
18
- const result = fn(...args);
19
- cache.set(key, result);
20
-
21
- return result;
13
+ return cache.getOrInsertComputed(key, () => fn(...args));
22
14
  }) as MemoizedFunction<T>;
23
15
 
24
16
  return memoizedFunction;