pervert-monkey 1.0.0 → 1.0.1

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 (46) hide show
  1. package/README.md +0 -3
  2. package/dist/core/pervertmonkey.core.es.d.ts +4 -4
  3. package/dist/core/pervertmonkey.core.es.js +24 -1
  4. package/dist/core/pervertmonkey.core.es.js.map +1 -1
  5. package/dist/core/pervertmonkey.core.umd.js +24 -1
  6. package/dist/core/pervertmonkey.core.umd.js.map +1 -1
  7. package/dist/userscripts/3hentai.user.js +1045 -1049
  8. package/dist/userscripts/camgirlfinder.user.js +38 -42
  9. package/dist/userscripts/camwhores.user.js +1429 -1433
  10. package/dist/userscripts/e-hentai.user.js +1077 -1081
  11. package/dist/userscripts/ebalka.user.js +1092 -1096
  12. package/dist/userscripts/eporner.user.js +1129 -1133
  13. package/dist/userscripts/erome.user.js +1109 -1113
  14. package/dist/userscripts/eroprofile.user.js +1062 -1066
  15. package/dist/userscripts/javhdporn.user.js +1046 -1050
  16. package/dist/userscripts/missav.user.js +1047 -1051
  17. package/dist/userscripts/motherless.user.js +1223 -1227
  18. package/dist/userscripts/namethatporn.user.js +1083 -1087
  19. package/dist/userscripts/nhentai.user.js +1123 -1127
  20. package/dist/userscripts/pornhub.user.js +1065 -1069
  21. package/dist/userscripts/spankbang.user.js +1097 -1101
  22. package/dist/userscripts/thisvid.user.js +2660 -0
  23. package/dist/userscripts/xhamster.user.js +1224 -1228
  24. package/dist/userscripts/xvideos.user.js +1116 -1120
  25. package/package.json +9 -9
  26. package/src/core/index.ts +4 -0
  27. package/src/index.ts +2 -42
  28. package/src/userscripts/scripts/3hentai.ts +1 -1
  29. package/src/userscripts/scripts/camwhores.ts +8 -9
  30. package/src/userscripts/scripts/e-hentai.ts +2 -2
  31. package/src/userscripts/scripts/ebalka.ts +2 -3
  32. package/src/userscripts/scripts/eporner.ts +2 -2
  33. package/src/userscripts/scripts/erome.ts +1 -1
  34. package/src/userscripts/scripts/eroprofile.ts +1 -1
  35. package/src/userscripts/scripts/javhdporn.ts +1 -1
  36. package/src/userscripts/scripts/missav.ts +1 -1
  37. package/src/userscripts/scripts/motherless.ts +2 -4
  38. package/src/userscripts/scripts/namethatporn.ts +1 -1
  39. package/src/userscripts/scripts/nhentai.ts +2 -2
  40. package/src/userscripts/scripts/pornhub.ts +1 -1
  41. package/src/userscripts/scripts/spankbang.ts +4 -5
  42. package/src/userscripts/scripts/xhamster.ts +5 -5
  43. package/src/userscripts/scripts/xvideos.ts +2 -3
  44. package/src/utils/index.ts +39 -0
  45. package/src/vite-env.d.ts +1 -1
  46. package/src/userscripts/scripts/thisvid.ts +0 -716
@@ -21,1162 +21,1158 @@
21
21
  // @run-at document-idle
22
22
  // ==/UserScript==
23
23
 
24
- (function () {
24
+ (function (jabroniOutfit) {
25
25
  'use strict';
26
26
 
27
- (function (jabroniOutfit) {
27
+ var _GM_addStyle = (() => typeof GM_addStyle != "undefined" ? GM_addStyle : undefined)();
28
28
 
29
- function splitWith(s, c = ",") {
30
- return s.split(c).map((s2) => s2.trim()).filter(Boolean);
31
- }
32
- function sanitizeStr(s) {
33
- return s?.replace(/\n|\t/g, " ").replace(/ {2,}/g, " ").trim() || "";
34
- }
29
+ function memoize(fn) {
30
+ const cache = new Map();
31
+ const memoizedFunction = ((...args) => {
32
+ const key = JSON.stringify(args);
33
+ if (cache.has(key)) {
34
+ return cache.get(key);
35
+ }
36
+ const result = fn(...args);
37
+ cache.set(key, result);
38
+ return result;
39
+ });
40
+ return memoizedFunction;
41
+ }
35
42
 
36
- function waitForElementToDisappear(observable, callback) {
37
- const observer = new MutationObserver((_mutations) => {
38
- if (!observable.isConnected) {
39
- observer.disconnect();
40
- callback();
41
- }
42
- });
43
- observer.observe(document.body, { childList: true, subtree: true });
44
- return observer;
45
- }
43
+ function splitWith(s, c = ",") {
44
+ return s.split(c).map((s2) => s2.trim()).filter(Boolean);
45
+ }
46
+ function sanitizeStr(s) {
47
+ return s?.replace(/\n|\t/g, " ").replace(/ {2,}/g, " ").trim() || "";
48
+ }
46
49
 
47
- function querySelectorLast(root = document, selector) {
48
- const nodes = root.querySelectorAll(selector);
49
- return nodes.length > 0 ? nodes[nodes.length - 1] : void 0;
50
- }
51
- function querySelectorText(e, selector) {
52
- if (typeof selector !== "string") return "";
53
- const text = e.querySelector(selector)?.innerText || "";
54
- return sanitizeStr(text);
55
- }
56
- function parseHtml(html) {
57
- const parsed = new DOMParser().parseFromString(html, "text/html").body;
58
- if (parsed.children.length > 1) return parsed;
59
- return parsed.firstElementChild;
60
- }
61
- function removeClassesAndDataAttributes(element, keyword) {
62
- Array.from(element.classList).forEach((className) => {
63
- if (className.includes(keyword)) {
64
- element.classList.remove(className);
65
- }
66
- });
67
- Array.from(element.attributes).forEach((attr) => {
68
- if (attr.name.startsWith("data-") && attr.name.includes(keyword)) {
69
- element.removeAttribute(attr.name);
70
- }
71
- });
72
- }
73
- function getCommonParents(elements) {
74
- const parents = Array.from(elements).map((el) => el.parentElement).filter((parent) => parent !== null);
75
- return [...new Set(parents)];
50
+ class RegexFilter {
51
+ regexes;
52
+ constructor(str, flags = "gi") {
53
+ this.regexes = memoize(this.compileSearchRegex)(str, flags);
76
54
  }
77
- function checkHomogenity(a, b, options) {
78
- if (!a || !b) return false;
79
- if (options.id) {
80
- if (a.id !== b.id) return false;
81
- }
82
- if (options.className) {
83
- const ca = a.className;
84
- const cb = b.className;
85
- if (!(ca.length > cb.length ? ca.includes(cb) : cb.includes(ca))) {
86
- return false;
87
- }
55
+ compileSearchRegex(str, flags) {
56
+ try {
57
+ if (str.startsWith("r:")) return [new RegExp(str.slice(2), flags)];
58
+ const regexes = splitWith(str).map(
59
+ (s) => s.replace(/f:(\w+)/g, (_, w) => `(^|\\ |,)${w}($|\\ |,)`)
60
+ ).map((_) => new RegExp(_, flags));
61
+ return regexes;
62
+ } catch (_) {
63
+ return [];
88
64
  }
89
- return true;
90
65
  }
91
-
92
- function formatTimeToHHMMSS(timeStr) {
93
- const pad = (num) => num.toString().padStart(2, "0");
94
- const h = timeStr.match(/(\d+)\s*h/)?.[1] || "0";
95
- const m = timeStr.match(/(\d+)\s*mi?n/)?.[1] || "0";
96
- const s = timeStr.match(/(\d+)\s*sec/)?.[1] || "0";
97
- return `${pad(+h)}:${pad(+m)}:${pad(+s)}`;
66
+ hasEvery(str) {
67
+ return this.regexes.every((r) => r.test(str));
98
68
  }
99
- function timeToSeconds(timeStr) {
100
- const normalized = /[a-zA-Z]/.test(timeStr) ? formatTimeToHHMMSS(timeStr) : timeStr;
101
- return normalized.split(":").reverse().reduce((total, unit, index) => total + parseInt(unit, 10) * 60 ** index, 0);
69
+ hasNone(str) {
70
+ return this.regexes.every((r) => !r.test(str));
102
71
  }
72
+ }
103
73
 
104
- function parseDataParams(str) {
105
- const paramsStr = decodeURI(str.trim()).split(";");
106
- return paramsStr.reduce(
107
- (acc, s) => {
108
- const parsed = s.match(/([+\w]+):([\w\- ]+)?/);
109
- if (parsed) {
110
- const [, key, value] = parsed;
111
- if (value) {
112
- key.split("+").forEach((p) => {
113
- acc[p] = value;
114
- });
115
- }
116
- }
117
- return acc;
118
- },
119
- {}
120
- );
74
+ class DataFilter {
75
+ constructor(rules) {
76
+ this.rules = rules;
77
+ this.registerFilters(rules.customDataSelectorFns);
78
+ this.applyCSSFilters();
121
79
  }
122
-
123
- var _GM_addStyle = (() => typeof GM_addStyle != "undefined" ? GM_addStyle : undefined)();
124
-
125
- function memoize(fn) {
126
- const cache = new Map();
127
- const memoizedFunction = ((...args) => {
128
- const key = JSON.stringify(args);
129
- if (cache.has(key)) {
130
- return cache.get(key);
131
- }
132
- const result = fn(...args);
133
- cache.set(key, result);
134
- return result;
135
- });
136
- return memoizedFunction;
80
+ filters = new Map();
81
+ static isFiltered(el) {
82
+ return el.className.includes("filter-");
137
83
  }
138
-
139
- class RegexFilter {
140
- regexes;
141
- constructor(str, flags = "gi") {
142
- this.regexes = memoize(this.compileSearchRegex)(str, flags);
143
- }
144
- compileSearchRegex(str, flags) {
145
- try {
146
- if (str.startsWith("r:")) return [new RegExp(str.slice(2), flags)];
147
- const regexes = splitWith(str).map(
148
- (s) => s.replace(/f:(\w+)/g, (_, w) => `(^|\\ |,)${w}($|\\ |,)`)
149
- ).map((_) => new RegExp(_, flags));
150
- return regexes;
151
- } catch (_) {
152
- return [];
84
+ applyCSSFilters(wrapper) {
85
+ this.filters.forEach((_, name) => {
86
+ const cssRule = `.filter-${name} { display: none !important; }`;
87
+ if (wrapper) {
88
+ _GM_addStyle(wrapper(cssRule));
89
+ } else {
90
+ _GM_addStyle(cssRule);
153
91
  }
154
- }
155
- hasEvery(str) {
156
- return this.regexes.every((r) => r.test(str));
157
- }
158
- hasNone(str) {
159
- return this.regexes.every((r) => !r.test(str));
160
- }
92
+ });
161
93
  }
162
-
163
- class DataFilter {
164
- constructor(rules) {
165
- this.rules = rules;
166
- this.registerFilters(rules.customDataSelectorFns);
167
- this.applyCSSFilters();
168
- }
169
- filters = new Map();
170
- static isFiltered(el) {
171
- return el.className.includes("filter-");
172
- }
173
- applyCSSFilters(wrapper) {
174
- this.filters.forEach((_, name) => {
175
- const cssRule = `.filter-${name} { display: none !important; }`;
176
- if (wrapper) {
177
- _GM_addStyle(wrapper(cssRule));
178
- } else {
179
- _GM_addStyle(cssRule);
180
- }
181
- });
182
- }
183
- customDataSelectorFns = {};
184
- registerFilters(customFilters) {
185
- customFilters.forEach((o) => {
186
- if (typeof o === "string") {
187
- this.customDataSelectorFns[o] = DataFilter.customDataSelectorFnsDefault[o];
188
- this.registerFilter(o);
189
- } else {
190
- const k = Object.keys(o)[0];
191
- this.customDataSelectorFns[k] = o[k];
192
- this.registerFilter(k);
193
- }
194
- });
195
- }
196
- customSelectorParser(name, selector) {
197
- if ("handle" in selector) {
198
- return selector;
94
+ customDataSelectorFns = {};
95
+ registerFilters(customFilters) {
96
+ customFilters.forEach((o) => {
97
+ if (typeof o === "string") {
98
+ this.customDataSelectorFns[o] = DataFilter.customDataSelectorFnsDefault[o];
99
+ this.registerFilter(o);
199
100
  } else {
200
- return { handle: selector, deps: [name] };
101
+ const k = Object.keys(o)[0];
102
+ this.customDataSelectorFns[k] = o[k];
103
+ this.registerFilter(k);
201
104
  }
105
+ });
106
+ }
107
+ customSelectorParser(name, selector) {
108
+ if ("handle" in selector) {
109
+ return selector;
110
+ } else {
111
+ return { handle: selector, deps: [name] };
202
112
  }
203
- registerFilter(customSelectorName) {
204
- const handler = this.customSelectorParser(
205
- customSelectorName,
206
- this.customDataSelectorFns[customSelectorName]
207
- );
208
- const tag = `filter-${customSelectorName}`;
209
- [customSelectorName, ...handler.deps || []]?.forEach((name) => {
210
- Object.assign(this.filterMapping, { [name]: customSelectorName });
211
- });
212
- const fn = () => {
213
- const preDefined = handler.$preDefine?.(this.rules.store.state);
214
- return (v) => {
215
- const condition = handler.handle(v, this.rules.store.state, preDefined);
216
- return {
217
- condition,
218
- tag
219
- };
113
+ }
114
+ registerFilter(customSelectorName) {
115
+ const handler = this.customSelectorParser(
116
+ customSelectorName,
117
+ this.customDataSelectorFns[customSelectorName]
118
+ );
119
+ const tag = `filter-${customSelectorName}`;
120
+ [customSelectorName, ...handler.deps || []]?.forEach((name) => {
121
+ Object.assign(this.filterMapping, { [name]: customSelectorName });
122
+ });
123
+ const fn = () => {
124
+ const preDefined = handler.$preDefine?.(this.rules.store.state);
125
+ return (v) => {
126
+ const condition = handler.handle(v, this.rules.store.state, preDefined);
127
+ return {
128
+ condition,
129
+ tag
220
130
  };
221
131
  };
222
- this.filters.set(customSelectorName, fn);
223
- }
224
- filterMapping = {};
225
- selectFilters(filters) {
226
- const selectedFilters = Object.keys(filters).filter((k) => k in this.filterMapping).map((k) => this.filterMapping[k]).map((k) => this.filters.get(k));
227
- return selectedFilters;
228
- }
229
- static customDataSelectorFnsDefault = {
230
- filterDuration: {
231
- handle(el, state, notInRange) {
232
- return state.filterDuration && notInRange(el.duration);
233
- },
234
- $preDefine: (state) => {
235
- const from = state.filterDurationFrom;
236
- const to = state.filterDurationTo;
237
- function notInRange(d) {
238
- return d < from || d > to;
239
- }
240
- return notInRange;
241
- },
242
- deps: ["filterDurationFrom", "filterDurationTo"]
243
- },
244
- filterExclude: {
245
- handle(el, state, searchFilter) {
246
- if (!state.filterExclude) return false;
247
- return !searchFilter.hasNone(el.title);
248
- },
249
- $preDefine: (state) => new RegexFilter(state.filterExcludeWords),
250
- deps: ["filterExcludeWords"]
251
- },
252
- filterInclude: {
253
- handle(el, state, searchFilter) {
254
- if (!state.filterInclude) return false;
255
- return !searchFilter.hasEvery(el.title);
256
- },
257
- $preDefine: (state) => new RegexFilter(state.filterIncludeWords),
258
- deps: ["filterIncludeWords"]
259
- }
260
132
  };
133
+ this.filters.set(customSelectorName, fn);
261
134
  }
262
-
263
- class LazyImgLoader {
264
- lazyImgObserver;
265
- attributeName = "data-lazy-load";
266
- constructor(shouldDelazify) {
267
- this.lazyImgObserver = new Observer((target) => {
268
- if (shouldDelazify(target)) {
269
- this.delazify(target);
135
+ filterMapping = {};
136
+ selectFilters(filters) {
137
+ const selectedFilters = Object.keys(filters).filter((k) => k in this.filterMapping).map((k) => this.filterMapping[k]).map((k) => this.filters.get(k));
138
+ return selectedFilters;
139
+ }
140
+ static customDataSelectorFnsDefault = {
141
+ filterDuration: {
142
+ handle(el, state, notInRange) {
143
+ return state.filterDuration && notInRange(el.duration);
144
+ },
145
+ $preDefine: (state) => {
146
+ const from = state.filterDurationFrom;
147
+ const to = state.filterDurationTo;
148
+ function notInRange(d) {
149
+ return d < from || d > to;
270
150
  }
271
- });
272
- }
273
- lazify(_target, img, imgSrc) {
274
- if (!img || !imgSrc) return;
275
- img.setAttribute(this.attributeName, imgSrc);
276
- img.src = "";
277
- this.lazyImgObserver.observe(img);
151
+ return notInRange;
152
+ },
153
+ deps: ["filterDurationFrom", "filterDurationTo"]
154
+ },
155
+ filterExclude: {
156
+ handle(el, state, searchFilter) {
157
+ if (!state.filterExclude) return false;
158
+ return !searchFilter.hasNone(el.title);
159
+ },
160
+ $preDefine: (state) => new RegexFilter(state.filterExcludeWords),
161
+ deps: ["filterExcludeWords"]
162
+ },
163
+ filterInclude: {
164
+ handle(el, state, searchFilter) {
165
+ if (!state.filterInclude) return false;
166
+ return !searchFilter.hasEvery(el.title);
167
+ },
168
+ $preDefine: (state) => new RegexFilter(state.filterIncludeWords),
169
+ deps: ["filterIncludeWords"]
278
170
  }
279
- delazify = (target) => {
280
- this.lazyImgObserver.observer.unobserve(target);
281
- target.src = target.getAttribute(this.attributeName);
282
- target.removeAttribute(this.attributeName);
283
- };
284
- }
171
+ };
172
+ }
285
173
 
286
- class Observer {
287
- constructor(callback) {
288
- this.callback = callback;
289
- this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
290
- }
291
- observer;
292
- timeout;
293
- observe(target) {
294
- this.observer.observe(target);
295
- }
296
- throttle(target, throttleTime) {
297
- this.observer.unobserve(target);
298
- this.timeout = window.setTimeout(() => this.observer.observe(target), throttleTime);
174
+ function waitForElementToDisappear(observable, callback) {
175
+ const observer = new MutationObserver((_mutations) => {
176
+ if (!observable.isConnected) {
177
+ observer.disconnect();
178
+ callback();
299
179
  }
300
- handleIntersection(entries) {
301
- for (const entry of entries) {
302
- if (entry.isIntersecting) {
303
- this.callback(entry.target);
304
- }
305
- }
180
+ });
181
+ observer.observe(document.body, { childList: true, subtree: true });
182
+ return observer;
183
+ }
184
+
185
+ function querySelectorLast(root = document, selector) {
186
+ const nodes = root.querySelectorAll(selector);
187
+ return nodes.length > 0 ? nodes[nodes.length - 1] : void 0;
188
+ }
189
+ function querySelectorText(e, selector) {
190
+ if (typeof selector !== "string") return "";
191
+ const text = e.querySelector(selector)?.innerText || "";
192
+ return sanitizeStr(text);
193
+ }
194
+ function parseHtml(html) {
195
+ const parsed = new DOMParser().parseFromString(html, "text/html").body;
196
+ if (parsed.children.length > 1) return parsed;
197
+ return parsed.firstElementChild;
198
+ }
199
+ function removeClassesAndDataAttributes(element, keyword) {
200
+ Array.from(element.classList).forEach((className) => {
201
+ if (className.includes(keyword)) {
202
+ element.classList.remove(className);
306
203
  }
307
- dispose() {
308
- if (this.timeout) clearTimeout(this.timeout);
309
- this.observer.disconnect();
204
+ });
205
+ Array.from(element.attributes).forEach((attr) => {
206
+ if (attr.name.startsWith("data-") && attr.name.includes(keyword)) {
207
+ element.removeAttribute(attr.name);
310
208
  }
311
- static observeWhile(target, callback, throttleTime) {
312
- const observer_ = new Observer(async (target2) => {
313
- const condition = await callback();
314
- if (condition) observer_.throttle(target2, throttleTime);
315
- });
316
- observer_.observe(target);
317
- return observer_;
209
+ });
210
+ }
211
+ function getCommonParents(elements) {
212
+ const parents = Array.from(elements).map((el) => el.parentElement).filter((parent) => parent !== null);
213
+ return [...new Set(parents)];
214
+ }
215
+ function checkHomogenity(a, b, options) {
216
+ if (!a || !b) return false;
217
+ if (options.id) {
218
+ if (a.id !== b.id) return false;
219
+ }
220
+ if (options.className) {
221
+ const ca = a.className;
222
+ const cb = b.className;
223
+ if (!(ca.length > cb.length ? ca.includes(cb) : cb.includes(ca))) {
224
+ return false;
318
225
  }
319
226
  }
227
+ return true;
228
+ }
320
229
 
321
- class DataManager {
322
- constructor(rules) {
323
- this.rules = rules;
324
- this.dataFilter = new DataFilter(this.rules);
325
- }
326
- data = new Map();
327
- lazyImgLoader = new LazyImgLoader(
328
- (target) => !DataFilter.isFiltered(target)
329
- );
330
- dataFilter;
331
- applyFilters = async (filters = {}, offset = 0) => {
332
- const filtersToApply = this.dataFilter.selectFilters(filters);
333
- if (filtersToApply.length === 0) return;
334
- const iterator = this.data.values().drop(offset);
335
- let finished = false;
336
- await new Promise((resolve) => {
337
- function runBatch(deadline) {
338
- const updates = [];
339
- while (deadline.timeRemaining() > 0) {
340
- const { value, done } = iterator.next();
341
- finished = !!done;
342
- if (done) break;
343
- for (const f of filtersToApply) {
344
- const { tag, condition } = f()(value);
345
- updates.push({ e: value.element, tag, condition });
346
- }
347
- }
348
- if (updates.length > 0) {
349
- requestAnimationFrame(() => {
350
- updates.forEach((u) => {
351
- u.e.classList.toggle(u.tag, u.condition);
352
- });
353
- });
354
- }
355
- if (!finished) {
356
- requestIdleCallback(runBatch);
357
- } else {
358
- resolve(true);
359
- }
360
- }
361
- requestIdleCallback(runBatch);
362
- });
363
- };
364
- filterAll = async (offset) => {
365
- const keys = Array.from(this.dataFilter.filters.keys());
366
- const filters = Object.fromEntries(
367
- keys.map((k) => [k, this.rules.store.state[k]])
368
- );
369
- await this.applyFilters(filters, offset);
370
- };
371
- parseDataParentHomogenity;
372
- parseData = (html, container, removeDuplicates = false, shouldLazify = true) => {
373
- const thumbs = this.rules.getThumbs(html);
374
- const dataOffset = this.data.size;
375
- const fragment = document.createDocumentFragment();
376
- const parent = container || this.rules.container;
377
- const homogenity = !!this.parseDataParentHomogenity;
378
- for (const thumbElement of thumbs) {
379
- const url = this.rules.getThumbUrl(thumbElement);
380
- if (!url || this.data.has(url) || parent !== container && parent?.contains(thumbElement) || homogenity && !checkHomogenity(
381
- parent,
382
- thumbElement.parentElement,
383
- this.parseDataParentHomogenity
384
- )) {
385
- if (removeDuplicates) thumbElement.remove();
386
- continue;
387
- }
388
- const data = this.rules.getThumbData(thumbElement);
389
- this.data.set(url, { element: thumbElement, ...data });
390
- if (shouldLazify) {
391
- const { img, imgSrc } = this.rules.getThumbImgData(thumbElement);
392
- this.lazyImgLoader.lazify(thumbElement, img, imgSrc);
393
- }
394
- fragment.append(thumbElement);
230
+ class LazyImgLoader {
231
+ lazyImgObserver;
232
+ attributeName = "data-lazy-load";
233
+ constructor(shouldDelazify) {
234
+ this.lazyImgObserver = new Observer((target) => {
235
+ if (shouldDelazify(target)) {
236
+ this.delazify(target);
395
237
  }
396
- this.filterAll(dataOffset).then(() => {
397
- requestAnimationFrame(() => {
398
- parent.appendChild(fragment);
399
- });
400
- });
401
- };
402
- sortBy(key, direction = true) {
403
- if (this.data.size < 2) return;
404
- let sorted = this.data.values().toArray().sort((a, b) => {
405
- return a[key] - b[key];
406
- });
407
- if (!direction) sorted = sorted.reverse();
408
- const container = sorted[0].element.parentElement;
409
- container.style.visibility = "hidden";
410
- sorted.forEach((s) => {
411
- container.append(s.element);
412
- });
413
- container.style.visibility = "visible";
414
- }
238
+ });
415
239
  }
416
-
417
- function wait(milliseconds) {
418
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
240
+ lazify(_target, img, imgSrc) {
241
+ if (!img || !imgSrc) return;
242
+ img.setAttribute(this.attributeName, imgSrc);
243
+ img.src = "";
244
+ this.lazyImgObserver.observe(img);
419
245
  }
420
-
421
- const MOBILE_UA = {
422
- "User-Agent": [
423
- "Mozilla/5.0 (Linux; Android 10; K)",
424
- "AppleWebKit/537.36 (KHTML, like Gecko)",
425
- "Chrome/114.0.0.0 Mobile Safari/537.36"
426
- ].join(" ")
246
+ delazify = (target) => {
247
+ this.lazyImgObserver.observer.unobserve(target);
248
+ target.src = target.getAttribute(this.attributeName);
249
+ target.removeAttribute(this.attributeName);
427
250
  };
428
- async function fetchWith(input, options) {
429
- const requestInit = options.init || {};
430
- if (options.mobile) {
431
- Object.assign(requestInit, { headers: new Headers(MOBILE_UA) });
432
- }
433
- const r = await fetch(input, requestInit).then((r2) => r2);
434
- return parseHtml(await r.text());
435
- }
436
- const fetchHtml = (input) => fetchWith(input, { });
251
+ }
437
252
 
438
- class InfiniteScroller {
439
- enabled = true;
440
- paginationOffset = 1;
441
- parseData;
442
- rules;
443
- observer;
444
- paginationGenerator;
445
- constructor(options) {
446
- this.rules = options.rules;
447
- this.paginationOffset = this.rules.paginationStrategy.getPaginationOffset();
448
- Object.assign(this, options);
449
- if (this.rules.getPaginationData) {
450
- this.getPaginationData = this.rules.getPaginationData;
253
+ class Observer {
254
+ constructor(callback) {
255
+ this.callback = callback;
256
+ this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
257
+ }
258
+ observer;
259
+ timeout;
260
+ observe(target) {
261
+ this.observer.observe(target);
262
+ }
263
+ throttle(target, throttleTime) {
264
+ this.observer.unobserve(target);
265
+ this.timeout = window.setTimeout(() => this.observer.observe(target), throttleTime);
266
+ }
267
+ handleIntersection(entries) {
268
+ for (const entry of entries) {
269
+ if (entry.isIntersecting) {
270
+ this.callback(entry.target);
451
271
  }
452
- this.paginationGenerator = this.rules.customGenerator || InfiniteScroller.generatorForPaginationStrategy(this.rules.paginationStrategy);
453
- this.setObserver(this.rules.observable);
454
- this.setAutoScroll();
455
- }
456
- dispose() {
457
- if (this.observer) this.observer.dispose();
458
- }
459
- setObserver(observable) {
460
- if (this.observer) this.observer.dispose();
461
- this.observer = Observer.observeWhile(
462
- observable,
463
- this.generatorConsumer,
464
- this.rules.store.state.delay
465
- );
466
- return this;
467
- }
468
- onScrollCBs = [];
469
- onScroll(callback, initCall = false) {
470
- if (initCall) callback(this);
471
- this.onScrollCBs.push(callback);
472
- return this;
473
272
  }
474
- _onScroll() {
475
- this.onScrollCBs.forEach((cb) => {
476
- cb(this);
477
- });
478
- }
479
- setAutoScroll() {
480
- const autoScrollWrapper = async () => {
481
- if (this.rules.store.state.autoScroll) {
482
- await wait(this.rules.store.state.delay);
483
- await this.generatorConsumer();
484
- await autoScrollWrapper();
273
+ }
274
+ dispose() {
275
+ if (this.timeout) clearTimeout(this.timeout);
276
+ this.observer.disconnect();
277
+ }
278
+ static observeWhile(target, callback, throttleTime) {
279
+ const observer_ = new Observer(async (target2) => {
280
+ const condition = await callback();
281
+ if (condition) observer_.throttle(target2, throttleTime);
282
+ });
283
+ observer_.observe(target);
284
+ return observer_;
285
+ }
286
+ }
287
+
288
+ class DataManager {
289
+ constructor(rules) {
290
+ this.rules = rules;
291
+ this.dataFilter = new DataFilter(this.rules);
292
+ }
293
+ data = new Map();
294
+ lazyImgLoader = new LazyImgLoader(
295
+ (target) => !DataFilter.isFiltered(target)
296
+ );
297
+ dataFilter;
298
+ applyFilters = async (filters = {}, offset = 0) => {
299
+ const filtersToApply = this.dataFilter.selectFilters(filters);
300
+ if (filtersToApply.length === 0) return;
301
+ const iterator = this.data.values().drop(offset);
302
+ let finished = false;
303
+ await new Promise((resolve) => {
304
+ function runBatch(deadline) {
305
+ const updates = [];
306
+ while (deadline.timeRemaining() > 0) {
307
+ const { value, done } = iterator.next();
308
+ finished = !!done;
309
+ if (done) break;
310
+ for (const f of filtersToApply) {
311
+ const { tag, condition } = f()(value);
312
+ updates.push({ e: value.element, tag, condition });
313
+ }
485
314
  }
486
- };
487
- autoScrollWrapper();
488
- this.rules.store.stateSubject.subscribe((type) => {
489
- if (type?.autoScroll) {
490
- autoScrollWrapper();
315
+ if (updates.length > 0) {
316
+ requestAnimationFrame(() => {
317
+ updates.forEach((u) => {
318
+ u.e.classList.toggle(u.tag, u.condition);
319
+ });
320
+ });
321
+ }
322
+ if (!finished) {
323
+ requestIdleCallback(runBatch);
324
+ } else {
325
+ resolve(true);
491
326
  }
492
- });
493
- }
494
- generatorConsumer = async () => {
495
- if (!this.enabled) return false;
496
- const {
497
- value: { url, offset },
498
- done
499
- } = await this.paginationGenerator.next();
500
- if (!done && url) {
501
- await this.doScroll(url, offset);
502
327
  }
503
- return !done;
504
- };
505
- async getPaginationData(url) {
506
- return await fetchHtml(url);
507
- }
508
- async doScroll(url, offset) {
509
- const nextPageHtml = await this.getPaginationData(url);
510
- const prevScrollPos = document.documentElement.scrollTop;
511
- this.paginationOffset = Math.max(this.paginationOffset, offset);
512
- this.parseData?.(nextPageHtml);
513
- this._onScroll();
514
- window.scrollTo(0, prevScrollPos);
515
- if (this.rules.store.state.writeHistory) {
516
- history.replaceState({}, "", url);
328
+ requestIdleCallback(runBatch);
329
+ });
330
+ };
331
+ filterAll = async (offset) => {
332
+ const keys = Array.from(this.dataFilter.filters.keys());
333
+ const filters = Object.fromEntries(
334
+ keys.map((k) => [k, this.rules.store.state[k]])
335
+ );
336
+ await this.applyFilters(filters, offset);
337
+ };
338
+ parseDataParentHomogenity;
339
+ parseData = (html, container, removeDuplicates = false, shouldLazify = true) => {
340
+ const thumbs = this.rules.getThumbs(html);
341
+ const dataOffset = this.data.size;
342
+ const fragment = document.createDocumentFragment();
343
+ const parent = container || this.rules.container;
344
+ const homogenity = !!this.parseDataParentHomogenity;
345
+ for (const thumbElement of thumbs) {
346
+ const url = this.rules.getThumbUrl(thumbElement);
347
+ if (!url || this.data.has(url) || parent !== container && parent?.contains(thumbElement) || homogenity && !checkHomogenity(
348
+ parent,
349
+ thumbElement.parentElement,
350
+ this.parseDataParentHomogenity
351
+ )) {
352
+ if (removeDuplicates) thumbElement.remove();
353
+ continue;
517
354
  }
518
- }
519
- static async *generatorForPaginationStrategy(pstrategy) {
520
- const _offset = pstrategy.getPaginationOffset();
521
- const end = pstrategy.getPaginationLast();
522
- const urlGenerator = pstrategy.getPaginationUrlGenerator();
523
- for (let offset = _offset; offset <= end; offset++) {
524
- const url = await urlGenerator(offset);
525
- yield { url, offset };
355
+ const data = this.rules.getThumbData(thumbElement);
356
+ this.data.set(url, { element: thumbElement, ...data });
357
+ if (shouldLazify) {
358
+ const { img, imgSrc } = this.rules.getThumbImgData(thumbElement);
359
+ this.lazyImgLoader.lazify(thumbElement, img, imgSrc);
526
360
  }
361
+ fragment.append(thumbElement);
527
362
  }
528
- static create(rules) {
529
- const enabled = rules.store.state.infiniteScrollEnabled;
530
- rules.store.state.$paginationLast = rules.paginationStrategy.getPaginationLast();
531
- const infiniteScroller = new InfiniteScroller({
532
- enabled,
533
- parseData: rules.dataManager.parseData,
534
- rules
535
- }).onScroll(({ paginationOffset }) => {
536
- rules.store.state.$paginationOffset = paginationOffset;
537
- }, true);
538
- rules.store.stateSubject.subscribe(() => {
539
- infiniteScroller.enabled = rules.store.state.infiniteScrollEnabled;
363
+ this.filterAll(dataOffset).then(() => {
364
+ requestAnimationFrame(() => {
365
+ parent.appendChild(fragment);
540
366
  });
541
- return infiniteScroller;
542
- }
367
+ });
368
+ };
369
+ sortBy(key, direction = true) {
370
+ if (this.data.size < 2) return;
371
+ let sorted = this.data.values().toArray().sort((a, b) => {
372
+ return a[key] - b[key];
373
+ });
374
+ if (!direction) sorted = sorted.reverse();
375
+ const container = sorted[0].element.parentElement;
376
+ container.style.visibility = "hidden";
377
+ sorted.forEach((s) => {
378
+ container.append(s.element);
379
+ });
380
+ container.style.visibility = "visible";
543
381
  }
382
+ }
544
383
 
545
- const DefaultScheme = [
546
- {
547
- title: "Text Filter",
548
- collapsed: true,
549
- content: [
550
- { filterExclude: false, label: "exclude" },
551
- {
552
- filterExcludeWords: "",
553
- label: "keywords",
554
- watch: "filterExclude",
555
- placeholder: "word, f:full_word, r:RegEx..."
556
- },
557
- { filterInclude: false, label: "include" },
558
- {
559
- filterIncludeWords: "",
560
- label: "keywords",
561
- watch: "filterInclude",
562
- placeholder: "word, f:full_word, r:RegEx..."
563
- }
564
- ]
565
- },
566
- {
567
- title: "Duration Filter",
568
- collapsed: true,
569
- content: [
570
- { filterDuration: false, label: "enable" },
571
- {
572
- filterDurationFrom: 0,
573
- watch: "filterDuration",
574
- label: "from",
575
- type: "time"
576
- },
577
- {
578
- filterDurationTo: 600,
579
- watch: "filterDuration",
580
- label: "to",
581
- type: "time"
582
- }
583
- ]
584
- },
585
- {
586
- title: "Sort By",
587
- content: [
588
- {
589
- "sort by views": () => {
590
- }
591
- },
592
- {
593
- "sort by duration": () => {
594
- }
595
- }
596
- ]
597
- },
598
- {
599
- title: "Privacy Filter",
600
- content: [
601
- { filterPrivate: false, label: "private" },
602
- { filterPublic: false, label: "public" },
603
- { "check access 🔓": () => {
604
- } }
605
- ]
606
- },
607
- {
608
- title: "Advanced",
609
- content: [
610
- {
611
- infiniteScrollEnabled: true,
612
- label: "infinite scroll"
613
- },
614
- {
615
- autoScroll: false,
616
- label: "auto scroll"
617
- },
618
- {
619
- delay: 250,
620
- label: "scroll delay"
621
- },
622
- {
623
- writeHistory: false,
624
- label: "write history"
625
- }
626
- ]
627
- },
628
- {
629
- title: "Badge",
630
- content: [
631
- {
632
- text: "return `${state.$paginationOffset}/${state.$paginationLast}`",
633
- vif: "return state.$paginationLast > 1"
634
- }
635
- ]
636
- }
637
- ];
638
-
639
- const StoreStateDefault = {
640
- enabled: true,
641
- collapsed: false,
642
- darkmode: true,
643
- $paginationLast: 1,
644
- $paginationOffset: 1
645
- };
384
+ function wait(milliseconds) {
385
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
386
+ }
646
387
 
647
- var Pe=Object.defineProperty;var a=(e,t)=>Pe(e,"name",{value:t,configurable:true});var P=class{type=3;name="";prefix="";value="";suffix="";modifier=3;constructor(t,r,n,c,l,f){this.type=t,this.name=r,this.prefix=n,this.value=c,this.suffix=l,this.modifier=f;}hasCustomName(){return this.name!==""&&typeof this.name!="number"}};a(P,"Part");var Re=/[$_\p{ID_Start}]/u,Ee=/[$_\u200C\u200D\p{ID_Continue}]/u,v=".*";function Oe(e,t){return (t?/^[\x00-\xFF]*$/:/^[\x00-\x7F]*$/).test(e)}a(Oe,"isASCII");function D(e,t=false){let r=[],n=0;for(;n<e.length;){let c=e[n],l=a(function(f){if(!t)throw new TypeError(f);r.push({type:"INVALID_CHAR",index:n,value:e[n++]});},"ErrorOrInvalid");if(c==="*"){r.push({type:"ASTERISK",index:n,value:e[n++]});continue}if(c==="+"||c==="?"){r.push({type:"OTHER_MODIFIER",index:n,value:e[n++]});continue}if(c==="\\"){r.push({type:"ESCAPED_CHAR",index:n++,value:e[n++]});continue}if(c==="{"){r.push({type:"OPEN",index:n,value:e[n++]});continue}if(c==="}"){r.push({type:"CLOSE",index:n,value:e[n++]});continue}if(c===":"){let f="",s=n+1;for(;s<e.length;){let i=e.substr(s,1);if(s===n+1&&Re.test(i)||s!==n+1&&Ee.test(i)){f+=e[s++];continue}break}if(!f){l(`Missing parameter name at ${n}`);continue}r.push({type:"NAME",index:n,value:f}),n=s;continue}if(c==="("){let f=1,s="",i=n+1,o=false;if(e[i]==="?"){l(`Pattern cannot start with "?" at ${i}`);continue}for(;i<e.length;){if(!Oe(e[i],false)){l(`Invalid character '${e[i]}' at ${i}.`),o=true;break}if(e[i]==="\\"){s+=e[i++]+e[i++];continue}if(e[i]===")"){if(f--,f===0){i++;break}}else if(e[i]==="("&&(f++,e[i+1]!=="?")){l(`Capturing groups are not allowed at ${i}`),o=true;break}s+=e[i++];}if(o)continue;if(f){l(`Unbalanced pattern at ${n}`);continue}if(!s){l(`Missing pattern at ${n}`);continue}r.push({type:"REGEX",index:n,value:s}),n=i;continue}r.push({type:"CHAR",index:n,value:e[n++]});}return r.push({type:"END",index:n,value:""}),r}a(D,"lexer");function F(e,t={}){let r=D(e);t.delimiter??="/#?",t.prefixes??="./";let n=`[^${x(t.delimiter)}]+?`,c=[],l=0,f=0,i=new Set,o=a(u=>{if(f<r.length&&r[f].type===u)return r[f++].value},"tryConsume"),h=a(()=>o("OTHER_MODIFIER")??o("ASTERISK"),"tryConsumeModifier"),p=a(u=>{let d=o(u);if(d!==void 0)return d;let{type:g,index:y}=r[f];throw new TypeError(`Unexpected ${g} at ${y}, expected ${u}`)},"mustConsume"),A=a(()=>{let u="",d;for(;d=o("CHAR")??o("ESCAPED_CHAR");)u+=d;return u},"consumeText"),xe=a(u=>u,"DefaultEncodePart"),N=t.encodePart||xe,H="",$=a(u=>{H+=u;},"appendToPendingFixedValue"),M=a(()=>{H.length&&(c.push(new P(3,"","",N(H),"",3)),H="");},"maybeAddPartFromPendingFixedValue"),X=a((u,d,g,y,Z)=>{let m=3;switch(Z){case "?":m=1;break;case "*":m=0;break;case "+":m=2;break}if(!d&&!g&&m===3){$(u);return}if(M(),!d&&!g){if(!u)return;c.push(new P(3,"","",N(u),"",m));return}let S;g?g==="*"?S=v:S=g:S=n;let k=2;S===n?(k=1,S=""):S===v&&(k=0,S="");let E;if(d?E=d:g&&(E=l++),i.has(E))throw new TypeError(`Duplicate name '${E}'.`);i.add(E),c.push(new P(k,E,N(u),S,N(y),m));},"addPart");for(;f<r.length;){let u=o("CHAR"),d=o("NAME"),g=o("REGEX");if(!d&&!g&&(g=o("ASTERISK")),d||g){let m=u??"";t.prefixes.indexOf(m)===-1&&($(m),m=""),M();let S=h();X(m,d,g,"",S);continue}let y=u??o("ESCAPED_CHAR");if(y){$(y);continue}if(o("OPEN")){let m=A(),S=o("NAME"),k=o("REGEX");!S&&!k&&(k=o("ASTERISK"));let E=A();p("CLOSE");let be=h();X(m,S,k,E,be);continue}M(),p("END");}return c}a(F,"parse");function x(e){return e.replace(/([.+*?^${}()[\]|/\\])/g,"\\$1")}a(x,"escapeString");function B(e){return e&&e.ignoreCase?"ui":"u"}a(B,"flags");function q(e,t,r){return W(F(e,r),t,r)}a(q,"stringToRegexp");function T(e){switch(e){case 0:return "*";case 1:return "?";case 2:return "+";case 3:return ""}}a(T,"modifierToString");function W(e,t,r={}){r.delimiter??="/#?",r.prefixes??="./",r.sensitive??=false,r.strict??=false,r.end??=true,r.start??=true,r.endsWith="";let n=r.start?"^":"";for(let s of e){if(s.type===3){s.modifier===3?n+=x(s.value):n+=`(?:${x(s.value)})${T(s.modifier)}`;continue}t&&t.push(s.name);let i=`[^${x(r.delimiter)}]+?`,o=s.value;if(s.type===1?o=i:s.type===0&&(o=v),!s.prefix.length&&!s.suffix.length){s.modifier===3||s.modifier===1?n+=`(${o})${T(s.modifier)}`:n+=`((?:${o})${T(s.modifier)})`;continue}if(s.modifier===3||s.modifier===1){n+=`(?:${x(s.prefix)}(${o})${x(s.suffix)})`,n+=T(s.modifier);continue}n+=`(?:${x(s.prefix)}`,n+=`((?:${o})(?:`,n+=x(s.suffix),n+=x(s.prefix),n+=`(?:${o}))*)${x(s.suffix)})`,s.modifier===0&&(n+="?");}let c=`[${x(r.endsWith)}]|$`,l=`[${x(r.delimiter)}]`;if(r.end)return r.strict||(n+=`${l}?`),r.endsWith.length?n+=`(?=${c})`:n+="$",new RegExp(n,B(r));r.strict||(n+=`(?:${l}(?=${c}))?`);let f=false;if(e.length){let s=e[e.length-1];s.type===3&&s.modifier===3&&(f=r.delimiter.indexOf(s)>-1);}return f||(n+=`(?=${l}|${c})`),new RegExp(n,B(r))}a(W,"partsToRegexp");var b={delimiter:"",prefixes:"",sensitive:true,strict:true},J={delimiter:".",prefixes:"",sensitive:true,strict:true},Q={delimiter:"/",prefixes:"/",sensitive:true,strict:true};function ee(e,t){return e.length?e[0]==="/"?true:!t||e.length<2?false:(e[0]=="\\"||e[0]=="{")&&e[1]=="/":false}a(ee,"isAbsolutePathname");function te(e,t){return e.startsWith(t)?e.substring(t.length,e.length):e}a(te,"maybeStripPrefix");function ke(e,t){return e.endsWith(t)?e.substr(0,e.length-t.length):e}a(ke,"maybeStripSuffix");function _(e){return !e||e.length<2?false:e[0]==="["||(e[0]==="\\"||e[0]==="{")&&e[1]==="["}a(_,"treatAsIPv6Hostname");var re=["ftp","file","http","https","ws","wss"];function U(e){if(!e)return true;for(let t of re)if(e.test(t))return true;return false}a(U,"isSpecialScheme");function ne(e,t){if(e=te(e,"#"),t||e==="")return e;let r=new URL("https://example.com");return r.hash=e,r.hash?r.hash.substring(1,r.hash.length):""}a(ne,"canonicalizeHash");function se(e,t){if(e=te(e,"?"),t||e==="")return e;let r=new URL("https://example.com");return r.search=e,r.search?r.search.substring(1,r.search.length):""}a(se,"canonicalizeSearch");function ie(e,t){return t||e===""?e:_(e)?K(e):j(e)}a(ie,"canonicalizeHostname");function ae(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.password=e,r.password}a(ae,"canonicalizePassword");function oe(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.username=e,r.username}a(oe,"canonicalizeUsername");function ce(e,t,r){if(r||e==="")return e;if(t&&!re.includes(t))return new URL(`${t}:${e}`).pathname;let n=e[0]=="/";return e=new URL(n?e:"/-"+e,"https://example.com").pathname,n||(e=e.substring(2,e.length)),e}a(ce,"canonicalizePathname");function le(e,t,r){return z(t)===e&&(e=""),r||e===""?e:G(e)}a(le,"canonicalizePort");function fe(e,t){return e=ke(e,":"),t||e===""?e:w(e)}a(fe,"canonicalizeProtocol");function z(e){switch(e){case "ws":case "http":return "80";case "wws":case "https":return "443";case "ftp":return "21";default:return ""}}a(z,"defaultPortForProtocol");function w(e){if(e==="")return e;if(/^[-+.A-Za-z0-9]*$/.test(e))return e.toLowerCase();throw new TypeError(`Invalid protocol '${e}'.`)}a(w,"protocolEncodeCallback");function he(e){if(e==="")return e;let t=new URL("https://example.com");return t.username=e,t.username}a(he,"usernameEncodeCallback");function ue(e){if(e==="")return e;let t=new URL("https://example.com");return t.password=e,t.password}a(ue,"passwordEncodeCallback");function j(e){if(e==="")return e;if(/[\t\n\r #%/:<>?@[\]^\\|]/g.test(e))throw new TypeError(`Invalid hostname '${e}'`);let t=new URL("https://example.com");return t.hostname=e,t.hostname}a(j,"hostnameEncodeCallback");function K(e){if(e==="")return e;if(/[^0-9a-fA-F[\]:]/g.test(e))throw new TypeError(`Invalid IPv6 hostname '${e}'`);return e.toLowerCase()}a(K,"ipv6HostnameEncodeCallback");function G(e){if(e===""||/^[0-9]*$/.test(e)&&parseInt(e)<=65535)return e;throw new TypeError(`Invalid port '${e}'.`)}a(G,"portEncodeCallback");function de(e){if(e==="")return e;let t=new URL("https://example.com");return t.pathname=e[0]!=="/"?"/-"+e:e,e[0]!=="/"?t.pathname.substring(2,t.pathname.length):t.pathname}a(de,"standardURLPathnameEncodeCallback");function pe(e){return e===""?e:new URL(`data:${e}`).pathname}a(pe,"pathURLPathnameEncodeCallback");function ge(e){if(e==="")return e;let t=new URL("https://example.com");return t.search=e,t.search.substring(1,t.search.length)}a(ge,"searchEncodeCallback");function me(e){if(e==="")return e;let t=new URL("https://example.com");return t.hash=e,t.hash.substring(1,t.hash.length)}a(me,"hashEncodeCallback");var C=class{#i;#n=[];#t={};#e=0;#s=1;#l=0;#o=0;#d=0;#p=0;#g=false;constructor(t){this.#i=t;}get result(){return this.#t}parse(){for(this.#n=D(this.#i,true);this.#e<this.#n.length;this.#e+=this.#s){if(this.#s=1,this.#n[this.#e].type==="END"){if(this.#o===0){this.#b(),this.#f()?this.#r(9,1):this.#h()?this.#r(8,1):this.#r(7,0);continue}else if(this.#o===2){this.#u(5);continue}this.#r(10,0);break}if(this.#d>0)if(this.#A())this.#d-=1;else continue;if(this.#T()){this.#d+=1;continue}switch(this.#o){case 0:this.#P()&&this.#u(1);break;case 1:if(this.#P()){this.#C();let t=7,r=1;this.#E()?(t=2,r=3):this.#g&&(t=2),this.#r(t,r);}break;case 2:this.#S()?this.#u(3):(this.#x()||this.#h()||this.#f())&&this.#u(5);break;case 3:this.#O()?this.#r(4,1):this.#S()&&this.#r(5,1);break;case 4:this.#S()&&this.#r(5,1);break;case 5:this.#y()?this.#p+=1:this.#w()&&(this.#p-=1),this.#k()&&!this.#p?this.#r(6,1):this.#x()?this.#r(7,0):this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 6:this.#x()?this.#r(7,0):this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 7:this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 8:this.#f()&&this.#r(9,1);break;}}this.#t.hostname!==void 0&&this.#t.port===void 0&&(this.#t.port="");}#r(t,r){switch(this.#o){case 0:break;case 1:this.#t.protocol=this.#c();break;case 2:break;case 3:this.#t.username=this.#c();break;case 4:this.#t.password=this.#c();break;case 5:this.#t.hostname=this.#c();break;case 6:this.#t.port=this.#c();break;case 7:this.#t.pathname=this.#c();break;case 8:this.#t.search=this.#c();break;case 9:this.#t.hash=this.#c();break;}this.#o!==0&&t!==10&&([1,2,3,4].includes(this.#o)&&[6,7,8,9].includes(t)&&(this.#t.hostname??=""),[1,2,3,4,5,6].includes(this.#o)&&[8,9].includes(t)&&(this.#t.pathname??=this.#g?"/":""),[1,2,3,4,5,6,7].includes(this.#o)&&t===9&&(this.#t.search??="")),this.#R(t,r);}#R(t,r){this.#o=t,this.#l=this.#e+r,this.#e+=r,this.#s=0;}#b(){this.#e=this.#l,this.#s=0;}#u(t){this.#b(),this.#o=t;}#m(t){return t<0&&(t=this.#n.length-t),t<this.#n.length?this.#n[t]:this.#n[this.#n.length-1]}#a(t,r){let n=this.#m(t);return n.value===r&&(n.type==="CHAR"||n.type==="ESCAPED_CHAR"||n.type==="INVALID_CHAR")}#P(){return this.#a(this.#e,":")}#E(){return this.#a(this.#e+1,"/")&&this.#a(this.#e+2,"/")}#S(){return this.#a(this.#e,"@")}#O(){return this.#a(this.#e,":")}#k(){return this.#a(this.#e,":")}#x(){return this.#a(this.#e,"/")}#h(){if(this.#a(this.#e,"?"))return true;if(this.#n[this.#e].value!=="?")return false;let t=this.#m(this.#e-1);return t.type!=="NAME"&&t.type!=="REGEX"&&t.type!=="CLOSE"&&t.type!=="ASTERISK"}#f(){return this.#a(this.#e,"#")}#T(){return this.#n[this.#e].type=="OPEN"}#A(){return this.#n[this.#e].type=="CLOSE"}#y(){return this.#a(this.#e,"[")}#w(){return this.#a(this.#e,"]")}#c(){let t=this.#n[this.#e],r=this.#m(this.#l).index;return this.#i.substring(r,t.index)}#C(){let t={};Object.assign(t,b),t.encodePart=w;let r=q(this.#c(),void 0,t);this.#g=U(r);}};a(C,"Parser");var V=["protocol","username","password","hostname","port","pathname","search","hash"],O="*";function Se(e,t){if(typeof e!="string")throw new TypeError("parameter 1 is not of type 'string'.");let r=new URL(e,t);return {protocol:r.protocol.substring(0,r.protocol.length-1),username:r.username,password:r.password,hostname:r.hostname,port:r.port,pathname:r.pathname,search:r.search!==""?r.search.substring(1,r.search.length):void 0,hash:r.hash!==""?r.hash.substring(1,r.hash.length):void 0}}a(Se,"extractValues");function R(e,t){return t?I(e):e}a(R,"processBaseURLString");function L(e,t,r){let n;if(typeof t.baseURL=="string")try{n=new URL(t.baseURL),t.protocol===void 0&&(e.protocol=R(n.protocol.substring(0,n.protocol.length-1),r)),!r&&t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.username===void 0&&(e.username=R(n.username,r)),!r&&t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.username===void 0&&t.password===void 0&&(e.password=R(n.password,r)),t.protocol===void 0&&t.hostname===void 0&&(e.hostname=R(n.hostname,r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&(e.port=R(n.port,r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.pathname===void 0&&(e.pathname=R(n.pathname,r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.pathname===void 0&&t.search===void 0&&(e.search=R(n.search.substring(1,n.search.length),r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.pathname===void 0&&t.search===void 0&&t.hash===void 0&&(e.hash=R(n.hash.substring(1,n.hash.length),r));}catch{throw new TypeError(`invalid baseURL '${t.baseURL}'.`)}if(typeof t.protocol=="string"&&(e.protocol=fe(t.protocol,r)),typeof t.username=="string"&&(e.username=oe(t.username,r)),typeof t.password=="string"&&(e.password=ae(t.password,r)),typeof t.hostname=="string"&&(e.hostname=ie(t.hostname,r)),typeof t.port=="string"&&(e.port=le(t.port,e.protocol,r)),typeof t.pathname=="string"){if(e.pathname=t.pathname,n&&!ee(e.pathname,r)){let c=n.pathname.lastIndexOf("/");c>=0&&(e.pathname=R(n.pathname.substring(0,c+1),r)+e.pathname);}e.pathname=ce(e.pathname,e.protocol,r);}return typeof t.search=="string"&&(e.search=se(t.search,r)),typeof t.hash=="string"&&(e.hash=ne(t.hash,r)),e}a(L,"applyInit");function I(e){return e.replace(/([+*?:{}()\\])/g,"\\$1")}a(I,"escapePatternString");function Te(e){return e.replace(/([.+*?^${}()[\]|/\\])/g,"\\$1")}a(Te,"escapeRegexpString");function Ae(e,t){t.delimiter??="/#?",t.prefixes??="./",t.sensitive??=false,t.strict??=false,t.end??=true,t.start??=true,t.endsWith="";let r=".*",n=`[^${Te(t.delimiter)}]+?`,c=/[$_\u200C\u200D\p{ID_Continue}]/u,l="";for(let f=0;f<e.length;++f){let s=e[f];if(s.type===3){if(s.modifier===3){l+=I(s.value);continue}l+=`{${I(s.value)}}${T(s.modifier)}`;continue}let i=s.hasCustomName(),o=!!s.suffix.length||!!s.prefix.length&&(s.prefix.length!==1||!t.prefixes.includes(s.prefix)),h=f>0?e[f-1]:null,p=f<e.length-1?e[f+1]:null;if(!o&&i&&s.type===1&&s.modifier===3&&p&&!p.prefix.length&&!p.suffix.length)if(p.type===3){let A=p.value.length>0?p.value[0]:"";o=c.test(A);}else o=!p.hasCustomName();if(!o&&!s.prefix.length&&h&&h.type===3){let A=h.value[h.value.length-1];o=t.prefixes.includes(A);}o&&(l+="{"),l+=I(s.prefix),i&&(l+=`:${s.name}`),s.type===2?l+=`(${s.value})`:s.type===1?i||(l+=`(${n})`):s.type===0&&(!i&&(!h||h.type===3||h.modifier!==3||o||s.prefix!=="")?l+="*":l+=`(${r})`),s.type===1&&i&&s.suffix.length&&c.test(s.suffix[0])&&(l+="\\"),l+=I(s.suffix),o&&(l+="}"),s.modifier!==3&&(l+=T(s.modifier));}return l}a(Ae,"partsToPattern");var Y=class{#i;#n={};#t={};#e={};#s={};#l=false;constructor(t={},r,n){try{let c;if(typeof r=="string"?c=r:n=r,typeof t=="string"){let i=new C(t);if(i.parse(),t=i.result,c===void 0&&typeof t.protocol!="string")throw new TypeError("A base URL must be provided for a relative constructor string.");t.baseURL=c;}else {if(!t||typeof t!="object")throw new TypeError("parameter 1 is not of type 'string' and cannot convert to dictionary.");if(c)throw new TypeError("parameter 1 is not of type 'string'.")}typeof n>"u"&&(n={ignoreCase:!1});let l={ignoreCase:n.ignoreCase===!0},f={pathname:O,protocol:O,username:O,password:O,hostname:O,port:O,search:O,hash:O};this.#i=L(f,t,!0),z(this.#i.protocol)===this.#i.port&&(this.#i.port="");let s;for(s of V){if(!(s in this.#i))continue;let i={},o=this.#i[s];switch(this.#t[s]=[],s){case "protocol":Object.assign(i,b),i.encodePart=w;break;case "username":Object.assign(i,b),i.encodePart=he;break;case "password":Object.assign(i,b),i.encodePart=ue;break;case "hostname":Object.assign(i,J),_(o)?i.encodePart=K:i.encodePart=j;break;case "port":Object.assign(i,b),i.encodePart=G;break;case "pathname":U(this.#n.protocol)?(Object.assign(i,Q,l),i.encodePart=de):(Object.assign(i,b,l),i.encodePart=pe);break;case "search":Object.assign(i,b,l),i.encodePart=ge;break;case "hash":Object.assign(i,b,l),i.encodePart=me;break}try{this.#s[s]=F(o,i),this.#n[s]=W(this.#s[s],this.#t[s],i),this.#e[s]=Ae(this.#s[s],i),this.#l=this.#l||this.#s[s].some(h=>h.type===2);}catch{throw new TypeError(`invalid ${s} pattern '${this.#i[s]}'.`)}}}catch(c){throw new TypeError(`Failed to construct 'URLPattern': ${c.message}`)}}get[Symbol.toStringTag](){return "URLPattern"}test(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return false;try{typeof t=="object"?n=L(n,t,!1):n=L(n,Se(t,r),!1);}catch{return false}let c;for(c of V)if(!this.#n[c].exec(n[c]))return false;return true}exec(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return;try{typeof t=="object"?n=L(n,t,!1):n=L(n,Se(t,r),!1);}catch{return null}let c={};r?c.inputs=[t,r]:c.inputs=[t];let l;for(l of V){let f=this.#n[l].exec(n[l]);if(!f)return null;let s={};for(let[i,o]of this.#t[l].entries())if(typeof o=="string"||typeof o=="number"){let h=f[i+1];s[o]=h;}c[l]={input:n[l]??"",groups:s};}return c}static compareComponent(t,r,n){let c=a((i,o)=>{for(let h of ["type","modifier","prefix","value","suffix"]){if(i[h]<o[h])return -1;if(i[h]===o[h])continue;return 1}return 0},"comparePart"),l=new P(3,"","","","",3),f=new P(0,"","","","",3),s=a((i,o)=>{let h=0;for(;h<Math.min(i.length,o.length);++h){let p=c(i[h],o[h]);if(p)return p}return i.length===o.length?0:c(i[h]??l,o[h]??l)},"comparePartList");return !r.#e[t]&&!n.#e[t]?0:r.#e[t]&&!n.#e[t]?s(r.#s[t],[f]):!r.#e[t]&&n.#e[t]?s([f],n.#s[t]):s(r.#s[t],n.#s[t])}get protocol(){return this.#e.protocol}get username(){return this.#e.username}get password(){return this.#e.password}get hostname(){return this.#e.hostname}get port(){return this.#e.port}get pathname(){return this.#e.pathname}get search(){return this.#e.search}get hash(){return this.#e.hash}get hasRegExpGroups(){return this.#l}};a(Y,"URLPattern");
388
+ const MOBILE_UA = {
389
+ "User-Agent": [
390
+ "Mozilla/5.0 (Linux; Android 10; K)",
391
+ "AppleWebKit/537.36 (KHTML, like Gecko)",
392
+ "Chrome/114.0.0.0 Mobile Safari/537.36"
393
+ ].join(" ")
394
+ };
395
+ async function fetchWith(input, options) {
396
+ const requestInit = options.init || {};
397
+ if (options.mobile) {
398
+ Object.assign(requestInit, { headers: new Headers(MOBILE_UA) });
399
+ }
400
+ const r = await fetch(input, requestInit).then((r2) => r2);
401
+ return parseHtml(await r.text());
402
+ }
403
+ const fetchHtml = (input) => fetchWith(input, { });
648
404
 
649
- function parseUrl(s) {
650
- return new URL(typeof s === "string" ? s : s.href);
651
- }
652
- function depaginatePathname(url, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
653
- const newUrl = new URL(url.toString());
654
- newUrl.pathname = newUrl.pathname.replace(pathnamePaginationSelector, "/");
655
- return newUrl;
656
- }
657
- function getPaginationLinks(doc = document, url = location.href, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
658
- const baseUrl = depaginatePathname(parseUrl(url), pathnamePaginationSelector);
659
- const pathnameStrict = doc instanceof Document;
660
- const host = doc.baseURI || baseUrl.origin;
661
- const urlPattern = new Y({
662
- pathname: pathnameStrict ? `${baseUrl.pathname}*` : "*",
663
- hostname: baseUrl.hostname
664
- });
665
- const pageLinks = [...doc.querySelectorAll("a[href]")].map((a) => a.href).filter((h) => URL.canParse(h));
666
- return pageLinks.filter((h) => {
667
- return urlPattern.test(new URL(h, host));
668
- });
405
+ class InfiniteScroller {
406
+ enabled = true;
407
+ paginationOffset = 1;
408
+ parseData;
409
+ rules;
410
+ observer;
411
+ paginationGenerator;
412
+ constructor(options) {
413
+ this.rules = options.rules;
414
+ this.paginationOffset = this.rules.paginationStrategy.getPaginationOffset();
415
+ Object.assign(this, options);
416
+ if (this.rules.getPaginationData) {
417
+ this.getPaginationData = this.rules.getPaginationData;
418
+ }
419
+ this.paginationGenerator = this.rules.customGenerator || InfiniteScroller.generatorForPaginationStrategy(this.rules.paginationStrategy);
420
+ this.setObserver(this.rules.observable);
421
+ this.setAutoScroll();
669
422
  }
670
- function upgradePathname(curr, links, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
671
- if (pathnamePaginationSelector.test(curr.pathname) || links.length < 1) return curr;
672
- const linksDepaginated = links.map(
673
- (l) => depaginatePathname(l, pathnamePaginationSelector)
423
+ dispose() {
424
+ if (this.observer) this.observer.dispose();
425
+ }
426
+ setObserver(observable) {
427
+ if (this.observer) this.observer.dispose();
428
+ this.observer = Observer.observeWhile(
429
+ observable,
430
+ this.generatorConsumer,
431
+ this.rules.store.state.delay
674
432
  );
675
- if (linksDepaginated.some((l) => l.pathname === curr.pathname)) return curr;
676
- const last = linksDepaginated.at(-1);
677
- if (last.pathname !== curr.pathname) curr.pathname = last.pathname;
678
- return curr;
433
+ return this;
679
434
  }
680
-
681
- class PaginationStrategy {
682
- doc = document;
683
- url;
684
- paginationSelector = ".pagination";
685
- searchParamSelector = "page";
686
- static _pathnameSelector = /\/(page\/)?\d+\/?$/;
687
- pathnameSelector = /\/(\d+)\/?$/;
688
- dataparamSelector = "[data-parameters *= from]";
689
- overwritePaginationLast;
690
- offsetMin = 1;
691
- constructor(options) {
692
- if (options) {
693
- Object.entries(options).forEach(([k, v]) => {
694
- Object.assign(this, { [k]: v });
695
- });
435
+ onScrollCBs = [];
436
+ onScroll(callback, initCall = false) {
437
+ if (initCall) callback(this);
438
+ this.onScrollCBs.push(callback);
439
+ return this;
440
+ }
441
+ _onScroll() {
442
+ this.onScrollCBs.forEach((cb) => {
443
+ cb(this);
444
+ });
445
+ }
446
+ setAutoScroll() {
447
+ const autoScrollWrapper = async () => {
448
+ if (this.rules.store.state.autoScroll) {
449
+ await wait(this.rules.store.state.delay);
450
+ await this.generatorConsumer();
451
+ await autoScrollWrapper();
696
452
  }
697
- this.url = parseUrl(options?.url || this.doc.URL);
698
- }
699
- getPaginationElement() {
700
- return this.doc.querySelector(this.paginationSelector);
701
- }
702
- get hasPagination() {
703
- return !!this.getPaginationElement();
704
- }
705
- getPaginationOffset() {
706
- return this.offsetMin;
707
- }
708
- getPaginationLast() {
709
- if (this.overwritePaginationLast) return this.overwritePaginationLast(1);
710
- return 1;
453
+ };
454
+ autoScrollWrapper();
455
+ this.rules.store.stateSubject.subscribe((type) => {
456
+ if (type?.autoScroll) {
457
+ autoScrollWrapper();
458
+ }
459
+ });
460
+ }
461
+ generatorConsumer = async () => {
462
+ if (!this.enabled) return false;
463
+ const {
464
+ value: { url, offset },
465
+ done
466
+ } = await this.paginationGenerator.next();
467
+ if (!done && url) {
468
+ await this.doScroll(url, offset);
469
+ }
470
+ return !done;
471
+ };
472
+ async getPaginationData(url) {
473
+ return await fetchHtml(url);
474
+ }
475
+ async doScroll(url, offset) {
476
+ const nextPageHtml = await this.getPaginationData(url);
477
+ const prevScrollPos = document.documentElement.scrollTop;
478
+ this.paginationOffset = Math.max(this.paginationOffset, offset);
479
+ this.parseData?.(nextPageHtml);
480
+ this._onScroll();
481
+ window.scrollTo(0, prevScrollPos);
482
+ if (this.rules.store.state.writeHistory) {
483
+ history.replaceState({}, "", url);
711
484
  }
712
- getPaginationUrlGenerator() {
713
- return (_) => this.url.href;
485
+ }
486
+ static async *generatorForPaginationStrategy(pstrategy) {
487
+ const _offset = pstrategy.getPaginationOffset();
488
+ const end = pstrategy.getPaginationLast();
489
+ const urlGenerator = pstrategy.getPaginationUrlGenerator();
490
+ for (let offset = _offset; offset <= end; offset++) {
491
+ const url = await urlGenerator(offset);
492
+ yield { url, offset };
714
493
  }
715
494
  }
495
+ static create(rules) {
496
+ const enabled = rules.store.state.infiniteScrollEnabled;
497
+ rules.store.state.$paginationLast = rules.paginationStrategy.getPaginationLast();
498
+ const infiniteScroller = new InfiniteScroller({
499
+ enabled,
500
+ parseData: rules.dataManager.parseData,
501
+ rules
502
+ }).onScroll(({ paginationOffset }) => {
503
+ rules.store.state.$paginationOffset = paginationOffset;
504
+ }, true);
505
+ rules.store.stateSubject.subscribe(() => {
506
+ infiniteScroller.enabled = rules.store.state.infiniteScrollEnabled;
507
+ });
508
+ return infiniteScroller;
509
+ }
510
+ }
511
+
512
+ var Pe=Object.defineProperty;var a=(e,t)=>Pe(e,"name",{value:t,configurable:true});var P=class{type=3;name="";prefix="";value="";suffix="";modifier=3;constructor(t,r,n,c,l,f){this.type=t,this.name=r,this.prefix=n,this.value=c,this.suffix=l,this.modifier=f;}hasCustomName(){return this.name!==""&&typeof this.name!="number"}};a(P,"Part");var Re=/[$_\p{ID_Start}]/u,Ee=/[$_\u200C\u200D\p{ID_Continue}]/u,v=".*";function Oe(e,t){return (t?/^[\x00-\xFF]*$/:/^[\x00-\x7F]*$/).test(e)}a(Oe,"isASCII");function D(e,t=false){let r=[],n=0;for(;n<e.length;){let c=e[n],l=a(function(f){if(!t)throw new TypeError(f);r.push({type:"INVALID_CHAR",index:n,value:e[n++]});},"ErrorOrInvalid");if(c==="*"){r.push({type:"ASTERISK",index:n,value:e[n++]});continue}if(c==="+"||c==="?"){r.push({type:"OTHER_MODIFIER",index:n,value:e[n++]});continue}if(c==="\\"){r.push({type:"ESCAPED_CHAR",index:n++,value:e[n++]});continue}if(c==="{"){r.push({type:"OPEN",index:n,value:e[n++]});continue}if(c==="}"){r.push({type:"CLOSE",index:n,value:e[n++]});continue}if(c===":"){let f="",s=n+1;for(;s<e.length;){let i=e.substr(s,1);if(s===n+1&&Re.test(i)||s!==n+1&&Ee.test(i)){f+=e[s++];continue}break}if(!f){l(`Missing parameter name at ${n}`);continue}r.push({type:"NAME",index:n,value:f}),n=s;continue}if(c==="("){let f=1,s="",i=n+1,o=false;if(e[i]==="?"){l(`Pattern cannot start with "?" at ${i}`);continue}for(;i<e.length;){if(!Oe(e[i],false)){l(`Invalid character '${e[i]}' at ${i}.`),o=true;break}if(e[i]==="\\"){s+=e[i++]+e[i++];continue}if(e[i]===")"){if(f--,f===0){i++;break}}else if(e[i]==="("&&(f++,e[i+1]!=="?")){l(`Capturing groups are not allowed at ${i}`),o=true;break}s+=e[i++];}if(o)continue;if(f){l(`Unbalanced pattern at ${n}`);continue}if(!s){l(`Missing pattern at ${n}`);continue}r.push({type:"REGEX",index:n,value:s}),n=i;continue}r.push({type:"CHAR",index:n,value:e[n++]});}return r.push({type:"END",index:n,value:""}),r}a(D,"lexer");function F(e,t={}){let r=D(e);t.delimiter??="/#?",t.prefixes??="./";let n=`[^${x(t.delimiter)}]+?`,c=[],l=0,f=0,i=new Set,o=a(u=>{if(f<r.length&&r[f].type===u)return r[f++].value},"tryConsume"),h=a(()=>o("OTHER_MODIFIER")??o("ASTERISK"),"tryConsumeModifier"),p=a(u=>{let d=o(u);if(d!==void 0)return d;let{type:g,index:y}=r[f];throw new TypeError(`Unexpected ${g} at ${y}, expected ${u}`)},"mustConsume"),A=a(()=>{let u="",d;for(;d=o("CHAR")??o("ESCAPED_CHAR");)u+=d;return u},"consumeText"),xe=a(u=>u,"DefaultEncodePart"),N=t.encodePart||xe,H="",$=a(u=>{H+=u;},"appendToPendingFixedValue"),M=a(()=>{H.length&&(c.push(new P(3,"","",N(H),"",3)),H="");},"maybeAddPartFromPendingFixedValue"),X=a((u,d,g,y,Z)=>{let m=3;switch(Z){case "?":m=1;break;case "*":m=0;break;case "+":m=2;break}if(!d&&!g&&m===3){$(u);return}if(M(),!d&&!g){if(!u)return;c.push(new P(3,"","",N(u),"",m));return}let S;g?g==="*"?S=v:S=g:S=n;let k=2;S===n?(k=1,S=""):S===v&&(k=0,S="");let E;if(d?E=d:g&&(E=l++),i.has(E))throw new TypeError(`Duplicate name '${E}'.`);i.add(E),c.push(new P(k,E,N(u),S,N(y),m));},"addPart");for(;f<r.length;){let u=o("CHAR"),d=o("NAME"),g=o("REGEX");if(!d&&!g&&(g=o("ASTERISK")),d||g){let m=u??"";t.prefixes.indexOf(m)===-1&&($(m),m=""),M();let S=h();X(m,d,g,"",S);continue}let y=u??o("ESCAPED_CHAR");if(y){$(y);continue}if(o("OPEN")){let m=A(),S=o("NAME"),k=o("REGEX");!S&&!k&&(k=o("ASTERISK"));let E=A();p("CLOSE");let be=h();X(m,S,k,E,be);continue}M(),p("END");}return c}a(F,"parse");function x(e){return e.replace(/([.+*?^${}()[\]|/\\])/g,"\\$1")}a(x,"escapeString");function B(e){return e&&e.ignoreCase?"ui":"u"}a(B,"flags");function q(e,t,r){return W(F(e,r),t,r)}a(q,"stringToRegexp");function T(e){switch(e){case 0:return "*";case 1:return "?";case 2:return "+";case 3:return ""}}a(T,"modifierToString");function W(e,t,r={}){r.delimiter??="/#?",r.prefixes??="./",r.sensitive??=false,r.strict??=false,r.end??=true,r.start??=true,r.endsWith="";let n=r.start?"^":"";for(let s of e){if(s.type===3){s.modifier===3?n+=x(s.value):n+=`(?:${x(s.value)})${T(s.modifier)}`;continue}t&&t.push(s.name);let i=`[^${x(r.delimiter)}]+?`,o=s.value;if(s.type===1?o=i:s.type===0&&(o=v),!s.prefix.length&&!s.suffix.length){s.modifier===3||s.modifier===1?n+=`(${o})${T(s.modifier)}`:n+=`((?:${o})${T(s.modifier)})`;continue}if(s.modifier===3||s.modifier===1){n+=`(?:${x(s.prefix)}(${o})${x(s.suffix)})`,n+=T(s.modifier);continue}n+=`(?:${x(s.prefix)}`,n+=`((?:${o})(?:`,n+=x(s.suffix),n+=x(s.prefix),n+=`(?:${o}))*)${x(s.suffix)})`,s.modifier===0&&(n+="?");}let c=`[${x(r.endsWith)}]|$`,l=`[${x(r.delimiter)}]`;if(r.end)return r.strict||(n+=`${l}?`),r.endsWith.length?n+=`(?=${c})`:n+="$",new RegExp(n,B(r));r.strict||(n+=`(?:${l}(?=${c}))?`);let f=false;if(e.length){let s=e[e.length-1];s.type===3&&s.modifier===3&&(f=r.delimiter.indexOf(s)>-1);}return f||(n+=`(?=${l}|${c})`),new RegExp(n,B(r))}a(W,"partsToRegexp");var b={delimiter:"",prefixes:"",sensitive:true,strict:true},J={delimiter:".",prefixes:"",sensitive:true,strict:true},Q={delimiter:"/",prefixes:"/",sensitive:true,strict:true};function ee(e,t){return e.length?e[0]==="/"?true:!t||e.length<2?false:(e[0]=="\\"||e[0]=="{")&&e[1]=="/":false}a(ee,"isAbsolutePathname");function te(e,t){return e.startsWith(t)?e.substring(t.length,e.length):e}a(te,"maybeStripPrefix");function ke(e,t){return e.endsWith(t)?e.substr(0,e.length-t.length):e}a(ke,"maybeStripSuffix");function _(e){return !e||e.length<2?false:e[0]==="["||(e[0]==="\\"||e[0]==="{")&&e[1]==="["}a(_,"treatAsIPv6Hostname");var re=["ftp","file","http","https","ws","wss"];function U(e){if(!e)return true;for(let t of re)if(e.test(t))return true;return false}a(U,"isSpecialScheme");function ne(e,t){if(e=te(e,"#"),t||e==="")return e;let r=new URL("https://example.com");return r.hash=e,r.hash?r.hash.substring(1,r.hash.length):""}a(ne,"canonicalizeHash");function se(e,t){if(e=te(e,"?"),t||e==="")return e;let r=new URL("https://example.com");return r.search=e,r.search?r.search.substring(1,r.search.length):""}a(se,"canonicalizeSearch");function ie(e,t){return t||e===""?e:_(e)?K(e):j(e)}a(ie,"canonicalizeHostname");function ae(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.password=e,r.password}a(ae,"canonicalizePassword");function oe(e,t){if(t||e==="")return e;let r=new URL("https://example.com");return r.username=e,r.username}a(oe,"canonicalizeUsername");function ce(e,t,r){if(r||e==="")return e;if(t&&!re.includes(t))return new URL(`${t}:${e}`).pathname;let n=e[0]=="/";return e=new URL(n?e:"/-"+e,"https://example.com").pathname,n||(e=e.substring(2,e.length)),e}a(ce,"canonicalizePathname");function le(e,t,r){return z(t)===e&&(e=""),r||e===""?e:G(e)}a(le,"canonicalizePort");function fe(e,t){return e=ke(e,":"),t||e===""?e:w(e)}a(fe,"canonicalizeProtocol");function z(e){switch(e){case "ws":case "http":return "80";case "wws":case "https":return "443";case "ftp":return "21";default:return ""}}a(z,"defaultPortForProtocol");function w(e){if(e==="")return e;if(/^[-+.A-Za-z0-9]*$/.test(e))return e.toLowerCase();throw new TypeError(`Invalid protocol '${e}'.`)}a(w,"protocolEncodeCallback");function he(e){if(e==="")return e;let t=new URL("https://example.com");return t.username=e,t.username}a(he,"usernameEncodeCallback");function ue(e){if(e==="")return e;let t=new URL("https://example.com");return t.password=e,t.password}a(ue,"passwordEncodeCallback");function j(e){if(e==="")return e;if(/[\t\n\r #%/:<>?@[\]^\\|]/g.test(e))throw new TypeError(`Invalid hostname '${e}'`);let t=new URL("https://example.com");return t.hostname=e,t.hostname}a(j,"hostnameEncodeCallback");function K(e){if(e==="")return e;if(/[^0-9a-fA-F[\]:]/g.test(e))throw new TypeError(`Invalid IPv6 hostname '${e}'`);return e.toLowerCase()}a(K,"ipv6HostnameEncodeCallback");function G(e){if(e===""||/^[0-9]*$/.test(e)&&parseInt(e)<=65535)return e;throw new TypeError(`Invalid port '${e}'.`)}a(G,"portEncodeCallback");function de(e){if(e==="")return e;let t=new URL("https://example.com");return t.pathname=e[0]!=="/"?"/-"+e:e,e[0]!=="/"?t.pathname.substring(2,t.pathname.length):t.pathname}a(de,"standardURLPathnameEncodeCallback");function pe(e){return e===""?e:new URL(`data:${e}`).pathname}a(pe,"pathURLPathnameEncodeCallback");function ge(e){if(e==="")return e;let t=new URL("https://example.com");return t.search=e,t.search.substring(1,t.search.length)}a(ge,"searchEncodeCallback");function me(e){if(e==="")return e;let t=new URL("https://example.com");return t.hash=e,t.hash.substring(1,t.hash.length)}a(me,"hashEncodeCallback");var C=class{#i;#n=[];#t={};#e=0;#s=1;#l=0;#o=0;#d=0;#p=0;#g=false;constructor(t){this.#i=t;}get result(){return this.#t}parse(){for(this.#n=D(this.#i,true);this.#e<this.#n.length;this.#e+=this.#s){if(this.#s=1,this.#n[this.#e].type==="END"){if(this.#o===0){this.#b(),this.#f()?this.#r(9,1):this.#h()?this.#r(8,1):this.#r(7,0);continue}else if(this.#o===2){this.#u(5);continue}this.#r(10,0);break}if(this.#d>0)if(this.#A())this.#d-=1;else continue;if(this.#T()){this.#d+=1;continue}switch(this.#o){case 0:this.#P()&&this.#u(1);break;case 1:if(this.#P()){this.#C();let t=7,r=1;this.#E()?(t=2,r=3):this.#g&&(t=2),this.#r(t,r);}break;case 2:this.#S()?this.#u(3):(this.#x()||this.#h()||this.#f())&&this.#u(5);break;case 3:this.#O()?this.#r(4,1):this.#S()&&this.#r(5,1);break;case 4:this.#S()&&this.#r(5,1);break;case 5:this.#y()?this.#p+=1:this.#w()&&(this.#p-=1),this.#k()&&!this.#p?this.#r(6,1):this.#x()?this.#r(7,0):this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 6:this.#x()?this.#r(7,0):this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 7:this.#h()?this.#r(8,1):this.#f()&&this.#r(9,1);break;case 8:this.#f()&&this.#r(9,1);break;}}this.#t.hostname!==void 0&&this.#t.port===void 0&&(this.#t.port="");}#r(t,r){switch(this.#o){case 0:break;case 1:this.#t.protocol=this.#c();break;case 2:break;case 3:this.#t.username=this.#c();break;case 4:this.#t.password=this.#c();break;case 5:this.#t.hostname=this.#c();break;case 6:this.#t.port=this.#c();break;case 7:this.#t.pathname=this.#c();break;case 8:this.#t.search=this.#c();break;case 9:this.#t.hash=this.#c();break;}this.#o!==0&&t!==10&&([1,2,3,4].includes(this.#o)&&[6,7,8,9].includes(t)&&(this.#t.hostname??=""),[1,2,3,4,5,6].includes(this.#o)&&[8,9].includes(t)&&(this.#t.pathname??=this.#g?"/":""),[1,2,3,4,5,6,7].includes(this.#o)&&t===9&&(this.#t.search??="")),this.#R(t,r);}#R(t,r){this.#o=t,this.#l=this.#e+r,this.#e+=r,this.#s=0;}#b(){this.#e=this.#l,this.#s=0;}#u(t){this.#b(),this.#o=t;}#m(t){return t<0&&(t=this.#n.length-t),t<this.#n.length?this.#n[t]:this.#n[this.#n.length-1]}#a(t,r){let n=this.#m(t);return n.value===r&&(n.type==="CHAR"||n.type==="ESCAPED_CHAR"||n.type==="INVALID_CHAR")}#P(){return this.#a(this.#e,":")}#E(){return this.#a(this.#e+1,"/")&&this.#a(this.#e+2,"/")}#S(){return this.#a(this.#e,"@")}#O(){return this.#a(this.#e,":")}#k(){return this.#a(this.#e,":")}#x(){return this.#a(this.#e,"/")}#h(){if(this.#a(this.#e,"?"))return true;if(this.#n[this.#e].value!=="?")return false;let t=this.#m(this.#e-1);return t.type!=="NAME"&&t.type!=="REGEX"&&t.type!=="CLOSE"&&t.type!=="ASTERISK"}#f(){return this.#a(this.#e,"#")}#T(){return this.#n[this.#e].type=="OPEN"}#A(){return this.#n[this.#e].type=="CLOSE"}#y(){return this.#a(this.#e,"[")}#w(){return this.#a(this.#e,"]")}#c(){let t=this.#n[this.#e],r=this.#m(this.#l).index;return this.#i.substring(r,t.index)}#C(){let t={};Object.assign(t,b),t.encodePart=w;let r=q(this.#c(),void 0,t);this.#g=U(r);}};a(C,"Parser");var V=["protocol","username","password","hostname","port","pathname","search","hash"],O="*";function Se(e,t){if(typeof e!="string")throw new TypeError("parameter 1 is not of type 'string'.");let r=new URL(e,t);return {protocol:r.protocol.substring(0,r.protocol.length-1),username:r.username,password:r.password,hostname:r.hostname,port:r.port,pathname:r.pathname,search:r.search!==""?r.search.substring(1,r.search.length):void 0,hash:r.hash!==""?r.hash.substring(1,r.hash.length):void 0}}a(Se,"extractValues");function R(e,t){return t?I(e):e}a(R,"processBaseURLString");function L(e,t,r){let n;if(typeof t.baseURL=="string")try{n=new URL(t.baseURL),t.protocol===void 0&&(e.protocol=R(n.protocol.substring(0,n.protocol.length-1),r)),!r&&t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.username===void 0&&(e.username=R(n.username,r)),!r&&t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.username===void 0&&t.password===void 0&&(e.password=R(n.password,r)),t.protocol===void 0&&t.hostname===void 0&&(e.hostname=R(n.hostname,r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&(e.port=R(n.port,r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.pathname===void 0&&(e.pathname=R(n.pathname,r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.pathname===void 0&&t.search===void 0&&(e.search=R(n.search.substring(1,n.search.length),r)),t.protocol===void 0&&t.hostname===void 0&&t.port===void 0&&t.pathname===void 0&&t.search===void 0&&t.hash===void 0&&(e.hash=R(n.hash.substring(1,n.hash.length),r));}catch{throw new TypeError(`invalid baseURL '${t.baseURL}'.`)}if(typeof t.protocol=="string"&&(e.protocol=fe(t.protocol,r)),typeof t.username=="string"&&(e.username=oe(t.username,r)),typeof t.password=="string"&&(e.password=ae(t.password,r)),typeof t.hostname=="string"&&(e.hostname=ie(t.hostname,r)),typeof t.port=="string"&&(e.port=le(t.port,e.protocol,r)),typeof t.pathname=="string"){if(e.pathname=t.pathname,n&&!ee(e.pathname,r)){let c=n.pathname.lastIndexOf("/");c>=0&&(e.pathname=R(n.pathname.substring(0,c+1),r)+e.pathname);}e.pathname=ce(e.pathname,e.protocol,r);}return typeof t.search=="string"&&(e.search=se(t.search,r)),typeof t.hash=="string"&&(e.hash=ne(t.hash,r)),e}a(L,"applyInit");function I(e){return e.replace(/([+*?:{}()\\])/g,"\\$1")}a(I,"escapePatternString");function Te(e){return e.replace(/([.+*?^${}()[\]|/\\])/g,"\\$1")}a(Te,"escapeRegexpString");function Ae(e,t){t.delimiter??="/#?",t.prefixes??="./",t.sensitive??=false,t.strict??=false,t.end??=true,t.start??=true,t.endsWith="";let r=".*",n=`[^${Te(t.delimiter)}]+?`,c=/[$_\u200C\u200D\p{ID_Continue}]/u,l="";for(let f=0;f<e.length;++f){let s=e[f];if(s.type===3){if(s.modifier===3){l+=I(s.value);continue}l+=`{${I(s.value)}}${T(s.modifier)}`;continue}let i=s.hasCustomName(),o=!!s.suffix.length||!!s.prefix.length&&(s.prefix.length!==1||!t.prefixes.includes(s.prefix)),h=f>0?e[f-1]:null,p=f<e.length-1?e[f+1]:null;if(!o&&i&&s.type===1&&s.modifier===3&&p&&!p.prefix.length&&!p.suffix.length)if(p.type===3){let A=p.value.length>0?p.value[0]:"";o=c.test(A);}else o=!p.hasCustomName();if(!o&&!s.prefix.length&&h&&h.type===3){let A=h.value[h.value.length-1];o=t.prefixes.includes(A);}o&&(l+="{"),l+=I(s.prefix),i&&(l+=`:${s.name}`),s.type===2?l+=`(${s.value})`:s.type===1?i||(l+=`(${n})`):s.type===0&&(!i&&(!h||h.type===3||h.modifier!==3||o||s.prefix!=="")?l+="*":l+=`(${r})`),s.type===1&&i&&s.suffix.length&&c.test(s.suffix[0])&&(l+="\\"),l+=I(s.suffix),o&&(l+="}"),s.modifier!==3&&(l+=T(s.modifier));}return l}a(Ae,"partsToPattern");var Y=class{#i;#n={};#t={};#e={};#s={};#l=false;constructor(t={},r,n){try{let c;if(typeof r=="string"?c=r:n=r,typeof t=="string"){let i=new C(t);if(i.parse(),t=i.result,c===void 0&&typeof t.protocol!="string")throw new TypeError("A base URL must be provided for a relative constructor string.");t.baseURL=c;}else {if(!t||typeof t!="object")throw new TypeError("parameter 1 is not of type 'string' and cannot convert to dictionary.");if(c)throw new TypeError("parameter 1 is not of type 'string'.")}typeof n>"u"&&(n={ignoreCase:!1});let l={ignoreCase:n.ignoreCase===!0},f={pathname:O,protocol:O,username:O,password:O,hostname:O,port:O,search:O,hash:O};this.#i=L(f,t,!0),z(this.#i.protocol)===this.#i.port&&(this.#i.port="");let s;for(s of V){if(!(s in this.#i))continue;let i={},o=this.#i[s];switch(this.#t[s]=[],s){case "protocol":Object.assign(i,b),i.encodePart=w;break;case "username":Object.assign(i,b),i.encodePart=he;break;case "password":Object.assign(i,b),i.encodePart=ue;break;case "hostname":Object.assign(i,J),_(o)?i.encodePart=K:i.encodePart=j;break;case "port":Object.assign(i,b),i.encodePart=G;break;case "pathname":U(this.#n.protocol)?(Object.assign(i,Q,l),i.encodePart=de):(Object.assign(i,b,l),i.encodePart=pe);break;case "search":Object.assign(i,b,l),i.encodePart=ge;break;case "hash":Object.assign(i,b,l),i.encodePart=me;break}try{this.#s[s]=F(o,i),this.#n[s]=W(this.#s[s],this.#t[s],i),this.#e[s]=Ae(this.#s[s],i),this.#l=this.#l||this.#s[s].some(h=>h.type===2);}catch{throw new TypeError(`invalid ${s} pattern '${this.#i[s]}'.`)}}}catch(c){throw new TypeError(`Failed to construct 'URLPattern': ${c.message}`)}}get[Symbol.toStringTag](){return "URLPattern"}test(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return false;try{typeof t=="object"?n=L(n,t,!1):n=L(n,Se(t,r),!1);}catch{return false}let c;for(c of V)if(!this.#n[c].exec(n[c]))return false;return true}exec(t={},r){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if(typeof t!="string"&&r)throw new TypeError("parameter 1 is not of type 'string'.");if(typeof t>"u")return;try{typeof t=="object"?n=L(n,t,!1):n=L(n,Se(t,r),!1);}catch{return null}let c={};r?c.inputs=[t,r]:c.inputs=[t];let l;for(l of V){let f=this.#n[l].exec(n[l]);if(!f)return null;let s={};for(let[i,o]of this.#t[l].entries())if(typeof o=="string"||typeof o=="number"){let h=f[i+1];s[o]=h;}c[l]={input:n[l]??"",groups:s};}return c}static compareComponent(t,r,n){let c=a((i,o)=>{for(let h of ["type","modifier","prefix","value","suffix"]){if(i[h]<o[h])return -1;if(i[h]===o[h])continue;return 1}return 0},"comparePart"),l=new P(3,"","","","",3),f=new P(0,"","","","",3),s=a((i,o)=>{let h=0;for(;h<Math.min(i.length,o.length);++h){let p=c(i[h],o[h]);if(p)return p}return i.length===o.length?0:c(i[h]??l,o[h]??l)},"comparePartList");return !r.#e[t]&&!n.#e[t]?0:r.#e[t]&&!n.#e[t]?s(r.#s[t],[f]):!r.#e[t]&&n.#e[t]?s([f],n.#s[t]):s(r.#s[t],n.#s[t])}get protocol(){return this.#e.protocol}get username(){return this.#e.username}get password(){return this.#e.password}get hostname(){return this.#e.hostname}get port(){return this.#e.port}get pathname(){return this.#e.pathname}get search(){return this.#e.search}get hash(){return this.#e.hash}get hasRegExpGroups(){return this.#l}};a(Y,"URLPattern");
513
+
514
+ function parseUrl(s) {
515
+ return new URL(typeof s === "string" ? s : s.href);
516
+ }
517
+ function depaginatePathname(url, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
518
+ const newUrl = new URL(url.toString());
519
+ newUrl.pathname = newUrl.pathname.replace(pathnamePaginationSelector, "/");
520
+ return newUrl;
521
+ }
522
+ function getPaginationLinks(doc = document, url = location.href, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
523
+ const baseUrl = depaginatePathname(parseUrl(url), pathnamePaginationSelector);
524
+ const pathnameStrict = doc instanceof Document;
525
+ const host = doc.baseURI || baseUrl.origin;
526
+ const urlPattern = new Y({
527
+ pathname: pathnameStrict ? `${baseUrl.pathname}*` : "*",
528
+ hostname: baseUrl.hostname
529
+ });
530
+ const pageLinks = [...doc.querySelectorAll("a[href]")].map((a) => a.href).filter((h) => URL.canParse(h));
531
+ return pageLinks.filter((h) => {
532
+ return urlPattern.test(new URL(h, host));
533
+ });
534
+ }
535
+ function upgradePathname(curr, links, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
536
+ if (pathnamePaginationSelector.test(curr.pathname) || links.length < 1) return curr;
537
+ const linksDepaginated = links.map(
538
+ (l) => depaginatePathname(l, pathnamePaginationSelector)
539
+ );
540
+ if (linksDepaginated.some((l) => l.pathname === curr.pathname)) return curr;
541
+ const last = linksDepaginated.at(-1);
542
+ if (last.pathname !== curr.pathname) curr.pathname = last.pathname;
543
+ return curr;
544
+ }
716
545
 
717
- class PaginationStrategyDataParams extends PaginationStrategy {
718
- getPaginationLast() {
719
- const links = this.getPaginationElement()?.querySelectorAll(this.dataparamSelector);
720
- const pages = Array.from(links || [], (l) => {
721
- const p = l.getAttribute("data-parameters");
722
- const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
723
- return parseInt(v);
546
+ class PaginationStrategy {
547
+ doc = document;
548
+ url;
549
+ paginationSelector = ".pagination";
550
+ searchParamSelector = "page";
551
+ static _pathnameSelector = /\/(page\/)?\d+\/?$/;
552
+ pathnameSelector = /\/(\d+)\/?$/;
553
+ dataparamSelector = "[data-parameters *= from]";
554
+ overwritePaginationLast;
555
+ offsetMin = 1;
556
+ constructor(options) {
557
+ if (options) {
558
+ Object.entries(options).forEach(([k, v]) => {
559
+ Object.assign(this, { [k]: v });
724
560
  });
725
- const lastPage = Math.max(...pages, this.offsetMin);
726
- if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
727
- return lastPage;
728
561
  }
729
- getPaginationOffset() {
730
- const link = this.getPaginationElement()?.querySelector(
731
- ".prev[data-parameters *= from], .prev [data-parameters *= from]"
732
- );
733
- if (!link) return this.offsetMin;
734
- const p = link.getAttribute("data-parameters");
562
+ this.url = parseUrl(options?.url || this.doc.URL);
563
+ }
564
+ getPaginationElement() {
565
+ return this.doc.querySelector(this.paginationSelector);
566
+ }
567
+ get hasPagination() {
568
+ return !!this.getPaginationElement();
569
+ }
570
+ getPaginationOffset() {
571
+ return this.offsetMin;
572
+ }
573
+ getPaginationLast() {
574
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(1);
575
+ return 1;
576
+ }
577
+ getPaginationUrlGenerator() {
578
+ return (_) => this.url.href;
579
+ }
580
+ }
581
+
582
+ function formatTimeToHHMMSS(timeStr) {
583
+ const pad = (num) => num.toString().padStart(2, "0");
584
+ const h = timeStr.match(/(\d+)\s*h/)?.[1] || "0";
585
+ const m = timeStr.match(/(\d+)\s*mi?n/)?.[1] || "0";
586
+ const s = timeStr.match(/(\d+)\s*sec/)?.[1] || "0";
587
+ return `${pad(+h)}:${pad(+m)}:${pad(+s)}`;
588
+ }
589
+ function timeToSeconds(timeStr) {
590
+ const normalized = /[a-zA-Z]/.test(timeStr) ? formatTimeToHHMMSS(timeStr) : timeStr;
591
+ return normalized.split(":").reverse().reduce((total, unit, index) => total + parseInt(unit, 10) * 60 ** index, 0);
592
+ }
593
+
594
+ function parseDataParams(str) {
595
+ const paramsStr = decodeURI(str.trim()).split(";");
596
+ return paramsStr.reduce(
597
+ (acc, s) => {
598
+ const parsed = s.match(/([+\w]+):([\w\- ]+)?/);
599
+ if (parsed) {
600
+ const [, key, value] = parsed;
601
+ if (value) {
602
+ key.split("+").forEach((p) => {
603
+ acc[p] = value;
604
+ });
605
+ }
606
+ }
607
+ return acc;
608
+ },
609
+ {}
610
+ );
611
+ }
612
+
613
+ class PaginationStrategyDataParams extends PaginationStrategy {
614
+ getPaginationLast() {
615
+ const links = this.getPaginationElement()?.querySelectorAll(this.dataparamSelector);
616
+ const pages = Array.from(links || [], (l) => {
617
+ const p = l.getAttribute("data-parameters");
735
618
  const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
736
619
  return parseInt(v);
737
- }
738
- getPaginationUrlGenerator() {
739
- const url = new URL(this.url.href);
740
- const parametersElement = this.getPaginationElement()?.querySelector(
741
- "a[data-block-id][data-parameters]"
742
- );
743
- const block_id = parametersElement?.getAttribute("data-block-id") || "";
744
- const parameters = parseDataParams(
745
- parametersElement?.getAttribute("data-parameters") || ""
746
- );
747
- const attrs = {
748
- block_id,
749
- function: "get_block",
750
- mode: "async",
751
- ...parameters
752
- };
620
+ });
621
+ const lastPage = Math.max(...pages, this.offsetMin);
622
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
623
+ return lastPage;
624
+ }
625
+ getPaginationOffset() {
626
+ const link = this.getPaginationElement()?.querySelector(
627
+ ".prev[data-parameters *= from], .prev [data-parameters *= from]"
628
+ );
629
+ if (!link) return this.offsetMin;
630
+ const p = link.getAttribute("data-parameters");
631
+ const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
632
+ return parseInt(v);
633
+ }
634
+ getPaginationUrlGenerator() {
635
+ const url = new URL(this.url.href);
636
+ const parametersElement = this.getPaginationElement()?.querySelector(
637
+ "a[data-block-id][data-parameters]"
638
+ );
639
+ const block_id = parametersElement?.getAttribute("data-block-id") || "";
640
+ const parameters = parseDataParams(
641
+ parametersElement?.getAttribute("data-parameters") || ""
642
+ );
643
+ const attrs = {
644
+ block_id,
645
+ function: "get_block",
646
+ mode: "async",
647
+ ...parameters
648
+ };
649
+ Object.keys(attrs).forEach((k) => {
650
+ url.searchParams.set(k, attrs[k]);
651
+ });
652
+ const paginationUrlGenerator = (n) => {
753
653
  Object.keys(attrs).forEach((k) => {
754
- url.searchParams.set(k, attrs[k]);
654
+ k.includes("from") && url.searchParams.set(k, n.toString());
755
655
  });
756
- const paginationUrlGenerator = (n) => {
757
- Object.keys(attrs).forEach((k) => {
758
- k.includes("from") && url.searchParams.set(k, n.toString());
759
- });
760
- url.searchParams.set("_", Date.now().toString());
761
- return url.href;
762
- };
763
- return paginationUrlGenerator;
764
- }
765
- static testLinks(doc = document) {
766
- const dataParamLinks = Array.from(
767
- doc.querySelectorAll("[data-parameters *= from]")
768
- );
769
- return dataParamLinks.length > 0;
770
- }
656
+ url.searchParams.set("_", Date.now().toString());
657
+ return url.href;
658
+ };
659
+ return paginationUrlGenerator;
771
660
  }
661
+ static testLinks(doc = document) {
662
+ const dataParamLinks = Array.from(
663
+ doc.querySelectorAll("[data-parameters *= from]")
664
+ );
665
+ return dataParamLinks.length > 0;
666
+ }
667
+ }
772
668
 
773
- class PaginationStrategyPathnameParams extends PaginationStrategy {
774
- extractPage = (a) => {
775
- const href = typeof a === "string" ? a : a.href;
776
- const { pathname } = new URL(href, this.doc.baseURI || this.url.origin);
777
- return parseInt(
778
- pathname.match(this.pathnameSelector)?.pop() || this.offsetMin.toString()
779
- );
780
- };
781
- static checkLink(link, pathnameSelector = PaginationStrategy._pathnameSelector) {
782
- return pathnameSelector.test(link.pathname);
783
- }
784
- static testLinks(links, options) {
785
- const result = links.some(
669
+ class PaginationStrategyPathnameParams extends PaginationStrategy {
670
+ extractPage = (a) => {
671
+ const href = typeof a === "string" ? a : a.href;
672
+ const { pathname } = new URL(href, this.doc.baseURI || this.url.origin);
673
+ return parseInt(
674
+ pathname.match(this.pathnameSelector)?.pop() || this.offsetMin.toString()
675
+ );
676
+ };
677
+ static checkLink(link, pathnameSelector = PaginationStrategy._pathnameSelector) {
678
+ return pathnameSelector.test(link.pathname);
679
+ }
680
+ static testLinks(links, options) {
681
+ const result = links.some(
682
+ (h) => PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector)
683
+ );
684
+ if (result) {
685
+ const pathnamesMatched = links.filter(
786
686
  (h) => PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector)
787
687
  );
788
- if (result) {
789
- const pathnamesMatched = links.filter(
790
- (h) => PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector)
791
- );
792
- options.url = upgradePathname(
793
- parseUrl(options.url),
794
- pathnamesMatched
795
- );
796
- }
797
- return result;
798
- }
799
- getPaginationLast() {
800
- const links = getPaginationLinks(
801
- this.getPaginationElement() || document,
802
- this.url.href,
803
- this.pathnameSelector
688
+ options.url = upgradePathname(
689
+ parseUrl(options.url),
690
+ pathnamesMatched
804
691
  );
805
- const pages = Array.from(links, this.extractPage);
806
- const lastPage = Math.max(...pages, this.offsetMin);
807
- if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
808
- return lastPage;
809
- }
810
- getPaginationOffset() {
811
- return this.extractPage(this.url.href);
812
- }
813
- getPaginationUrlGenerator(url_ = this.url) {
814
- const url = new URL(url_.href);
815
- const pathnameSelectorPlaceholder = this.pathnameSelector.toString().replace(/[/|\\|$|?|(|)]+/g, "/");
816
- if (!this.pathnameSelector.test(url.pathname)) {
817
- url.pathname = url.pathname.concat(pathnameSelectorPlaceholder.replace(/d\+/, this.offsetMin.toString())).replace(/\/{2,}/g, "/");
818
- }
819
- const paginationUrlGenerator = (offset) => {
820
- url.pathname = url.pathname.replace(
821
- this.pathnameSelector,
822
- pathnameSelectorPlaceholder.replace(/d\+/, offset.toString())
823
- );
824
- return url.href;
825
- };
826
- return paginationUrlGenerator;
827
692
  }
693
+ return result;
828
694
  }
829
-
830
- class PaginationStrategySearchParams extends PaginationStrategy {
831
- extractPage = (a) => {
832
- const href = typeof a === "string" ? a : a.href;
833
- const p = new URL(href).searchParams.get(this.searchParamSelector);
834
- return parseInt(p) || this.offsetMin;
835
- };
836
- getPaginationLast() {
837
- const links = getPaginationLinks(
838
- this.getPaginationElement() || document,
839
- this.url.href
840
- ).filter(
841
- (h) => PaginationStrategySearchParams.checkLink(new URL(h), this.searchParamSelector)
842
- );
843
- const pages = links.map(this.extractPage);
844
- const lastPage = Math.max(...pages, this.offsetMin);
845
- if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
846
- return lastPage;
847
- }
848
- getPaginationOffset() {
849
- if (this.doc === document) {
850
- return this.extractPage(this.url);
851
- }
852
- const link = this.getPaginationElement()?.querySelector(
853
- `a.active[href *= "${this.searchParamSelector}="]`
854
- );
855
- return this.extractPage(link);
856
- }
857
- getPaginationUrlGenerator() {
858
- const url = new URL(this.url.href);
859
- const paginationUrlGenerator = (offset) => {
860
- url.searchParams.set(this.searchParamSelector, offset.toString());
861
- return url.href;
862
- };
863
- return paginationUrlGenerator;
864
- }
865
- static checkLink(link, searchParamSelector) {
866
- const searchParamSelectors = ["page", "p"];
867
- if (searchParamSelector) searchParamSelectors.push(searchParamSelector);
868
- return searchParamSelectors.some((p) => link.searchParams.get(p) !== null);
869
- }
870
- static testLinks(links, searchParamSelector) {
871
- return links.some(
872
- (h) => PaginationStrategySearchParams.checkLink(h, searchParamSelector)
695
+ getPaginationLast() {
696
+ const links = getPaginationLinks(
697
+ this.getPaginationElement() || document,
698
+ this.url.href,
699
+ this.pathnameSelector
700
+ );
701
+ const pages = Array.from(links, this.extractPage);
702
+ const lastPage = Math.max(...pages, this.offsetMin);
703
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
704
+ return lastPage;
705
+ }
706
+ getPaginationOffset() {
707
+ return this.extractPage(this.url.href);
708
+ }
709
+ getPaginationUrlGenerator(url_ = this.url) {
710
+ const url = new URL(url_.href);
711
+ const pathnameSelectorPlaceholder = this.pathnameSelector.toString().replace(/[/|\\|$|?|(|)]+/g, "/");
712
+ if (!this.pathnameSelector.test(url.pathname)) {
713
+ url.pathname = url.pathname.concat(pathnameSelectorPlaceholder.replace(/d\+/, this.offsetMin.toString())).replace(/\/{2,}/g, "/");
714
+ }
715
+ const paginationUrlGenerator = (offset) => {
716
+ url.pathname = url.pathname.replace(
717
+ this.pathnameSelector,
718
+ pathnameSelectorPlaceholder.replace(/d\+/, offset.toString())
873
719
  );
874
- }
720
+ return url.href;
721
+ };
722
+ return paginationUrlGenerator;
875
723
  }
724
+ }
876
725
 
877
- function getPaginationStrategy(options) {
878
- const _paginationStrategy = new PaginationStrategy(options);
879
- const pagination = _paginationStrategy.getPaginationElement();
880
- Object.assign(options, { ..._paginationStrategy });
881
- const { url, searchParamSelector } = options;
882
- if (!pagination) {
883
- return _paginationStrategy;
884
- }
885
- if (typeof options.getPaginationUrlGenerator === "function") {
886
- return new PaginationStrategy(options);
726
+ class PaginationStrategySearchParams extends PaginationStrategy {
727
+ extractPage = (a) => {
728
+ const href = typeof a === "string" ? a : a.href;
729
+ const p = new URL(href).searchParams.get(this.searchParamSelector);
730
+ return parseInt(p) || this.offsetMin;
731
+ };
732
+ getPaginationLast() {
733
+ const links = getPaginationLinks(
734
+ this.getPaginationElement() || document,
735
+ this.url.href
736
+ ).filter(
737
+ (h) => PaginationStrategySearchParams.checkLink(new URL(h), this.searchParamSelector)
738
+ );
739
+ const pages = links.map(this.extractPage);
740
+ const lastPage = Math.max(...pages, this.offsetMin);
741
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
742
+ return lastPage;
743
+ }
744
+ getPaginationOffset() {
745
+ if (this.doc === document) {
746
+ return this.extractPage(this.url);
887
747
  }
888
- const pageLinks = getPaginationLinks(pagination, url).map((l) => new URL(l));
889
- const selectStrategy = () => {
890
- if (PaginationStrategyDataParams.testLinks(pagination)) {
891
- return PaginationStrategyDataParams;
892
- }
893
- if (PaginationStrategySearchParams.testLinks(pageLinks, searchParamSelector)) {
894
- return PaginationStrategySearchParams;
895
- }
896
- if (PaginationStrategyPathnameParams.testLinks(pageLinks, options)) {
897
- return PaginationStrategyPathnameParams;
898
- }
899
- console.error("Found No Strategy");
900
- return PaginationStrategy;
748
+ const link = this.getPaginationElement()?.querySelector(
749
+ `a.active[href *= "${this.searchParamSelector}="]`
750
+ );
751
+ return this.extractPage(link);
752
+ }
753
+ getPaginationUrlGenerator() {
754
+ const url = new URL(this.url.href);
755
+ const paginationUrlGenerator = (offset) => {
756
+ url.searchParams.set(this.searchParamSelector, offset.toString());
757
+ return url.href;
901
758
  };
902
- const PaginationStrategyConstructor = selectStrategy();
903
- const paginationStrategy = new PaginationStrategyConstructor(options);
904
- return paginationStrategy;
759
+ return paginationUrlGenerator;
760
+ }
761
+ static checkLink(link, searchParamSelector) {
762
+ const searchParamSelectors = ["page", "p"];
763
+ if (searchParamSelector) searchParamSelectors.push(searchParamSelector);
764
+ return searchParamSelectors.some((p) => link.searchParams.get(p) !== null);
765
+ }
766
+ static testLinks(links, searchParamSelector) {
767
+ return links.some(
768
+ (h) => PaginationStrategySearchParams.checkLink(h, searchParamSelector)
769
+ );
905
770
  }
771
+ }
906
772
 
907
- class RulesGlobal {
908
- delay;
909
- customGenerator;
910
- getThumbUrl(thumb) {
911
- return (thumb.querySelector("a[href]") || thumb).href;
773
+ function getPaginationStrategy(options) {
774
+ const _paginationStrategy = new PaginationStrategy(options);
775
+ const pagination = _paginationStrategy.getPaginationElement();
776
+ Object.assign(options, { ..._paginationStrategy });
777
+ const { url, searchParamSelector } = options;
778
+ if (!pagination) {
779
+ return _paginationStrategy;
780
+ }
781
+ if (typeof options.getPaginationUrlGenerator === "function") {
782
+ return new PaginationStrategy(options);
783
+ }
784
+ const pageLinks = getPaginationLinks(pagination, url).map((l) => new URL(l));
785
+ const selectStrategy = () => {
786
+ if (PaginationStrategyDataParams.testLinks(pagination)) {
787
+ return PaginationStrategyDataParams;
912
788
  }
913
- titleSelector;
914
- uploaderSelector;
915
- durationSelector;
916
- customThumbDataSelectors;
917
- getThumbDataStrategy = "default";
918
- getThumbDataCallback;
919
- getThumbData(thumb) {
920
- let { titleSelector, uploaderSelector, durationSelector } = this;
921
- const thumbData = { title: "" };
922
- if (this.getThumbDataStrategy === "auto-text") {
923
- const text = sanitizeStr(thumb.innerText);
924
- thumbData.title = text;
925
- thumbData.duration = timeToSeconds(text.match(/\d+m|\d+:\d+/)?.[0] || "");
926
- return thumbData;
927
- }
928
- if (this.getThumbDataStrategy === "auto-select") {
929
- titleSelector = "[class *= title],[title]";
930
- durationSelector = "[class *= duration]";
931
- uploaderSelector = "[class *= uploader], [class *= user], [class *= name]";
932
- }
933
- if (this.getThumbDataStrategy === "auto-select") {
934
- const selected = querySelectorLast(thumb, titleSelector);
935
- if (selected) {
936
- thumbData.title = sanitizeStr(selected.innerText);
937
- } else {
938
- thumbData.title = sanitizeStr(thumb.innerText);
939
- }
940
- } else {
941
- thumbData.title = querySelectorText(thumb, titleSelector);
942
- }
943
- if (uploaderSelector) {
944
- const uploader = querySelectorText(thumb, uploaderSelector);
945
- thumbData.title = `${thumbData.title} user:${uploader}`;
789
+ if (PaginationStrategySearchParams.testLinks(pageLinks, searchParamSelector)) {
790
+ return PaginationStrategySearchParams;
791
+ }
792
+ if (PaginationStrategyPathnameParams.testLinks(pageLinks, options)) {
793
+ return PaginationStrategyPathnameParams;
794
+ }
795
+ console.error("Found No Strategy");
796
+ return PaginationStrategy;
797
+ };
798
+ const PaginationStrategyConstructor = selectStrategy();
799
+ const paginationStrategy = new PaginationStrategyConstructor(options);
800
+ return paginationStrategy;
801
+ }
802
+
803
+ const DefaultScheme = [
804
+ {
805
+ title: "Text Filter",
806
+ collapsed: true,
807
+ content: [
808
+ { filterExclude: false, label: "exclude" },
809
+ {
810
+ filterExcludeWords: "",
811
+ label: "keywords",
812
+ watch: "filterExclude",
813
+ placeholder: "word, f:full_word, r:RegEx..."
814
+ },
815
+ { filterInclude: false, label: "include" },
816
+ {
817
+ filterIncludeWords: "",
818
+ label: "keywords",
819
+ watch: "filterInclude",
820
+ placeholder: "word, f:full_word, r:RegEx..."
946
821
  }
947
- if (durationSelector) {
948
- const duration = timeToSeconds(querySelectorText(thumb, durationSelector));
949
- thumbData.duration = duration;
822
+ ]
823
+ },
824
+ {
825
+ title: "Duration Filter",
826
+ collapsed: true,
827
+ content: [
828
+ { filterDuration: false, label: "enable" },
829
+ {
830
+ filterDurationFrom: 0,
831
+ watch: "filterDuration",
832
+ label: "from",
833
+ type: "time"
834
+ },
835
+ {
836
+ filterDurationTo: 600,
837
+ watch: "filterDuration",
838
+ label: "to",
839
+ type: "time"
950
840
  }
951
- this.getThumbDataCallback?.(thumb, thumbData);
952
- function getCustomThumbData(selector, type) {
953
- if (type === "boolean") {
954
- return !!thumb.querySelector(selector);
841
+ ]
842
+ },
843
+ {
844
+ title: "Sort By",
845
+ content: [
846
+ {
847
+ "sort by views": () => {
955
848
  }
956
- if (type === "string") {
957
- return querySelectorText(thumb, selector);
849
+ },
850
+ {
851
+ "sort by duration": () => {
958
852
  }
959
- return Number.parseInt(querySelectorText(thumb, selector));
960
853
  }
961
- if (this.customThumbDataSelectors) {
962
- Object.entries(this.customThumbDataSelectors).forEach(([name, x]) => {
963
- const data = getCustomThumbData(x.selector, x.type);
964
- Object.assign(thumbData, { [name]: data });
965
- });
854
+ ]
855
+ },
856
+ {
857
+ title: "Privacy Filter",
858
+ content: [
859
+ { filterPrivate: false, label: "private" },
860
+ { filterPublic: false, label: "public" },
861
+ { "check access 🔓": () => {
862
+ } }
863
+ ]
864
+ },
865
+ {
866
+ title: "Advanced",
867
+ content: [
868
+ {
869
+ infiniteScrollEnabled: true,
870
+ label: "infinite scroll"
871
+ },
872
+ {
873
+ autoScroll: false,
874
+ label: "auto scroll"
875
+ },
876
+ {
877
+ delay: 250,
878
+ label: "scroll delay"
879
+ },
880
+ {
881
+ writeHistory: false,
882
+ label: "write history"
966
883
  }
967
- return thumbData;
968
- }
969
- getThumbImgDataAttrSelector;
970
- getThumbImgDataAttrDelete;
971
- getThumbImgDataStrategy = "default";
972
- getThumbImgData(thumb) {
973
- const result = {};
974
- if (this.getThumbImgDataStrategy === "auto") {
975
- const img = thumb.querySelector("img");
976
- if (!img) return {};
977
- result.img = img;
978
- if (typeof this.getThumbImgDataAttrSelector === "function") {
979
- result.imgSrc = this.getThumbImgDataAttrSelector(img);
980
- } else {
981
- const possibleAttrs = this.getThumbImgDataAttrSelector ? [this.getThumbImgDataAttrSelector].flat() : ["data-src", "src"];
982
- for (const attr of possibleAttrs) {
983
- const imgSrc = img.getAttribute(attr);
984
- if (imgSrc) {
985
- result.imgSrc = imgSrc;
986
- img.removeAttribute(attr);
987
- break;
988
- }
989
- }
990
- }
991
- if (this.getThumbImgDataAttrDelete) {
992
- if (this.getThumbImgDataAttrDelete === "auto") {
993
- removeClassesAndDataAttributes(img, "lazy");
994
- } else {
995
- if (this.getThumbImgDataAttrDelete.startsWith(".")) {
996
- img.classList.remove(this.getThumbImgDataAttrDelete.slice(1));
997
- } else {
998
- img.removeAttribute(this.getThumbImgDataAttrDelete);
999
- }
1000
- }
1001
- if (img.src.includes("data:image")) {
1002
- result.img.src = "";
1003
- }
1004
- if (img.complete && img.naturalWidth > 0) {
1005
- return {};
1006
- }
1007
- }
884
+ ]
885
+ },
886
+ {
887
+ title: "Badge",
888
+ content: [
889
+ {
890
+ text: "return `${state.$paginationOffset}/${state.$paginationLast}`",
891
+ vif: "return state.$paginationLast > 1"
1008
892
  }
1009
- return result;
1010
- }
1011
- containerSelector = ".container";
1012
- containerSelectorLast;
1013
- intersectionObservableSelector;
1014
- get intersectionObservable() {
1015
- return this.intersectionObservableSelector && document.querySelector(this.intersectionObservableSelector);
1016
- }
1017
- get observable() {
1018
- return this.intersectionObservable || this.paginationStrategy.getPaginationElement();
893
+ ]
894
+ }
895
+ ];
896
+
897
+ const StoreStateDefault = {
898
+ enabled: true,
899
+ collapsed: false,
900
+ darkmode: true,
901
+ $paginationLast: 1,
902
+ $paginationOffset: 1
903
+ };
904
+
905
+ class RulesGlobal {
906
+ delay;
907
+ customGenerator;
908
+ getThumbUrl(thumb) {
909
+ return (thumb.querySelector("a[href]") || thumb).href;
910
+ }
911
+ titleSelector;
912
+ uploaderSelector;
913
+ durationSelector;
914
+ customThumbDataSelectors;
915
+ getThumbDataStrategy = "default";
916
+ getThumbDataCallback;
917
+ getThumbData(thumb) {
918
+ let { titleSelector, uploaderSelector, durationSelector } = this;
919
+ const thumbData = { title: "" };
920
+ if (this.getThumbDataStrategy === "auto-text") {
921
+ const text = sanitizeStr(thumb.innerText);
922
+ thumbData.title = text;
923
+ thumbData.duration = timeToSeconds(text.match(/\d+m|\d+:\d+/)?.[0] || "");
924
+ return thumbData;
1019
925
  }
1020
- get container() {
1021
- if (typeof this.containerSelectorLast === "string") {
1022
- return querySelectorLast(document, this.containerSelectorLast);
1023
- }
1024
- if (typeof this.containerSelector === "string") {
1025
- return document.querySelector(this.containerSelector);
1026
- }
1027
- return this.containerSelector();
926
+ if (this.getThumbDataStrategy === "auto-select") {
927
+ titleSelector = "[class *= title],[title]";
928
+ durationSelector = "[class *= duration]";
929
+ uploaderSelector = "[class *= uploader], [class *= user], [class *= name]";
1028
930
  }
1029
- thumbsSelector = ".thumb";
1030
- getThumbsStrategy = "default";
1031
- getThumbsTransform;
1032
- getThumbs(html) {
1033
- if (!html) return [];
1034
- let thumbs;
1035
- if (this.getThumbsStrategy === "auto") {
1036
- if (typeof this.containerSelector !== "string") return [];
1037
- const container = html.querySelector(this.containerSelector);
1038
- thumbs = [...container?.children || []];
1039
- }
1040
- thumbs = Array.from(html.querySelectorAll(this.thumbsSelector));
1041
- if (typeof this.getThumbsTransform === "function") {
1042
- thumbs.forEach(this.getThumbsTransform);
931
+ if (this.getThumbDataStrategy === "auto-select") {
932
+ const selected = querySelectorLast(thumb, titleSelector);
933
+ if (selected) {
934
+ thumbData.title = sanitizeStr(selected.innerText);
935
+ } else {
936
+ thumbData.title = sanitizeStr(thumb.innerText);
1043
937
  }
1044
- return thumbs;
938
+ } else {
939
+ thumbData.title = querySelectorText(thumb, titleSelector);
1045
940
  }
1046
- paginationStrategyOptions = {};
1047
- paginationStrategy;
1048
- customDataSelectorFns = [
1049
- "filterInclude",
1050
- "filterExclude",
1051
- "filterDuration"
1052
- ];
1053
- animatePreview;
1054
- storeOptions;
1055
- createStore() {
1056
- const config = { ...StoreStateDefault, ...this.storeOptions };
1057
- this.store = new jabroniOutfit.JabronioStore(config);
1058
- return this.store;
941
+ if (uploaderSelector) {
942
+ const uploader = querySelectorText(thumb, uploaderSelector);
943
+ thumbData.title = `${thumbData.title} user:${uploader}`;
1059
944
  }
1060
- schemeOptions = [];
1061
- createGui() {
1062
- const scheme = jabroniOutfit.setupScheme(
1063
- this.schemeOptions,
1064
- DefaultScheme
1065
- );
1066
- this.gui = new jabroniOutfit.JabronioGUI(scheme, this.store);
1067
- return this.gui;
945
+ if (durationSelector) {
946
+ const duration = timeToSeconds(querySelectorText(thumb, durationSelector));
947
+ thumbData.duration = duration;
1068
948
  }
1069
- store;
1070
- gui;
1071
- dataManager;
1072
- infiniteScroller;
1073
- getPaginationData;
1074
- resetInfiniteScroller() {
1075
- this.infiniteScroller?.dispose();
1076
- if (!this.paginationStrategy.hasPagination) return;
1077
- this.infiniteScroller = InfiniteScroller.create(this);
1078
- }
1079
- gropeStrategy = "all-in-one";
1080
- gropeInit() {
1081
- if (!this.gropeStrategy) return;
1082
- if (this.gropeStrategy === "all-in-one") {
1083
- this.dataManager?.parseData(this.container, this.container);
949
+ this.getThumbDataCallback?.(thumb, thumbData);
950
+ function getCustomThumbData(selector, type) {
951
+ if (type === "boolean") {
952
+ return !!thumb.querySelector(selector);
1084
953
  }
1085
- if (this.gropeStrategy === "all-in-all") {
1086
- getCommonParents(this.getThumbs(document.body)).forEach((c) => {
1087
- this.dataManager.parseData(c, c, true);
1088
- });
954
+ if (type === "string") {
955
+ return querySelectorText(thumb, selector);
1089
956
  }
957
+ return Number.parseInt(querySelectorText(thumb, selector));
1090
958
  }
1091
- get isEmbedded() {
1092
- return window.self !== window.top;
959
+ if (this.customThumbDataSelectors) {
960
+ Object.entries(this.customThumbDataSelectors).forEach(([name, x]) => {
961
+ const data = getCustomThumbData(x.selector, x.type);
962
+ Object.assign(thumbData, { [name]: data });
963
+ });
1093
964
  }
1094
- setupStoreListeners() {
1095
- const eventsMap = {
1096
- "sort by duration": {
1097
- action: (direction2) => this.dataManager.sortBy("duration", direction2)
965
+ return thumbData;
966
+ }
967
+ getThumbImgDataAttrSelector;
968
+ getThumbImgDataAttrDelete;
969
+ getThumbImgDataStrategy = "default";
970
+ getThumbImgData(thumb) {
971
+ const result = {};
972
+ if (this.getThumbImgDataStrategy === "auto") {
973
+ const img = thumb.querySelector("img");
974
+ if (!img) return {};
975
+ result.img = img;
976
+ if (typeof this.getThumbImgDataAttrSelector === "function") {
977
+ result.imgSrc = this.getThumbImgDataAttrSelector(img);
978
+ } else {
979
+ const possibleAttrs = this.getThumbImgDataAttrSelector ? [this.getThumbImgDataAttrSelector].flat() : ["data-src", "src"];
980
+ for (const attr of possibleAttrs) {
981
+ const imgSrc = img.getAttribute(attr);
982
+ if (imgSrc) {
983
+ result.imgSrc = imgSrc;
984
+ img.removeAttribute(attr);
985
+ break;
986
+ }
1098
987
  }
1099
- };
1100
- let lastEvent;
1101
- let direction = true;
1102
- this.store.eventSubject.subscribe((event) => {
1103
- if (event === lastEvent) {
1104
- direction = !direction;
988
+ }
989
+ if (this.getThumbImgDataAttrDelete) {
990
+ if (this.getThumbImgDataAttrDelete === "auto") {
991
+ removeClassesAndDataAttributes(img, "lazy");
1105
992
  } else {
1106
- lastEvent = event;
1107
- direction = true;
993
+ if (this.getThumbImgDataAttrDelete.startsWith(".")) {
994
+ img.classList.remove(this.getThumbImgDataAttrDelete.slice(1));
995
+ } else {
996
+ img.removeAttribute(this.getThumbImgDataAttrDelete);
997
+ }
1108
998
  }
1109
- if (event in eventsMap) {
1110
- const ev = eventsMap[event];
1111
- ev?.action(direction);
999
+ if (img.src.includes("data:image")) {
1000
+ result.img.src = "";
1001
+ }
1002
+ if (img.complete && img.naturalWidth > 0) {
1003
+ return {};
1112
1004
  }
1113
- });
1114
- this.store.stateSubject.subscribe((a) => {
1115
- this.dataManager.applyFilters(a);
1116
- });
1117
- }
1118
- dataManagerOptions = {};
1119
- setupDataManager() {
1120
- this.dataManager = new DataManager(this);
1121
- if (this.dataManagerOptions) {
1122
- Object.assign(this.dataManager, this.dataManagerOptions);
1123
1005
  }
1124
- return this.dataManager;
1125
1006
  }
1126
- mutationObservers = [];
1127
- resetOnPaginationOrContainerDeath = true;
1128
- resetOn() {
1129
- if (!this.resetOnPaginationOrContainerDeath) return;
1130
- const observables = [
1131
- this.container,
1132
- this.intersectionObservable || this.paginationStrategy.getPaginationElement()
1133
- ].filter(Boolean);
1134
- if (observables.length === 0) return;
1135
- observables.forEach((o) => {
1136
- const observer = waitForElementToDisappear(o, () => {
1137
- this.reset();
1138
- });
1139
- this.mutationObservers.push(observer);
1140
- });
1007
+ return result;
1008
+ }
1009
+ containerSelector = ".container";
1010
+ containerSelectorLast;
1011
+ intersectionObservableSelector;
1012
+ get intersectionObservable() {
1013
+ return this.intersectionObservableSelector && document.querySelector(this.intersectionObservableSelector);
1014
+ }
1015
+ get observable() {
1016
+ return this.intersectionObservable || this.paginationStrategy.getPaginationElement();
1017
+ }
1018
+ get container() {
1019
+ if (typeof this.containerSelectorLast === "string") {
1020
+ return querySelectorLast(document, this.containerSelectorLast);
1021
+ }
1022
+ if (typeof this.containerSelector === "string") {
1023
+ return document.querySelector(this.containerSelector);
1141
1024
  }
1142
- onResetCallback;
1143
- reset() {
1144
- this.mutationObservers.forEach((o) => {
1145
- o.disconnect();
1025
+ return this.containerSelector();
1026
+ }
1027
+ thumbsSelector = ".thumb";
1028
+ getThumbsStrategy = "default";
1029
+ getThumbsTransform;
1030
+ getThumbs(html) {
1031
+ if (!html) return [];
1032
+ let thumbs;
1033
+ if (this.getThumbsStrategy === "auto") {
1034
+ if (typeof this.containerSelector !== "string") return [];
1035
+ const container = html.querySelector(this.containerSelector);
1036
+ thumbs = [...container?.children || []];
1037
+ }
1038
+ thumbs = Array.from(html.querySelectorAll(this.thumbsSelector));
1039
+ if (typeof this.getThumbsTransform === "function") {
1040
+ thumbs.forEach(this.getThumbsTransform);
1041
+ }
1042
+ return thumbs;
1043
+ }
1044
+ paginationStrategyOptions = {};
1045
+ paginationStrategy;
1046
+ customDataSelectorFns = [
1047
+ "filterInclude",
1048
+ "filterExclude",
1049
+ "filterDuration"
1050
+ ];
1051
+ animatePreview;
1052
+ storeOptions;
1053
+ createStore() {
1054
+ const config = { ...StoreStateDefault, ...this.storeOptions };
1055
+ this.store = new jabroniOutfit.JabronioStore(config);
1056
+ return this.store;
1057
+ }
1058
+ schemeOptions = [];
1059
+ createGui() {
1060
+ const scheme = jabroniOutfit.setupScheme(
1061
+ this.schemeOptions,
1062
+ DefaultScheme
1063
+ );
1064
+ this.gui = new jabroniOutfit.JabronioGUI(scheme, this.store);
1065
+ return this.gui;
1066
+ }
1067
+ store;
1068
+ gui;
1069
+ dataManager;
1070
+ infiniteScroller;
1071
+ getPaginationData;
1072
+ resetInfiniteScroller() {
1073
+ this.infiniteScroller?.dispose();
1074
+ if (!this.paginationStrategy.hasPagination) return;
1075
+ this.infiniteScroller = InfiniteScroller.create(this);
1076
+ }
1077
+ gropeStrategy = "all-in-one";
1078
+ gropeInit() {
1079
+ if (!this.gropeStrategy) return;
1080
+ if (this.gropeStrategy === "all-in-one") {
1081
+ this.dataManager?.parseData(this.container, this.container);
1082
+ }
1083
+ if (this.gropeStrategy === "all-in-all") {
1084
+ getCommonParents(this.getThumbs(document.body)).forEach((c) => {
1085
+ this.dataManager.parseData(c, c, true);
1146
1086
  });
1147
- this.mutationObservers = [];
1148
- this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
1149
- this.setupDataManager();
1150
- this.setupStoreListeners();
1151
- this.resetInfiniteScroller();
1152
- this.container && this.animatePreview?.(this.container);
1153
- this.gropeInit();
1154
- this.onResetCallback?.();
1155
- this.resetOn();
1156
1087
  }
1157
- constructor(options) {
1158
- if (this.isEmbedded) throw Error("Embedded is not supported");
1159
- Object.assign(this, options);
1160
- this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
1161
- this.store = this.createStore();
1162
- this.gui = this.createGui();
1163
- this.dataManager = this.setupDataManager();
1164
- this.reset();
1088
+ }
1089
+ get isEmbedded() {
1090
+ return window.self !== window.top;
1091
+ }
1092
+ setupStoreListeners() {
1093
+ const eventsMap = {
1094
+ "sort by duration": {
1095
+ action: (direction2) => this.dataManager.sortBy("duration", direction2)
1096
+ }
1097
+ };
1098
+ let lastEvent;
1099
+ let direction = true;
1100
+ this.store.eventSubject.subscribe((event) => {
1101
+ if (event === lastEvent) {
1102
+ direction = !direction;
1103
+ } else {
1104
+ lastEvent = event;
1105
+ direction = true;
1106
+ }
1107
+ if (event in eventsMap) {
1108
+ const ev = eventsMap[event];
1109
+ ev?.action(direction);
1110
+ }
1111
+ });
1112
+ this.store.stateSubject.subscribe((a) => {
1113
+ this.dataManager.applyFilters(a);
1114
+ });
1115
+ }
1116
+ dataManagerOptions = {};
1117
+ setupDataManager() {
1118
+ this.dataManager = new DataManager(this);
1119
+ if (this.dataManagerOptions) {
1120
+ Object.assign(this.dataManager, this.dataManagerOptions);
1165
1121
  }
1122
+ return this.dataManager;
1166
1123
  }
1124
+ mutationObservers = [];
1125
+ resetOnPaginationOrContainerDeath = true;
1126
+ resetOn() {
1127
+ if (!this.resetOnPaginationOrContainerDeath) return;
1128
+ const observables = [
1129
+ this.container,
1130
+ this.intersectionObservable || this.paginationStrategy.getPaginationElement()
1131
+ ].filter(Boolean);
1132
+ if (observables.length === 0) return;
1133
+ observables.forEach((o) => {
1134
+ const observer = waitForElementToDisappear(o, () => {
1135
+ this.reset();
1136
+ });
1137
+ this.mutationObservers.push(observer);
1138
+ });
1139
+ }
1140
+ onResetCallback;
1141
+ reset() {
1142
+ this.mutationObservers.forEach((o) => {
1143
+ o.disconnect();
1144
+ });
1145
+ this.mutationObservers = [];
1146
+ this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
1147
+ this.setupDataManager();
1148
+ this.setupStoreListeners();
1149
+ this.resetInfiniteScroller();
1150
+ this.container && this.animatePreview?.(this.container);
1151
+ this.gropeInit();
1152
+ this.onResetCallback?.();
1153
+ this.resetOn();
1154
+ }
1155
+ constructor(options) {
1156
+ if (this.isEmbedded) throw Error("Embedded is not supported");
1157
+ Object.assign(this, options);
1158
+ this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
1159
+ this.store = this.createStore();
1160
+ this.gui = this.createGui();
1161
+ this.dataManager = this.setupDataManager();
1162
+ this.reset();
1163
+ }
1164
+ }
1167
1165
 
1168
- new RulesGlobal({
1169
- paginationStrategyOptions: {
1170
- paginationSelector: "nav[x-data]"
1171
- },
1172
- containerSelector: ".grid[x-data]",
1173
- thumbsSelector: "div:has(> .thumbnail.group)",
1174
- getThumbImgDataStrategy: "auto",
1175
- titleSelector: "div > div > a.text-secondary",
1176
- durationSelector: "div > a > span.text-xs",
1177
- schemeOptions: ["Text Filter", "Duration Filter", "Badge", "Advanced"]
1178
- });
1179
-
1180
- })(jabronioutfit);
1166
+ new RulesGlobal({
1167
+ paginationStrategyOptions: {
1168
+ paginationSelector: "nav[x-data]"
1169
+ },
1170
+ containerSelector: ".grid[x-data]",
1171
+ thumbsSelector: "div:has(> .thumbnail.group)",
1172
+ getThumbImgDataStrategy: "auto",
1173
+ titleSelector: "div > div > a.text-secondary",
1174
+ durationSelector: "div > a > span.text-xs",
1175
+ schemeOptions: ["Text Filter", "Duration Filter", "Badge", "Advanced"]
1176
+ });
1181
1177
 
1182
- })();
1178
+ })(jabronioutfit);