pervert-monkey 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/core/pervertmonkey.core.es.d.ts +391 -0
  4. package/dist/core/pervertmonkey.core.es.js +8497 -0
  5. package/dist/core/pervertmonkey.core.es.js.map +1 -0
  6. package/dist/core/pervertmonkey.core.umd.js +8500 -0
  7. package/dist/core/pervertmonkey.core.umd.js.map +1 -0
  8. package/dist/userscripts/3hentai.user.js +1176 -0
  9. package/dist/userscripts/camgirlfinder.user.js +68 -0
  10. package/dist/userscripts/camwhores.user.js +1602 -0
  11. package/dist/userscripts/e-hentai.user.js +1212 -0
  12. package/dist/userscripts/ebalka.user.js +1231 -0
  13. package/dist/userscripts/eporner.user.js +1265 -0
  14. package/dist/userscripts/erome.user.js +1245 -0
  15. package/dist/userscripts/eroprofile.user.js +1194 -0
  16. package/dist/userscripts/javhdporn.user.js +1178 -0
  17. package/dist/userscripts/missav.user.js +1182 -0
  18. package/dist/userscripts/motherless.user.js +1380 -0
  19. package/dist/userscripts/namethatporn.user.js +1218 -0
  20. package/dist/userscripts/nhentai.user.js +1262 -0
  21. package/dist/userscripts/pornhub.user.js +1199 -0
  22. package/dist/userscripts/spankbang.user.js +1239 -0
  23. package/dist/userscripts/xhamster.user.js +1374 -0
  24. package/dist/userscripts/xvideos.user.js +1254 -0
  25. package/package.json +54 -0
  26. package/src/core/data-control/data-filter.ts +143 -0
  27. package/src/core/data-control/data-manager.ts +144 -0
  28. package/src/core/data-control/index.ts +2 -0
  29. package/src/core/infinite-scroll/index.ts +143 -0
  30. package/src/core/jabroni-config/default-scheme.ts +97 -0
  31. package/src/core/jabroni-config/default-store.ts +9 -0
  32. package/src/core/pagination-parsing/index.ts +55 -0
  33. package/src/core/pagination-parsing/pagination-strategies/PaginationStrategy.ts +44 -0
  34. package/src/core/pagination-parsing/pagination-strategies/PaginationStrategyDataParams.ts +66 -0
  35. package/src/core/pagination-parsing/pagination-strategies/PaginationStrategyPathnameParams.ts +77 -0
  36. package/src/core/pagination-parsing/pagination-strategies/PaginationStrategySearchParams.ts +56 -0
  37. package/src/core/pagination-parsing/pagination-strategies/index.ts +4 -0
  38. package/src/core/pagination-parsing/pagination-utils/index.ts +84 -0
  39. package/src/core/rules/index.ts +385 -0
  40. package/src/index.ts +42 -0
  41. package/src/types/index.ts +7 -0
  42. package/src/userscripts/ascii-logos.js +468 -0
  43. package/src/userscripts/index.ts +1 -0
  44. package/src/userscripts/meta.json +11 -0
  45. package/src/userscripts/scripts/3hentai.ts +20 -0
  46. package/src/userscripts/scripts/camgirlfinder.ts +68 -0
  47. package/src/userscripts/scripts/camwhores.ts +382 -0
  48. package/src/userscripts/scripts/e-hentai.ts +68 -0
  49. package/src/userscripts/scripts/ebalka.ts +58 -0
  50. package/src/userscripts/scripts/eporner.ts +90 -0
  51. package/src/userscripts/scripts/erome.ts +105 -0
  52. package/src/userscripts/scripts/eroprofile.ts +38 -0
  53. package/src/userscripts/scripts/javhdporn.ts +24 -0
  54. package/src/userscripts/scripts/missav.ts +28 -0
  55. package/src/userscripts/scripts/motherless.ts +222 -0
  56. package/src/userscripts/scripts/namethatporn.ts +68 -0
  57. package/src/userscripts/scripts/nhentai.ts +135 -0
  58. package/src/userscripts/scripts/pornhub.ts +53 -0
  59. package/src/userscripts/scripts/spankbang.ts +61 -0
  60. package/src/userscripts/scripts/thisvid.ts +716 -0
  61. package/src/userscripts/scripts/xhamster.ts +179 -0
  62. package/src/userscripts/scripts/xvideos.ts +83 -0
  63. package/src/utils/arrays/index.ts +15 -0
  64. package/src/utils/async/index.ts +3 -0
  65. package/src/utils/dom/dom-observers.ts +76 -0
  66. package/src/utils/dom/index.ts +156 -0
  67. package/src/utils/events/index.ts +2 -0
  68. package/src/utils/events/on-pointer-over-and-leave.ts +35 -0
  69. package/src/utils/events/tick.ts +27 -0
  70. package/src/utils/fetch/index.ts +37 -0
  71. package/src/utils/math/index.ts +3 -0
  72. package/src/utils/objects/index.ts +9 -0
  73. package/src/utils/objects/memoize.ts +25 -0
  74. package/src/utils/observers/index.ts +44 -0
  75. package/src/utils/observers/lazy-image-loader.ts +27 -0
  76. package/src/utils/parsers/index.ts +30 -0
  77. package/src/utils/parsers/time-parser.ts +28 -0
  78. package/src/utils/strings/index.ts +10 -0
  79. package/src/utils/strings/regexes.ts +35 -0
  80. package/src/vite-env.d.ts +4 -0
@@ -0,0 +1,1602 @@
1
+ // ==UserScript==
2
+ // @name CamWhores PervertMonkey
3
+ // @namespace pervertmonkey
4
+ // @version 3.0.3
5
+ // @author violent-orangutan
6
+ // @description Infinite scroll [optional]. Filter by Title, Duration and Private/Public. Mass friend request button. Download button
7
+ // @license MIT
8
+ // @icon https://www.google.com/s2/favicons?sz=64&domain=3hentai.net
9
+ // @homepage https://github.com/smartacephale/sleazy-fork
10
+ // @homepageURL https://github.com/smartacephale/sleazy-fork
11
+ // @source github:smartacephale/sleazy-fork
12
+ // @supportURL https://github.com/smartacephale/sleazy-fork/issues
13
+ // @match https://*.camwhores.*/*
14
+ // @match https://*.camwhores.tv
15
+ // @exclude https://*.camwhores.tv/*mode=async*
16
+ // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@2.1.1/dist/jabroni-outfit.umd.js
17
+ // @grant GM_addStyle
18
+ // @grant unsafeWindow
19
+ // @run-at document-idle
20
+ // ==/UserScript==
21
+
22
+ (function () {
23
+ 'use strict';
24
+
25
+ (function (jabroniOutfit) {
26
+
27
+ class LSKDB {
28
+ constructor(prefix = "lsm-", lockKey = "lsmngr-lock") {
29
+ this.prefix = prefix;
30
+ this.lockKey = lockKey;
31
+ }
32
+ prefixedKey(key) {
33
+ return `${this.prefix}${key}`;
34
+ }
35
+ getAllKeys() {
36
+ const res = [];
37
+ for (const key in localStorage) {
38
+ if (key.startsWith(this.prefix)) {
39
+ res.push(key.slice(this.prefix.length));
40
+ }
41
+ }
42
+ return res;
43
+ }
44
+ getKeys(n = 12, remove = true) {
45
+ const res = [];
46
+ for (const key in localStorage) {
47
+ if (res.length >= n) break;
48
+ if (key.startsWith(this.prefix)) {
49
+ res.push(key.slice(this.prefix.length));
50
+ }
51
+ }
52
+ if (remove) {
53
+ res.forEach((k) => this.removeKey(k));
54
+ }
55
+ return res;
56
+ }
57
+ hasKey(key) {
58
+ return localStorage.getItem(this.prefixedKey(key)) !== null;
59
+ }
60
+ removeKey(key) {
61
+ localStorage.removeItem(this.prefixedKey(key));
62
+ }
63
+ setKey(key) {
64
+ localStorage.setItem(this.prefixedKey(key), "");
65
+ }
66
+ isLocked() {
67
+ const lock = parseInt(localStorage.getItem(this.lockKey));
68
+ const locktime = 5 * 60 * 1e3;
69
+ return !(!lock || Date.now() - lock > locktime);
70
+ }
71
+ lock(value) {
72
+ if (value) {
73
+ localStorage.setItem(this.lockKey, JSON.stringify(Date.now()));
74
+ } else {
75
+ localStorage.removeItem(this.lockKey);
76
+ }
77
+ }
78
+ }
79
+
80
+ var _GM_addStyle = (() => typeof GM_addStyle != "undefined" ? GM_addStyle : undefined)();
81
+ var _unsafeWindow = (() => typeof unsafeWindow != "undefined" ? unsafeWindow : undefined)();
82
+
83
+ function wait(milliseconds) {
84
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
85
+ }
86
+
87
+ function splitWith(s, c = ",") {
88
+ return s.split(c).map((s2) => s2.trim()).filter(Boolean);
89
+ }
90
+ function sanitizeStr(s) {
91
+ return s?.replace(/\n|\t/g, " ").replace(/ {2,}/g, " ").trim() || "";
92
+ }
93
+
94
+ function waitForElementToAppear(parent, selector, callback) {
95
+ const observer = new MutationObserver((_mutations) => {
96
+ const el = parent.querySelector(selector);
97
+ if (el) {
98
+ observer.disconnect();
99
+ callback(el);
100
+ }
101
+ });
102
+ observer.observe(document.body, { childList: true, subtree: true });
103
+ return observer;
104
+ }
105
+ function waitForElementToDisappear(observable, callback) {
106
+ const observer = new MutationObserver((_mutations) => {
107
+ if (!observable.isConnected) {
108
+ observer.disconnect();
109
+ callback();
110
+ }
111
+ });
112
+ observer.observe(document.body, { childList: true, subtree: true });
113
+ return observer;
114
+ }
115
+
116
+ function querySelectorLast(root = document, selector) {
117
+ const nodes = root.querySelectorAll(selector);
118
+ return nodes.length > 0 ? nodes[nodes.length - 1] : void 0;
119
+ }
120
+ function querySelectorLastNumber(selector, e = document) {
121
+ const text = querySelectorText(e, selector);
122
+ return Number(text.match(/\d+/g)?.pop() || 0);
123
+ }
124
+ function querySelectorText(e, selector) {
125
+ if (typeof selector !== "string") return "";
126
+ const text = e.querySelector(selector)?.innerText || "";
127
+ return sanitizeStr(text);
128
+ }
129
+ function parseHtml(html) {
130
+ const parsed = new DOMParser().parseFromString(html, "text/html").body;
131
+ if (parsed.children.length > 1) return parsed;
132
+ return parsed.firstElementChild;
133
+ }
134
+ function removeClassesAndDataAttributes(element, keyword) {
135
+ Array.from(element.classList).forEach((className) => {
136
+ if (className.includes(keyword)) {
137
+ element.classList.remove(className);
138
+ }
139
+ });
140
+ Array.from(element.attributes).forEach((attr) => {
141
+ if (attr.name.startsWith("data-") && attr.name.includes(keyword)) {
142
+ element.removeAttribute(attr.name);
143
+ }
144
+ });
145
+ }
146
+ function getCommonParents(elements) {
147
+ const parents = Array.from(elements).map((el) => el.parentElement).filter((parent) => parent !== null);
148
+ return [...new Set(parents)];
149
+ }
150
+ function checkHomogenity(a, b, options) {
151
+ if (!a || !b) return false;
152
+ if (options.id) {
153
+ if (a.id !== b.id) return false;
154
+ }
155
+ if (options.className) {
156
+ const ca = a.className;
157
+ const cb = b.className;
158
+ if (!(ca.length > cb.length ? ca.includes(cb) : cb.includes(ca))) {
159
+ return false;
160
+ }
161
+ }
162
+ return true;
163
+ }
164
+ function downloader(options = { append: "", after: "", button: "", cbBefore: () => {
165
+ } }) {
166
+ const btn = parseHtml(options.button);
167
+ if (options.append) document.querySelector(options.append)?.append(btn);
168
+ if (options.after) document.querySelector(options.after)?.after(btn);
169
+ btn.addEventListener("click", (e) => {
170
+ e.preventDefault();
171
+ if (options.cbBefore) options.cbBefore();
172
+ waitForElementToAppear(document.body, "video", (video) => {
173
+ window.location.href = video.getAttribute("src");
174
+ });
175
+ });
176
+ }
177
+
178
+ const MOBILE_UA = {
179
+ "User-Agent": [
180
+ "Mozilla/5.0 (Linux; Android 10; K)",
181
+ "AppleWebKit/537.36 (KHTML, like Gecko)",
182
+ "Chrome/114.0.0.0 Mobile Safari/537.36"
183
+ ].join(" ")
184
+ };
185
+ async function fetchWith(input, options) {
186
+ const requestInit = options.init || {};
187
+ if (options.mobile) {
188
+ Object.assign(requestInit, { headers: new Headers(MOBILE_UA) });
189
+ }
190
+ const r = await fetch(input, requestInit).then((r2) => r2);
191
+ return parseHtml(await r.text());
192
+ }
193
+ const fetchHtml = (input) => fetchWith(input, { });
194
+
195
+ class LazyImgLoader {
196
+ lazyImgObserver;
197
+ attributeName = "data-lazy-load";
198
+ constructor(shouldDelazify) {
199
+ this.lazyImgObserver = new Observer((target) => {
200
+ if (shouldDelazify(target)) {
201
+ this.delazify(target);
202
+ }
203
+ });
204
+ }
205
+ lazify(_target, img, imgSrc) {
206
+ if (!img || !imgSrc) return;
207
+ img.setAttribute(this.attributeName, imgSrc);
208
+ img.src = "";
209
+ this.lazyImgObserver.observe(img);
210
+ }
211
+ delazify = (target) => {
212
+ this.lazyImgObserver.observer.unobserve(target);
213
+ target.src = target.getAttribute(this.attributeName);
214
+ target.removeAttribute(this.attributeName);
215
+ };
216
+ }
217
+
218
+ class Observer {
219
+ constructor(callback) {
220
+ this.callback = callback;
221
+ this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
222
+ }
223
+ observer;
224
+ timeout;
225
+ observe(target) {
226
+ this.observer.observe(target);
227
+ }
228
+ throttle(target, throttleTime) {
229
+ this.observer.unobserve(target);
230
+ this.timeout = window.setTimeout(() => this.observer.observe(target), throttleTime);
231
+ }
232
+ handleIntersection(entries) {
233
+ for (const entry of entries) {
234
+ if (entry.isIntersecting) {
235
+ this.callback(entry.target);
236
+ }
237
+ }
238
+ }
239
+ dispose() {
240
+ if (this.timeout) clearTimeout(this.timeout);
241
+ this.observer.disconnect();
242
+ }
243
+ static observeWhile(target, callback, throttleTime) {
244
+ const observer_ = new Observer(async (target2) => {
245
+ const condition = await callback();
246
+ if (condition) observer_.throttle(target2, throttleTime);
247
+ });
248
+ observer_.observe(target);
249
+ return observer_;
250
+ }
251
+ }
252
+
253
+ class InfiniteScroller {
254
+ enabled = true;
255
+ paginationOffset = 1;
256
+ parseData;
257
+ rules;
258
+ observer;
259
+ paginationGenerator;
260
+ constructor(options) {
261
+ this.rules = options.rules;
262
+ this.paginationOffset = this.rules.paginationStrategy.getPaginationOffset();
263
+ Object.assign(this, options);
264
+ if (this.rules.getPaginationData) {
265
+ this.getPaginationData = this.rules.getPaginationData;
266
+ }
267
+ this.paginationGenerator = this.rules.customGenerator || InfiniteScroller.generatorForPaginationStrategy(this.rules.paginationStrategy);
268
+ this.setObserver(this.rules.observable);
269
+ this.setAutoScroll();
270
+ }
271
+ dispose() {
272
+ if (this.observer) this.observer.dispose();
273
+ }
274
+ setObserver(observable) {
275
+ if (this.observer) this.observer.dispose();
276
+ this.observer = Observer.observeWhile(
277
+ observable,
278
+ this.generatorConsumer,
279
+ this.rules.store.state.delay
280
+ );
281
+ return this;
282
+ }
283
+ onScrollCBs = [];
284
+ onScroll(callback, initCall = false) {
285
+ if (initCall) callback(this);
286
+ this.onScrollCBs.push(callback);
287
+ return this;
288
+ }
289
+ _onScroll() {
290
+ this.onScrollCBs.forEach((cb) => {
291
+ cb(this);
292
+ });
293
+ }
294
+ setAutoScroll() {
295
+ const autoScrollWrapper = async () => {
296
+ if (this.rules.store.state.autoScroll) {
297
+ await wait(this.rules.store.state.delay);
298
+ await this.generatorConsumer();
299
+ await autoScrollWrapper();
300
+ }
301
+ };
302
+ autoScrollWrapper();
303
+ this.rules.store.stateSubject.subscribe((type) => {
304
+ if (type?.autoScroll) {
305
+ autoScrollWrapper();
306
+ }
307
+ });
308
+ }
309
+ generatorConsumer = async () => {
310
+ if (!this.enabled) return false;
311
+ const {
312
+ value: { url, offset },
313
+ done
314
+ } = await this.paginationGenerator.next();
315
+ if (!done && url) {
316
+ await this.doScroll(url, offset);
317
+ }
318
+ return !done;
319
+ };
320
+ async getPaginationData(url) {
321
+ return await fetchHtml(url);
322
+ }
323
+ async doScroll(url, offset) {
324
+ const nextPageHtml = await this.getPaginationData(url);
325
+ const prevScrollPos = document.documentElement.scrollTop;
326
+ this.paginationOffset = Math.max(this.paginationOffset, offset);
327
+ this.parseData?.(nextPageHtml);
328
+ this._onScroll();
329
+ window.scrollTo(0, prevScrollPos);
330
+ if (this.rules.store.state.writeHistory) {
331
+ history.replaceState({}, "", url);
332
+ }
333
+ }
334
+ static async *generatorForPaginationStrategy(pstrategy) {
335
+ const _offset = pstrategy.getPaginationOffset();
336
+ const end = pstrategy.getPaginationLast();
337
+ const urlGenerator = pstrategy.getPaginationUrlGenerator();
338
+ for (let offset = _offset; offset <= end; offset++) {
339
+ const url = await urlGenerator(offset);
340
+ yield { url, offset };
341
+ }
342
+ }
343
+ static create(rules) {
344
+ const enabled = rules.store.state.infiniteScrollEnabled;
345
+ rules.store.state.$paginationLast = rules.paginationStrategy.getPaginationLast();
346
+ const infiniteScroller = new InfiniteScroller({
347
+ enabled,
348
+ parseData: rules.dataManager.parseData,
349
+ rules
350
+ }).onScroll(({ paginationOffset }) => {
351
+ rules.store.state.$paginationOffset = paginationOffset;
352
+ }, true);
353
+ rules.store.stateSubject.subscribe(() => {
354
+ infiniteScroller.enabled = rules.store.state.infiniteScrollEnabled;
355
+ });
356
+ return infiniteScroller;
357
+ }
358
+ }
359
+
360
+ 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");
361
+
362
+ function parseUrl(s) {
363
+ return new URL(typeof s === "string" ? s : s.href);
364
+ }
365
+ function depaginatePathname(url, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
366
+ const newUrl = new URL(url.toString());
367
+ newUrl.pathname = newUrl.pathname.replace(pathnamePaginationSelector, "/");
368
+ return newUrl;
369
+ }
370
+ function getPaginationLinks(doc = document, url = location.href, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
371
+ const baseUrl = depaginatePathname(parseUrl(url), pathnamePaginationSelector);
372
+ const pathnameStrict = doc instanceof Document;
373
+ const host = doc.baseURI || baseUrl.origin;
374
+ const urlPattern = new Y({
375
+ pathname: pathnameStrict ? `${baseUrl.pathname}*` : "*",
376
+ hostname: baseUrl.hostname
377
+ });
378
+ const pageLinks = [...doc.querySelectorAll("a[href]")].map((a) => a.href).filter((h) => URL.canParse(h));
379
+ return pageLinks.filter((h) => {
380
+ return urlPattern.test(new URL(h, host));
381
+ });
382
+ }
383
+ function upgradePathname(curr, links, pathnamePaginationSelector = /\/(page\/)?\d+\/?$/) {
384
+ if (pathnamePaginationSelector.test(curr.pathname) || links.length < 1) return curr;
385
+ const linksDepaginated = links.map(
386
+ (l) => depaginatePathname(l, pathnamePaginationSelector)
387
+ );
388
+ if (linksDepaginated.some((l) => l.pathname === curr.pathname)) return curr;
389
+ const last = linksDepaginated.at(-1);
390
+ if (last.pathname !== curr.pathname) curr.pathname = last.pathname;
391
+ return curr;
392
+ }
393
+
394
+ class PaginationStrategy {
395
+ doc = document;
396
+ url;
397
+ paginationSelector = ".pagination";
398
+ searchParamSelector = "page";
399
+ static _pathnameSelector = /\/(page\/)?\d+\/?$/;
400
+ pathnameSelector = /\/(\d+)\/?$/;
401
+ dataparamSelector = "[data-parameters *= from]";
402
+ overwritePaginationLast;
403
+ offsetMin = 1;
404
+ constructor(options) {
405
+ if (options) {
406
+ Object.entries(options).forEach(([k, v]) => {
407
+ Object.assign(this, { [k]: v });
408
+ });
409
+ }
410
+ this.url = parseUrl(options?.url || this.doc.URL);
411
+ }
412
+ getPaginationElement() {
413
+ return this.doc.querySelector(this.paginationSelector);
414
+ }
415
+ get hasPagination() {
416
+ return !!this.getPaginationElement();
417
+ }
418
+ getPaginationOffset() {
419
+ return this.offsetMin;
420
+ }
421
+ getPaginationLast() {
422
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(1);
423
+ return 1;
424
+ }
425
+ getPaginationUrlGenerator() {
426
+ return (_) => this.url.href;
427
+ }
428
+ }
429
+
430
+ function formatTimeToHHMMSS(timeStr) {
431
+ const pad = (num) => num.toString().padStart(2, "0");
432
+ const h = timeStr.match(/(\d+)\s*h/)?.[1] || "0";
433
+ const m = timeStr.match(/(\d+)\s*mi?n/)?.[1] || "0";
434
+ const s = timeStr.match(/(\d+)\s*sec/)?.[1] || "0";
435
+ return `${pad(+h)}:${pad(+m)}:${pad(+s)}`;
436
+ }
437
+ function timeToSeconds(timeStr) {
438
+ const normalized = /[a-zA-Z]/.test(timeStr) ? formatTimeToHHMMSS(timeStr) : timeStr;
439
+ return normalized.split(":").reverse().reduce((total, unit, index) => total + parseInt(unit, 10) * 60 ** index, 0);
440
+ }
441
+
442
+ function parseDataParams(str) {
443
+ const paramsStr = decodeURI(str.trim()).split(";");
444
+ return paramsStr.reduce(
445
+ (acc, s) => {
446
+ const parsed = s.match(/([+\w]+):([\w\- ]+)?/);
447
+ if (parsed) {
448
+ const [, key, value] = parsed;
449
+ if (value) {
450
+ key.split("+").forEach((p) => {
451
+ acc[p] = value;
452
+ });
453
+ }
454
+ }
455
+ return acc;
456
+ },
457
+ {}
458
+ );
459
+ }
460
+
461
+ class PaginationStrategyDataParams extends PaginationStrategy {
462
+ getPaginationLast() {
463
+ const links = this.getPaginationElement()?.querySelectorAll(this.dataparamSelector);
464
+ const pages = Array.from(links || [], (l) => {
465
+ const p = l.getAttribute("data-parameters");
466
+ const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
467
+ return parseInt(v);
468
+ });
469
+ const lastPage = Math.max(...pages, this.offsetMin);
470
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
471
+ return lastPage;
472
+ }
473
+ getPaginationOffset() {
474
+ const link = this.getPaginationElement()?.querySelector(
475
+ ".prev[data-parameters *= from], .prev [data-parameters *= from]"
476
+ );
477
+ if (!link) return this.offsetMin;
478
+ const p = link.getAttribute("data-parameters");
479
+ const v = p?.match(/from\w*:(\d+)/)?.[1] || this.offsetMin.toString();
480
+ return parseInt(v);
481
+ }
482
+ getPaginationUrlGenerator() {
483
+ const url = new URL(this.url.href);
484
+ const parametersElement = this.getPaginationElement()?.querySelector(
485
+ "a[data-block-id][data-parameters]"
486
+ );
487
+ const block_id = parametersElement?.getAttribute("data-block-id") || "";
488
+ const parameters = parseDataParams(
489
+ parametersElement?.getAttribute("data-parameters") || ""
490
+ );
491
+ const attrs = {
492
+ block_id,
493
+ function: "get_block",
494
+ mode: "async",
495
+ ...parameters
496
+ };
497
+ Object.keys(attrs).forEach((k) => {
498
+ url.searchParams.set(k, attrs[k]);
499
+ });
500
+ const paginationUrlGenerator = (n) => {
501
+ Object.keys(attrs).forEach((k) => {
502
+ k.includes("from") && url.searchParams.set(k, n.toString());
503
+ });
504
+ url.searchParams.set("_", Date.now().toString());
505
+ return url.href;
506
+ };
507
+ return paginationUrlGenerator;
508
+ }
509
+ static testLinks(doc = document) {
510
+ const dataParamLinks = Array.from(
511
+ doc.querySelectorAll("[data-parameters *= from]")
512
+ );
513
+ return dataParamLinks.length > 0;
514
+ }
515
+ }
516
+
517
+ class PaginationStrategyPathnameParams extends PaginationStrategy {
518
+ extractPage = (a) => {
519
+ const href = typeof a === "string" ? a : a.href;
520
+ const { pathname } = new URL(href, this.doc.baseURI || this.url.origin);
521
+ return parseInt(
522
+ pathname.match(this.pathnameSelector)?.pop() || this.offsetMin.toString()
523
+ );
524
+ };
525
+ static checkLink(link, pathnameSelector = PaginationStrategy._pathnameSelector) {
526
+ return pathnameSelector.test(link.pathname);
527
+ }
528
+ static testLinks(links, options) {
529
+ const result = links.some(
530
+ (h) => PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector)
531
+ );
532
+ if (result) {
533
+ const pathnamesMatched = links.filter(
534
+ (h) => PaginationStrategyPathnameParams.checkLink(h, options.pathnameSelector)
535
+ );
536
+ options.url = upgradePathname(
537
+ parseUrl(options.url),
538
+ pathnamesMatched
539
+ );
540
+ }
541
+ return result;
542
+ }
543
+ getPaginationLast() {
544
+ const links = getPaginationLinks(
545
+ this.getPaginationElement() || document,
546
+ this.url.href,
547
+ this.pathnameSelector
548
+ );
549
+ const pages = Array.from(links, this.extractPage);
550
+ const lastPage = Math.max(...pages, this.offsetMin);
551
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
552
+ return lastPage;
553
+ }
554
+ getPaginationOffset() {
555
+ return this.extractPage(this.url.href);
556
+ }
557
+ getPaginationUrlGenerator(url_ = this.url) {
558
+ const url = new URL(url_.href);
559
+ const pathnameSelectorPlaceholder = this.pathnameSelector.toString().replace(/[/|\\|$|?|(|)]+/g, "/");
560
+ if (!this.pathnameSelector.test(url.pathname)) {
561
+ url.pathname = url.pathname.concat(pathnameSelectorPlaceholder.replace(/d\+/, this.offsetMin.toString())).replace(/\/{2,}/g, "/");
562
+ }
563
+ const paginationUrlGenerator = (offset) => {
564
+ url.pathname = url.pathname.replace(
565
+ this.pathnameSelector,
566
+ pathnameSelectorPlaceholder.replace(/d\+/, offset.toString())
567
+ );
568
+ return url.href;
569
+ };
570
+ return paginationUrlGenerator;
571
+ }
572
+ }
573
+
574
+ class PaginationStrategySearchParams extends PaginationStrategy {
575
+ extractPage = (a) => {
576
+ const href = typeof a === "string" ? a : a.href;
577
+ const p = new URL(href).searchParams.get(this.searchParamSelector);
578
+ return parseInt(p) || this.offsetMin;
579
+ };
580
+ getPaginationLast() {
581
+ const links = getPaginationLinks(
582
+ this.getPaginationElement() || document,
583
+ this.url.href
584
+ ).filter(
585
+ (h) => PaginationStrategySearchParams.checkLink(new URL(h), this.searchParamSelector)
586
+ );
587
+ const pages = links.map(this.extractPage);
588
+ const lastPage = Math.max(...pages, this.offsetMin);
589
+ if (this.overwritePaginationLast) return this.overwritePaginationLast(lastPage);
590
+ return lastPage;
591
+ }
592
+ getPaginationOffset() {
593
+ if (this.doc === document) {
594
+ return this.extractPage(this.url);
595
+ }
596
+ const link = this.getPaginationElement()?.querySelector(
597
+ `a.active[href *= "${this.searchParamSelector}="]`
598
+ );
599
+ return this.extractPage(link);
600
+ }
601
+ getPaginationUrlGenerator() {
602
+ const url = new URL(this.url.href);
603
+ const paginationUrlGenerator = (offset) => {
604
+ url.searchParams.set(this.searchParamSelector, offset.toString());
605
+ return url.href;
606
+ };
607
+ return paginationUrlGenerator;
608
+ }
609
+ static checkLink(link, searchParamSelector) {
610
+ const searchParamSelectors = ["page", "p"];
611
+ if (searchParamSelector) searchParamSelectors.push(searchParamSelector);
612
+ return searchParamSelectors.some((p) => link.searchParams.get(p) !== null);
613
+ }
614
+ static testLinks(links, searchParamSelector) {
615
+ return links.some(
616
+ (h) => PaginationStrategySearchParams.checkLink(h, searchParamSelector)
617
+ );
618
+ }
619
+ }
620
+
621
+ function getPaginationStrategy(options) {
622
+ const _paginationStrategy = new PaginationStrategy(options);
623
+ const pagination = _paginationStrategy.getPaginationElement();
624
+ Object.assign(options, { ..._paginationStrategy });
625
+ const { url, searchParamSelector } = options;
626
+ if (!pagination) {
627
+ return _paginationStrategy;
628
+ }
629
+ if (typeof options.getPaginationUrlGenerator === "function") {
630
+ return new PaginationStrategy(options);
631
+ }
632
+ const pageLinks = getPaginationLinks(pagination, url).map((l) => new URL(l));
633
+ const selectStrategy = () => {
634
+ if (PaginationStrategyDataParams.testLinks(pagination)) {
635
+ return PaginationStrategyDataParams;
636
+ }
637
+ if (PaginationStrategySearchParams.testLinks(pageLinks, searchParamSelector)) {
638
+ return PaginationStrategySearchParams;
639
+ }
640
+ if (PaginationStrategyPathnameParams.testLinks(pageLinks, options)) {
641
+ return PaginationStrategyPathnameParams;
642
+ }
643
+ console.error("Found No Strategy");
644
+ return PaginationStrategy;
645
+ };
646
+ const PaginationStrategyConstructor = selectStrategy();
647
+ const paginationStrategy = new PaginationStrategyConstructor(options);
648
+ return paginationStrategy;
649
+ }
650
+
651
+ function memoize(fn) {
652
+ const cache = new Map();
653
+ const memoizedFunction = ((...args) => {
654
+ const key = JSON.stringify(args);
655
+ if (cache.has(key)) {
656
+ return cache.get(key);
657
+ }
658
+ const result = fn(...args);
659
+ cache.set(key, result);
660
+ return result;
661
+ });
662
+ return memoizedFunction;
663
+ }
664
+
665
+ function objectToFormData(obj) {
666
+ const formData = new FormData();
667
+ Object.entries(obj).forEach(([k, v]) => {
668
+ formData.append(k, v);
669
+ });
670
+ return formData;
671
+ }
672
+
673
+ class RegexFilter {
674
+ regexes;
675
+ constructor(str, flags = "gi") {
676
+ this.regexes = memoize(this.compileSearchRegex)(str, flags);
677
+ }
678
+ compileSearchRegex(str, flags) {
679
+ try {
680
+ if (str.startsWith("r:")) return [new RegExp(str.slice(2), flags)];
681
+ const regexes = splitWith(str).map(
682
+ (s) => s.replace(/f:(\w+)/g, (_, w) => `(^|\\ |,)${w}($|\\ |,)`)
683
+ ).map((_) => new RegExp(_, flags));
684
+ return regexes;
685
+ } catch (_) {
686
+ return [];
687
+ }
688
+ }
689
+ hasEvery(str) {
690
+ return this.regexes.every((r) => r.test(str));
691
+ }
692
+ hasNone(str) {
693
+ return this.regexes.every((r) => !r.test(str));
694
+ }
695
+ }
696
+
697
+ class DataFilter {
698
+ constructor(rules) {
699
+ this.rules = rules;
700
+ this.registerFilters(rules.customDataSelectorFns);
701
+ this.applyCSSFilters();
702
+ }
703
+ filters = new Map();
704
+ static isFiltered(el) {
705
+ return el.className.includes("filter-");
706
+ }
707
+ applyCSSFilters(wrapper) {
708
+ this.filters.forEach((_, name) => {
709
+ const cssRule = `.filter-${name} { display: none !important; }`;
710
+ if (wrapper) {
711
+ _GM_addStyle(wrapper(cssRule));
712
+ } else {
713
+ _GM_addStyle(cssRule);
714
+ }
715
+ });
716
+ }
717
+ customDataSelectorFns = {};
718
+ registerFilters(customFilters) {
719
+ customFilters.forEach((o) => {
720
+ if (typeof o === "string") {
721
+ this.customDataSelectorFns[o] = DataFilter.customDataSelectorFnsDefault[o];
722
+ this.registerFilter(o);
723
+ } else {
724
+ const k = Object.keys(o)[0];
725
+ this.customDataSelectorFns[k] = o[k];
726
+ this.registerFilter(k);
727
+ }
728
+ });
729
+ }
730
+ customSelectorParser(name, selector) {
731
+ if ("handle" in selector) {
732
+ return selector;
733
+ } else {
734
+ return { handle: selector, deps: [name] };
735
+ }
736
+ }
737
+ registerFilter(customSelectorName) {
738
+ const handler = this.customSelectorParser(
739
+ customSelectorName,
740
+ this.customDataSelectorFns[customSelectorName]
741
+ );
742
+ const tag = `filter-${customSelectorName}`;
743
+ [customSelectorName, ...handler.deps || []]?.forEach((name) => {
744
+ Object.assign(this.filterMapping, { [name]: customSelectorName });
745
+ });
746
+ const fn = () => {
747
+ const preDefined = handler.$preDefine?.(this.rules.store.state);
748
+ return (v) => {
749
+ const condition = handler.handle(v, this.rules.store.state, preDefined);
750
+ return {
751
+ condition,
752
+ tag
753
+ };
754
+ };
755
+ };
756
+ this.filters.set(customSelectorName, fn);
757
+ }
758
+ filterMapping = {};
759
+ selectFilters(filters) {
760
+ const selectedFilters = Object.keys(filters).filter((k) => k in this.filterMapping).map((k) => this.filterMapping[k]).map((k) => this.filters.get(k));
761
+ return selectedFilters;
762
+ }
763
+ static customDataSelectorFnsDefault = {
764
+ filterDuration: {
765
+ handle(el, state, notInRange) {
766
+ return state.filterDuration && notInRange(el.duration);
767
+ },
768
+ $preDefine: (state) => {
769
+ const from = state.filterDurationFrom;
770
+ const to = state.filterDurationTo;
771
+ function notInRange(d) {
772
+ return d < from || d > to;
773
+ }
774
+ return notInRange;
775
+ },
776
+ deps: ["filterDurationFrom", "filterDurationTo"]
777
+ },
778
+ filterExclude: {
779
+ handle(el, state, searchFilter) {
780
+ if (!state.filterExclude) return false;
781
+ return !searchFilter.hasNone(el.title);
782
+ },
783
+ $preDefine: (state) => new RegexFilter(state.filterExcludeWords),
784
+ deps: ["filterExcludeWords"]
785
+ },
786
+ filterInclude: {
787
+ handle(el, state, searchFilter) {
788
+ if (!state.filterInclude) return false;
789
+ return !searchFilter.hasEvery(el.title);
790
+ },
791
+ $preDefine: (state) => new RegexFilter(state.filterIncludeWords),
792
+ deps: ["filterIncludeWords"]
793
+ }
794
+ };
795
+ }
796
+
797
+ class DataManager {
798
+ constructor(rules) {
799
+ this.rules = rules;
800
+ this.dataFilter = new DataFilter(this.rules);
801
+ }
802
+ data = new Map();
803
+ lazyImgLoader = new LazyImgLoader(
804
+ (target) => !DataFilter.isFiltered(target)
805
+ );
806
+ dataFilter;
807
+ applyFilters = async (filters = {}, offset = 0) => {
808
+ const filtersToApply = this.dataFilter.selectFilters(filters);
809
+ if (filtersToApply.length === 0) return;
810
+ const iterator = this.data.values().drop(offset);
811
+ let finished = false;
812
+ await new Promise((resolve) => {
813
+ function runBatch(deadline) {
814
+ const updates = [];
815
+ while (deadline.timeRemaining() > 0) {
816
+ const { value, done } = iterator.next();
817
+ finished = !!done;
818
+ if (done) break;
819
+ for (const f of filtersToApply) {
820
+ const { tag, condition } = f()(value);
821
+ updates.push({ e: value.element, tag, condition });
822
+ }
823
+ }
824
+ if (updates.length > 0) {
825
+ requestAnimationFrame(() => {
826
+ updates.forEach((u) => {
827
+ u.e.classList.toggle(u.tag, u.condition);
828
+ });
829
+ });
830
+ }
831
+ if (!finished) {
832
+ requestIdleCallback(runBatch);
833
+ } else {
834
+ resolve(true);
835
+ }
836
+ }
837
+ requestIdleCallback(runBatch);
838
+ });
839
+ };
840
+ filterAll = async (offset) => {
841
+ const keys = Array.from(this.dataFilter.filters.keys());
842
+ const filters = Object.fromEntries(
843
+ keys.map((k) => [k, this.rules.store.state[k]])
844
+ );
845
+ await this.applyFilters(filters, offset);
846
+ };
847
+ parseDataParentHomogenity;
848
+ parseData = (html, container, removeDuplicates = false, shouldLazify = true) => {
849
+ const thumbs = this.rules.getThumbs(html);
850
+ const dataOffset = this.data.size;
851
+ const fragment = document.createDocumentFragment();
852
+ const parent = container || this.rules.container;
853
+ const homogenity = !!this.parseDataParentHomogenity;
854
+ for (const thumbElement of thumbs) {
855
+ const url = this.rules.getThumbUrl(thumbElement);
856
+ if (!url || this.data.has(url) || parent !== container && parent?.contains(thumbElement) || homogenity && !checkHomogenity(
857
+ parent,
858
+ thumbElement.parentElement,
859
+ this.parseDataParentHomogenity
860
+ )) {
861
+ if (removeDuplicates) thumbElement.remove();
862
+ continue;
863
+ }
864
+ const data = this.rules.getThumbData(thumbElement);
865
+ this.data.set(url, { element: thumbElement, ...data });
866
+ if (shouldLazify) {
867
+ const { img, imgSrc } = this.rules.getThumbImgData(thumbElement);
868
+ this.lazyImgLoader.lazify(thumbElement, img, imgSrc);
869
+ }
870
+ fragment.append(thumbElement);
871
+ }
872
+ this.filterAll(dataOffset).then(() => {
873
+ requestAnimationFrame(() => {
874
+ parent.appendChild(fragment);
875
+ });
876
+ });
877
+ };
878
+ sortBy(key, direction = true) {
879
+ if (this.data.size < 2) return;
880
+ let sorted = this.data.values().toArray().sort((a, b) => {
881
+ return a[key] - b[key];
882
+ });
883
+ if (!direction) sorted = sorted.reverse();
884
+ const container = sorted[0].element.parentElement;
885
+ container.style.visibility = "hidden";
886
+ sorted.forEach((s) => {
887
+ container.append(s.element);
888
+ });
889
+ container.style.visibility = "visible";
890
+ }
891
+ }
892
+
893
+ const DefaultScheme = [
894
+ {
895
+ title: "Text Filter",
896
+ collapsed: true,
897
+ content: [
898
+ { filterExclude: false, label: "exclude" },
899
+ {
900
+ filterExcludeWords: "",
901
+ label: "keywords",
902
+ watch: "filterExclude",
903
+ placeholder: "word, f:full_word, r:RegEx..."
904
+ },
905
+ { filterInclude: false, label: "include" },
906
+ {
907
+ filterIncludeWords: "",
908
+ label: "keywords",
909
+ watch: "filterInclude",
910
+ placeholder: "word, f:full_word, r:RegEx..."
911
+ }
912
+ ]
913
+ },
914
+ {
915
+ title: "Duration Filter",
916
+ collapsed: true,
917
+ content: [
918
+ { filterDuration: false, label: "enable" },
919
+ {
920
+ filterDurationFrom: 0,
921
+ watch: "filterDuration",
922
+ label: "from",
923
+ type: "time"
924
+ },
925
+ {
926
+ filterDurationTo: 600,
927
+ watch: "filterDuration",
928
+ label: "to",
929
+ type: "time"
930
+ }
931
+ ]
932
+ },
933
+ {
934
+ title: "Sort By",
935
+ content: [
936
+ {
937
+ "sort by views": () => {
938
+ }
939
+ },
940
+ {
941
+ "sort by duration": () => {
942
+ }
943
+ }
944
+ ]
945
+ },
946
+ {
947
+ title: "Privacy Filter",
948
+ content: [
949
+ { filterPrivate: false, label: "private" },
950
+ { filterPublic: false, label: "public" },
951
+ { "check access 🔓": () => {
952
+ } }
953
+ ]
954
+ },
955
+ {
956
+ title: "Advanced",
957
+ content: [
958
+ {
959
+ infiniteScrollEnabled: true,
960
+ label: "infinite scroll"
961
+ },
962
+ {
963
+ autoScroll: false,
964
+ label: "auto scroll"
965
+ },
966
+ {
967
+ delay: 250,
968
+ label: "scroll delay"
969
+ },
970
+ {
971
+ writeHistory: false,
972
+ label: "write history"
973
+ }
974
+ ]
975
+ },
976
+ {
977
+ title: "Badge",
978
+ content: [
979
+ {
980
+ text: "return `${state.$paginationOffset}/${state.$paginationLast}`",
981
+ vif: "return state.$paginationLast > 1"
982
+ }
983
+ ]
984
+ }
985
+ ];
986
+
987
+ const StoreStateDefault = {
988
+ enabled: true,
989
+ collapsed: false,
990
+ darkmode: true,
991
+ $paginationLast: 1,
992
+ $paginationOffset: 1
993
+ };
994
+
995
+ class RulesGlobal {
996
+ delay;
997
+ customGenerator;
998
+ getThumbUrl(thumb) {
999
+ return (thumb.querySelector("a[href]") || thumb).href;
1000
+ }
1001
+ titleSelector;
1002
+ uploaderSelector;
1003
+ durationSelector;
1004
+ customThumbDataSelectors;
1005
+ getThumbDataStrategy = "default";
1006
+ getThumbDataCallback;
1007
+ getThumbData(thumb) {
1008
+ let { titleSelector, uploaderSelector, durationSelector } = this;
1009
+ const thumbData = { title: "" };
1010
+ if (this.getThumbDataStrategy === "auto-text") {
1011
+ const text = sanitizeStr(thumb.innerText);
1012
+ thumbData.title = text;
1013
+ thumbData.duration = timeToSeconds(text.match(/\d+m|\d+:\d+/)?.[0] || "");
1014
+ return thumbData;
1015
+ }
1016
+ if (this.getThumbDataStrategy === "auto-select") {
1017
+ titleSelector = "[class *= title],[title]";
1018
+ durationSelector = "[class *= duration]";
1019
+ uploaderSelector = "[class *= uploader], [class *= user], [class *= name]";
1020
+ }
1021
+ if (this.getThumbDataStrategy === "auto-select") {
1022
+ const selected = querySelectorLast(thumb, titleSelector);
1023
+ if (selected) {
1024
+ thumbData.title = sanitizeStr(selected.innerText);
1025
+ } else {
1026
+ thumbData.title = sanitizeStr(thumb.innerText);
1027
+ }
1028
+ } else {
1029
+ thumbData.title = querySelectorText(thumb, titleSelector);
1030
+ }
1031
+ if (uploaderSelector) {
1032
+ const uploader = querySelectorText(thumb, uploaderSelector);
1033
+ thumbData.title = `${thumbData.title} user:${uploader}`;
1034
+ }
1035
+ if (durationSelector) {
1036
+ const duration = timeToSeconds(querySelectorText(thumb, durationSelector));
1037
+ thumbData.duration = duration;
1038
+ }
1039
+ this.getThumbDataCallback?.(thumb, thumbData);
1040
+ function getCustomThumbData(selector, type) {
1041
+ if (type === "boolean") {
1042
+ return !!thumb.querySelector(selector);
1043
+ }
1044
+ if (type === "string") {
1045
+ return querySelectorText(thumb, selector);
1046
+ }
1047
+ return Number.parseInt(querySelectorText(thumb, selector));
1048
+ }
1049
+ if (this.customThumbDataSelectors) {
1050
+ Object.entries(this.customThumbDataSelectors).forEach(([name, x]) => {
1051
+ const data = getCustomThumbData(x.selector, x.type);
1052
+ Object.assign(thumbData, { [name]: data });
1053
+ });
1054
+ }
1055
+ return thumbData;
1056
+ }
1057
+ getThumbImgDataAttrSelector;
1058
+ getThumbImgDataAttrDelete;
1059
+ getThumbImgDataStrategy = "default";
1060
+ getThumbImgData(thumb) {
1061
+ const result = {};
1062
+ if (this.getThumbImgDataStrategy === "auto") {
1063
+ const img = thumb.querySelector("img");
1064
+ if (!img) return {};
1065
+ result.img = img;
1066
+ if (typeof this.getThumbImgDataAttrSelector === "function") {
1067
+ result.imgSrc = this.getThumbImgDataAttrSelector(img);
1068
+ } else {
1069
+ const possibleAttrs = this.getThumbImgDataAttrSelector ? [this.getThumbImgDataAttrSelector].flat() : ["data-src", "src"];
1070
+ for (const attr of possibleAttrs) {
1071
+ const imgSrc = img.getAttribute(attr);
1072
+ if (imgSrc) {
1073
+ result.imgSrc = imgSrc;
1074
+ img.removeAttribute(attr);
1075
+ break;
1076
+ }
1077
+ }
1078
+ }
1079
+ if (this.getThumbImgDataAttrDelete) {
1080
+ if (this.getThumbImgDataAttrDelete === "auto") {
1081
+ removeClassesAndDataAttributes(img, "lazy");
1082
+ } else {
1083
+ if (this.getThumbImgDataAttrDelete.startsWith(".")) {
1084
+ img.classList.remove(this.getThumbImgDataAttrDelete.slice(1));
1085
+ } else {
1086
+ img.removeAttribute(this.getThumbImgDataAttrDelete);
1087
+ }
1088
+ }
1089
+ if (img.src.includes("data:image")) {
1090
+ result.img.src = "";
1091
+ }
1092
+ if (img.complete && img.naturalWidth > 0) {
1093
+ return {};
1094
+ }
1095
+ }
1096
+ }
1097
+ return result;
1098
+ }
1099
+ containerSelector = ".container";
1100
+ containerSelectorLast;
1101
+ intersectionObservableSelector;
1102
+ get intersectionObservable() {
1103
+ return this.intersectionObservableSelector && document.querySelector(this.intersectionObservableSelector);
1104
+ }
1105
+ get observable() {
1106
+ return this.intersectionObservable || this.paginationStrategy.getPaginationElement();
1107
+ }
1108
+ get container() {
1109
+ if (typeof this.containerSelectorLast === "string") {
1110
+ return querySelectorLast(document, this.containerSelectorLast);
1111
+ }
1112
+ if (typeof this.containerSelector === "string") {
1113
+ return document.querySelector(this.containerSelector);
1114
+ }
1115
+ return this.containerSelector();
1116
+ }
1117
+ thumbsSelector = ".thumb";
1118
+ getThumbsStrategy = "default";
1119
+ getThumbsTransform;
1120
+ getThumbs(html) {
1121
+ if (!html) return [];
1122
+ let thumbs;
1123
+ if (this.getThumbsStrategy === "auto") {
1124
+ if (typeof this.containerSelector !== "string") return [];
1125
+ const container = html.querySelector(this.containerSelector);
1126
+ thumbs = [...container?.children || []];
1127
+ }
1128
+ thumbs = Array.from(html.querySelectorAll(this.thumbsSelector));
1129
+ if (typeof this.getThumbsTransform === "function") {
1130
+ thumbs.forEach(this.getThumbsTransform);
1131
+ }
1132
+ return thumbs;
1133
+ }
1134
+ paginationStrategyOptions = {};
1135
+ paginationStrategy;
1136
+ customDataSelectorFns = [
1137
+ "filterInclude",
1138
+ "filterExclude",
1139
+ "filterDuration"
1140
+ ];
1141
+ animatePreview;
1142
+ storeOptions;
1143
+ createStore() {
1144
+ const config = { ...StoreStateDefault, ...this.storeOptions };
1145
+ this.store = new jabroniOutfit.JabronioStore(config);
1146
+ return this.store;
1147
+ }
1148
+ schemeOptions = [];
1149
+ createGui() {
1150
+ const scheme = jabroniOutfit.setupScheme(
1151
+ this.schemeOptions,
1152
+ DefaultScheme
1153
+ );
1154
+ this.gui = new jabroniOutfit.JabronioGUI(scheme, this.store);
1155
+ return this.gui;
1156
+ }
1157
+ store;
1158
+ gui;
1159
+ dataManager;
1160
+ infiniteScroller;
1161
+ getPaginationData;
1162
+ resetInfiniteScroller() {
1163
+ this.infiniteScroller?.dispose();
1164
+ if (!this.paginationStrategy.hasPagination) return;
1165
+ this.infiniteScroller = InfiniteScroller.create(this);
1166
+ }
1167
+ gropeStrategy = "all-in-one";
1168
+ gropeInit() {
1169
+ if (!this.gropeStrategy) return;
1170
+ if (this.gropeStrategy === "all-in-one") {
1171
+ this.dataManager?.parseData(this.container, this.container);
1172
+ }
1173
+ if (this.gropeStrategy === "all-in-all") {
1174
+ getCommonParents(this.getThumbs(document.body)).forEach((c) => {
1175
+ this.dataManager.parseData(c, c, true);
1176
+ });
1177
+ }
1178
+ }
1179
+ get isEmbedded() {
1180
+ return window.self !== window.top;
1181
+ }
1182
+ setupStoreListeners() {
1183
+ const eventsMap = {
1184
+ "sort by duration": {
1185
+ action: (direction2) => this.dataManager.sortBy("duration", direction2)
1186
+ }
1187
+ };
1188
+ let lastEvent;
1189
+ let direction = true;
1190
+ this.store.eventSubject.subscribe((event) => {
1191
+ if (event === lastEvent) {
1192
+ direction = !direction;
1193
+ } else {
1194
+ lastEvent = event;
1195
+ direction = true;
1196
+ }
1197
+ if (event in eventsMap) {
1198
+ const ev = eventsMap[event];
1199
+ ev?.action(direction);
1200
+ }
1201
+ });
1202
+ this.store.stateSubject.subscribe((a) => {
1203
+ this.dataManager.applyFilters(a);
1204
+ });
1205
+ }
1206
+ dataManagerOptions = {};
1207
+ setupDataManager() {
1208
+ this.dataManager = new DataManager(this);
1209
+ if (this.dataManagerOptions) {
1210
+ Object.assign(this.dataManager, this.dataManagerOptions);
1211
+ }
1212
+ return this.dataManager;
1213
+ }
1214
+ mutationObservers = [];
1215
+ resetOnPaginationOrContainerDeath = true;
1216
+ resetOn() {
1217
+ if (!this.resetOnPaginationOrContainerDeath) return;
1218
+ const observables = [
1219
+ this.container,
1220
+ this.intersectionObservable || this.paginationStrategy.getPaginationElement()
1221
+ ].filter(Boolean);
1222
+ if (observables.length === 0) return;
1223
+ observables.forEach((o) => {
1224
+ const observer = waitForElementToDisappear(o, () => {
1225
+ this.reset();
1226
+ });
1227
+ this.mutationObservers.push(observer);
1228
+ });
1229
+ }
1230
+ onResetCallback;
1231
+ reset() {
1232
+ this.mutationObservers.forEach((o) => {
1233
+ o.disconnect();
1234
+ });
1235
+ this.mutationObservers = [];
1236
+ this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
1237
+ this.setupDataManager();
1238
+ this.setupStoreListeners();
1239
+ this.resetInfiniteScroller();
1240
+ this.container && this.animatePreview?.(this.container);
1241
+ this.gropeInit();
1242
+ this.onResetCallback?.();
1243
+ this.resetOn();
1244
+ }
1245
+ constructor(options) {
1246
+ if (this.isEmbedded) throw Error("Embedded is not supported");
1247
+ Object.assign(this, options);
1248
+ this.paginationStrategy = getPaginationStrategy(this.paginationStrategyOptions);
1249
+ this.store = this.createStore();
1250
+ this.gui = this.createGui();
1251
+ this.dataManager = this.setupDataManager();
1252
+ this.reset();
1253
+ }
1254
+ }
1255
+
1256
+ function onPointerOverAndLeave(container, subjectSelector, onOver, onLeave) {
1257
+ let target;
1258
+ let onOverFinally;
1259
+ function handleLeaveEvent() {
1260
+ onLeave?.(target);
1261
+ onOverFinally?.();
1262
+ target = void 0;
1263
+ }
1264
+ function handleEvent(e) {
1265
+ const currentTarget = e.target;
1266
+ if (!subjectSelector(currentTarget) || target === currentTarget) return;
1267
+ target = currentTarget;
1268
+ const result = onOver(target);
1269
+ onOverFinally = result?.onOverCallback;
1270
+ const leaveSubject = result?.leaveTarget || target;
1271
+ leaveSubject.addEventListener("pointerleave", handleLeaveEvent, {
1272
+ once: true
1273
+ });
1274
+ }
1275
+ container.addEventListener("pointerover", handleEvent);
1276
+ }
1277
+
1278
+ class Tick {
1279
+ constructor(delay, startImmediate = true) {
1280
+ this.delay = delay;
1281
+ this.startImmediate = startImmediate;
1282
+ }
1283
+ tick;
1284
+ callbackFinal;
1285
+ start(callback, callbackFinal) {
1286
+ this.stop();
1287
+ this.callbackFinal = callbackFinal;
1288
+ if (this.startImmediate) callback();
1289
+ this.tick = window.setInterval(callback, this.delay);
1290
+ }
1291
+ stop() {
1292
+ if (this.tick !== void 0) {
1293
+ clearInterval(this.tick);
1294
+ this.tick = void 0;
1295
+ }
1296
+ if (this.callbackFinal) {
1297
+ this.callbackFinal();
1298
+ this.callbackFinal = void 0;
1299
+ }
1300
+ }
1301
+ }
1302
+
1303
+ function circularShift(n, c = 6, s = 1) {
1304
+ return (n + s) % c || c;
1305
+ }
1306
+
1307
+ const $ = _unsafeWindow.$;
1308
+ _GM_addStyle(`
1309
+ .item.private .thumb, .item .thumb.private { opacity: 1 !important; }
1310
+ .haveNoAccess { background: linear-gradient(to bottom, #b50000 0%, #2c2c2c 100%) red !important; }
1311
+ .haveAccess { background: linear-gradient(to bottom, #4e9299 0%, #2c2c2c 100%) green !important; }
1312
+ .friend-button { background: radial-gradient(#5ccbf4, #e1ccb1) !important; }
1313
+ `);
1314
+ const IS_MEMBER_PAGE = /^(\/members\/\d+\/|\/my\/)$/.test(location.pathname);
1315
+ const IS_MESSAGES = /^\/my\/messages\//.test(location.pathname);
1316
+ const IS_COMMUNITY_LIST = /\/members\/$/.test(location.pathname);
1317
+ const IS_VIDEO_PAGE = /^(\/videos)?\/\d+\//.test(location.pathname);
1318
+ const IS_LOGGED_IN = document.cookie.includes("kt_member");
1319
+ const rules = new RulesGlobal({
1320
+ containerSelector: '[id*="playlist"]:has(> .item .title),[id*="videos"]:has(> .item .title),form:has(>.item .title)',
1321
+ paginationStrategyOptions: {
1322
+ paginationSelector: ".pagination:not([id *= member])",
1323
+ overwritePaginationLast: IS_MEMBER_PAGE ? () => 1 : (x) => x === 9 ? 9999 : x
1324
+ },
1325
+ getThumbImgDataAttrSelector: "data-original",
1326
+ getThumbImgDataStrategy: "auto",
1327
+ thumbsSelector: ".list-videos .item, .playlist .item, .list-playlists > div > .item, .item:has(.title)",
1328
+ gropeStrategy: "all-in-all",
1329
+ getThumbDataStrategy: "auto-select",
1330
+ customThumbDataSelectors: {
1331
+ private: { type: "boolean", selector: "[class*=private]" }
1332
+ },
1333
+ customDataSelectorFns: [
1334
+ "filterInclude",
1335
+ "filterExclude",
1336
+ "filterDuration",
1337
+ {
1338
+ filterPrivate: (e, state) => state.filterPrivate && e.private
1339
+ },
1340
+ {
1341
+ filterPublic: (e, state) => state.filterPublic && !e.private
1342
+ }
1343
+ ],
1344
+ schemeOptions: [
1345
+ "Text Filter",
1346
+ "Duration Filter",
1347
+ "Privacy Filter",
1348
+ "Badge",
1349
+ {
1350
+ title: "Advanced",
1351
+ content: [
1352
+ {
1353
+ autoRequestAccess: false,
1354
+ label: "auto send friend request on check access"
1355
+ }
1356
+ ]
1357
+ }
1358
+ ],
1359
+ animatePreview
1360
+ });
1361
+ function animatePreview(container) {
1362
+ const tick = new Tick(500);
1363
+ function killjquery(n = 10) {
1364
+ if (n > 0) {
1365
+ n--;
1366
+ $("img[data-cnt]").off();
1367
+ setTimeout(() => killjquery(n), 250);
1368
+ }
1369
+ }
1370
+ killjquery();
1371
+ function rotateImg(src, count) {
1372
+ return src.replace(/(\d)(?=\.jpg$)/, (_, n) => `${circularShift(parseInt(n), count)}`);
1373
+ }
1374
+ onPointerOverAndLeave(
1375
+ container,
1376
+ (target) => target.tagName === "IMG" && target.classList.contains("thumb") && !!target.getAttribute("src") && !/data:image|avatar/.test(target.getAttribute("src")),
1377
+ (_target) => {
1378
+ const target = _target;
1379
+ const origin = target.src;
1380
+ const count = parseInt(target.getAttribute("data-cnt")) || 5;
1381
+ tick.start(
1382
+ () => {
1383
+ target.src = rotateImg(target.src, count);
1384
+ },
1385
+ () => {
1386
+ target.src = origin;
1387
+ }
1388
+ );
1389
+ return {
1390
+ leaveTarget: target.closest(".item")
1391
+ };
1392
+ },
1393
+ () => tick.stop()
1394
+ );
1395
+ }
1396
+ const createDownloadButton = () => downloader({
1397
+ append: ".tabs-menu > ul",
1398
+ after: "",
1399
+ button: '<li><a href="#tab_comments" class="toggle-button" style="text-decoration: none;">download 📼</a></li>',
1400
+ cbBefore: () => $(".fp-ui").click()
1401
+ });
1402
+ const DEFAULT_FRIEND_REQUEST_FORMDATA = objectToFormData({
1403
+ message: "",
1404
+ action: "add_to_friends_complete",
1405
+ function: "get_block",
1406
+ block_id: "member_profile_view_view_profile",
1407
+ format: "json",
1408
+ mode: "async"
1409
+ });
1410
+ const lskdb = new LSKDB();
1411
+ async function friendRequest(id) {
1412
+ const url = Number.isInteger(id) ? `${location.origin}/members/${id}/` : id;
1413
+ await fetch(url, { body: DEFAULT_FRIEND_REQUEST_FORMDATA, method: "post" });
1414
+ }
1415
+ function getMemberLinks(e) {
1416
+ return Array.from(
1417
+ e?.querySelectorAll(".item > a") || [],
1418
+ (l) => l.href
1419
+ ).filter((l) => /\/members\/\d+\/$/.test(l));
1420
+ }
1421
+ async function getMemberFriends(id) {
1422
+ const url = new URL(
1423
+ IS_COMMUNITY_LIST ? "/members/" : `/members/${id}/friends/`,
1424
+ location.origin
1425
+ );
1426
+ const doc = await fetchHtml(url.href);
1427
+ const paginationStrategy = getPaginationStrategy({
1428
+ doc,
1429
+ url,
1430
+ overwritePaginationLast: (x) => x === 9 ? 999 : x
1431
+ });
1432
+ const gen = InfiniteScroller.generatorForPaginationStrategy(paginationStrategy);
1433
+ for (const url2 in gen) {
1434
+ const doc2 = await fetchHtml(url2);
1435
+ getMemberLinks(doc2).forEach((a) => {
1436
+ const id2 = a.match(/\d+/)?.[0];
1437
+ lskdb.setKey(id2);
1438
+ });
1439
+ }
1440
+ await processFriendship();
1441
+ }
1442
+ let processFriendshipStarted = false;
1443
+ async function processFriendship(batchSize = 1, interval = 5e3) {
1444
+ if (lskdb.isLocked()) return;
1445
+ const friendlist = lskdb.getKeys(batchSize);
1446
+ if (friendlist?.length < 1) return;
1447
+ if (!processFriendshipStarted) {
1448
+ processFriendshipStarted = true;
1449
+ console.log("processFriendshipStarted");
1450
+ }
1451
+ lskdb.lock(true);
1452
+ const urls = friendlist.map((id) => `${location.origin}/members/${id}/`);
1453
+ for (const url of urls) {
1454
+ await wait(interval);
1455
+ await friendRequest(url);
1456
+ }
1457
+ lskdb.lock(false);
1458
+ await processFriendship();
1459
+ }
1460
+ function createPrivateVideoFriendButton() {
1461
+ if (!document.querySelector(".no-player")) return;
1462
+ const member = document.querySelector(".no-player a")?.href;
1463
+ const button = parseHtml(
1464
+ '<button class="friend-button"><span>Friend Request</span></button>'
1465
+ );
1466
+ document.querySelector(".no-player .message")?.append(button);
1467
+ button.addEventListener("click", () => friendRequest(member), { once: true });
1468
+ }
1469
+ function createFriendButton() {
1470
+ const button = parseHtml(
1471
+ '<a href="#friend_everyone" class="button friend-button"><span>Friend Everyone</span></a>'
1472
+ );
1473
+ document.querySelector(".main-container-user > .headline, .headline")?.append(button);
1474
+ const memberid = location.pathname.match(/\d+/)?.[0];
1475
+ button.addEventListener(
1476
+ "click",
1477
+ () => {
1478
+ button.style.background = "radial-gradient(#ff6114, #5babc4)";
1479
+ button.innerText = "processing requests";
1480
+ getMemberFriends(memberid).then(() => {
1481
+ button.style.background = "radial-gradient(blue, lightgreen)";
1482
+ button.innerText = "friend requests sent";
1483
+ });
1484
+ },
1485
+ { once: true }
1486
+ );
1487
+ }
1488
+ async function requestAccess() {
1489
+ checkPrivateVidsAccess();
1490
+ setTimeout(processFriendship, 5e3);
1491
+ }
1492
+ async function checkPrivateVidsAccess() {
1493
+ const checkAccess = async (item) => {
1494
+ const videoURL = item.firstElementChild.href;
1495
+ const doc = await fetchHtml(videoURL);
1496
+ if (!doc.querySelector(".player")) return;
1497
+ const haveAccess = !doc.querySelector(".no-player");
1498
+ if (!haveAccess && rules.store.state.autoRequestAccess) {
1499
+ const anchor = doc.querySelector(".message a");
1500
+ const uid = anchor?.href.match(/\d+/)?.at(-1);
1501
+ lskdb.setKey(uid);
1502
+ }
1503
+ item.classList.add(haveAccess ? "haveAccess" : "haveNoAccess");
1504
+ };
1505
+ const thumbs = document.querySelectorAll(
1506
+ ".item.private:not(.haveAccess,.haveNoAccess)"
1507
+ );
1508
+ for (const thumb of thumbs) {
1509
+ await checkAccess(thumb);
1510
+ }
1511
+ }
1512
+ function getUserInfo(e) {
1513
+ const uploadedCount = querySelectorLastNumber("#list_videos_uploaded_videos strong", e);
1514
+ const friendsCount = querySelectorLastNumber("#list_members_friends .headline", e);
1515
+ return { uploadedCount, friendsCount };
1516
+ }
1517
+ async function acceptFriendRequest(id) {
1518
+ const url = new URL(`/my/messages/${id}/`, location.origin);
1519
+ const memberUrl = new URL(`/members/${id}/`, location.origin);
1520
+ await fetch(url, {
1521
+ headers: {
1522
+ Accept: "*/*",
1523
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
1524
+ },
1525
+ body: `action=confirm_add_to_friends&message_from_user_id=${id}&function=get_block&block_id=list_messages_my_conversation_messages&confirm=Confirm&format=json&mode=async`,
1526
+ method: "POST"
1527
+ });
1528
+ await fetchHtml(memberUrl).then(
1529
+ (doc) => console.log("userInfo", memberUrl.href, getUserInfo(doc))
1530
+ );
1531
+ }
1532
+ async function clearMessages() {
1533
+ const pages = InfiniteScroller.generatorForPaginationStrategy(
1534
+ getPaginationStrategy({
1535
+ overwritePaginationLast: (x) => x === 9 ? 999 : x
1536
+ })
1537
+ );
1538
+ for await (const p of pages) {
1539
+ const doc = await fetchHtml(p.url);
1540
+ const messages = Array.from(
1541
+ doc.querySelectorAll(
1542
+ "#list_members_my_conversations_items .item > a"
1543
+ ) || []
1544
+ ).map((a) => a.href);
1545
+ for (const m of messages) {
1546
+ await checkMessageHistory(m);
1547
+ }
1548
+ }
1549
+ async function deleteMessage(url, id) {
1550
+ const deleteURL = `${url}?mode=async&format=json&function=get_block&block_id=list_messages_my_conversation_messages&action=delete_conversation&conversation_user_id=${id}`;
1551
+ await fetch(deleteURL);
1552
+ }
1553
+ async function getConversation(url) {
1554
+ const doc = await fetchHtml(url);
1555
+ const hasFriendRequest = !!doc.querySelector("input[value=confirm_add_to_friends]");
1556
+ const originalText = querySelectorText(doc, ".original-text");
1557
+ const id = url.match(/\d+/)?.[0];
1558
+ const messages = querySelectorText(doc, ".list-messages");
1559
+ return {
1560
+ id,
1561
+ hasFriendRequest,
1562
+ originalText,
1563
+ messages
1564
+ };
1565
+ }
1566
+ async function checkMessageHistory(url) {
1567
+ const { originalText, hasFriendRequest, id, messages } = await getConversation(url);
1568
+ if (!(originalText || hasFriendRequest)) {
1569
+ await deleteMessage(url, id);
1570
+ } else {
1571
+ console.log({ originalText, url, messages });
1572
+ if (hasFriendRequest) {
1573
+ await acceptFriendRequest(id);
1574
+ }
1575
+ }
1576
+ }
1577
+ }
1578
+ const FRIEND_REQUEST_INTERVAL = 5e3;
1579
+ if (IS_LOGGED_IN) {
1580
+ setTimeout(processFriendship, FRIEND_REQUEST_INTERVAL);
1581
+ if (IS_MEMBER_PAGE || IS_COMMUNITY_LIST) {
1582
+ createFriendButton();
1583
+ }
1584
+ }
1585
+ if (IS_VIDEO_PAGE) {
1586
+ createDownloadButton();
1587
+ createPrivateVideoFriendButton();
1588
+ }
1589
+ if (IS_MESSAGES) {
1590
+ const button = parseHtml("<button>clear messages</button>");
1591
+ document.querySelector(".headline")?.append(button);
1592
+ button.addEventListener("click", clearMessages);
1593
+ }
1594
+ rules.store.eventSubject.subscribe((event) => {
1595
+ if (event.includes("check access")) {
1596
+ requestAccess();
1597
+ }
1598
+ });
1599
+
1600
+ })(jabronioutfit);
1601
+
1602
+ })();