pervert-monkey 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/core/pervertmonkey.core.es.d.ts +391 -0
- package/dist/core/pervertmonkey.core.es.js +8497 -0
- package/dist/core/pervertmonkey.core.es.js.map +1 -0
- package/dist/core/pervertmonkey.core.umd.js +8500 -0
- package/dist/core/pervertmonkey.core.umd.js.map +1 -0
- package/dist/userscripts/3hentai.user.js +1176 -0
- package/dist/userscripts/camgirlfinder.user.js +68 -0
- package/dist/userscripts/camwhores.user.js +1602 -0
- package/dist/userscripts/e-hentai.user.js +1212 -0
- package/dist/userscripts/ebalka.user.js +1231 -0
- package/dist/userscripts/eporner.user.js +1265 -0
- package/dist/userscripts/erome.user.js +1245 -0
- package/dist/userscripts/eroprofile.user.js +1194 -0
- package/dist/userscripts/javhdporn.user.js +1178 -0
- package/dist/userscripts/missav.user.js +1182 -0
- package/dist/userscripts/motherless.user.js +1380 -0
- package/dist/userscripts/namethatporn.user.js +1218 -0
- package/dist/userscripts/nhentai.user.js +1262 -0
- package/dist/userscripts/pornhub.user.js +1199 -0
- package/dist/userscripts/spankbang.user.js +1239 -0
- package/dist/userscripts/xhamster.user.js +1374 -0
- package/dist/userscripts/xvideos.user.js +1254 -0
- package/package.json +54 -0
- package/src/core/data-control/data-filter.ts +143 -0
- package/src/core/data-control/data-manager.ts +144 -0
- package/src/core/data-control/index.ts +2 -0
- package/src/core/infinite-scroll/index.ts +143 -0
- package/src/core/jabroni-config/default-scheme.ts +97 -0
- package/src/core/jabroni-config/default-store.ts +9 -0
- package/src/core/pagination-parsing/index.ts +55 -0
- package/src/core/pagination-parsing/pagination-strategies/PaginationStrategy.ts +44 -0
- package/src/core/pagination-parsing/pagination-strategies/PaginationStrategyDataParams.ts +66 -0
- package/src/core/pagination-parsing/pagination-strategies/PaginationStrategyPathnameParams.ts +77 -0
- package/src/core/pagination-parsing/pagination-strategies/PaginationStrategySearchParams.ts +56 -0
- package/src/core/pagination-parsing/pagination-strategies/index.ts +4 -0
- package/src/core/pagination-parsing/pagination-utils/index.ts +84 -0
- package/src/core/rules/index.ts +385 -0
- package/src/index.ts +42 -0
- package/src/types/index.ts +7 -0
- package/src/userscripts/ascii-logos.js +468 -0
- package/src/userscripts/index.ts +1 -0
- package/src/userscripts/meta.json +11 -0
- package/src/userscripts/scripts/3hentai.ts +20 -0
- package/src/userscripts/scripts/camgirlfinder.ts +68 -0
- package/src/userscripts/scripts/camwhores.ts +382 -0
- package/src/userscripts/scripts/e-hentai.ts +68 -0
- package/src/userscripts/scripts/ebalka.ts +58 -0
- package/src/userscripts/scripts/eporner.ts +90 -0
- package/src/userscripts/scripts/erome.ts +105 -0
- package/src/userscripts/scripts/eroprofile.ts +38 -0
- package/src/userscripts/scripts/javhdporn.ts +24 -0
- package/src/userscripts/scripts/missav.ts +28 -0
- package/src/userscripts/scripts/motherless.ts +222 -0
- package/src/userscripts/scripts/namethatporn.ts +68 -0
- package/src/userscripts/scripts/nhentai.ts +135 -0
- package/src/userscripts/scripts/pornhub.ts +53 -0
- package/src/userscripts/scripts/spankbang.ts +61 -0
- package/src/userscripts/scripts/thisvid.ts +716 -0
- package/src/userscripts/scripts/xhamster.ts +179 -0
- package/src/userscripts/scripts/xvideos.ts +83 -0
- package/src/utils/arrays/index.ts +15 -0
- package/src/utils/async/index.ts +3 -0
- package/src/utils/dom/dom-observers.ts +76 -0
- package/src/utils/dom/index.ts +156 -0
- package/src/utils/events/index.ts +2 -0
- package/src/utils/events/on-pointer-over-and-leave.ts +35 -0
- package/src/utils/events/tick.ts +27 -0
- package/src/utils/fetch/index.ts +37 -0
- package/src/utils/math/index.ts +3 -0
- package/src/utils/objects/index.ts +9 -0
- package/src/utils/objects/memoize.ts +25 -0
- package/src/utils/observers/index.ts +44 -0
- package/src/utils/observers/lazy-image-loader.ts +27 -0
- package/src/utils/parsers/index.ts +30 -0
- package/src/utils/parsers/time-parser.ts +28 -0
- package/src/utils/strings/index.ts +10 -0
- package/src/utils/strings/regexes.ts +35 -0
- package/src/vite-env.d.ts +4 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { parseDataParams } from '../../../utils/parsers';
|
|
2
|
+
import { PaginationStrategy } from './PaginationStrategy';
|
|
3
|
+
|
|
4
|
+
export class PaginationStrategyDataParams extends PaginationStrategy {
|
|
5
|
+
getPaginationLast() {
|
|
6
|
+
const links = this.getPaginationElement()?.querySelectorAll(this.dataparamSelector);
|
|
7
|
+
const pages = Array.from(links || [], (l) => {
|
|
8
|
+
const p = l.getAttribute('data-parameters');
|
|
9
|
+
const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
|
|
10
|
+
return parseInt(v);
|
|
11
|
+
});
|
|
12
|
+
const lastPage = Math.max(...pages, this.offsetMin);
|
|
13
|
+
if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
|
|
14
|
+
return lastPage;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getPaginationOffset() {
|
|
18
|
+
const link = this.getPaginationElement()?.querySelector(
|
|
19
|
+
'.prev[data-parameters *= from], .prev [data-parameters *= from]',
|
|
20
|
+
);
|
|
21
|
+
if (!link) return this.offsetMin;
|
|
22
|
+
const p = link.getAttribute('data-parameters');
|
|
23
|
+
const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
|
|
24
|
+
return parseInt(v);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getPaginationUrlGenerator() {
|
|
28
|
+
const url = new URL(this.url.href);
|
|
29
|
+
|
|
30
|
+
const parametersElement = this.getPaginationElement()?.querySelector(
|
|
31
|
+
'a[data-block-id][data-parameters]',
|
|
32
|
+
);
|
|
33
|
+
const block_id = parametersElement?.getAttribute('data-block-id') || '';
|
|
34
|
+
const parameters = parseDataParams(
|
|
35
|
+
parametersElement?.getAttribute('data-parameters') || '',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const attrs: Record<string, string> = {
|
|
39
|
+
block_id,
|
|
40
|
+
function: 'get_block',
|
|
41
|
+
mode: 'async',
|
|
42
|
+
...parameters,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
Object.keys(attrs).forEach((k) => {
|
|
46
|
+
url.searchParams.set(k, attrs[k]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const paginationUrlGenerator = (n: number) => {
|
|
50
|
+
Object.keys(attrs).forEach((k) => {
|
|
51
|
+
k.includes('from') && url.searchParams.set(k, n.toString());
|
|
52
|
+
});
|
|
53
|
+
url.searchParams.set('_', Date.now().toString());
|
|
54
|
+
return url.href;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return paginationUrlGenerator;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static testLinks(doc: HTMLElement | Document = document) {
|
|
61
|
+
const dataParamLinks = Array.from(
|
|
62
|
+
doc.querySelectorAll<HTMLElement>('[data-parameters *= from]'),
|
|
63
|
+
);
|
|
64
|
+
return dataParamLinks.length > 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getPaginationLinks, parseUrl, upgradePathname } from '../pagination-utils';
|
|
2
|
+
import { PaginationStrategy } from './PaginationStrategy';
|
|
3
|
+
|
|
4
|
+
export class PaginationStrategyPathnameParams extends PaginationStrategy {
|
|
5
|
+
extractPage = (a: HTMLAnchorElement | Location | string): number => {
|
|
6
|
+
const href = typeof a === 'string' ? a : a.href;
|
|
7
|
+
const { pathname } = new URL(href, this.doc.baseURI || this.url.origin);
|
|
8
|
+
return parseInt(
|
|
9
|
+
pathname.match(this.pathnameSelector)?.pop() || this.offsetMin.toString(),
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
static checkLink(
|
|
14
|
+
link: URL,
|
|
15
|
+
pathnameSelector: RegExp = PaginationStrategy._pathnameSelector,
|
|
16
|
+
): boolean {
|
|
17
|
+
return pathnameSelector.test(link.pathname);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static testLinks(links: URL[], options: Partial<PaginationStrategy>): boolean {
|
|
21
|
+
const result = links.some((h) =>
|
|
22
|
+
PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (result) {
|
|
26
|
+
const pathnamesMatched = links.filter((h) =>
|
|
27
|
+
PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector),
|
|
28
|
+
);
|
|
29
|
+
options.url = upgradePathname(
|
|
30
|
+
parseUrl(options.url as unknown as string),
|
|
31
|
+
pathnamesMatched,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getPaginationLast() {
|
|
39
|
+
const links = getPaginationLinks(
|
|
40
|
+
(this.getPaginationElement() || document) as HTMLElement,
|
|
41
|
+
this.url.href,
|
|
42
|
+
this.pathnameSelector,
|
|
43
|
+
);
|
|
44
|
+
const pages = Array.from(links, this.extractPage);
|
|
45
|
+
const lastPage = Math.max(...pages, this.offsetMin);
|
|
46
|
+
if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
|
|
47
|
+
return lastPage;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getPaginationOffset() {
|
|
51
|
+
return this.extractPage(this.url.href);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getPaginationUrlGenerator(url_: URL = this.url) {
|
|
55
|
+
const url = new URL(url_.href);
|
|
56
|
+
|
|
57
|
+
const pathnameSelectorPlaceholder = this.pathnameSelector
|
|
58
|
+
.toString()
|
|
59
|
+
.replace(/[/|\\|$|?|(|)]+/g, '/');
|
|
60
|
+
|
|
61
|
+
if (!this.pathnameSelector.test(url.pathname)) {
|
|
62
|
+
url.pathname = url.pathname
|
|
63
|
+
.concat(pathnameSelectorPlaceholder.replace(/d\+/, this.offsetMin.toString()))
|
|
64
|
+
.replace(/\/{2,}/g, '/');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const paginationUrlGenerator = (offset: number) => {
|
|
68
|
+
url.pathname = url.pathname.replace(
|
|
69
|
+
this.pathnameSelector,
|
|
70
|
+
pathnameSelectorPlaceholder.replace(/d\+/, offset.toString()),
|
|
71
|
+
);
|
|
72
|
+
return url.href;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return paginationUrlGenerator;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getPaginationLinks } from '../pagination-utils';
|
|
2
|
+
import { PaginationStrategy } from './PaginationStrategy';
|
|
3
|
+
|
|
4
|
+
export class PaginationStrategySearchParams extends PaginationStrategy {
|
|
5
|
+
extractPage = (a: HTMLAnchorElement | Location | URL | string): number => {
|
|
6
|
+
const href = typeof a === 'string' ? a : a.href;
|
|
7
|
+
const p = new URL(href).searchParams.get(this.searchParamSelector) as string;
|
|
8
|
+
return parseInt(p) || this.offsetMin;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
getPaginationLast() {
|
|
12
|
+
const links = getPaginationLinks(
|
|
13
|
+
(this.getPaginationElement() || document) as HTMLElement,
|
|
14
|
+
this.url.href,
|
|
15
|
+
).filter((h) =>
|
|
16
|
+
PaginationStrategySearchParams.checkLink(new URL(h), this.searchParamSelector),
|
|
17
|
+
);
|
|
18
|
+
const pages = links.map(this.extractPage);
|
|
19
|
+
const lastPage = Math.max(...pages, this.offsetMin);
|
|
20
|
+
if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
|
|
21
|
+
return lastPage;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getPaginationOffset() {
|
|
25
|
+
if (this.doc === document) {
|
|
26
|
+
return this.extractPage(this.url);
|
|
27
|
+
}
|
|
28
|
+
const link = this.getPaginationElement()?.querySelector(
|
|
29
|
+
`a.active[href *= "${this.searchParamSelector}="]`,
|
|
30
|
+
) as HTMLAnchorElement;
|
|
31
|
+
return this.extractPage(link);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getPaginationUrlGenerator() {
|
|
35
|
+
const url = new URL(this.url.href);
|
|
36
|
+
|
|
37
|
+
const paginationUrlGenerator = (offset: number) => {
|
|
38
|
+
url.searchParams.set(this.searchParamSelector, offset.toString());
|
|
39
|
+
return url.href;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return paginationUrlGenerator;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static checkLink(link: URL, searchParamSelector?: string): boolean {
|
|
46
|
+
const searchParamSelectors = ['page', 'p'];
|
|
47
|
+
if (searchParamSelector) searchParamSelectors.push(searchParamSelector);
|
|
48
|
+
return searchParamSelectors.some((p) => link.searchParams.get(p) !== null);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static testLinks(links: URL[], searchParamSelector?: string) {
|
|
52
|
+
return links.some((h) =>
|
|
53
|
+
PaginationStrategySearchParams.checkLink(h, searchParamSelector),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { PaginationStrategy } from './PaginationStrategy';
|
|
2
|
+
export { PaginationStrategyDataParams } from './PaginationStrategyDataParams';
|
|
3
|
+
export { PaginationStrategyPathnameParams } from './PaginationStrategyPathnameParams';
|
|
4
|
+
export { PaginationStrategySearchParams } from './PaginationStrategySearchParams';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { URLPattern } from 'urlpattern-polyfill/urlpattern';
|
|
2
|
+
|
|
3
|
+
export function parseUrl(s: HTMLAnchorElement | Location | URL | string): URL {
|
|
4
|
+
return new URL(typeof s === 'string' ? s : s.href);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function depaginatePathname(
|
|
8
|
+
url: URL,
|
|
9
|
+
pathnamePaginationSelector = /\/(page\/)?\d+\/?$/,
|
|
10
|
+
): URL {
|
|
11
|
+
const newUrl = new URL(url.toString());
|
|
12
|
+
newUrl.pathname = newUrl.pathname.replace(pathnamePaginationSelector, '/');
|
|
13
|
+
return newUrl;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getPaginationLinks(
|
|
17
|
+
doc: Element | HTMLElement | Document = document,
|
|
18
|
+
url: Location | URL | string = location.href,
|
|
19
|
+
pathnamePaginationSelector = /\/(page\/)?\d+\/?$/,
|
|
20
|
+
): string[] {
|
|
21
|
+
const baseUrl = depaginatePathname(parseUrl(url), pathnamePaginationSelector);
|
|
22
|
+
const pathnameStrict = doc instanceof Document;
|
|
23
|
+
const host = doc.baseURI || baseUrl.origin;
|
|
24
|
+
|
|
25
|
+
const urlPattern = new URLPattern({
|
|
26
|
+
pathname: pathnameStrict ? `${baseUrl.pathname}*` : '*',
|
|
27
|
+
hostname: baseUrl.hostname,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const pageLinks = [...doc.querySelectorAll<HTMLAnchorElement>('a[href]')]
|
|
31
|
+
.map((a) => a.href)
|
|
32
|
+
.filter((h) => URL.canParse(h));
|
|
33
|
+
|
|
34
|
+
return pageLinks.filter((h) => {
|
|
35
|
+
return urlPattern.test(new URL(h, host));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Nonsens
|
|
41
|
+
* WTF IS THIS? JFC...
|
|
42
|
+
* @description
|
|
43
|
+
* curr: website.com, links: [webiste.com/new/23] => wegsite.com/new
|
|
44
|
+
*/
|
|
45
|
+
export function upgradePathname(
|
|
46
|
+
curr: URL,
|
|
47
|
+
links: URL[],
|
|
48
|
+
pathnamePaginationSelector = /\/(page\/)?\d+\/?$/,
|
|
49
|
+
): URL {
|
|
50
|
+
if (pathnamePaginationSelector.test(curr.pathname) || links.length < 1) return curr;
|
|
51
|
+
|
|
52
|
+
const linksDepaginated = links.map((l) =>
|
|
53
|
+
depaginatePathname(l, pathnamePaginationSelector),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (linksDepaginated.some((l) => l.pathname === curr.pathname)) return curr;
|
|
57
|
+
|
|
58
|
+
const last = linksDepaginated.at(-1) as URL;
|
|
59
|
+
if (last.pathname !== curr.pathname) curr.pathname = last.pathname;
|
|
60
|
+
return curr;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @description
|
|
65
|
+
* website.com/search => website.com/search+word1...+-word2
|
|
66
|
+
*/
|
|
67
|
+
export function applyUrlLevelSearchFilters(
|
|
68
|
+
searchFilter: string,
|
|
69
|
+
queryType: keyof Pick<Location, 'pathname' | 'search'>,
|
|
70
|
+
): void {
|
|
71
|
+
const wordsToFilter =
|
|
72
|
+
searchFilter.replace(/f:/g, '').match(/(?<!user:)\b\w+\b(?!\s*:)/g) || [];
|
|
73
|
+
|
|
74
|
+
if (!wordsToFilter.some((w) => !location.href.includes(w))) return;
|
|
75
|
+
|
|
76
|
+
let query = location[queryType];
|
|
77
|
+
|
|
78
|
+
wordsToFilter.forEach((w) => {
|
|
79
|
+
if (query.includes(w)) return;
|
|
80
|
+
query += `+-${w.trim()}`;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
window.location[queryType] = query;
|
|
84
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { JabronioGUI, JabronioStore, type JabroniTypes, setupScheme } from 'jabroni-outfit';
|
|
2
|
+
import {
|
|
3
|
+
getCommonParents,
|
|
4
|
+
querySelectorLast,
|
|
5
|
+
querySelectorText,
|
|
6
|
+
removeClassesAndDataAttributes,
|
|
7
|
+
waitForElementToDisappear,
|
|
8
|
+
} from '../../utils/dom';
|
|
9
|
+
import { timeToSeconds } from '../../utils/parsers';
|
|
10
|
+
import { sanitizeStr } from '../../utils/strings';
|
|
11
|
+
import { DataManager } from '../data-control';
|
|
12
|
+
import type { DataSelectorFn } from '../data-control/data-filter';
|
|
13
|
+
import { InfiniteScroller, type OffsetGenerator } from '../infinite-scroll/';
|
|
14
|
+
import { DefaultScheme, type SchemeOptions } from '../jabroni-config/default-scheme';
|
|
15
|
+
import { StoreStateDefault } from '../jabroni-config/default-store';
|
|
16
|
+
import { getPaginationStrategy } from '../pagination-parsing';
|
|
17
|
+
import type { PaginationStrategy } from '../pagination-parsing/pagination-strategies';
|
|
18
|
+
|
|
19
|
+
type ThumbData = {
|
|
20
|
+
title: string;
|
|
21
|
+
duration?: number;
|
|
22
|
+
} & { [x: string]: string | boolean | number };
|
|
23
|
+
|
|
24
|
+
type _CustomThumbDataSelector = {
|
|
25
|
+
selector: string;
|
|
26
|
+
type: 'boolean' | 'string' | 'number';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type CustomThumbDataSelector = {
|
|
30
|
+
[x: string]: _CustomThumbDataSelector;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class RulesGlobal {
|
|
34
|
+
public delay?: number;
|
|
35
|
+
|
|
36
|
+
public customGenerator?: OffsetGenerator;
|
|
37
|
+
|
|
38
|
+
public getThumbUrl(thumb: HTMLElement | HTMLAnchorElement) {
|
|
39
|
+
return ((thumb.querySelector('a[href]') || thumb) as HTMLAnchorElement).href;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public titleSelector: undefined | string;
|
|
43
|
+
public uploaderSelector: undefined | string;
|
|
44
|
+
public durationSelector: undefined | string;
|
|
45
|
+
|
|
46
|
+
public customThumbDataSelectors: undefined | CustomThumbDataSelector;
|
|
47
|
+
public getThumbDataStrategy: 'default' | 'auto-select' | 'auto-text' = 'default';
|
|
48
|
+
|
|
49
|
+
public getThumbDataCallback?: (thumb: HTMLElement, thumbData: ThumbData) => void;
|
|
50
|
+
|
|
51
|
+
public getThumbData(thumb: HTMLElement): ThumbData {
|
|
52
|
+
let { titleSelector, uploaderSelector, durationSelector } = this;
|
|
53
|
+
const thumbData: ThumbData = { title: '' };
|
|
54
|
+
|
|
55
|
+
if (this.getThumbDataStrategy === 'auto-text') {
|
|
56
|
+
const text = sanitizeStr(thumb.innerText);
|
|
57
|
+
thumbData.title = text;
|
|
58
|
+
thumbData.duration = timeToSeconds(text.match(/\d+m|\d+:\d+/)?.[0] || '');
|
|
59
|
+
return thumbData;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.getThumbDataStrategy === 'auto-select') {
|
|
63
|
+
titleSelector = '[class *= title],[title]';
|
|
64
|
+
durationSelector = '[class *= duration]';
|
|
65
|
+
uploaderSelector = '[class *= uploader], [class *= user], [class *= name]';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.getThumbDataStrategy === 'auto-select') {
|
|
69
|
+
const selected = querySelectorLast(thumb, titleSelector as string);
|
|
70
|
+
if (selected) {
|
|
71
|
+
thumbData.title = sanitizeStr(selected.innerText as string);
|
|
72
|
+
} else {
|
|
73
|
+
thumbData.title = sanitizeStr(thumb.innerText);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
thumbData.title = querySelectorText(thumb, titleSelector);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (uploaderSelector) {
|
|
80
|
+
const uploader = querySelectorText(thumb, uploaderSelector);
|
|
81
|
+
thumbData.title = `${thumbData.title} user:${uploader}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (durationSelector) {
|
|
85
|
+
const duration = timeToSeconds(querySelectorText(thumb, durationSelector));
|
|
86
|
+
thumbData.duration = duration;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.getThumbDataCallback?.(thumb, thumbData);
|
|
90
|
+
|
|
91
|
+
function getCustomThumbData(
|
|
92
|
+
selector: _CustomThumbDataSelector['selector'],
|
|
93
|
+
type: _CustomThumbDataSelector['type'],
|
|
94
|
+
): string | number | boolean {
|
|
95
|
+
if (type === 'boolean') {
|
|
96
|
+
return !!thumb.querySelector(selector);
|
|
97
|
+
}
|
|
98
|
+
if (type === 'string') {
|
|
99
|
+
return querySelectorText(thumb, selector);
|
|
100
|
+
}
|
|
101
|
+
return Number.parseInt(querySelectorText(thumb, selector));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (this.customThumbDataSelectors) {
|
|
105
|
+
Object.entries(this.customThumbDataSelectors).forEach(([name, x]) => {
|
|
106
|
+
const data = getCustomThumbData(x.selector, x.type);
|
|
107
|
+
Object.assign(thumbData, { [name]: data });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return thumbData;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public getThumbImgDataAttrSelector?:
|
|
115
|
+
| string
|
|
116
|
+
| string[]
|
|
117
|
+
| ((img: HTMLImageElement) => string);
|
|
118
|
+
public getThumbImgDataAttrDelete?: 'auto' | string;
|
|
119
|
+
public getThumbImgDataStrategy: 'default' | 'auto' = 'default';
|
|
120
|
+
|
|
121
|
+
public getThumbImgData(thumb: HTMLElement) {
|
|
122
|
+
const result: { img?: HTMLImageElement; imgSrc?: string } = {};
|
|
123
|
+
|
|
124
|
+
if (this.getThumbImgDataStrategy === 'auto') {
|
|
125
|
+
const img = thumb.querySelector<HTMLImageElement>('img');
|
|
126
|
+
if (!img) return {};
|
|
127
|
+
|
|
128
|
+
result.img = img;
|
|
129
|
+
|
|
130
|
+
if (typeof this.getThumbImgDataAttrSelector === 'function') {
|
|
131
|
+
result.imgSrc = this.getThumbImgDataAttrSelector(img);
|
|
132
|
+
} else {
|
|
133
|
+
const possibleAttrs = this.getThumbImgDataAttrSelector
|
|
134
|
+
? [this.getThumbImgDataAttrSelector].flat()
|
|
135
|
+
: ['data-src', 'src'];
|
|
136
|
+
|
|
137
|
+
for (const attr of possibleAttrs) {
|
|
138
|
+
const imgSrc = img.getAttribute(attr);
|
|
139
|
+
if (imgSrc) {
|
|
140
|
+
result.imgSrc = imgSrc;
|
|
141
|
+
img.removeAttribute(attr);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (this.getThumbImgDataAttrDelete) {
|
|
148
|
+
if (this.getThumbImgDataAttrDelete === 'auto') {
|
|
149
|
+
removeClassesAndDataAttributes(img, 'lazy');
|
|
150
|
+
} else {
|
|
151
|
+
if (this.getThumbImgDataAttrDelete.startsWith('.')) {
|
|
152
|
+
img.classList.remove(this.getThumbImgDataAttrDelete.slice(1));
|
|
153
|
+
} else {
|
|
154
|
+
img.removeAttribute(this.getThumbImgDataAttrDelete);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (img.src.includes('data:image')) {
|
|
159
|
+
result.img.src = '';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (img.complete && img.naturalWidth > 0) {
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public containerSelector: string | (() => HTMLElement) = '.container';
|
|
172
|
+
public containerSelectorLast?: string;
|
|
173
|
+
|
|
174
|
+
public intersectionObservableSelector?: string;
|
|
175
|
+
|
|
176
|
+
public get intersectionObservable() {
|
|
177
|
+
return (
|
|
178
|
+
this.intersectionObservableSelector &&
|
|
179
|
+
document.querySelector(this.intersectionObservableSelector)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public get observable(): HTMLElement {
|
|
184
|
+
return (this.intersectionObservable ||
|
|
185
|
+
this.paginationStrategy.getPaginationElement()) as HTMLElement;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
get container() {
|
|
189
|
+
if (typeof this.containerSelectorLast === 'string') {
|
|
190
|
+
return querySelectorLast(document, this.containerSelectorLast) as HTMLElement;
|
|
191
|
+
}
|
|
192
|
+
if (typeof this.containerSelector === 'string') {
|
|
193
|
+
return document.querySelector<HTMLElement>(this.containerSelector) as HTMLElement;
|
|
194
|
+
}
|
|
195
|
+
return this.containerSelector();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public thumbsSelector = '.thumb';
|
|
199
|
+
public getThumbsStrategy: 'default' | 'auto' = 'default';
|
|
200
|
+
public getThumbsTransform?: (thumb: HTMLElement) => void;
|
|
201
|
+
|
|
202
|
+
public getThumbs(html: HTMLElement): HTMLElement[] {
|
|
203
|
+
if (!html) return [];
|
|
204
|
+
let thumbs: HTMLElement[];
|
|
205
|
+
|
|
206
|
+
if (this.getThumbsStrategy === 'auto') {
|
|
207
|
+
if (typeof this.containerSelector !== 'string') return [];
|
|
208
|
+
const container = html.querySelector(this.containerSelector);
|
|
209
|
+
thumbs = [...(container?.children || [])] as HTMLElement[];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
thumbs = Array.from(html.querySelectorAll<HTMLElement>(this.thumbsSelector));
|
|
213
|
+
|
|
214
|
+
if (typeof this.getThumbsTransform === 'function') {
|
|
215
|
+
thumbs.forEach(this.getThumbsTransform);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return thumbs;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public paginationStrategyOptions: Partial<PaginationStrategy> = {};
|
|
222
|
+
public paginationStrategy: PaginationStrategy;
|
|
223
|
+
|
|
224
|
+
public customDataSelectorFns: (Record<string, DataSelectorFn<any>> | string)[] = [
|
|
225
|
+
'filterInclude',
|
|
226
|
+
'filterExclude',
|
|
227
|
+
'filterDuration',
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
public animatePreview?: (doc: HTMLElement) => void;
|
|
231
|
+
|
|
232
|
+
public storeOptions?: JabroniTypes.StoreStateOptions;
|
|
233
|
+
|
|
234
|
+
private createStore() {
|
|
235
|
+
const config = { ...StoreStateDefault, ...this.storeOptions };
|
|
236
|
+
this.store = new JabronioStore(config);
|
|
237
|
+
return this.store;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public schemeOptions: SchemeOptions = [];
|
|
241
|
+
|
|
242
|
+
private createGui() {
|
|
243
|
+
const scheme = setupScheme(
|
|
244
|
+
this.schemeOptions as Parameters<typeof setupScheme>[0],
|
|
245
|
+
DefaultScheme,
|
|
246
|
+
);
|
|
247
|
+
this.gui = new JabronioGUI(scheme, this.store);
|
|
248
|
+
return this.gui;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public store: JabronioStore;
|
|
252
|
+
public gui: JabronioGUI;
|
|
253
|
+
public dataManager: DataManager;
|
|
254
|
+
|
|
255
|
+
public infiniteScroller?: InfiniteScroller;
|
|
256
|
+
public getPaginationData?: InfiniteScroller['getPaginationData'];
|
|
257
|
+
|
|
258
|
+
private resetInfiniteScroller() {
|
|
259
|
+
this.infiniteScroller?.dispose();
|
|
260
|
+
if (!this.paginationStrategy.hasPagination) return;
|
|
261
|
+
this.infiniteScroller = InfiniteScroller.create(this);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public gropeStrategy: 'all-in-one' | 'all-in-all' = 'all-in-one';
|
|
265
|
+
|
|
266
|
+
public gropeInit() {
|
|
267
|
+
if (!this.gropeStrategy) return;
|
|
268
|
+
if (this.gropeStrategy === 'all-in-one') {
|
|
269
|
+
this.dataManager?.parseData(this.container, this.container);
|
|
270
|
+
}
|
|
271
|
+
if (this.gropeStrategy === 'all-in-all') {
|
|
272
|
+
getCommonParents(this.getThumbs(document.body)).forEach((c) => {
|
|
273
|
+
this.dataManager.parseData(c, c, true);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public get isEmbedded() {
|
|
279
|
+
return window.self !== window.top;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private setupStoreListeners() {
|
|
283
|
+
const eventsMap = {
|
|
284
|
+
'sort by duration': {
|
|
285
|
+
action: (direction: boolean) => this.dataManager.sortBy('duration', direction),
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
let lastEvent: undefined | string;
|
|
290
|
+
let direction = true;
|
|
291
|
+
|
|
292
|
+
this.store.eventSubject.subscribe((event) => {
|
|
293
|
+
if (event === lastEvent) {
|
|
294
|
+
direction = !direction;
|
|
295
|
+
} else {
|
|
296
|
+
lastEvent = event;
|
|
297
|
+
direction = true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (event in eventsMap) {
|
|
301
|
+
const ev = eventsMap[event as keyof typeof eventsMap];
|
|
302
|
+
ev?.action(direction);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
this.store.stateSubject.subscribe((a) => {
|
|
307
|
+
this.dataManager.applyFilters(a as { [key: string]: boolean });
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public dataManagerOptions: Partial<DataManager> = {};
|
|
312
|
+
|
|
313
|
+
private setupDataManager() {
|
|
314
|
+
this.dataManager = new DataManager(this);
|
|
315
|
+
if (this.dataManagerOptions) {
|
|
316
|
+
Object.assign(this.dataManager, this.dataManagerOptions);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return this.dataManager;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private mutationObservers: MutationObserver[] = [];
|
|
323
|
+
|
|
324
|
+
public resetOnPaginationOrContainerDeath = true;
|
|
325
|
+
|
|
326
|
+
private resetOn() {
|
|
327
|
+
if (!this.resetOnPaginationOrContainerDeath) return;
|
|
328
|
+
|
|
329
|
+
const observables = [
|
|
330
|
+
this.container,
|
|
331
|
+
this.intersectionObservable || this.paginationStrategy.getPaginationElement(),
|
|
332
|
+
].filter(Boolean);
|
|
333
|
+
|
|
334
|
+
if (observables.length === 0) return;
|
|
335
|
+
|
|
336
|
+
observables.forEach((o) => {
|
|
337
|
+
const observer = waitForElementToDisappear(o as HTMLElement, () => {
|
|
338
|
+
this.reset();
|
|
339
|
+
});
|
|
340
|
+
this.mutationObservers.push(observer);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public onResetCallback?: () => void;
|
|
345
|
+
|
|
346
|
+
private reset() {
|
|
347
|
+
// console.log('\nRESET\n');
|
|
348
|
+
|
|
349
|
+
this.mutationObservers.forEach((o) => {
|
|
350
|
+
o.disconnect();
|
|
351
|
+
});
|
|
352
|
+
this.mutationObservers = [];
|
|
353
|
+
|
|
354
|
+
this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
|
|
355
|
+
|
|
356
|
+
this.setupDataManager();
|
|
357
|
+
this.setupStoreListeners();
|
|
358
|
+
|
|
359
|
+
this.resetInfiniteScroller();
|
|
360
|
+
|
|
361
|
+
this.container && this.animatePreview?.(this.container);
|
|
362
|
+
|
|
363
|
+
this.gropeInit();
|
|
364
|
+
|
|
365
|
+
this.onResetCallback?.();
|
|
366
|
+
|
|
367
|
+
this.resetOn();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
constructor(options: Partial<RulesGlobal>) {
|
|
371
|
+
if (this.isEmbedded) throw Error('Embedded is not supported');
|
|
372
|
+
|
|
373
|
+
Object.assign(this, options);
|
|
374
|
+
|
|
375
|
+
this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
|
|
376
|
+
|
|
377
|
+
this.store = this.createStore();
|
|
378
|
+
this.gui = this.createGui();
|
|
379
|
+
|
|
380
|
+
this.dataManager = this.setupDataManager();
|
|
381
|
+
|
|
382
|
+
this.reset();
|
|
383
|
+
// console.log('data', this.dataManager.data.values().toArray());
|
|
384
|
+
}
|
|
385
|
+
}
|