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
@@ -4,8 +4,9 @@ import { Rules } from '../../core';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'Erome PervertMonkey',
7
- version: '5.0.3',
8
- description: 'Infinite scroll [optional], Filter by Title and Video/Photo albums',
7
+ version: '5.0.6',
8
+ description:
9
+ 'Infinite scroll [optional], Filter by Title, Uploader and Video/Photo albums, Sort by Views',
9
10
  match: ['*://*.erome.com/*'],
10
11
  };
11
12
 
@@ -22,13 +23,12 @@ const rules = new Rules({
22
23
  selectors: {
23
24
  title: '.album-title',
24
25
  uploader: '.album-user',
25
- videoAlbum: { type: 'boolean', selector: '.album-videos' },
26
+ videoAlbum: { selector: '.album-videos', type: 'boolean' },
27
+ views: { selector: '.album-bottom-views', type: 'float' },
26
28
  },
27
29
  },
28
30
  storeOptions: { showPhotos: true },
29
- customDataSelectorFns: [
30
- 'filterInclude',
31
- 'filterExclude',
31
+ customDataFilterFns: [
32
32
  {
33
33
  filterPhotoAlbums: (el, state) =>
34
34
  (state.filterPhotoAlbums && !el.videoAlbum) as boolean,
@@ -39,7 +39,8 @@ const rules = new Rules({
39
39
  },
40
40
  ],
41
41
  schemeOptions: [
42
- 'Text Filter',
42
+ 'Title Filter',
43
+ 'Uploader Filter',
43
44
  {
44
45
  title: 'Filter Albums',
45
46
  content: [
@@ -53,6 +54,7 @@ const rules = new Rules({
53
54
  },
54
55
  ],
55
56
  },
57
+ 'Sort By Views',
56
58
  'Badge',
57
59
  'Advanced',
58
60
  ],
@@ -3,8 +3,8 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Eroprofile PervertMonkey',
6
- version: '2.0.3',
7
- description: 'Infinite scroll [optional], Filter by Title and Duration',
6
+ version: '2.0.6',
7
+ description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
8
8
  match: ['https://*.eroprofile.com/*'],
9
9
  };
10
10
 
@@ -23,18 +23,10 @@ const rules = new Rules({
23
23
  },
24
24
  },
25
25
  containerSelector: '.videoGrid',
26
- customDataSelectorFns: ['filterInclude', 'filterExclude', 'filterDuration'],
27
26
  schemeOptions: [
28
- 'Text Filter',
27
+ 'Title Filter',
29
28
  'Duration Filter',
30
- {
31
- title: 'Sort By ',
32
- content: [
33
- {
34
- 'sort by duration': () => {},
35
- },
36
- ],
37
- },
29
+ 'Sort By Duration',
38
30
  'Badge',
39
31
  'Advanced',
40
32
  ],
@@ -3,12 +3,10 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Javhdporn PervertMonkey',
6
- version: '3.0.3',
7
- description: 'Infinite scroll [optional], Filter by Title and Duration',
8
- match: [
9
- "https://*.javhdporn.net/*",
10
- "https://*.javhdporn.*/*"
11
- ],
6
+ version: '3.0.6',
7
+ description:
8
+ 'Infinite scroll [optional], Filter by Title and Duration, Sort By Duration and Views',
9
+ match: ['https://*.javhdporn.net/*', 'https://*.javhdporn.*/*'],
12
10
  };
13
11
 
14
12
  const rules = new Rules({
@@ -18,10 +16,11 @@ const rules = new Rules({
18
16
  selectors: {
19
17
  title: 'header.entry-header',
20
18
  duration: '.duration',
21
- }
19
+ views: { selector: '.views', type: 'float' },
20
+ },
22
21
  },
23
22
  paginationStrategyOptions: {
24
23
  pathnameSelector: /\/page\/(\d+)\/?$/,
25
24
  },
26
- schemeOptions: ['Text Filter', 'Badge', 'Duration Filter', 'Advanced'],
25
+ schemeOptions: ['Title Filter', 'Duration Filter', 'Sort By', 'Badge', 'Advanced'],
27
26
  });
@@ -3,8 +3,8 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'Missav PervertMonkey',
6
- version: '3.0.3',
7
- description: 'Infinite scroll [optional], Filter by Title and Duration',
6
+ version: '3.0.6',
7
+ description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
8
8
  match: [
9
9
  'https://*.missav123.com/*',
10
10
  'https://*.missav.*/*',
@@ -24,8 +24,14 @@ const rules = new Rules({
24
24
  selectors: {
25
25
  title: 'div > div > a.text-secondary',
26
26
  duration: 'div > a > span.text-xs',
27
- }
27
+ },
28
28
  },
29
29
  thumbImg: { strategy: 'auto' },
30
- schemeOptions: ['Text Filter', 'Duration Filter', 'Badge', 'Advanced'],
30
+ schemeOptions: [
31
+ 'Title Filter',
32
+ 'Duration Filter',
33
+ 'Sort By Duration',
34
+ 'Badge',
35
+ 'Advanced',
36
+ ],
31
37
  });
@@ -5,8 +5,8 @@ import { fetchWith, OnHover, replaceElementTag, Tick } from '../../utils';
5
5
 
6
6
  export const meta: MonkeyUserScript = {
7
7
  name: 'Motherless PervertMonkey',
8
- version: '5.0.4',
9
- description: 'Infinite scroll [optional], Filter by Title and Duration',
8
+ version: '5.0.7',
9
+ description: 'Infinite scroll [optional], Filter by Title, Uploader and Duration, Sort by Duration and Views',
10
10
  match: ['https://motherless.com/*'],
11
11
  grant: ['GM_addElement', 'GM_addStyle', 'unsafeWindow'],
12
12
  };
@@ -31,7 +31,14 @@ const rules = new Rules({
31
31
  },
32
32
  animatePreview,
33
33
  gropeStrategy: 'all-in-all',
34
- schemeOptions: ['Text Filter', 'Sort By', 'Duration Filter', 'Badge', 'Advanced'],
34
+ schemeOptions: [
35
+ 'Title Filter',
36
+ 'Uploader Filter',
37
+ 'Duration Filter',
38
+ 'Sort By',
39
+ 'Badge',
40
+ 'Advanced',
41
+ ],
35
42
  });
36
43
 
37
44
  function animatePreview(_: HTMLElement) {
@@ -174,9 +181,7 @@ function applySearchFilters() {
174
181
  let pathname = window.location.pathname;
175
182
 
176
183
  const wordsToFilter =
177
- (rules.store.state.filterExcludeWords as string)
178
- .replace(/f:/g, '')
179
- .match(/(?<!user:)\b\w+\b(?!\s*:)/g) || [];
184
+ (rules.store.state.filterExcludeWords as string).match(/\w+/g) || [];
180
185
 
181
186
  wordsToFilter
182
187
  .filter((w) => !pathname.includes(w))
@@ -4,8 +4,8 @@ import { Rules } from '../../core';
4
4
 
5
5
  export const meta: MonkeyUserScript = {
6
6
  name: 'NameThatPorn PervertMonkey',
7
- version: '3.0.3',
8
- description: 'Infinite scroll [optional], Filter by Title and Un/Solved',
7
+ version: '3.0.6',
8
+ description: 'Infinite scroll [optional], Filter by Title, Uploader and Solved/Unsolved',
9
9
  match: ['https://namethatporn.com/*'],
10
10
  };
11
11
 
@@ -16,26 +16,18 @@ const rules = new Rules({
16
16
  selectors: {
17
17
  title: '.item_title, .nsw_r_tit',
18
18
  uploader: '.item_answer b, .nsw_r_desc',
19
- solved: {
20
- type: 'boolean',
21
- selector: '.item_solved, .nsw_r_slvd',
22
- },
19
+ solved: { selector: '.item_solved, .nsw_r_slvd', type: 'boolean' },
23
20
  },
24
21
  },
25
22
  thumbImg: {
26
- selector: (img: HTMLImageElement) => {
27
- return (
28
- img.getAttribute('data-dyn')?.concat('.webp') || (img.getAttribute('src') as string)
29
- );
30
- },
23
+ selector: (img: HTMLImageElement) =>
24
+ img.getAttribute('data-dyn')?.concat('.webp') || (img.getAttribute('src') as string),
31
25
  },
32
26
  paginationStrategyOptions: {
33
27
  paginationSelector: '#smi_wrp, #nsw_p',
34
28
  },
35
29
  gropeStrategy: 'all-in-all',
36
- customDataSelectorFns: [
37
- 'filterInclude',
38
- 'filterExclude',
30
+ customDataFilterFns: [
39
31
  {
40
32
  filterSolved: (el, state) => (state.filterSolved && el.solved) as boolean,
41
33
  },
@@ -44,7 +36,8 @@ const rules = new Rules({
44
36
  },
45
37
  ],
46
38
  schemeOptions: [
47
- 'Text Filter',
39
+ 'Title Filter',
40
+ 'Uploader Filter',
48
41
  {
49
42
  title: 'Filter Status',
50
43
  content: [
@@ -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.3',
7
+ version: '4.0.6',
8
8
  description: 'Infinite scroll [optional], Filter by Title',
9
9
  match: ['https://*.nhentai.net/*', 'https://*.nhentai.*/*'],
10
10
  };
@@ -14,18 +14,10 @@ const IS_SEARCH_PAGE = /^\/search\//.test(location.pathname);
14
14
 
15
15
  const nhentaiRules = new Rules({
16
16
  thumbs: { selector: '.gallery' },
17
- thumb: {
18
- selectors: {
19
- title: '.caption'
20
- }
21
- },
22
- thumbImg: {
23
- strategy: 'auto',
24
- remove: 'auto'
25
- },
17
+ thumb: { selectors: { title: '.caption' } },
18
+ thumbImg: { strategy: 'auto', remove: 'auto' },
26
19
  containerSelectorLast: '.index-container, .container',
27
- customDataSelectorFns: ['filterInclude', 'filterExclude'],
28
- schemeOptions: ['Text Filter', 'Badge', 'Advanced'],
20
+ schemeOptions: ['Title Filter', 'Badge', 'Advanced'],
29
21
  gropeStrategy: 'all-in-all',
30
22
  });
31
23
 
@@ -57,7 +49,8 @@ function filtersUI() {
57
49
  const btns = parseHtml(`<div class="sort-type"></div>`);
58
50
  groupOfButtons.forEach((k) => {
59
51
  const btn = parseHtml(
60
- `<a href="#" ${state.custom[k] ? 'style="background: rgba(59, 49, 70, 1)"' : ''
52
+ `<a href="#" ${
53
+ state.custom[k] ? 'style="background: rgba(59, 49, 70, 1)"' : ''
61
54
  }>${filterDescriptors[k as keyof typeof filterDescriptors].name}</a>`,
62
55
  );
63
56
  btn.addEventListener('click', (e) => {
@@ -5,8 +5,8 @@ import { fetchHtml, parseUrl } from '../../utils';
5
5
 
6
6
  export const meta: MonkeyUserScript = {
7
7
  name: 'Obmensvem PervertMonkey',
8
- version: '1.0.3',
9
- description: 'Infinite scroll [optional], Filter by Title and Duration',
8
+ version: '1.0.6',
9
+ description: 'Infinite scroll [optional], Filter by Title and Duration, Sort by Duration',
10
10
  match: ['https://*.obmenvsem.com/*', 'https://*.obmenvsem.*/*'],
11
11
  grant: ['GM_addStyle', 'GM_addElement', 'unsafeWindow'],
12
12
  };
@@ -67,5 +67,11 @@ const rules = new Rules({
67
67
  return img.src;
68
68
  },
69
69
  },
70
- schemeOptions: ['Text Filter', 'Duration Filter', 'Badge', 'Advanced'],
70
+ schemeOptions: [
71
+ 'Title Filter',
72
+ 'Duration Filter',
73
+ 'Sort By Duration',
74
+ 'Badge',
75
+ 'Advanced',
76
+ ],
71
77
  });
@@ -3,8 +3,9 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'PornHub PervertMonkey',
6
- version: '4.0.3',
7
- description: 'Infinite scroll [optional]. Filter by Title and Duration',
6
+ version: '4.0.6',
7
+ description:
8
+ 'Infinite scroll [optional]. Filter by Title, Uploader and Duration. Sort by Duration and Views',
8
9
  match: ['https://*.pornhub.com/*'],
9
10
  exclude: 'https://*.pornhub.com/embed/*',
10
11
  };
@@ -19,20 +20,28 @@ const rules = new Rules({
19
20
  .filter((e) => e.children.length > 0 && e.checkVisibility())
20
21
  .pop() as HTMLElement,
21
22
 
22
- dataHomogenity: { id: true, className: true },
23
+ containerHomogenity: { id: true, className: true },
23
24
  thumbs: { selector: 'li[data-video-vkey]' },
24
25
  thumb: {
25
26
  selectors: {
26
27
  title: 'span.title',
27
28
  uploader: '.usernameWrap',
28
29
  duration: '.duration',
30
+ views: { selector: '.views', type: 'float' },
29
31
  },
30
32
  },
31
33
  thumbImg: {
32
34
  selector: ['data-mediumthumb', 'data-image'],
33
35
  },
34
36
  gropeStrategy: 'all-in-all',
35
- schemeOptions: ['Text Filter', 'Duration Filter', 'Badge', 'Advanced'],
37
+ schemeOptions: [
38
+ 'Title Filter',
39
+ 'Uploader Filter',
40
+ 'Duration Filter',
41
+ 'Sort By',
42
+ 'Badge',
43
+ 'Advanced',
44
+ ],
36
45
  });
37
46
 
38
47
  function bypassAgeVerification() {
@@ -3,8 +3,9 @@ import { Rules } from '../../core';
3
3
 
4
4
  export const meta: MonkeyUserScript = {
5
5
  name: 'SpankBang.com PervertMonkey',
6
- version: '4.0.3',
7
- description: 'Infinite scroll [optional]. Filter by Title and Duration',
6
+ version: '4.0.6',
7
+ description:
8
+ 'Infinite scroll [optional]. Filter by Title and Duration. Sort by Duration and Views',
8
9
  match: ['https://*.spankbang.com/*', 'https://*.spankbang.*/*'],
9
10
  };
10
11
 
@@ -17,11 +18,34 @@ const rules = new Rules({
17
18
  thumb: {
18
19
  selectors: {
19
20
  title: '[title]',
20
- tags: { selector: '[data-testid="title"]', type: 'string' },
21
21
  duration: '[data-testid="video-item-length"]',
22
- }
22
+ // tags: { selector: '[data-testid="title"]', type: 'string' },
23
+ views: { selector: '[data-testid="views"]', type: 'float' },
24
+ quality: { selector: '[data-testid="video-item-resolution"]', type: 'string' },
25
+ },
23
26
  },
24
27
  thumbImg: { strategy: 'auto' },
25
28
  gropeStrategy: 'all-in-all',
26
- schemeOptions: ['Text Filter', 'Duration Filter', 'Badge', 'Advanced'],
29
+ customDataFilterFns: [
30
+ { qualityLow: (el, state) => !!state.qualityLow && el.quality !== '' },
31
+ { qualityHD: (el, state) => !!state.qualityHD && el.quality !== 'HD' },
32
+ { quality4k: (el, state) => !!state.quality4k && el.quality !== '4K' },
33
+ ],
34
+ schemeOptions: [
35
+ 'Title Filter',
36
+ 'Duration Filter',
37
+ {
38
+ title: 'Quality Filter',
39
+ content: [
40
+ { qualityLow: false, label: 'Low' },
41
+ { qualityHD: false, label: 'HD' },
42
+ { quality4k: false, label: '4K' },
43
+ ],
44
+ },
45
+ 'Sort By',
46
+ 'Badge',
47
+ 'Advanced',
48
+ ],
27
49
  });
50
+
51
+ console.log(rules.dataManager.data.values().toArray());
@@ -13,6 +13,7 @@ import {
13
13
  objectToFormData,
14
14
  parseCssUrl,
15
15
  parseHtml,
16
+ querySelectorLast,
16
17
  querySelectorLastNumber,
17
18
  range,
18
19
  replaceElementTag,
@@ -21,9 +22,9 @@ import {
21
22
 
22
23
  export const meta: MonkeyUserScript = {
23
24
  name: 'ThisVid.com Improved',
24
- version: '8.0.3',
25
+ version: '8.0.6',
25
26
  description:
26
- 'Infinite scroll [optional]. Preview for private videos. Filter: title, duration, public/private. Check access to private vids. Mass friend request button. Sorts messages. Download button 📼',
27
+ '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
28
  match: ['https://*.thisvid.com/*'],
28
29
  };
29
30
 
@@ -88,36 +89,18 @@ const defaultRulesConfig: RulesConfig = {
88
89
  return { img, imgSrc };
89
90
  },
90
91
  },
91
- containerSelectorLast: '.thumbs-items',
92
+ containerSelector: () =>
93
+ (querySelectorLast(
94
+ document,
95
+ 'div:has(> .tumbpu[title]):not(.thumbs-photo), div:has(> .thumb-holder)',
96
+ ) as HTMLElement) || document.querySelector<HTMLElement>('.thumbs-items'),
92
97
  animatePreview,
93
- customDataSelectorFns: [
94
- 'filterInclude',
95
- 'filterExclude',
96
- 'filterDuration',
97
- {
98
- filterPrivate: (el, state) => (state.filterPrivate && el.private) as boolean,
99
- },
100
- {
101
- filterPublic: (el, state) => (state.filterPublic && !el.private) as boolean,
102
- },
103
- {
104
- filterHD: (el, state) => (state.filterHD && !el.hd) as boolean,
105
- },
106
- {
107
- filterNonHD: (el, state) => (state.filterNonHD && el.hd) as boolean,
108
- },
109
- ],
110
98
  schemeOptions: [
111
- 'Text Filter',
99
+ 'Title Filter',
112
100
  'Duration Filter',
113
101
  'Privacy Filter',
114
- {
115
- title: 'HD Filter',
116
- content: [
117
- { filterHD: false, label: 'hd' },
118
- { filterNonHD: false, label: 'non-hd' },
119
- ],
120
- },
102
+ 'HD Filter',
103
+ 'Sort By',
121
104
  'Badge',
122
105
  {
123
106
  title: 'Advanced',
@@ -398,6 +381,8 @@ function animatePreview(_: HTMLElement) {
398
381
  (target) => {
399
382
  const img = target.querySelector('img') as HTMLImageElement;
400
383
  const orig = img.getAttribute('src') as string;
384
+ if (!orig) return;
385
+
401
386
  tick.start(
402
387
  () => iteratePreviewFrames(img),
403
388
  () => {
@@ -610,7 +595,7 @@ async function createPrivateFeed() {
610
595
  paginationSelector: '.footer',
611
596
  },
612
597
  schemeOptions: [
613
- 'Text Filter',
598
+ 'Title Filter',
614
599
  'Duration Filter',
615
600
  'Privacy Filter',
616
601
  'Badge',
@@ -11,12 +11,12 @@ import {
11
11
  waitForElementToAppear,
12
12
  watchElementChildrenCount,
13
13
  } from '../../utils';
14
- import { findSelfOrChild } from '../../utils/dom';
15
14
 
16
15
  export const meta: MonkeyUserScript = {
17
16
  name: 'Xhamster Improved',
18
- version: '5.0.4',
19
- description: 'Infinite scroll [optional], Filter by Title and Duration',
17
+ version: '5.0.7',
18
+ description:
19
+ 'Infinite scroll [optional], Filter by Title, Duration and Watched/Unwatched. Sort by Duration and Views',
20
20
  match: ['https://*.xhamster.com/*', 'https://*.xhamster.*/*'],
21
21
  exclude: 'https://*.xhamster.com/embed*',
22
22
  grant: ['GM_addElement', 'GM_addStyle', 'unsafeWindow'],
@@ -93,10 +93,8 @@ const rules = new Rules({
93
93
  selectors: {
94
94
  title: '.video-thumb-info__name,.video-thumb-info>a',
95
95
  duration: '.thumb-image-container__duration',
96
- watched: {
97
- type: 'boolean',
98
- selector: '[data-role="video-watched',
99
- },
96
+ watched: { selector: '[data-role="video-watched', type: 'boolean' },
97
+ views: { selector: '.video-thumb-views', type: 'float' },
100
98
  },
101
99
  },
102
100
  thumbImg: {
@@ -104,10 +102,7 @@ const rules = new Rules({
104
102
  remove: '[loading]',
105
103
  },
106
104
  gropeStrategy: 'all-in-all',
107
- customDataSelectorFns: [
108
- 'filterInclude',
109
- 'filterExclude',
110
- 'filterDuration',
105
+ customDataFilterFns: [
111
106
  {
112
107
  filterWatched: (el, state) => !!(state.filterWatched && el.watched),
113
108
  },
@@ -116,8 +111,7 @@ const rules = new Rules({
116
111
  },
117
112
  ],
118
113
  schemeOptions: [
119
- 'Text Filter',
120
- 'Badge',
114
+ 'Title Filter',
121
115
  {
122
116
  title: 'Filter Watched',
123
117
  content: [
@@ -125,7 +119,9 @@ const rules = new Rules({
125
119
  { filterUnwatched: false, label: 'unwatched' },
126
120
  ],
127
121
  },
122
+ 'Sort By',
128
123
  'Duration Filter',
124
+ 'Badge',
129
125
  'Advanced',
130
126
  ],
131
127
  animatePreview,
@@ -156,7 +152,6 @@ function animatePreview() {
156
152
  const videoSrc = e
157
153
  .querySelector('[data-previewvideo]')
158
154
  ?.getAttribute('data-previewvideo') as string;
159
- console.log(container, videoSrc);
160
155
  return createPreviewVideoElement(videoSrc, container);
161
156
  });
162
157
  }
@@ -5,8 +5,9 @@ import { exterminateVideo, OnHover, parseHtml } from '../../utils';
5
5
 
6
6
  export const meta: MonkeyUserScript = {
7
7
  name: 'XVideos Improved',
8
- version: '4.0.4',
9
- description: 'Infinite scroll [optional], Filter by Title and Duration',
8
+ version: '4.0.7',
9
+ description:
10
+ 'Infinite scroll [optional], Filter by Title, Uploader and Duration. Sort by Duration and Views.',
10
11
  match: 'https://*.xvideos.com/*',
11
12
  };
12
13
 
@@ -24,6 +25,8 @@ const rules = new Rules({
24
25
  title: '[class*=title]',
25
26
  uploader: '[class*=name]',
26
27
  duration: '[class*=duration]',
28
+ views: { selector: '.metadata a ~ span', type: 'float' },
29
+ quality: { selector: '.video-hd-mark', type: 'string' },
27
30
  },
28
31
  callback: (thumb) => {
29
32
  setTimeout(() => {
@@ -32,7 +35,33 @@ const rules = new Rules({
32
35
  }, 200);
33
36
  },
34
37
  },
35
- schemeOptions: ['Text Filter', 'Duration Filter', 'Badge', 'Advanced'],
38
+ customDataFilterFns: [
39
+ { qualityLow: (el, state) => !!state.qualityLow && el.quality !== '' },
40
+ { quality360: (el, state) => !!state.quality360 && el.quality !== '360p' },
41
+ { quality720: (el, state) => !!state.quality720 && el.quality !== '720p' },
42
+ { quality1080: (el, state) => !!state.quality1080 && el.quality !== '1080p' },
43
+ { quality1440: (el, state) => !!state.quality1440 && el.quality !== '1440p' },
44
+ { quality4k: (el, state) => !!state.quality4k && el.quality !== '4k' },
45
+ ],
46
+ schemeOptions: [
47
+ 'Title Filter',
48
+ 'Uploader Filter',
49
+ 'Duration Filter',
50
+ {
51
+ title: 'Quality Filter',
52
+ content: [
53
+ { qualityLow: false, label: 'Low' },
54
+ { quality360: false, label: '360p' },
55
+ { quality720: false, label: '720p' },
56
+ { quality1080: false, label: '1080p' },
57
+ { quality1440: false, label: '1440p' },
58
+ { quality4k: false, label: '4k' },
59
+ ],
60
+ },
61
+ 'Sort By',
62
+ 'Badge',
63
+ 'Advanced',
64
+ ],
36
65
  animatePreview,
37
66
  });
38
67
 
@@ -11,11 +11,14 @@ export function parseIntegerOr(n: string | number, or: number): number {
11
11
 
12
12
  export function parseNumberWithLetter(str: string): number {
13
13
  const multipliers = { k: 1e3, m: 1e6 } as const;
14
- const match = str.trim().match(/^([\d.]+)(\w)?$/);
14
+ const match = str.trim().match(/([\d., ]+)(\w)?/);
15
15
 
16
16
  if (!match) return 0;
17
17
 
18
- const num = parseFloat(match[1]);
18
+ const s1 = match[1].replace(/,/g, '.').replace(/[ ]/g, '');
19
+ const s2 = s1.split('.').filter(Boolean).length < 3 ? s1 : s1.replace('.', '');
20
+
21
+ const num = parseFloat(s2);
19
22
  const suffix = match[2]?.toLowerCase() as keyof typeof multipliers;
20
23
 
21
24
  if (suffix && suffix in multipliers) {