pervert-monkey 1.0.13 → 1.0.17
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/dist/core/pervertmonkey.core.es.d.ts +90 -36
- package/dist/core/pervertmonkey.core.es.js +258 -129
- package/dist/core/pervertmonkey.core.es.js.map +1 -1
- package/dist/core/pervertmonkey.core.umd.js +258 -129
- package/dist/core/pervertmonkey.core.umd.js.map +1 -1
- package/dist/userscripts/3hentai.user.js +4 -5
- package/dist/userscripts/camgirlfinder.user.js +2 -2
- package/dist/userscripts/camwhores.user.js +7 -16
- package/dist/userscripts/e-hentai.user.js +8 -8
- package/dist/userscripts/ebalka.user.js +18 -10
- package/dist/userscripts/eporner.user.js +24 -41
- package/dist/userscripts/erome.user.js +13 -16
- package/dist/userscripts/eroprofile.user.js +5 -14
- package/dist/userscripts/javhdporn.user.js +6 -5
- package/dist/userscripts/missav.user.js +10 -4
- package/dist/userscripts/motherless.user.js +13 -6
- package/dist/userscripts/namethatporn.user.js +10 -16
- package/dist/userscripts/nhentai.user.js +5 -13
- package/dist/userscripts/obmenvsem.user.js +11 -4
- package/dist/userscripts/pornhub.user.js +14 -6
- package/dist/userscripts/spankbang.user.js +28 -7
- package/dist/userscripts/thisvid.user.js +15 -33
- package/dist/userscripts/xhamster.user.js +13 -18
- package/dist/userscripts/xvideos.user.js +33 -5
- package/package.json +1 -1
- package/src/core/data-handler/data-filter-fn-defaults.ts +52 -0
- package/src/core/data-handler/data-filter-fn.ts +62 -0
- package/src/core/data-handler/data-filter.ts +31 -103
- package/src/core/data-handler/data-manager.ts +91 -28
- package/src/core/jabroni-config/default-scheme.ts +54 -5
- package/src/core/jabroni-config/index.ts +1 -0
- package/src/core/jabroni-config/jabroni-gui-controller.ts +1 -1
- package/src/core/jabroni-config/scheme-selectors-mapping.ts +12 -0
- package/src/core/parsers/thumb-data-parser.ts +15 -19
- package/src/core/rules/index.ts +15 -9
- package/src/userscripts/index.ts +1 -1
- package/src/userscripts/scripts/3hentai.ts +3 -4
- package/src/userscripts/scripts/camgirlfinder.ts +1 -1
- package/src/userscripts/scripts/camwhores.ts +5 -14
- package/src/userscripts/scripts/e-hentai.ts +12 -12
- package/src/userscripts/scripts/ebalka.ts +16 -8
- package/src/userscripts/scripts/eporner.ts +23 -39
- package/src/userscripts/scripts/erome.ts +13 -17
- package/src/userscripts/scripts/eroprofile.ts +4 -12
- package/src/userscripts/scripts/javhdporn.ts +7 -8
- package/src/userscripts/scripts/missav.ts +10 -4
- package/src/userscripts/scripts/motherless.ts +13 -7
- package/src/userscripts/scripts/namethatporn.ts +10 -17
- package/src/userscripts/scripts/nhentai.ts +6 -13
- package/src/userscripts/scripts/obmenvsem.ts +14 -4
- package/src/userscripts/scripts/pornhub.ts +13 -4
- package/src/userscripts/scripts/spankbang.ts +29 -5
- package/src/userscripts/scripts/thisvid.ts +16 -31
- package/src/userscripts/scripts/xhamster.ts +13 -18
- package/src/userscripts/scripts/xvideos.ts +32 -3
- package/src/utils/dom/dom-observers.ts +3 -3
- package/src/utils/dom/index.ts +1 -1
- package/src/utils/parsers/index.ts +5 -2
|
@@ -1,139 +1,67 @@
|
|
|
1
|
-
import type { StoreState } from 'jabroni-outfit';
|
|
2
1
|
import { GM_addStyle } from 'vite-plugin-monkey/dist/client';
|
|
3
|
-
import { RegexFilter } from '../../utils';
|
|
4
2
|
import type { Rules } from '../rules';
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
$preDefine?: (state: StoreState) => R;
|
|
12
|
-
deps?: string[];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type DataSelectorFn<R> = DataSelectorFnAdvanced<R> | DataSelectorFnShort;
|
|
16
|
-
|
|
17
|
-
interface DataFilterResult {
|
|
18
|
-
tag: string;
|
|
19
|
-
condition: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type DataFilterFn = (v: DataElement) => DataFilterResult;
|
|
3
|
+
import {
|
|
4
|
+
DataFilterFn,
|
|
5
|
+
type DataFilterFnFrom,
|
|
6
|
+
type DataFilterFnRendered,
|
|
7
|
+
} from './data-filter-fn';
|
|
8
|
+
import { defaultDataFilterFns } from './data-filter-fn-defaults';
|
|
23
9
|
|
|
24
10
|
export class DataFilter {
|
|
25
|
-
public filters = new Map<string, () =>
|
|
11
|
+
public filters = new Map<string, () => DataFilterFnRendered>();
|
|
12
|
+
public filterMapping: Record<string, string> = {};
|
|
26
13
|
|
|
27
14
|
constructor(private rules: Rules) {
|
|
28
|
-
this.registerFilters(rules.
|
|
29
|
-
this.
|
|
15
|
+
this.registerFilters(rules.customDataFilterFns);
|
|
16
|
+
this.createCssFilters();
|
|
30
17
|
}
|
|
31
18
|
|
|
32
|
-
public static isFiltered(
|
|
33
|
-
return
|
|
19
|
+
public static isFiltered(e: HTMLElement): boolean {
|
|
20
|
+
return e.className.includes(DataFilterFn.prefix);
|
|
34
21
|
}
|
|
35
22
|
|
|
36
|
-
public
|
|
23
|
+
public createCssFilters(wrapper?: (cssRule: string) => string) {
|
|
37
24
|
this.filters.forEach((_, name) => {
|
|
38
|
-
const
|
|
25
|
+
const className = DataFilterFn.setPrefix(name);
|
|
26
|
+
const cssRule = `.${className} { display: none !important; }`;
|
|
39
27
|
GM_addStyle(wrapper ? wrapper(cssRule) : cssRule);
|
|
40
28
|
});
|
|
41
29
|
}
|
|
42
30
|
|
|
43
|
-
public
|
|
31
|
+
public customDataFilterFns: Record<string, DataFilterFnFrom<any>> = {};
|
|
44
32
|
|
|
45
|
-
public registerFilters(
|
|
33
|
+
public registerFilters(
|
|
34
|
+
customFilters: (Record<string, DataFilterFnFrom<any>> | string)[],
|
|
35
|
+
) {
|
|
46
36
|
customFilters.forEach((o) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.registerFilter(o);
|
|
50
|
-
} else {
|
|
51
|
-
const k = Object.keys(o)[0];
|
|
52
|
-
this.customDataSelectorFns[k] = o[k];
|
|
53
|
-
this.registerFilter(k);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
37
|
+
const isStr = typeof o === 'string';
|
|
38
|
+
const k = isStr ? o : Object.keys(o)[0];
|
|
57
39
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
): DataSelectorFnAdvanced<T> {
|
|
62
|
-
if ('handle' in selector) {
|
|
63
|
-
return selector as DataSelectorFnAdvanced<T>;
|
|
64
|
-
} else {
|
|
65
|
-
return { handle: selector, deps: [name] } as DataSelectorFnAdvanced<T>;
|
|
66
|
-
}
|
|
40
|
+
this.customDataFilterFns[k] = isStr ? defaultDataFilterFns[o] : o[k];
|
|
41
|
+
this.registerFilter(k);
|
|
42
|
+
});
|
|
67
43
|
}
|
|
68
44
|
|
|
69
45
|
public registerFilter(customSelectorName: string) {
|
|
70
|
-
const
|
|
46
|
+
const dataFilterFn = DataFilterFn.from(
|
|
47
|
+
this.customDataFilterFns[customSelectorName],
|
|
71
48
|
customSelectorName,
|
|
72
|
-
this.customDataSelectorFns[customSelectorName],
|
|
73
49
|
);
|
|
74
|
-
const tag = `filter-${customSelectorName}`;
|
|
75
50
|
|
|
76
|
-
|
|
51
|
+
dataFilterFn.deps.push(customSelectorName);
|
|
52
|
+
|
|
53
|
+
dataFilterFn.deps.forEach((name) => {
|
|
77
54
|
Object.assign(this.filterMapping, { [name]: customSelectorName });
|
|
78
55
|
});
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
const preDefined = handler.$preDefine?.(this.rules.store.state);
|
|
82
|
-
|
|
83
|
-
return (v: DataElement) => {
|
|
84
|
-
const condition = handler.handle(v, this.rules.store.state, preDefined);
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
condition,
|
|
88
|
-
tag,
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
this.filters.set(customSelectorName, fn);
|
|
57
|
+
this.filters.set(customSelectorName, dataFilterFn.renderFn(this.rules.store.state));
|
|
94
58
|
}
|
|
95
59
|
|
|
96
|
-
public filterMapping: Record<string, string> = {};
|
|
97
|
-
|
|
98
60
|
public selectFilters(filters: { [key: string]: boolean }) {
|
|
99
61
|
const selectedFilters = Object.keys(filters)
|
|
100
62
|
.filter((k) => k in this.filterMapping)
|
|
101
63
|
.map((k) => this.filterMapping[k])
|
|
102
|
-
.map((k) => this.filters.get(k) as () =>
|
|
64
|
+
.map((k) => this.filters.get(k) as () => DataFilterFnRendered);
|
|
103
65
|
return selectedFilters;
|
|
104
66
|
}
|
|
105
|
-
|
|
106
|
-
static customDataSelectorFnsDefault: Record<string, DataSelectorFn<any>> = {
|
|
107
|
-
filterDuration: {
|
|
108
|
-
handle(el, state, notInRange) {
|
|
109
|
-
if (!state.filterDuration) return false;
|
|
110
|
-
return notInRange(el.duration);
|
|
111
|
-
},
|
|
112
|
-
$preDefine: (state) => {
|
|
113
|
-
const from = state.filterDurationFrom as number;
|
|
114
|
-
const to = state.filterDurationTo as number;
|
|
115
|
-
function notInRange(d: number): boolean {
|
|
116
|
-
return d < from || d > to;
|
|
117
|
-
}
|
|
118
|
-
return notInRange;
|
|
119
|
-
},
|
|
120
|
-
deps: ['filterDurationFrom', 'filterDurationTo'],
|
|
121
|
-
},
|
|
122
|
-
filterExclude: {
|
|
123
|
-
handle(el, state, searchFilter) {
|
|
124
|
-
if (!state.filterExclude) return false;
|
|
125
|
-
return !(searchFilter as RegexFilter).hasNone(el.title as string);
|
|
126
|
-
},
|
|
127
|
-
$preDefine: (state) => new RegexFilter(state.filterExcludeWords as string),
|
|
128
|
-
deps: ['filterExcludeWords'],
|
|
129
|
-
},
|
|
130
|
-
filterInclude: {
|
|
131
|
-
handle(el, state, searchFilter) {
|
|
132
|
-
if (!state.filterInclude) return false;
|
|
133
|
-
return !(searchFilter as RegexFilter).hasEvery(el.title as string);
|
|
134
|
-
},
|
|
135
|
-
$preDefine: (state) => new RegexFilter(state.filterIncludeWords as string),
|
|
136
|
-
deps: ['filterIncludeWords'],
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
67
|
}
|
|
@@ -3,7 +3,10 @@ import { checkHomogenity, LazyImgLoader } from '../../utils';
|
|
|
3
3
|
import type { Rules } from '../rules';
|
|
4
4
|
import { DataFilter } from './data-filter';
|
|
5
5
|
|
|
6
|
-
export type DataElement =
|
|
6
|
+
export type DataElement = {
|
|
7
|
+
element: HTMLElement;
|
|
8
|
+
[key: string]: HTMLElement | string | number | boolean;
|
|
9
|
+
};
|
|
7
10
|
|
|
8
11
|
export class DataManager {
|
|
9
12
|
public data = new Map<string, DataElement>();
|
|
@@ -14,7 +17,7 @@ export class DataManager {
|
|
|
14
17
|
|
|
15
18
|
constructor(
|
|
16
19
|
private rules: Rules,
|
|
17
|
-
private
|
|
20
|
+
private containerHomogenity?: Parameters<typeof checkHomogenity>[2],
|
|
18
21
|
) {
|
|
19
22
|
this.dataFilter = new DataFilter(this.rules);
|
|
20
23
|
}
|
|
@@ -29,29 +32,21 @@ export class DataManager {
|
|
|
29
32
|
const iterator = this.data.values().drop(offset);
|
|
30
33
|
let finished = false;
|
|
31
34
|
|
|
35
|
+
const updates: { e: HTMLElement; name: string; condition: boolean }[] = [];
|
|
36
|
+
|
|
32
37
|
await new Promise((resolve) => {
|
|
33
38
|
function runBatch(deadline: IdleDeadline) {
|
|
34
|
-
const updates: { e: HTMLElement; tag: string; condition: boolean }[] = [];
|
|
35
|
-
|
|
36
39
|
while (deadline.timeRemaining() > 0) {
|
|
37
40
|
const { value, done } = iterator.next();
|
|
38
41
|
finished = !!done;
|
|
39
42
|
if (done) break;
|
|
40
43
|
|
|
41
44
|
for (const f of filtersToApply) {
|
|
42
|
-
const {
|
|
43
|
-
updates.push({ e: value.element as HTMLElement,
|
|
45
|
+
const { name, condition } = f()(value);
|
|
46
|
+
updates.push({ e: value.element as HTMLElement, name, condition });
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
if (updates.length > 0) {
|
|
48
|
-
requestAnimationFrame(() => {
|
|
49
|
-
updates.forEach((u) => {
|
|
50
|
-
u.e.classList.toggle(u.tag, u.condition);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
50
|
if (!finished) {
|
|
56
51
|
requestIdleCallback(runBatch);
|
|
57
52
|
} else {
|
|
@@ -61,8 +56,42 @@ export class DataManager {
|
|
|
61
56
|
|
|
62
57
|
requestIdleCallback(runBatch);
|
|
63
58
|
});
|
|
59
|
+
|
|
60
|
+
const parents = [...new Set(updates.map((u) => u.e.parentElement))].filter(
|
|
61
|
+
(_) => _ !== null,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
requestAnimationFrame(() => {
|
|
65
|
+
const revertDisplayStyle = parents.map((p) => {
|
|
66
|
+
const display = p.style.display;
|
|
67
|
+
p.style.display = 'none';
|
|
68
|
+
this.layoutStylePaint(p);
|
|
69
|
+
p.style.willChange = 'contents';
|
|
70
|
+
return () => {
|
|
71
|
+
p.style.display = display;
|
|
72
|
+
requestAnimationFrame(() => {
|
|
73
|
+
p.style.willChange = 'auto';
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
updates.forEach((u) => {
|
|
79
|
+
u.e.classList.toggle(u.name, u.condition);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
revertDisplayStyle.forEach((f) => {
|
|
83
|
+
f?.();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
64
86
|
};
|
|
65
87
|
|
|
88
|
+
public layoutStylePaintEnabled = false;
|
|
89
|
+
|
|
90
|
+
private layoutStylePaint(e: HTMLElement) {
|
|
91
|
+
if (!this.layoutStylePaintEnabled) return;
|
|
92
|
+
e.style.contain = 'layout style paint';
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
public filterAll = async (offset?: number): Promise<void> => {
|
|
67
96
|
const keys = Array.from(this.dataFilter.filters.keys());
|
|
68
97
|
const filters = Object.fromEntries(
|
|
@@ -82,7 +111,7 @@ export class DataManager {
|
|
|
82
111
|
const dataOffset = this.data.size;
|
|
83
112
|
const fragment = document.createDocumentFragment();
|
|
84
113
|
const parent = container || this.rules.container;
|
|
85
|
-
const homogenity = !!this.
|
|
114
|
+
const homogenity = !!this.containerHomogenity;
|
|
86
115
|
|
|
87
116
|
for (const thumbElement of thumbs) {
|
|
88
117
|
const url = this.rules.thumbDataParser.getUrl(thumbElement);
|
|
@@ -94,7 +123,7 @@ export class DataManager {
|
|
|
94
123
|
!checkHomogenity(
|
|
95
124
|
parent,
|
|
96
125
|
thumbElement.parentElement as HTMLElement,
|
|
97
|
-
this.
|
|
126
|
+
this.containerHomogenity as object,
|
|
98
127
|
))
|
|
99
128
|
) {
|
|
100
129
|
if (removeDuplicates) thumbElement.remove();
|
|
@@ -113,8 +142,19 @@ export class DataManager {
|
|
|
113
142
|
}
|
|
114
143
|
|
|
115
144
|
this.filterAll(dataOffset).then(() => {
|
|
145
|
+
if (!parent) return;
|
|
146
|
+
//
|
|
147
|
+
// THIS LINE BRAKES RENDERING, EVERYTHING DISAPPEARS
|
|
148
|
+
//
|
|
149
|
+
// this.layoutStylePaint(parent)
|
|
150
|
+
//
|
|
151
|
+
parent.style.willChange = 'contents';
|
|
116
152
|
requestAnimationFrame(() => {
|
|
117
|
-
parent.
|
|
153
|
+
// parent.style.contain = 'layout style paint';
|
|
154
|
+
parent?.appendChild(fragment);
|
|
155
|
+
requestAnimationFrame(() => {
|
|
156
|
+
parent.style.willChange = 'auto';
|
|
157
|
+
});
|
|
118
158
|
});
|
|
119
159
|
});
|
|
120
160
|
};
|
|
@@ -122,23 +162,46 @@ export class DataManager {
|
|
|
122
162
|
public sortBy<K extends keyof DataElement>(key: K, direction = true): void {
|
|
123
163
|
if (this.data.size < 2) return;
|
|
124
164
|
|
|
125
|
-
|
|
165
|
+
const elements = this.data
|
|
126
166
|
.values()
|
|
127
167
|
.toArray()
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
});
|
|
168
|
+
.filter((e) => e.element.parentElement !== null)
|
|
169
|
+
.map((e) => e);
|
|
131
170
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
171
|
+
const containers = new Set(elements.map((e) => e.element.parentElement as HTMLElement));
|
|
172
|
+
containers.forEach((c) => {
|
|
173
|
+
this.layoutStylePaint(c);
|
|
174
|
+
c.style.willChange = 'contents';
|
|
175
|
+
});
|
|
135
176
|
|
|
136
|
-
|
|
177
|
+
const elementsByContainers = new Map<HTMLElement, DataElement[]>();
|
|
178
|
+
containers.forEach((c) => {
|
|
179
|
+
elementsByContainers.set(c, []);
|
|
180
|
+
});
|
|
137
181
|
|
|
138
|
-
|
|
139
|
-
|
|
182
|
+
elements.forEach((e) => {
|
|
183
|
+
const parent = e.element.parentElement as HTMLElement;
|
|
184
|
+
const container = elementsByContainers.get(parent);
|
|
185
|
+
container?.push(e);
|
|
140
186
|
});
|
|
141
187
|
|
|
142
|
-
|
|
188
|
+
const dir = direction ? -1 : 1;
|
|
189
|
+
|
|
190
|
+
for (const [container, items] of elementsByContainers) {
|
|
191
|
+
items.sort((a, b) => ((a[key] as number) - (b[key] as number)) * dir);
|
|
192
|
+
const domNodes = items.map((e) => e.element);
|
|
193
|
+
|
|
194
|
+
const display = container.style.display;
|
|
195
|
+
container.style.display = 'none';
|
|
196
|
+
|
|
197
|
+
container.replaceChildren(...domNodes);
|
|
198
|
+
|
|
199
|
+
requestAnimationFrame(() => {
|
|
200
|
+
container.style.display = display;
|
|
201
|
+
requestAnimationFrame(() => {
|
|
202
|
+
container.style.willChange = 'auto';
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
143
206
|
}
|
|
144
207
|
}
|
|
@@ -2,7 +2,7 @@ import type { JabroniTypes, SchemeInput, setupScheme } from 'jabroni-outfit';
|
|
|
2
2
|
|
|
3
3
|
export const DefaultScheme = [
|
|
4
4
|
{
|
|
5
|
-
title: '
|
|
5
|
+
title: 'Title Filter',
|
|
6
6
|
collapsed: true,
|
|
7
7
|
content: [
|
|
8
8
|
{ filterExclude: false, label: 'exclude' },
|
|
@@ -21,6 +21,26 @@ export const DefaultScheme = [
|
|
|
21
21
|
},
|
|
22
22
|
],
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
title: 'Uploader Filter',
|
|
26
|
+
collapsed: true,
|
|
27
|
+
content: [
|
|
28
|
+
{ filterUploaderExclude: false, label: 'exclude' },
|
|
29
|
+
{
|
|
30
|
+
filterUploaderExcludeWords: '',
|
|
31
|
+
label: 'keywords',
|
|
32
|
+
watch: 'filterUploaderExclude',
|
|
33
|
+
placeholder: 'word, f:full_word, r:RegEx...',
|
|
34
|
+
},
|
|
35
|
+
{ filterUploaderInclude: false, label: 'include' },
|
|
36
|
+
{
|
|
37
|
+
filterUploaderIncludeWords: '',
|
|
38
|
+
label: 'keywords',
|
|
39
|
+
watch: 'filterUploaderInclude',
|
|
40
|
+
placeholder: 'word, f:full_word, r:RegEx...',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
24
44
|
{
|
|
25
45
|
title: 'Duration Filter',
|
|
26
46
|
collapsed: true,
|
|
@@ -42,6 +62,7 @@ export const DefaultScheme = [
|
|
|
42
62
|
},
|
|
43
63
|
{
|
|
44
64
|
title: 'Sort By',
|
|
65
|
+
collapsed: true,
|
|
45
66
|
content: [
|
|
46
67
|
{
|
|
47
68
|
'sort by views': () => {},
|
|
@@ -51,14 +72,40 @@ export const DefaultScheme = [
|
|
|
51
72
|
},
|
|
52
73
|
],
|
|
53
74
|
},
|
|
75
|
+
{
|
|
76
|
+
title: 'Sort By Duration',
|
|
77
|
+
collapsed: true,
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
'sort by duration': () => {},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
title: 'Sort By Views',
|
|
86
|
+
collapsed: true,
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
'sort by views': () => {},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
54
93
|
{
|
|
55
94
|
title: 'Privacy Filter',
|
|
95
|
+
collapsed: true,
|
|
56
96
|
content: [
|
|
57
97
|
{ filterPrivate: false, label: 'private' },
|
|
58
98
|
{ filterPublic: false, label: 'public' },
|
|
59
99
|
{ 'check access 🔓': () => {} },
|
|
60
100
|
],
|
|
61
101
|
},
|
|
102
|
+
{
|
|
103
|
+
title: 'HD Filter',
|
|
104
|
+
content: [
|
|
105
|
+
{ filterHD: false, label: 'hd' },
|
|
106
|
+
{ filterNonHD: false, label: 'non-hd' },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
62
109
|
{
|
|
63
110
|
title: 'Advanced',
|
|
64
111
|
collapsed: true,
|
|
@@ -79,6 +126,9 @@ export const DefaultScheme = [
|
|
|
79
126
|
writeHistory: false,
|
|
80
127
|
label: 'write history',
|
|
81
128
|
},
|
|
129
|
+
{
|
|
130
|
+
reset: () => { localStorage.removeItem('state_acephale'); }
|
|
131
|
+
}
|
|
82
132
|
],
|
|
83
133
|
},
|
|
84
134
|
{
|
|
@@ -92,7 +142,6 @@ export const DefaultScheme = [
|
|
|
92
142
|
},
|
|
93
143
|
] as const satisfies SchemeInput;
|
|
94
144
|
|
|
95
|
-
export type
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)[];
|
|
145
|
+
export type SchemeKeys = JabroniTypes.ExtractValuesByKey<typeof DefaultScheme, 'title'>;
|
|
146
|
+
|
|
147
|
+
export type SchemeOptions = (Parameters<typeof setupScheme>[0][0] | SchemeKeys)[];
|
|
@@ -51,7 +51,7 @@ export class JabronioGuiController {
|
|
|
51
51
|
|
|
52
52
|
private setupStoreListeners() {
|
|
53
53
|
this.directionalEventObservable$?.subscribe((e) => {
|
|
54
|
-
this.eventsMap[e.type](e.direction);
|
|
54
|
+
this.eventsMap[e.type]?.(e.direction);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
this.store.stateSubject.pipe(takeUntil(this.destroy$)).subscribe((a) => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defaultDataFilterFns } from '../data-handler/data-filter-fn-defaults';
|
|
2
|
+
import { DefaultScheme, type SchemeKeys } from './default-scheme';
|
|
3
|
+
|
|
4
|
+
export function getSelectorFnsFromScheme(xs: SchemeKeys[]) {
|
|
5
|
+
const keys = xs.flatMap((s) => {
|
|
6
|
+
const schemeBlock = DefaultScheme.find((e) => e.title === s);
|
|
7
|
+
if (!schemeBlock) return [];
|
|
8
|
+
return schemeBlock.content.flatMap((c) => Object.keys(c));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return keys.filter((k) => k in defaultDataFilterFns);
|
|
12
|
+
}
|
|
@@ -7,13 +7,15 @@ import {
|
|
|
7
7
|
} from '../../utils';
|
|
8
8
|
|
|
9
9
|
type Primitive = string | number | boolean;
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
type ThumbData = Record<string, Primitive>;
|
|
12
|
+
|
|
12
13
|
type ThumbDataSelector = {
|
|
13
14
|
name: string;
|
|
14
15
|
selector: string;
|
|
15
|
-
type:
|
|
16
|
+
type: 'boolean' | 'string' | 'number' | 'float' | 'duration';
|
|
16
17
|
};
|
|
18
|
+
|
|
17
19
|
type ThumbDataSelectorsRaw = Record<
|
|
18
20
|
string,
|
|
19
21
|
string | Pick<ThumbDataSelector, 'selector' | 'type'>
|
|
@@ -21,8 +23,13 @@ type ThumbDataSelectorsRaw = Record<
|
|
|
21
23
|
|
|
22
24
|
export class ThumbDataParser {
|
|
23
25
|
private autoParseText(thumb: HTMLElement): ThumbData {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
let title = sanitizeStr(thumb.innerText);
|
|
27
|
+
|
|
28
|
+
const durationStr = title.match(/(\d+:\d+:?\d+?)|\d+m/)?.[0] || '';
|
|
29
|
+
const duration = timeToSeconds(durationStr);
|
|
30
|
+
|
|
31
|
+
title = title.replaceAll(durationStr, '');
|
|
32
|
+
|
|
26
33
|
return { title, duration };
|
|
27
34
|
}
|
|
28
35
|
|
|
@@ -56,6 +63,7 @@ export class ThumbDataParser {
|
|
|
56
63
|
selector: '[class *= uploader], [class *= user], [class *= name]',
|
|
57
64
|
},
|
|
58
65
|
{ name: 'duration', type: 'duration', selector: '[class *= duration]' },
|
|
66
|
+
// { name: 'views', type: 'float', selector: '[class *= view]' },
|
|
59
67
|
];
|
|
60
68
|
|
|
61
69
|
private getThumbDataWith(
|
|
@@ -82,17 +90,14 @@ export class ThumbDataParser {
|
|
|
82
90
|
public strategy: 'manual' | 'auto-select' | 'auto-text' = 'manual',
|
|
83
91
|
public selectors: ThumbDataSelectorsRaw = {},
|
|
84
92
|
public callback?: (thumb: HTMLElement, thumbData: ThumbData) => void,
|
|
85
|
-
public stringsMeltInTitle = true,
|
|
86
93
|
) {
|
|
87
94
|
this.preprocessCustomThumbDataSelectors();
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
public static create(
|
|
91
|
-
o: Partial<
|
|
92
|
-
Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback' | 'stringsMeltInTitle'>
|
|
93
|
-
> = {},
|
|
98
|
+
o: Partial<Pick<ThumbDataParser, 'strategy' | 'selectors' | 'callback'>> = {},
|
|
94
99
|
) {
|
|
95
|
-
return new ThumbDataParser(o.strategy, o.selectors, o.callback
|
|
100
|
+
return new ThumbDataParser(o.strategy, o.selectors, o.callback);
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
public getThumbData(thumb: HTMLElement): ThumbData {
|
|
@@ -101,22 +106,13 @@ export class ThumbDataParser {
|
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
if (this.strategy === 'auto-select') {
|
|
104
|
-
this.thumbDataSelectors
|
|
109
|
+
this.thumbDataSelectors.push(...this.defaultThumbDataSelectors);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
const thumbData = Object.fromEntries(
|
|
108
113
|
this.thumbDataSelectors.map((s) => [s.name, this.getThumbDataWith(thumb, s)]),
|
|
109
114
|
);
|
|
110
115
|
|
|
111
|
-
if (this.stringsMeltInTitle) {
|
|
112
|
-
Object.entries(thumbData).forEach(([k, v]) => {
|
|
113
|
-
if (typeof v === 'string' && k !== 'title') {
|
|
114
|
-
thumbData.title = `${thumbData.title} ${k}:${v}`;
|
|
115
|
-
delete thumbData[k];
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
116
|
this.callback?.(thumb, thumbData);
|
|
121
117
|
|
|
122
118
|
return thumbData;
|
package/src/core/rules/index.ts
CHANGED
|
@@ -4,10 +4,12 @@ import {
|
|
|
4
4
|
querySelectorLast,
|
|
5
5
|
waitForElementToDisappear,
|
|
6
6
|
} from '../../utils';
|
|
7
|
-
import { DataManager
|
|
7
|
+
import { DataManager } from '../data-handler';
|
|
8
|
+
import type { DataFilterFnFrom } from '../data-handler/data-filter-fn';
|
|
8
9
|
import { InfiniteScroller, type OffsetGenerator } from '../infinite-scroll';
|
|
9
10
|
import {
|
|
10
11
|
DefaultScheme,
|
|
12
|
+
getSelectorFnsFromScheme,
|
|
11
13
|
JabronioGuiController,
|
|
12
14
|
type SchemeOptions,
|
|
13
15
|
StoreStateDefault,
|
|
@@ -61,12 +63,15 @@ export class Rules {
|
|
|
61
63
|
public paginationStrategy: PaginationStrategy;
|
|
62
64
|
|
|
63
65
|
public dataManager: DataManager;
|
|
64
|
-
public
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
public containerHomogenity: ConstructorParameters<typeof DataManager>[1];
|
|
67
|
+
|
|
68
|
+
public customDataFilterFns: (Record<string, DataFilterFnFrom<any>> | string)[] = [];
|
|
69
|
+
private hookDataFilterFns() {
|
|
70
|
+
const defaultFilterFns = getSelectorFnsFromScheme(
|
|
71
|
+
this.schemeOptions.filter((s) => typeof s === 'string'),
|
|
72
|
+
);
|
|
73
|
+
this.customDataFilterFns.push(...defaultFilterFns);
|
|
74
|
+
}
|
|
70
75
|
|
|
71
76
|
public animatePreview?: (doc: HTMLElement) => void;
|
|
72
77
|
|
|
@@ -151,7 +156,7 @@ export class Rules {
|
|
|
151
156
|
|
|
152
157
|
this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
|
|
153
158
|
|
|
154
|
-
this.dataManager = new DataManager(this, this.
|
|
159
|
+
this.dataManager = new DataManager(this, this.containerHomogenity);
|
|
155
160
|
|
|
156
161
|
this.inputController.dispose();
|
|
157
162
|
this.inputController = new JabronioGuiController(this.store, this.dataManager);
|
|
@@ -181,7 +186,8 @@ export class Rules {
|
|
|
181
186
|
this.store = this.createStore();
|
|
182
187
|
this.gui = this.createGui();
|
|
183
188
|
|
|
184
|
-
this.
|
|
189
|
+
this.hookDataFilterFns();
|
|
190
|
+
this.dataManager = new DataManager(this, this.containerHomogenity);
|
|
185
191
|
this.inputController = new JabronioGuiController(this.store, this.dataManager);
|
|
186
192
|
|
|
187
193
|
this.reset();
|
package/src/userscripts/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import './scripts/
|
|
1
|
+
import './scripts/ebalka';
|
|
@@ -4,8 +4,8 @@ import { circularShift, OnHover, Tick } from '../../utils';
|
|
|
4
4
|
|
|
5
5
|
export const meta: MonkeyUserScript = {
|
|
6
6
|
name: '3Hentai PervertMonkey',
|
|
7
|
-
version: '1.0.
|
|
8
|
-
description: 'Infinite scroll [optional], Filter by Title',
|
|
7
|
+
version: '1.0.7',
|
|
8
|
+
description: 'Infinite scroll [optional], Filter by Title, thumb preview',
|
|
9
9
|
match: 'https://*.3hentai.net/*',
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -23,8 +23,7 @@ const rules = new Rules({
|
|
|
23
23
|
strategy: 'auto',
|
|
24
24
|
},
|
|
25
25
|
gropeStrategy: 'all-in-all',
|
|
26
|
-
|
|
27
|
-
schemeOptions: ['Text Filter', 'Badge', 'Advanced'],
|
|
26
|
+
schemeOptions: ['Title Filter', 'Badge', 'Advanced'],
|
|
28
27
|
animatePreview,
|
|
29
28
|
});
|
|
30
29
|
|
|
@@ -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.
|
|
5
|
+
version: '1.6.6',
|
|
6
6
|
description:
|
|
7
7
|
'Adds model links for CamWhores, webcamrecordings, recu.me, camvideos, privat-zapisi',
|
|
8
8
|
match: ['https://camgirlfinder.net/*'],
|