billy-herrington-utils 1.3.1 → 1.3.2
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "billy-herrington-utils",
|
|
3
3
|
"description": "daddy told us not to be ashamed of our utils",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": ["utils", "observer", "dom", "fetch", "arrays", "typescript"],
|
|
7
7
|
"author": "smartacephale atm.mormon@protonmail.com (https://github.com/smartacephale)",
|
package/src/index.ts
CHANGED
|
@@ -20,3 +20,5 @@ export { computeAsyncOneAtTime, AsyncPool, wait } from './utils/async';
|
|
|
20
20
|
export { chunks, range } from './utils/arrays';
|
|
21
21
|
export { DataManager } from './data-manager';
|
|
22
22
|
export { InfiniteScroller } from './utils/infinite-scroll';
|
|
23
|
+
export { RulesHelper } from './utils/userscript-utils/rules';
|
|
24
|
+
export type { IRulesHelper } from './utils/userscript-utils/rules';
|
|
@@ -4,7 +4,7 @@ import { Observer } from '../observers';
|
|
|
4
4
|
interface IInfiniteScroller {
|
|
5
5
|
delay: number;
|
|
6
6
|
enabled: boolean;
|
|
7
|
-
writeHistory
|
|
7
|
+
writeHistory?: boolean;
|
|
8
8
|
paginationOffset: number;
|
|
9
9
|
paginationLast: number;
|
|
10
10
|
paginationElement: HTMLElement;
|
|
@@ -63,7 +63,7 @@ export class InfiniteScroller {
|
|
|
63
63
|
|
|
64
64
|
private onScrollCBs: Array<(scroller: InfiniteScroller) => void> = [];
|
|
65
65
|
|
|
66
|
-
public onScroll(callback: (scroller: InfiniteScroller) => void, initCall
|
|
66
|
+
public onScroll(callback: (scroller: InfiniteScroller) => void, initCall = false) {
|
|
67
67
|
if (initCall) callback(this);
|
|
68
68
|
this.onScrollCBs.push(callback);
|
|
69
69
|
return this;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { InfiniteScroller } from '../infinite-scroll';
|
|
2
|
+
import type { RulesHelper } from '../userscript-utils/rules';
|
|
3
|
+
|
|
4
|
+
export interface JabroniStore {
|
|
5
|
+
state: Record<string, boolean | string | number>;
|
|
6
|
+
localState: Record<string, boolean | string | number>;
|
|
7
|
+
subscribe: (callback: () => void) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createInfiniteScroller(
|
|
11
|
+
store: JabroniStore,
|
|
12
|
+
handleHtmlCallback: (document: HTMLElement) => void,
|
|
13
|
+
rules: RulesHelper,
|
|
14
|
+
) {
|
|
15
|
+
const enabled = store.state.infiniteScrollEnabled as boolean;
|
|
16
|
+
const iscroller = new InfiniteScroller({
|
|
17
|
+
enabled,
|
|
18
|
+
handleHtmlCallback,
|
|
19
|
+
...rules,
|
|
20
|
+
}).onScroll(({ paginationLast, paginationOffset }) => {
|
|
21
|
+
store.localState.pagIndexLast = paginationLast;
|
|
22
|
+
store.localState.pagIndexCur = paginationOffset;
|
|
23
|
+
}, true);
|
|
24
|
+
|
|
25
|
+
store.subscribe(() => {
|
|
26
|
+
iscroller.enabled = store.state.infiniteScrollEnabled as boolean;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return iscroller;
|
|
30
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import type { InfiniteScroller } from '../infinite-scroll';
|
|
2
|
+
import type { JabroniStore } from '../jabroni-outfit-wrap';
|
|
3
|
+
import { createInfiniteScroller } from '../jabroni-outfit-wrap';
|
|
4
|
+
import { timeToSeconds } from '../parsers';
|
|
5
|
+
import { sanitizeStr } from '../strings';
|
|
6
|
+
|
|
7
|
+
export interface IRulesHelper {
|
|
8
|
+
delay?: number;
|
|
9
|
+
IS_VIDEO_PAGE: boolean | RegExp;
|
|
10
|
+
IS_SEARCH_PAGE: boolean | RegExp;
|
|
11
|
+
THUMB_URL: string | ((thumb: HTMLElement) => string);
|
|
12
|
+
GET_THUMBS: string | ((html: HTMLElement) => Array<HTMLElement>);
|
|
13
|
+
THUMB_DATA:
|
|
14
|
+
| { title: string; uploader?: string; duration?: string }
|
|
15
|
+
| ((thumb: HTMLElement) => { title: string; duration: number });
|
|
16
|
+
THUMB_IMG_DATA:
|
|
17
|
+
| { img?: string; imgSrc?: string; lazyloading?: string }
|
|
18
|
+
| ((thumb: HTMLElement) => { img?: HTMLElement; imgSrc?: string });
|
|
19
|
+
paginationUrlGenerator:
|
|
20
|
+
| ((offset: number) => string)
|
|
21
|
+
| { searchPage?: string; pathnameLast?: boolean };
|
|
22
|
+
paginationElement: string | ((html?: HTMLElement) => HTMLElement);
|
|
23
|
+
paginationOffset: number;
|
|
24
|
+
paginationLast: number;
|
|
25
|
+
CONTAINER: string | ((html?: HTMLElement) => HTMLElement);
|
|
26
|
+
router?: (
|
|
27
|
+
rules: RulesHelper,
|
|
28
|
+
store: JabroniStore,
|
|
29
|
+
handleHtmlCallback: (document: HTMLElement) => void,
|
|
30
|
+
scroller: InfiniteScroller,
|
|
31
|
+
) => void;
|
|
32
|
+
URL_DATA?: () => {
|
|
33
|
+
paginationOffset: number;
|
|
34
|
+
paginationUrlGenerator: (offset: number) => string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class RulesHelper {
|
|
39
|
+
public delay = 250;
|
|
40
|
+
public IS_VIDEO_PAGE: boolean;
|
|
41
|
+
public IS_SEARCH_PAGE: boolean;
|
|
42
|
+
public paginationElement: HTMLElement;
|
|
43
|
+
public paginationOffset: number;
|
|
44
|
+
public paginationLast: number;
|
|
45
|
+
public URL_DATA:
|
|
46
|
+
| undefined
|
|
47
|
+
| (() => {
|
|
48
|
+
paginationOffset: number;
|
|
49
|
+
paginationUrlGenerator: (offset: number) => string;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
constructor(private options: IRulesHelper) {
|
|
53
|
+
this.delay = options?.delay || this.delay;
|
|
54
|
+
|
|
55
|
+
this.paginationOffset = this.options.paginationOffset;
|
|
56
|
+
this.paginationLast = this.options.paginationLast;
|
|
57
|
+
|
|
58
|
+
this.IS_VIDEO_PAGE = this._IS_VIDEO_PAGE();
|
|
59
|
+
this.IS_SEARCH_PAGE = this._IS_SEARCH_PAGE();
|
|
60
|
+
|
|
61
|
+
this.paginationElement = this._paginationElement();
|
|
62
|
+
|
|
63
|
+
if (options.URL_DATA) {
|
|
64
|
+
this.URL_DATA = options.URL_DATA;
|
|
65
|
+
Object.assign(this, this.URL_DATA());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public router(store: JabroniStore, handleHtmlCallback: (document: HTMLElement) => void): void {
|
|
70
|
+
if (!this.options.router) return;
|
|
71
|
+
const scroller = createInfiniteScroller(store, handleHtmlCallback, this);
|
|
72
|
+
this.options.router(this, store, handleHtmlCallback, scroller);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public paginationUrlGenerator = (offset: number): string => {
|
|
76
|
+
const opt = this.options.paginationUrlGenerator;
|
|
77
|
+
if (typeof opt === 'function') return opt(offset);
|
|
78
|
+
|
|
79
|
+
const url = new URL(location.href);
|
|
80
|
+
|
|
81
|
+
if (opt.searchPage) {
|
|
82
|
+
url.searchParams.set(opt.searchPage, offset.toString());
|
|
83
|
+
return url.href;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (opt.pathnameLast) {
|
|
87
|
+
if (url.pathname === '/') url.pathname = '/1';
|
|
88
|
+
if (/\d+$/.test(url.pathname)) {
|
|
89
|
+
url.pathname = url.pathname.replace(/\d+$/, offset.toString());
|
|
90
|
+
} else {
|
|
91
|
+
url.pathname = `${url.pathname}/${offset}`;
|
|
92
|
+
}
|
|
93
|
+
return url.href;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return url.href;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
public _IS_VIDEO_PAGE = () => {
|
|
100
|
+
if (typeof this.options.IS_VIDEO_PAGE === 'boolean') {
|
|
101
|
+
return this.options.IS_VIDEO_PAGE;
|
|
102
|
+
}
|
|
103
|
+
return this.options.IS_VIDEO_PAGE.test(location.pathname);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
public _IS_SEARCH_PAGE = () => {
|
|
107
|
+
if (typeof this.options.IS_SEARCH_PAGE === 'boolean') {
|
|
108
|
+
return this.options.IS_SEARCH_PAGE;
|
|
109
|
+
}
|
|
110
|
+
return this.options.IS_SEARCH_PAGE.test(location.pathname);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
public _paginationElement = (html = document): HTMLElement => {
|
|
114
|
+
if (typeof this.options.paginationElement === 'function') {
|
|
115
|
+
return this.options.paginationElement(html as unknown as HTMLElement);
|
|
116
|
+
}
|
|
117
|
+
return [...html.querySelectorAll(this.options.paginationElement)].pop() as HTMLElement;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
public CONTAINER = (html = document): HTMLElement => {
|
|
121
|
+
if (typeof this.options.CONTAINER === 'function') {
|
|
122
|
+
return this.options.CONTAINER(html as unknown as HTMLElement);
|
|
123
|
+
}
|
|
124
|
+
return [...html.querySelectorAll(this.options.CONTAINER)].pop() as HTMLElement;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
public THUMB_URL = (thumb: HTMLElement) => {
|
|
128
|
+
if (typeof this.options.THUMB_URL === 'string') {
|
|
129
|
+
return (thumb.querySelector(this.options.THUMB_URL) as HTMLAnchorElement).href || '';
|
|
130
|
+
}
|
|
131
|
+
return this.options.THUMB_URL(thumb);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
public GET_THUMBS = (html: HTMLElement) => {
|
|
135
|
+
if (typeof this.options.GET_THUMBS === 'string') {
|
|
136
|
+
return [...html.querySelectorAll(this.options.GET_THUMBS)] as Array<HTMLElement>;
|
|
137
|
+
}
|
|
138
|
+
return this.options.GET_THUMBS(html);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
public THUMB_DATA = (thumb: HTMLElement): { title: string; duration: number } => {
|
|
142
|
+
const opt = this.options.THUMB_DATA;
|
|
143
|
+
if (typeof opt === 'function') return opt(thumb);
|
|
144
|
+
|
|
145
|
+
let title = sanitizeStr((thumb.querySelector(opt.title) as HTMLElement)?.innerText || '');
|
|
146
|
+
|
|
147
|
+
if (opt.uploader) {
|
|
148
|
+
const uploader = sanitizeStr(
|
|
149
|
+
(thumb.querySelector(opt.title) as HTMLElement)?.innerText || '',
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
title = `${title} user:${uploader}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const duration = !opt.duration
|
|
156
|
+
? 0
|
|
157
|
+
: timeToSeconds(
|
|
158
|
+
sanitizeStr((thumb.querySelector(opt.duration) as HTMLElement)?.innerText || ''),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return { title, duration };
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
public THUMB_IMG_DATA = (thumb: HTMLElement) => {
|
|
165
|
+
const opt = this.options.THUMB_IMG_DATA;
|
|
166
|
+
if (typeof opt === 'function') return opt(thumb);
|
|
167
|
+
|
|
168
|
+
const result = {};
|
|
169
|
+
|
|
170
|
+
if (opt.img) {
|
|
171
|
+
const img = thumb.querySelector(opt.img) as HTMLImageElement;
|
|
172
|
+
const imgSrc = img.getAttribute(opt.imgSrc || 'data-src') || img.getAttribute('src');
|
|
173
|
+
|
|
174
|
+
if (opt.lazyloading) {
|
|
175
|
+
img.classList.remove(opt.lazyloading);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Object.assign(result, { img, imgSrc });
|
|
179
|
+
|
|
180
|
+
if (img.complete && img.getAttribute('src') && !img.src.includes('data:image')) {
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
} else return {};
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const _3HENTAI_RULES = new RulesHelper({
|
|
188
|
+
IS_VIDEO_PAGE: /^\/g\/\d+/.test(location.pathname),
|
|
189
|
+
IS_SEARCH_PAGE: /^\/search\//.test(location.pathname),
|
|
190
|
+
THUMB_URL: 'a',
|
|
191
|
+
GET_THUMBS: '.doujin-col',
|
|
192
|
+
THUMB_DATA: { title: '.title' },
|
|
193
|
+
THUMB_IMG_DATA: { img: 'img', lazyloading: 'lazy' },
|
|
194
|
+
paginationUrlGenerator: { searchPage: 'page' },
|
|
195
|
+
paginationElement: '.pagination',
|
|
196
|
+
paginationOffset: 1,
|
|
197
|
+
paginationLast: Math.max(
|
|
198
|
+
...Array.from(document.querySelectorAll('.pagination .page-link') || [], (e) =>
|
|
199
|
+
parseInt((e as HTMLElement).innerText),
|
|
200
|
+
).filter(Number),
|
|
201
|
+
1,
|
|
202
|
+
),
|
|
203
|
+
CONTAINER: '.listing-container',
|
|
204
|
+
URL_DATA() {
|
|
205
|
+
const IS_SEARCH_PAGE = /^\/search\//.test(location.pathname);
|
|
206
|
+
const url = new URL(window.location.href);
|
|
207
|
+
|
|
208
|
+
let paginationOffset = parseInt(url.searchParams.get('page') || "1");
|
|
209
|
+
let paginationUrlGenerator = (offset: number) => {
|
|
210
|
+
url.searchParams.set('page', offset.toString());
|
|
211
|
+
return url.href;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (!IS_SEARCH_PAGE) {
|
|
215
|
+
paginationOffset = parseInt(url.pathname.match(/\d+$/)?.[0] || "1");
|
|
216
|
+
if (url.pathname === '/') url.pathname = '/1';
|
|
217
|
+
paginationUrlGenerator = (offset: number) => {
|
|
218
|
+
if (/\d+$/.test(url.pathname)) {
|
|
219
|
+
url.pathname = url.pathname.replace(/\d+$/, offset.toString());
|
|
220
|
+
} else {
|
|
221
|
+
url.pathname = `${url.pathname}/${offset}`;
|
|
222
|
+
}
|
|
223
|
+
return url.href;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return { paginationOffset, paginationUrlGenerator };
|
|
228
|
+
},
|
|
229
|
+
router: () => {},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
// const __NHENTAI_RULES = new RulesHelper({
|
|
235
|
+
// IS_VIDEO_PAGE: /^\/g\/\d+/.test(location.pathname),
|
|
236
|
+
// IS_SEARCH_PAGE: /^\/search\//,
|
|
237
|
+
// THUMB_URL: ".cover",
|
|
238
|
+
// GET_THUMBS: ".gallery",
|
|
239
|
+
// THUMB_DATA: { title: ".caption" },
|
|
240
|
+
// THUMB_IMG_DATA: thumb => {
|
|
241
|
+
// const img = thumb.querySelector(".cover img")
|
|
242
|
+
// let imgSrc = img.getAttribute("data-src") || img.getAttribute("src") || ""
|
|
243
|
+
// if (!/^\/g\/\d+/.test(location.pathname))
|
|
244
|
+
// imgSrc = imgSrc?.replace("t5", "t3")
|
|
245
|
+
// img.classList.remove("lazyload")
|
|
246
|
+
// if (
|
|
247
|
+
// img.complete &&
|
|
248
|
+
// img.getAttribute("src") &&
|
|
249
|
+
// !img.src.includes("data:image")
|
|
250
|
+
// ) {
|
|
251
|
+
// return {}
|
|
252
|
+
// }
|
|
253
|
+
// return { img, imgSrc }
|
|
254
|
+
// },
|
|
255
|
+
// paginationUrlGenerator: { searchPage: "page" },
|
|
256
|
+
// paginationElement: ".pagination",
|
|
257
|
+
// paginationOffset:
|
|
258
|
+
// parseInt(new URL(location.href).searchParams.get("page")) || 1,
|
|
259
|
+
// paginationLast: parseInt(
|
|
260
|
+
// document.querySelector(".pagination .last")?.href.match(/\d+/)?.[0] || "1"
|
|
261
|
+
// ),
|
|
262
|
+
// CONTAINER: ".index-container, .container",
|
|
263
|
+
// router: () => {}
|
|
264
|
+
// })
|