billy-herrington-utils 2.0.7 → 2.1.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/README.md +360 -62
- package/dist/billy-herrington-utils.es.js +275 -6251
- package/dist/billy-herrington-utils.es.js.map +1 -1
- package/dist/billy-herrington-utils.umd.js +321 -6253
- package/dist/billy-herrington-utils.umd.js.map +1 -1
- package/dist/index.d.ts +69 -190
- package/package.json +18 -7
- package/src/index.ts +1 -44
- package/src/types/index.ts +7 -0
- package/src/utils/arrays/index.ts +11 -5
- package/src/utils/async/index.ts +0 -88
- package/src/utils/dom/{observers.ts → dom-observers.ts} +3 -3
- package/src/utils/dom/index.ts +97 -16
- package/src/utils/events/index.ts +2 -37
- package/src/utils/events/on-hover.ts +42 -0
- package/src/utils/events/tick.ts +27 -0
- package/src/utils/fetch/index.ts +30 -28
- package/src/utils/index.ts +39 -0
- package/src/utils/objects/index.ts +19 -18
- package/src/utils/objects/memoize.ts +25 -0
- package/src/utils/observers/index.ts +4 -28
- package/src/utils/observers/lazy-image-loader.ts +27 -0
- package/src/utils/parsers/index.ts +2 -20
- package/src/utils/parsers/time-parser.ts +28 -0
- package/src/utils/strings/regexes.ts +12 -8
- package/src/types/globals.d.ts +0 -0
- package/src/userscripts/data-manager/data-filter.ts +0 -110
- package/src/userscripts/data-manager/index.ts +0 -141
- package/src/userscripts/infinite-scroll/index.ts +0 -106
- package/src/userscripts/pagination-parsing/index.ts +0 -55
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategy.ts +0 -42
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyDataParams.ts +0 -66
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyPathnameParams.ts +0 -77
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategySearchParams.ts +0 -56
- package/src/userscripts/pagination-parsing/pagination-strategies/PaginationStrategyTrash.ts +0 -33
- package/src/userscripts/pagination-parsing/pagination-strategies/index.ts +0 -5
- package/src/userscripts/pagination-parsing/pagination-utils/index.ts +0 -69
- package/src/userscripts/router/router.ts +0 -71
- package/src/userscripts/rules/index.ts +0 -241
- package/src/userscripts/types/index.ts +0 -19
- package/src/utils/device/index.ts +0 -3
- package/src/utils/userscript/index.ts +0 -10
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
export function parseURL(s: HTMLAnchorElement | Location | URL | string): URL {
|
|
2
|
-
if (typeof s === 'string') return new URL(s);
|
|
3
|
-
return new URL(s.href);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function getPaginationLinks(
|
|
7
|
-
doc: Element | HTMLElement | Document = document,
|
|
8
|
-
url: Location | URL | string = location.href,
|
|
9
|
-
pathnameSelector = /\/(page\/)?\d+\/?$/,
|
|
10
|
-
): string[] {
|
|
11
|
-
const currentUrl = parseURL(url);
|
|
12
|
-
currentUrl.pathname = currentUrl.pathname.replace(pathnameSelector, '/');
|
|
13
|
-
const pathnameStrict = doc instanceof Document;
|
|
14
|
-
|
|
15
|
-
const pageLinks = Array.from(
|
|
16
|
-
(doc.querySelectorAll('a[href]') as NodeListOf<HTMLAnchorElement>) || [],
|
|
17
|
-
(a) => a.href,
|
|
18
|
-
).filter((h) => {
|
|
19
|
-
try {
|
|
20
|
-
const linkUrl = new URL(h.replace(/#\w*$/, ''), doc.baseURI || currentUrl.origin);
|
|
21
|
-
return (
|
|
22
|
-
linkUrl.hostname === currentUrl.hostname &&
|
|
23
|
-
(pathnameStrict ? linkUrl.pathname.startsWith(currentUrl.pathname) : true)
|
|
24
|
-
);
|
|
25
|
-
} catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
return pageLinks;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @description
|
|
34
|
-
* curr: website.com, links: [webiste.com/new/23] => wegsite.com/new
|
|
35
|
-
*/
|
|
36
|
-
export function upgradePathname(curr: URL, links: URL[]): URL {
|
|
37
|
-
if (/\/(page\/)?\d+\/?$/.test(curr.pathname) || links.length < 1) return curr;
|
|
38
|
-
const linksDepaginated = links.map((l) => {
|
|
39
|
-
l.pathname = l.pathname.replace(/\/(page\/)?\d+\/?$/, '/');
|
|
40
|
-
return l;
|
|
41
|
-
});
|
|
42
|
-
if (linksDepaginated.some((l) => l.pathname === curr.pathname)) return curr;
|
|
43
|
-
const last = linksDepaginated.at(-1) as URL;
|
|
44
|
-
if (last.pathname !== curr.pathname) curr.pathname = last.pathname;
|
|
45
|
-
return curr;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @description
|
|
50
|
-
* website.com/search => website.com/search+word1...+-word2
|
|
51
|
-
*/
|
|
52
|
-
export function applyURLLevelSearchFilters(
|
|
53
|
-
searchFilter: string,
|
|
54
|
-
queryType: keyof Pick<Location, 'pathname' | 'search'>,
|
|
55
|
-
): void {
|
|
56
|
-
const wordsToFilter =
|
|
57
|
-
searchFilter.replace(/f:/g, '').match(/(?<!user:)\b\w+\b(?!\s*:)/g) || [];
|
|
58
|
-
|
|
59
|
-
if (!wordsToFilter.some((w) => !location.href.includes(w))) return;
|
|
60
|
-
|
|
61
|
-
let query = location[queryType];
|
|
62
|
-
|
|
63
|
-
wordsToFilter.forEach((w) => {
|
|
64
|
-
if (query.includes(w)) return;
|
|
65
|
-
query += `+-${w.trim()}`;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
window.location[queryType] = query;
|
|
69
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
At this point I have no idea what I'm doing
|
|
4
|
-
...
|
|
5
|
-
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// import { JabronioGUI, JabronioStore, setupScheme } from 'jabroni-outfit';
|
|
9
|
-
// import { DataManager } from '../data-manager';
|
|
10
|
-
// import { InfiniteScroller } from '../infinite-scroll';
|
|
11
|
-
// import { RulesGlobal } from '../rules';
|
|
12
|
-
|
|
13
|
-
// import type { NonFunctionProperties } from '../types';
|
|
14
|
-
|
|
15
|
-
// // const store = new JabroniOutfitStore(defaultStateWithDurationAndPrivacy);
|
|
16
|
-
// // const { state } = store;
|
|
17
|
-
|
|
18
|
-
// type RouterProp = NonFunctionProperties<Router>;
|
|
19
|
-
|
|
20
|
-
// export class Router {
|
|
21
|
-
// constructor(
|
|
22
|
-
// public rules: typeof RulesGlobal,
|
|
23
|
-
// public scheme: SchemeInput
|
|
24
|
-
|
|
25
|
-
// ) {
|
|
26
|
-
// this.rules = new RulesGlobal();
|
|
27
|
-
// }
|
|
28
|
-
|
|
29
|
-
// public defaultRouterRules: typeof this.routerRules = {
|
|
30
|
-
// initStore: ({ rules }) => {
|
|
31
|
-
// this.store = new JabronioStore();
|
|
32
|
-
// this.dataManager = new DataManager(rules, this.store.state);
|
|
33
|
-
// this.store.subscribe(() => this.dataManager?.applyFilters());
|
|
34
|
-
// },
|
|
35
|
-
// initUI: () => {
|
|
36
|
-
// this.ui = new JabronioGUI([], this.store as JabronioStore);
|
|
37
|
-
// },
|
|
38
|
-
// grabInit: ({ rules, dataManager }) => {
|
|
39
|
-
// if (!rules.container) return;
|
|
40
|
-
// // if find many containers > then for each
|
|
41
|
-
// dataManager?.parseData(rules.container, rules.container);
|
|
42
|
-
// },
|
|
43
|
-
// initInfiniteScroll: ({ rules, dataManager, store }) => {
|
|
44
|
-
// if (rules.paginationStrategy.hasPagination) {
|
|
45
|
-
// InfiniteScroller.create(
|
|
46
|
-
// store as JabronioStore,
|
|
47
|
-
// (dataManager as DataManager)?.parseData,
|
|
48
|
-
// rules,
|
|
49
|
-
// );
|
|
50
|
-
// rules.animatePreview?.();
|
|
51
|
-
// }
|
|
52
|
-
// },
|
|
53
|
-
// };
|
|
54
|
-
|
|
55
|
-
// public routerRules: Record<string, (args: RouterProp) => void> = {};
|
|
56
|
-
|
|
57
|
-
// // you are a tricky motherfucker aren't you?
|
|
58
|
-
// public add<O extends typeof this.routerRules, A extends keyof O, B extends O[string]>(
|
|
59
|
-
// name: A,
|
|
60
|
-
// fn: B,
|
|
61
|
-
// ) {
|
|
62
|
-
// Object.assign(this.routerRules, { [name]: fn });
|
|
63
|
-
// }
|
|
64
|
-
|
|
65
|
-
// public run() {
|
|
66
|
-
// Object.assign(this.routerRules, this.defaultRouterRules);
|
|
67
|
-
// Object.entries(this.routerRules).forEach(([_, f]) => {
|
|
68
|
-
// f(this);
|
|
69
|
-
// });
|
|
70
|
-
// }
|
|
71
|
-
// }
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { JabronioGUI, JabronioStore, type JabroniTypes, setupScheme } from 'jabroni-outfit';
|
|
2
|
-
import {
|
|
3
|
-
getAllUniqueParents,
|
|
4
|
-
querySelectorText,
|
|
5
|
-
waitForElementToDisappear,
|
|
6
|
-
} from '../../utils/dom';
|
|
7
|
-
import { timeToSeconds } from '../../utils/parsers';
|
|
8
|
-
import { sanitizeStr } from '../../utils/strings';
|
|
9
|
-
import { DataManager } from '../data-manager';
|
|
10
|
-
import type { CustomSelector } from '../data-manager/data-filter';
|
|
11
|
-
import { InfiniteScroller, type OffsetGenerator } from '../infinite-scroll/';
|
|
12
|
-
import { getPaginationStrategy } from '../pagination-parsing';
|
|
13
|
-
import type { PaginationStrategy } from '../pagination-parsing/pagination-strategies';
|
|
14
|
-
|
|
15
|
-
type ThumbData = { title: string; duration?: number };
|
|
16
|
-
|
|
17
|
-
export class RulesGlobal {
|
|
18
|
-
public delay?: number;
|
|
19
|
-
|
|
20
|
-
public alternativeGenerator?: () => OffsetGenerator;
|
|
21
|
-
|
|
22
|
-
public getThumbUrl(thumb: HTMLElement | HTMLAnchorElement) {
|
|
23
|
-
return ((thumb.querySelector('a[href]') || thumb) as HTMLAnchorElement).href;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public titleSelector: undefined | string;
|
|
27
|
-
public uploaderSelector: undefined | string;
|
|
28
|
-
public durationSelector: undefined | string;
|
|
29
|
-
public getThumbDataStrategy: 'default' | 'auto-select' | 'auto-text' = 'default';
|
|
30
|
-
// durationParseStrategy: 'default' | 'auto' = 'default';
|
|
31
|
-
// timeToSeconds(thumb.innerText.match(/\d+m/)?.[0]);
|
|
32
|
-
|
|
33
|
-
public getThumbDataCallback?: (thumb: HTMLElement, thumbData: ThumbData) => void;
|
|
34
|
-
|
|
35
|
-
public getThumbData(thumb: HTMLElement): ThumbData {
|
|
36
|
-
let { titleSelector, uploaderSelector, durationSelector } = this;
|
|
37
|
-
const thumbData: ThumbData = { title: '' };
|
|
38
|
-
|
|
39
|
-
if (this.getThumbDataStrategy === 'auto-text') {
|
|
40
|
-
const text = sanitizeStr(thumb.innerText);
|
|
41
|
-
thumbData.title = text;
|
|
42
|
-
thumbData.duration = timeToSeconds(text.match(/\d+m|\d+:\d+/)?.[0] || '');
|
|
43
|
-
return thumbData;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* experimental */
|
|
47
|
-
if (this.getThumbDataStrategy === 'auto-select') {
|
|
48
|
-
// CamWhores, Ebalka, Erome, EropProfile, Javhdporn, Motherless
|
|
49
|
-
titleSelector = '[class *= title],[title]';
|
|
50
|
-
// CamWhores, Javhdporn,
|
|
51
|
-
durationSelector = '[class *= duration]';
|
|
52
|
-
// Erome, Eporner, Motherless
|
|
53
|
-
uploaderSelector = '[class *= uploader], [class *= user], [class *= name]';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const title = querySelectorText(thumb, titleSelector);
|
|
57
|
-
|
|
58
|
-
if (uploaderSelector) {
|
|
59
|
-
const uploader = querySelectorText(thumb, uploaderSelector);
|
|
60
|
-
thumbData.title = `${title} user:${uploader}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (durationSelector) {
|
|
64
|
-
const duration = timeToSeconds(querySelectorText(thumb, durationSelector));
|
|
65
|
-
thumbData.duration = duration;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
this.getThumbDataCallback?.(thumb, thumbData);
|
|
69
|
-
|
|
70
|
-
return thumbData;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
public getThumbImgDataAttrSelector: string | undefined;
|
|
74
|
-
public getThumbImgDataStrategy: 'default' | 'auto' = 'default';
|
|
75
|
-
// getThumbImgDataRemLazy: boolean | string = false;
|
|
76
|
-
|
|
77
|
-
public getThumbImgData(thumb: HTMLElement) {
|
|
78
|
-
const result: { img?: HTMLImageElement; imgSrc?: string } = {};
|
|
79
|
-
|
|
80
|
-
if (this.getThumbImgDataStrategy === 'auto') {
|
|
81
|
-
const img = thumb.querySelector<HTMLImageElement>('img');
|
|
82
|
-
if (!img) return {};
|
|
83
|
-
|
|
84
|
-
result.img = img;
|
|
85
|
-
|
|
86
|
-
const possibleAttrs = this.getThumbImgDataAttrSelector
|
|
87
|
-
? [this.getThumbImgDataAttrSelector]
|
|
88
|
-
: ['data-src', 'src'];
|
|
89
|
-
|
|
90
|
-
for (const attr of possibleAttrs) {
|
|
91
|
-
const imgSrc = img.getAttribute(attr);
|
|
92
|
-
if (imgSrc) {
|
|
93
|
-
result.imgSrc = imgSrc;
|
|
94
|
-
img.removeAttribute(attr);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// remove lazy- attrs and so on
|
|
100
|
-
|
|
101
|
-
// img.naturalWidth === 0
|
|
102
|
-
// if (img.complete && img.getAttribute('src') && !img.src.includes('data:image')) {
|
|
103
|
-
// return {};
|
|
104
|
-
// }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
public containerSelector: string | (() => HTMLElement) = '.container';
|
|
111
|
-
public intersectionObservable?: HTMLElement;
|
|
112
|
-
|
|
113
|
-
get container() {
|
|
114
|
-
if (typeof this.containerSelector === 'string') {
|
|
115
|
-
return document.querySelector<HTMLElement>(this.containerSelector) as HTMLElement;
|
|
116
|
-
}
|
|
117
|
-
return this.containerSelector();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
public thumbsSelector = '.thumb';
|
|
121
|
-
public getThumbsStrategy: 'default' | 'auto' = 'default';
|
|
122
|
-
|
|
123
|
-
public getThumbs(html: HTMLElement): HTMLElement[] {
|
|
124
|
-
if (this.getThumbsStrategy === 'auto') {
|
|
125
|
-
if (typeof this.containerSelector !== 'string') return [];
|
|
126
|
-
const container = html.querySelector(this.containerSelector);
|
|
127
|
-
const thumbs = [...(container?.children || [])] as HTMLElement[];
|
|
128
|
-
return thumbs;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const thumbs = Array.from<HTMLElement>(html.querySelectorAll(this.thumbsSelector));
|
|
132
|
-
return thumbs;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
public paginationStrategyOptions: Partial<PaginationStrategy> = {};
|
|
136
|
-
public paginationStrategy: PaginationStrategy;
|
|
137
|
-
|
|
138
|
-
// get paginationElement() {
|
|
139
|
-
// return this.paginationStrategy.getPaginationElement();
|
|
140
|
-
// }
|
|
141
|
-
|
|
142
|
-
// get paginationOffset() {
|
|
143
|
-
// return this.paginaCustomSelectorNametionStrategy.getPaginationOffset();
|
|
144
|
-
// }
|
|
145
|
-
|
|
146
|
-
// get paginationLast() {
|
|
147
|
-
// return this.paginationStrategy.getPaginationLast();
|
|
148
|
-
// }
|
|
149
|
-
|
|
150
|
-
// get paginationUrlGenerator() {
|
|
151
|
-
// return this.paginationStrategy.getPaginationUrlGenerator();
|
|
152
|
-
// }
|
|
153
|
-
|
|
154
|
-
public get observable(): HTMLElement {
|
|
155
|
-
return (this.intersectionObservable ||
|
|
156
|
-
this.paginationStrategy.getPaginationElement()) as HTMLElement;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
mutationObservers: MutationObserver[] = [];
|
|
160
|
-
|
|
161
|
-
reset() {
|
|
162
|
-
this.mutationObservers.forEach((o) => {
|
|
163
|
-
o.disconnect();
|
|
164
|
-
});
|
|
165
|
-
this.resetInfiniteScroller();
|
|
166
|
-
this.resetOn();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
public resetOnPaginationOrContainerDeath = true;
|
|
170
|
-
|
|
171
|
-
resetOn() {
|
|
172
|
-
if (!this.resetOnPaginationOrContainerDeath) return;
|
|
173
|
-
|
|
174
|
-
const observables = [
|
|
175
|
-
this.container,
|
|
176
|
-
this.intersectionObservable || this.paginationStrategy.getPaginationElement(),
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
observables.forEach((o) => {
|
|
180
|
-
const observer = waitForElementToDisappear(o as HTMLElement, () => {
|
|
181
|
-
this.reset();
|
|
182
|
-
});
|
|
183
|
-
this.mutationObservers.push(observer);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
public customSelectors: Record<string, CustomSelector<any>> = {};
|
|
188
|
-
|
|
189
|
-
public animatePreview?: (doc?: HTMLElement) => void;
|
|
190
|
-
|
|
191
|
-
public storeOptions?: JabroniTypes.StoreStateOptions;
|
|
192
|
-
public customScheme?: JabroniTypes.SchemeInput;
|
|
193
|
-
public defaultSchemeOptions: Parameters<typeof setupScheme>[0] = [];
|
|
194
|
-
|
|
195
|
-
public store: JabronioStore;
|
|
196
|
-
public gui: JabronioGUI;
|
|
197
|
-
public dataManager: DataManager;
|
|
198
|
-
|
|
199
|
-
public infiniteScroller?: InfiniteScroller;
|
|
200
|
-
|
|
201
|
-
private resetInfiniteScroller() {
|
|
202
|
-
this.infiniteScroller?.dispose();
|
|
203
|
-
if (!this.paginationStrategy.hasPagination) return;
|
|
204
|
-
this.infiniteScroller = InfiniteScroller.create(
|
|
205
|
-
this.store,
|
|
206
|
-
this,
|
|
207
|
-
this.dataManager.parseData,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
public initialGrope: 'all-in-one' | 'all-in-all' | undefined;
|
|
212
|
-
|
|
213
|
-
public gropeInit() {
|
|
214
|
-
if (!this.initialGrope) return;
|
|
215
|
-
if (this.initialGrope === 'all-in-one') {
|
|
216
|
-
this.dataManager?.parseData(this.container, this.container);
|
|
217
|
-
}
|
|
218
|
-
if (this.initialGrope === 'all-in-all') {
|
|
219
|
-
getAllUniqueParents(this.getThumbs(document.body)).forEach((c) => {
|
|
220
|
-
this.dataManager.parseData(c, c, true);
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
constructor(options: Partial<RulesGlobal>) {
|
|
226
|
-
Object.assign(this, options);
|
|
227
|
-
|
|
228
|
-
this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
|
|
229
|
-
|
|
230
|
-
this.store = new JabronioStore(this.storeOptions);
|
|
231
|
-
const scheme = setupScheme(this.defaultSchemeOptions, this.customScheme);
|
|
232
|
-
this.gui = new JabronioGUI(scheme, this.store);
|
|
233
|
-
this.dataManager = new DataManager(this, this.store.state);
|
|
234
|
-
|
|
235
|
-
this.store.subscribe(() => this.dataManager.applyFilters());
|
|
236
|
-
this.resetInfiniteScroller();
|
|
237
|
-
this.resetOn();
|
|
238
|
-
this.animatePreview?.();
|
|
239
|
-
this.gropeInit();
|
|
240
|
-
}
|
|
241
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export interface DataFilterState {
|
|
2
|
-
filterPublic: boolean;
|
|
3
|
-
filterPrivate: boolean;
|
|
4
|
-
filterHD: boolean;
|
|
5
|
-
filterDuration: boolean;
|
|
6
|
-
filterDurationFrom: number;
|
|
7
|
-
filterDurationTo: number;
|
|
8
|
-
filterExclude: boolean;
|
|
9
|
-
filterExcludeWords: string;
|
|
10
|
-
filterInclude: boolean;
|
|
11
|
-
filterIncludeWords: string;
|
|
12
|
-
infiniteScrollEnabled: true;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type NonFunctionKeys<T> = {
|
|
16
|
-
[K in keyof T]: T[K] extends Function ? never : K;
|
|
17
|
-
}[keyof T];
|
|
18
|
-
|
|
19
|
-
export type NonFunctionProperties<T> = Pick<T, NonFunctionKeys<T>>;
|