perfectionist-plugins 2.4.7 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -52
- package/auto-accept.js +296 -294
- package/auto-claimer.js +1 -37
- package/auto-read.js +107 -42
- package/ban-blocker.js +483 -677
- package/better-client.js +362 -793
- package/dodge-helper.js +375 -673
- package/hide-unowned-skins.js +63 -686
- package/package.json +40 -40
- package/perfectionist-utils.js +540 -301
- package/unlock-profile-skins.js +174 -620
package/better-client.js
CHANGED
|
@@ -1,793 +1,362 @@
|
|
|
1
|
-
const PLUGIN_ID = "better-client";
|
|
2
|
-
const version = "
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
let mediaObserver = null;
|
|
367
|
-
let mediaScanObserver = null;
|
|
368
|
-
let mediaVisibilityListener = null;
|
|
369
|
-
let mediaPaused = new WeakSet();
|
|
370
|
-
|
|
371
|
-
let imageObserver = null;
|
|
372
|
-
let imageQueue = [];
|
|
373
|
-
let imageFlushTimer = null;
|
|
374
|
-
let imageFlushIdle = false;
|
|
375
|
-
let imageVisibilityListener = null;
|
|
376
|
-
|
|
377
|
-
function markAsyncDecode(img) {
|
|
378
|
-
if (!img || img.tagName !== "IMG" || img.decoding === "async") return 0;
|
|
379
|
-
try {
|
|
380
|
-
img.decoding = "async";
|
|
381
|
-
return 1;
|
|
382
|
-
} catch {
|
|
383
|
-
return 0;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function documentView() {
|
|
388
|
-
try {
|
|
389
|
-
return document.defaultView ?? null;
|
|
390
|
-
} catch {
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function pageHidden() {
|
|
396
|
-
try {
|
|
397
|
-
return document.hidden === true;
|
|
398
|
-
} catch {
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function scheduleImageFlush() {
|
|
404
|
-
if (imageFlushTimer !== null) return;
|
|
405
|
-
const view = documentView();
|
|
406
|
-
if (typeof view?.requestIdleCallback === "function") {
|
|
407
|
-
imageFlushIdle = true;
|
|
408
|
-
imageFlushTimer = view.requestIdleCallback(flushImageQueue, { timeout: 600 });
|
|
409
|
-
} else {
|
|
410
|
-
imageFlushIdle = false;
|
|
411
|
-
imageFlushTimer = setTimeout(flushImageQueue, 150);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function cancelImageFlush() {
|
|
416
|
-
if (imageFlushTimer === null) return;
|
|
417
|
-
const view = documentView();
|
|
418
|
-
if (imageFlushIdle && typeof view?.cancelIdleCallback === "function") view.cancelIdleCallback(imageFlushTimer);
|
|
419
|
-
else clearTimeout(imageFlushTimer);
|
|
420
|
-
imageFlushTimer = null;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function scanImages(root) {
|
|
424
|
-
let found;
|
|
425
|
-
try {
|
|
426
|
-
found = root.querySelectorAll("img[src]:not([decoding='async'])");
|
|
427
|
-
} catch {
|
|
428
|
-
return 0;
|
|
429
|
-
}
|
|
430
|
-
let marked = 0;
|
|
431
|
-
for (const img of found) marked += markAsyncDecode(img);
|
|
432
|
-
return marked;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function flushImageQueue() {
|
|
436
|
-
imageFlushTimer = null;
|
|
437
|
-
if (pageHidden()) {
|
|
438
|
-
scheduleImageFlush();
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
const batch = imageQueue.splice(0, IMAGE_BATCH);
|
|
442
|
-
for (const root of batch) {
|
|
443
|
-
if (!root?.isConnected) continue;
|
|
444
|
-
markAsyncDecode(root);
|
|
445
|
-
scanImages(root);
|
|
446
|
-
}
|
|
447
|
-
if (imageQueue.length > 0) scheduleImageFlush();
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function enqueueImages(root) {
|
|
451
|
-
if (!root || root.nodeType !== 1) return 0;
|
|
452
|
-
if (imageQueue.length >= IMAGE_QUEUE_LIMIT) return 0;
|
|
453
|
-
if (root.tagName !== "IMG" && root.childElementCount === 0) return 0;
|
|
454
|
-
imageQueue.push(root);
|
|
455
|
-
scheduleImageFlush();
|
|
456
|
-
return 1;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function pauseDecorativeVideo(video) {
|
|
460
|
-
if (!video || video.tagName !== "VIDEO" || video.paused) return;
|
|
461
|
-
if (!video.autoplay && !video.muted) return;
|
|
462
|
-
try {
|
|
463
|
-
video.pause();
|
|
464
|
-
mediaPaused.add(video);
|
|
465
|
-
} catch {}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function resumeDecorativeVideo(video) {
|
|
469
|
-
if (!video || !mediaPaused.has(video)) return;
|
|
470
|
-
mediaPaused.delete(video);
|
|
471
|
-
try {
|
|
472
|
-
const play = video.play?.();
|
|
473
|
-
if (play?.catch) play.catch(() => {});
|
|
474
|
-
} catch {}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function installMediaIdle() {
|
|
478
|
-
if (typeof document === "undefined" || !document.documentElement) return () => {};
|
|
479
|
-
const Obs = document.defaultView?.IntersectionObserver ?? IntersectionObserver;
|
|
480
|
-
const Mut = document.defaultView?.MutationObserver ?? MutationObserver;
|
|
481
|
-
if (!Obs || !Mut) return () => {};
|
|
482
|
-
mediaObserver = new Obs(
|
|
483
|
-
(entries) => {
|
|
484
|
-
for (const entry of entries) {
|
|
485
|
-
if (entry.isIntersecting) resumeDecorativeVideo(entry.target);
|
|
486
|
-
else pauseDecorativeVideo(entry.target);
|
|
487
|
-
}
|
|
488
|
-
},
|
|
489
|
-
{ rootMargin: "240px" },
|
|
490
|
-
);
|
|
491
|
-
const scan = () => {
|
|
492
|
-
for (const video of document.querySelectorAll("video")) {
|
|
493
|
-
if (video.__betterMedia) continue;
|
|
494
|
-
video.__betterMedia = true;
|
|
495
|
-
try {
|
|
496
|
-
mediaObserver.observe(video);
|
|
497
|
-
} catch {}
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
scan();
|
|
501
|
-
mediaScanObserver = new Mut(scan);
|
|
502
|
-
try {
|
|
503
|
-
mediaScanObserver.observe(document.documentElement, { childList: true, subtree: true });
|
|
504
|
-
} catch {}
|
|
505
|
-
mediaVisibilityListener = () => {
|
|
506
|
-
if (pageHidden()) {
|
|
507
|
-
for (const video of document.querySelectorAll("video")) pauseDecorativeVideo(video);
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
scan();
|
|
511
|
-
for (const video of document.querySelectorAll("video")) {
|
|
512
|
-
if (video.getClientRects().length > 0) resumeDecorativeVideo(video);
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
document.addEventListener("visibilitychange", mediaVisibilityListener);
|
|
516
|
-
return () => {
|
|
517
|
-
document.removeEventListener("visibilitychange", mediaVisibilityListener);
|
|
518
|
-
mediaVisibilityListener = null;
|
|
519
|
-
mediaScanObserver?.disconnect();
|
|
520
|
-
mediaScanObserver = null;
|
|
521
|
-
mediaObserver?.disconnect();
|
|
522
|
-
mediaObserver = null;
|
|
523
|
-
mediaPaused = new WeakSet();
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function installImageDecode() {
|
|
528
|
-
if (typeof document === "undefined" || !document.documentElement) return () => {};
|
|
529
|
-
const Obs = document.defaultView?.MutationObserver ?? MutationObserver;
|
|
530
|
-
if (imageObserver || !Obs) return () => {};
|
|
531
|
-
imageObserver = new Obs((records) => {
|
|
532
|
-
let queued = 0;
|
|
533
|
-
for (const record of records) {
|
|
534
|
-
if (record.type === "attributes") {
|
|
535
|
-
if (record.target?.tagName === "IMG") queued += enqueueImages(record.target);
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
const nodes = record.addedNodes;
|
|
539
|
-
for (let index = 0; index < nodes.length; index++) {
|
|
540
|
-
const node = nodes[index];
|
|
541
|
-
if (node?.nodeType === 1) queued += enqueueImages(node);
|
|
542
|
-
}
|
|
543
|
-
if (queued > 400) break;
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
try {
|
|
547
|
-
imageObserver.observe(document.documentElement, { childList: true, subtree: true });
|
|
548
|
-
} catch {
|
|
549
|
-
imageObserver = null;
|
|
550
|
-
return () => {};
|
|
551
|
-
}
|
|
552
|
-
enqueueImages(document.documentElement);
|
|
553
|
-
imageVisibilityListener = () => {
|
|
554
|
-
if (pageHidden()) return;
|
|
555
|
-
enqueueImages(document.documentElement);
|
|
556
|
-
scheduleImageFlush();
|
|
557
|
-
};
|
|
558
|
-
document.addEventListener("visibilitychange", imageVisibilityListener);
|
|
559
|
-
return () => {
|
|
560
|
-
document.removeEventListener("visibilitychange", imageVisibilityListener);
|
|
561
|
-
imageVisibilityListener = null;
|
|
562
|
-
cancelImageFlush();
|
|
563
|
-
imageQueue = [];
|
|
564
|
-
imageObserver?.disconnect();
|
|
565
|
-
imageObserver = null;
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function ungateNode(node) {
|
|
570
|
-
if (!node?.querySelectorAll) return 0;
|
|
571
|
-
let changed = 0;
|
|
572
|
-
const candidates = [];
|
|
573
|
-
try {
|
|
574
|
-
if (node.matches?.(BLOCK_MARKER)) candidates.push(node);
|
|
575
|
-
candidates.push(...node.querySelectorAll(BLOCK_MARKER));
|
|
576
|
-
} catch {
|
|
577
|
-
return 0;
|
|
578
|
-
}
|
|
579
|
-
for (const element of candidates) {
|
|
580
|
-
if (element.disabled === true) {
|
|
581
|
-
element.disabled = false;
|
|
582
|
-
changed += 1;
|
|
583
|
-
}
|
|
584
|
-
if (element.hasAttribute?.("disabled")) {
|
|
585
|
-
element.removeAttribute("disabled");
|
|
586
|
-
changed += 1;
|
|
587
|
-
}
|
|
588
|
-
if (element.getAttribute?.("aria-disabled") === "true") {
|
|
589
|
-
element.removeAttribute("aria-disabled");
|
|
590
|
-
changed += 1;
|
|
591
|
-
}
|
|
592
|
-
if (element.style?.pointerEvents === "none") {
|
|
593
|
-
element.style.pointerEvents = "";
|
|
594
|
-
changed += 1;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return changed;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function ungatePass() {
|
|
601
|
-
if (!active || phase !== "InProgress") return 0;
|
|
602
|
-
let nodes;
|
|
603
|
-
try {
|
|
604
|
-
nodes = document.querySelectorAll(UNGATE_SELECTOR);
|
|
605
|
-
} catch {
|
|
606
|
-
return 0;
|
|
607
|
-
}
|
|
608
|
-
let changed = 0;
|
|
609
|
-
for (const node of nodes) changed += ungateNode(node);
|
|
610
|
-
return changed;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function ungateDelay() {
|
|
614
|
-
return ungateIdle >= UNGATE_BACKOFF_AFTER ? UNGATE_IDLE_MS : UNGATE_MS;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function ungateTick() {
|
|
618
|
-
ungateTimer = null;
|
|
619
|
-
if (!active || phase !== "InProgress") return;
|
|
620
|
-
if (pageHidden()) {
|
|
621
|
-
ungateTimer = setTimeout(ungateTick, UNGATE_IDLE_MS);
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
const changed = ungatePass();
|
|
625
|
-
ungateIdle = changed > 0 ? 0 : ungateIdle + 1;
|
|
626
|
-
ungateTimer = setTimeout(ungateTick, ungateDelay());
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function startUngate() {
|
|
630
|
-
if (ungateTimer) return;
|
|
631
|
-
ungateIdle = 0;
|
|
632
|
-
ungateTick();
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function stopUngate() {
|
|
636
|
-
clearTimeout(ungateTimer);
|
|
637
|
-
ungateTimer = null;
|
|
638
|
-
ungateIdle = 0;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function onPhase(next) {
|
|
642
|
-
const value = typeof next === "string" ? next : next?.phase ?? phase;
|
|
643
|
-
if (!value || value === phase) {
|
|
644
|
-
if (value === "InProgress") startUngate();
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
phase = value;
|
|
648
|
-
clearVolatileCache();
|
|
649
|
-
if (phase === "InProgress") startUngate();
|
|
650
|
-
else stopUngate();
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
async function refreshPhase() {
|
|
654
|
-
if (!active || pageHidden()) return;
|
|
655
|
-
try {
|
|
656
|
-
const current = await api?.lcu?.get?.("/lol-gameflow/v1/gameflow-phase");
|
|
657
|
-
if (current) onPhase(current);
|
|
658
|
-
} catch {}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function startPhaseWatch() {
|
|
662
|
-
stopPhaseWatch();
|
|
663
|
-
if (unsubscribes.length === 0) {
|
|
664
|
-
phaseTimer = setInterval(() => {
|
|
665
|
-
if (!pageHidden()) refreshPhase();
|
|
666
|
-
}, PHASE_POLL_FALLBACK_MS);
|
|
667
|
-
}
|
|
668
|
-
phaseWatchListener = () => {
|
|
669
|
-
if (!pageHidden()) refreshPhase();
|
|
670
|
-
};
|
|
671
|
-
document.addEventListener("visibilitychange", phaseWatchListener);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
function stopPhaseWatch() {
|
|
675
|
-
clearInterval(phaseTimer);
|
|
676
|
-
phaseTimer = null;
|
|
677
|
-
if (phaseWatchListener) {
|
|
678
|
-
document.removeEventListener("visibilitychange", phaseWatchListener);
|
|
679
|
-
phaseWatchListener = null;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function applyPerformanceMode() {
|
|
684
|
-
if (!api) return;
|
|
685
|
-
if (performanceMode) {
|
|
686
|
-
if (!removePerformanceStyle) removePerformanceStyle = api.addStyle(PERF_STYLE_ID, PERFORMANCE_CSS);
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
if (removePerformanceStyle) {
|
|
690
|
-
removePerformanceStyle();
|
|
691
|
-
removePerformanceStyle = null;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function installNetwork(env) {
|
|
696
|
-
const installed = [];
|
|
697
|
-
try {
|
|
698
|
-
const originalFetch = env.fetch;
|
|
699
|
-
env.fetch = createFetch(env);
|
|
700
|
-
installed.push(() => {
|
|
701
|
-
env.fetch = originalFetch;
|
|
702
|
-
});
|
|
703
|
-
} catch {}
|
|
704
|
-
const xhr = installXhr(env);
|
|
705
|
-
if (xhr) installed.push(xhr);
|
|
706
|
-
const beacon = installBeacon(env);
|
|
707
|
-
if (beacon) installed.push(beacon);
|
|
708
|
-
return () => {
|
|
709
|
-
for (const restore of installed.reverse()) {
|
|
710
|
-
try {
|
|
711
|
-
restore();
|
|
712
|
-
} catch {}
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
function onEnable(utils) {
|
|
718
|
-
api = utils;
|
|
719
|
-
active = true;
|
|
720
|
-
restores = [installNetwork(window), installImageDecode(), installMediaIdle()];
|
|
721
|
-
unsubscribes = [];
|
|
722
|
-
try {
|
|
723
|
-
const off = api.subscribe("/lol-gameflow/v1/gameflow-phase", onPhase);
|
|
724
|
-
if (off) unsubscribes.push(off);
|
|
725
|
-
} catch {}
|
|
726
|
-
try {
|
|
727
|
-
api
|
|
728
|
-
.lcu?.get?.("/lol-gameflow/v1/gameflow-phase")
|
|
729
|
-
.then((value) => onPhase(value))
|
|
730
|
-
.catch(() => {});
|
|
731
|
-
} catch {}
|
|
732
|
-
startPhaseWatch();
|
|
733
|
-
performanceMode = api.getOption?.(PLUGIN_ID, "performanceMode") === true;
|
|
734
|
-
applyPerformanceMode();
|
|
735
|
-
if (phase === "InProgress") startUngate();
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
function onDisable() {
|
|
739
|
-
active = false;
|
|
740
|
-
stopPhaseWatch();
|
|
741
|
-
stopUngate();
|
|
742
|
-
for (const off of unsubscribes) {
|
|
743
|
-
try {
|
|
744
|
-
off();
|
|
745
|
-
} catch {}
|
|
746
|
-
}
|
|
747
|
-
unsubscribes = [];
|
|
748
|
-
for (const restore of restores.reverse()) {
|
|
749
|
-
try {
|
|
750
|
-
restore();
|
|
751
|
-
} catch {}
|
|
752
|
-
}
|
|
753
|
-
restores = [];
|
|
754
|
-
if (removePerformanceStyle) {
|
|
755
|
-
removePerformanceStyle();
|
|
756
|
-
removePerformanceStyle = null;
|
|
757
|
-
}
|
|
758
|
-
performanceMode = false;
|
|
759
|
-
inflight.clear();
|
|
760
|
-
blockDecisions.clear();
|
|
761
|
-
for (const key of [...cache.keys()]) evictOne(key);
|
|
762
|
-
cacheBytes = 0;
|
|
763
|
-
api = null;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
function onOptionChange(key, value) {
|
|
767
|
-
if (key === "performanceMode") {
|
|
768
|
-
performanceMode = value === true;
|
|
769
|
-
applyPerformanceMode();
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
export function init() {
|
|
774
|
-
const registerWith = (utils) => {
|
|
775
|
-
utils.registerPlugin({
|
|
776
|
-
id: PLUGIN_ID,
|
|
777
|
-
name: "BetterClient",
|
|
778
|
-
description:
|
|
779
|
-
"Debloats the client: blocks telemetry and analytics calls, merges duplicate requests, caches static client data, decodes images asynchronously and keeps settings, chat and friend requests usable during a game.",
|
|
780
|
-
version,
|
|
781
|
-
options: OPTIONS,
|
|
782
|
-
onEnable,
|
|
783
|
-
onDisable,
|
|
784
|
-
onOptionChange,
|
|
785
|
-
});
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
if (window.PerfectionistUtils) {
|
|
789
|
-
registerWith(window.PerfectionistUtils);
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
addEventListener("perfectionist:utils-ready", (e) => registerWith(e.detail ?? window.PerfectionistUtils), { once: true });
|
|
793
|
-
}
|
|
1
|
+
const PLUGIN_ID = "better-client";
|
|
2
|
+
const version = "2.0.0";
|
|
3
|
+
|
|
4
|
+
const PHASE_URI = "/lol-gameflow/v1/gameflow-phase";
|
|
5
|
+
|
|
6
|
+
// The client reports UI events, metrics and traces through these local endpoints and through Datadog RUM.
|
|
7
|
+
// None of it is needed for the client to work; blocked calls get an empty 200 so callers carry on.
|
|
8
|
+
const TELEMETRY_LOCAL = /^\/(?:telemetry\/|lol-telemetry\/|tracing\/v1\/|riotclient\/addorupdatemetric|lol-rms\/v1\/telemetry|error-monitor\/v1\/ux-logs)/i;
|
|
9
|
+
const TELEMETRY_HOST = /(^|\.)(?:datadoghq\.(?:com|eu)|datadog-?intake\.\w+|sentry\.io|bugsnag\.com|browser-intake[\w.-]*|google-analytics\.com|googletagmanager\.com|doubleclick\.net|segment\.io|mixpanel\.com|amplitude\.com)$/i;
|
|
10
|
+
const LOCAL_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
11
|
+
const DECISION_LIMIT = 512;
|
|
12
|
+
const SWEEP_MS = 60 * 1000;
|
|
13
|
+
|
|
14
|
+
const BACKGROUND_CLASS = "perf-bc-background";
|
|
15
|
+
const LITE_CLASS = "perf-bc-lite";
|
|
16
|
+
const STYLE_ID = "perfectionist-better-client";
|
|
17
|
+
const CSS = `
|
|
18
|
+
html.${BACKGROUND_CLASS} *, html.${BACKGROUND_CLASS} *::before, html.${BACKGROUND_CLASS} *::after {
|
|
19
|
+
animation-play-state: paused !important;
|
|
20
|
+
}
|
|
21
|
+
html.${LITE_CLASS} *, html.${LITE_CLASS} *::before, html.${LITE_CLASS} *::after {
|
|
22
|
+
animation-duration: 1ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important;
|
|
23
|
+
transition-duration: 0s !important; transition-delay: 0s !important;
|
|
24
|
+
backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
|
|
25
|
+
}`;
|
|
26
|
+
|
|
27
|
+
const OPTIONS = [
|
|
28
|
+
{
|
|
29
|
+
key: "backgroundMode",
|
|
30
|
+
label: "Pause the client while you play",
|
|
31
|
+
description:
|
|
32
|
+
"While a game is running and the client is not focused, pauses the client's videos and animations. They resume the moment you open the client.",
|
|
33
|
+
default: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: "performanceMode",
|
|
37
|
+
label: "Performance mode",
|
|
38
|
+
description:
|
|
39
|
+
"For weaker PCs: stops looping background videos and animated effects, skips UI animations and turns off background blur.",
|
|
40
|
+
default: false,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
let api = null;
|
|
45
|
+
let active = false;
|
|
46
|
+
let phase = "None";
|
|
47
|
+
let backgroundMode = false;
|
|
48
|
+
let liteMode = false;
|
|
49
|
+
let unsubscribe = null;
|
|
50
|
+
let restores = [];
|
|
51
|
+
|
|
52
|
+
function option(key) {
|
|
53
|
+
return api?.getOption?.(PLUGIN_ID, key) === true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Telemetry */
|
|
57
|
+
|
|
58
|
+
const decisions = new Map();
|
|
59
|
+
|
|
60
|
+
function isBlocked(raw) {
|
|
61
|
+
const text = typeof raw === "string" ? raw : raw?.url ?? raw?.href ?? String(raw ?? "");
|
|
62
|
+
const known = decisions.get(text);
|
|
63
|
+
if (known !== undefined) return known;
|
|
64
|
+
let verdict = false;
|
|
65
|
+
if (text.startsWith("/") && !text.startsWith("//")) {
|
|
66
|
+
verdict = TELEMETRY_LOCAL.test(text);
|
|
67
|
+
} else {
|
|
68
|
+
try {
|
|
69
|
+
const url = new URL(text, location.href);
|
|
70
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
71
|
+
verdict = LOCAL_HOSTS.has(url.hostname) ? TELEMETRY_LOCAL.test(url.pathname) : TELEMETRY_HOST.test(url.hostname);
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
if (decisions.size >= DECISION_LIMIT) decisions.clear();
|
|
76
|
+
decisions.set(text, verdict);
|
|
77
|
+
return verdict;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function installFetch() {
|
|
81
|
+
const native = window.fetch;
|
|
82
|
+
const patched = function (input, init) {
|
|
83
|
+
if (active && isBlocked(typeof input === "string" ? input : input?.url ?? "")) {
|
|
84
|
+
return Promise.resolve(new Response("{}", { status: 200, headers: { "Content-Type": "application/json" } }));
|
|
85
|
+
}
|
|
86
|
+
return native.call(this, input, init);
|
|
87
|
+
};
|
|
88
|
+
window.fetch = patched;
|
|
89
|
+
return () => {
|
|
90
|
+
if (window.fetch === patched) window.fetch = native;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function finishBlockedXhr(xhr) {
|
|
95
|
+
try {
|
|
96
|
+
Object.defineProperty(xhr, "readyState", { configurable: true, get: () => 4 });
|
|
97
|
+
Object.defineProperty(xhr, "status", { configurable: true, get: () => 200 });
|
|
98
|
+
Object.defineProperty(xhr, "statusText", { configurable: true, get: () => "OK" });
|
|
99
|
+
Object.defineProperty(xhr, "responseText", { configurable: true, get: () => "{}" });
|
|
100
|
+
Object.defineProperty(xhr, "response", { configurable: true, get: () => (xhr.responseType === "json" ? {} : "{}") });
|
|
101
|
+
Object.defineProperty(xhr, "getAllResponseHeaders", { configurable: true, value: () => "content-type: application/json\r\n" });
|
|
102
|
+
Object.defineProperty(xhr, "getResponseHeader", { configurable: true, value: (name) => (/content-type/i.test(name) ? "application/json" : null) });
|
|
103
|
+
} catch {}
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
for (const type of ["readystatechange", "load", "loadend"]) {
|
|
106
|
+
try {
|
|
107
|
+
xhr.dispatchEvent(new Event(type));
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
110
|
+
}, 0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function installXhr() {
|
|
114
|
+
const proto = XMLHttpRequest.prototype;
|
|
115
|
+
const nativeOpen = proto.open;
|
|
116
|
+
const nativeSend = proto.send;
|
|
117
|
+
const open = function (method, url, ...rest) {
|
|
118
|
+
this.__perfBcUrl = url;
|
|
119
|
+
return nativeOpen.call(this, method, url, ...rest);
|
|
120
|
+
};
|
|
121
|
+
const send = function (body) {
|
|
122
|
+
if (active && this.__perfBcUrl !== undefined && isBlocked(String(this.__perfBcUrl))) {
|
|
123
|
+
finishBlockedXhr(this);
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return nativeSend.call(this, body);
|
|
127
|
+
};
|
|
128
|
+
proto.open = open;
|
|
129
|
+
proto.send = send;
|
|
130
|
+
return () => {
|
|
131
|
+
if (proto.open === open) proto.open = nativeOpen;
|
|
132
|
+
if (proto.send === send) proto.send = nativeSend;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function installBeacon() {
|
|
137
|
+
const nav = window.navigator;
|
|
138
|
+
if (typeof nav?.sendBeacon !== "function") return () => {};
|
|
139
|
+
const native = nav.sendBeacon;
|
|
140
|
+
const patched = function (url, data) {
|
|
141
|
+
if (active && isBlocked(String(url))) return true;
|
|
142
|
+
return native.call(this, url, data);
|
|
143
|
+
};
|
|
144
|
+
nav.sendBeacon = patched;
|
|
145
|
+
return () => {
|
|
146
|
+
if (nav.sendBeacon === patched) nav.sendBeacon = native;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Riot samples some sessions into Datadog RUM, which then records every interaction, resource and long task.
|
|
151
|
+
function quietDatadog() {
|
|
152
|
+
try {
|
|
153
|
+
window.DD_RUM?.setTrackingConsent?.("not-granted");
|
|
154
|
+
} catch {}
|
|
155
|
+
return () => {
|
|
156
|
+
try {
|
|
157
|
+
window.DD_RUM?.setTrackingConsent?.("granted");
|
|
158
|
+
} catch {}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Media governor
|
|
163
|
+
*
|
|
164
|
+
* The client keeps looping videos and lottie animations running while they are not rendered at all (the
|
|
165
|
+
* home screen's activity tabs alone keep ~20 videos decoding forever). Media is held while nobody can see it
|
|
166
|
+
* (not rendered, or the client is in the background during a game, or it is a decorative loop in performance
|
|
167
|
+
* mode) and resumed as soon as that stops being true. Only media held here is ever resumed. */
|
|
168
|
+
|
|
169
|
+
const held = new WeakSet();
|
|
170
|
+
const tracked = new Set();
|
|
171
|
+
let observer = null;
|
|
172
|
+
|
|
173
|
+
function inBackground() {
|
|
174
|
+
return backgroundMode && phase === "InProgress" && !document.hasFocus();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function lottieOf(element) {
|
|
178
|
+
return element.tagName === "LOL-UIKIT-LOTTIE" ? element.animation ?? null : null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isLoop(element) {
|
|
182
|
+
return element.tagName === "VIDEO" ? element.loop : lottieOf(element)?.loop === true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isPlaying(element) {
|
|
186
|
+
if (element.tagName === "VIDEO") return !element.paused;
|
|
187
|
+
const animation = lottieOf(element);
|
|
188
|
+
return Boolean(animation) && animation.isPaused === false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function rendered(element) {
|
|
192
|
+
return element.isConnected && element.getClientRects().length > 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function shouldHold(element, visible = rendered(element)) {
|
|
196
|
+
return !visible || inBackground() || (liteMode && isLoop(element));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function hold(element) {
|
|
200
|
+
if (!isPlaying(element)) return;
|
|
201
|
+
try {
|
|
202
|
+
if (element.tagName === "VIDEO") element.pause();
|
|
203
|
+
else lottieOf(element)?.pause();
|
|
204
|
+
held.add(element);
|
|
205
|
+
} catch {}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function release(element) {
|
|
209
|
+
if (!held.has(element)) return;
|
|
210
|
+
held.delete(element);
|
|
211
|
+
if (!element.isConnected) return;
|
|
212
|
+
try {
|
|
213
|
+
if (element.tagName === "VIDEO") {
|
|
214
|
+
if (element.loop || !element.ended) element.play()?.catch?.(() => {});
|
|
215
|
+
} else {
|
|
216
|
+
lottieOf(element)?.play();
|
|
217
|
+
}
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function settle(element, visible) {
|
|
222
|
+
if (shouldHold(element, visible)) hold(element);
|
|
223
|
+
else release(element);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function onMediaPlay(event) {
|
|
227
|
+
const element = event.target;
|
|
228
|
+
if (shouldHold(element)) queueMicrotask(() => shouldHold(element) && hold(element));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function track(element) {
|
|
232
|
+
if (tracked.has(element)) return;
|
|
233
|
+
tracked.add(element);
|
|
234
|
+
if (element.tagName === "VIDEO") element.addEventListener("play", onMediaPlay);
|
|
235
|
+
observer?.observe(element);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function untrack(element) {
|
|
239
|
+
observer?.unobserve(element);
|
|
240
|
+
element.removeEventListener("play", onMediaPlay);
|
|
241
|
+
tracked.delete(element);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function settleAll() {
|
|
245
|
+
for (const element of tracked) {
|
|
246
|
+
if (element.isConnected) settle(element);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function installMediaGovernor() {
|
|
251
|
+
observer = new IntersectionObserver(
|
|
252
|
+
(entries) => {
|
|
253
|
+
for (const entry of entries) settle(entry.target, entry.isIntersecting);
|
|
254
|
+
},
|
|
255
|
+
{ rootMargin: "64px" },
|
|
256
|
+
);
|
|
257
|
+
const unwatch = api.watch("video, lol-uikit-lottie", track);
|
|
258
|
+
// Lottie animations start after their JSON loads, which the observer does not see; re-check periodically.
|
|
259
|
+
const sweeper = setInterval(() => {
|
|
260
|
+
for (const element of tracked) {
|
|
261
|
+
if (!element.isConnected) untrack(element);
|
|
262
|
+
else if (element.tagName !== "VIDEO" && isPlaying(element) && shouldHold(element)) hold(element);
|
|
263
|
+
}
|
|
264
|
+
}, SWEEP_MS / 4);
|
|
265
|
+
return () => {
|
|
266
|
+
clearInterval(sweeper);
|
|
267
|
+
unwatch();
|
|
268
|
+
observer?.disconnect();
|
|
269
|
+
observer = null;
|
|
270
|
+
for (const element of [...tracked]) {
|
|
271
|
+
untrack(element);
|
|
272
|
+
if (rendered(element)) release(element);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Background and performance modes */
|
|
278
|
+
|
|
279
|
+
function applyModes() {
|
|
280
|
+
const root = document.documentElement;
|
|
281
|
+
root.classList.toggle(BACKGROUND_CLASS, active && inBackground());
|
|
282
|
+
root.classList.toggle(LITE_CLASS, active && liteMode);
|
|
283
|
+
settleAll();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function installModes() {
|
|
287
|
+
const removeStyle = api.addStyle(STYLE_ID, CSS);
|
|
288
|
+
window.addEventListener("focus", applyModes);
|
|
289
|
+
window.addEventListener("blur", applyModes);
|
|
290
|
+
document.addEventListener("visibilitychange", applyModes);
|
|
291
|
+
return () => {
|
|
292
|
+
window.removeEventListener("focus", applyModes);
|
|
293
|
+
window.removeEventListener("blur", applyModes);
|
|
294
|
+
document.removeEventListener("visibilitychange", applyModes);
|
|
295
|
+
document.documentElement.classList.remove(BACKGROUND_CLASS, LITE_CLASS);
|
|
296
|
+
removeStyle();
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function onPhase(raw) {
|
|
301
|
+
const next = typeof raw === "string" ? raw : raw?.data;
|
|
302
|
+
if (typeof next !== "string" || !next || next === phase) return;
|
|
303
|
+
phase = next;
|
|
304
|
+
applyModes();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function onEnable(utils) {
|
|
308
|
+
api = utils;
|
|
309
|
+
active = true;
|
|
310
|
+
backgroundMode = option("backgroundMode");
|
|
311
|
+
liteMode = option("performanceMode");
|
|
312
|
+
restores = [installFetch(), installXhr(), installBeacon(), quietDatadog(), installModes(), installMediaGovernor()];
|
|
313
|
+
unsubscribe = utils.subscribe(PHASE_URI, onPhase);
|
|
314
|
+
utils.lcu
|
|
315
|
+
.get(PHASE_URI)
|
|
316
|
+
.then(onPhase)
|
|
317
|
+
.catch(() => {});
|
|
318
|
+
applyModes();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function onDisable() {
|
|
322
|
+
active = false;
|
|
323
|
+
unsubscribe?.();
|
|
324
|
+
unsubscribe = null;
|
|
325
|
+
for (const restore of restores.reverse()) {
|
|
326
|
+
try {
|
|
327
|
+
restore();
|
|
328
|
+
} catch {}
|
|
329
|
+
}
|
|
330
|
+
restores = [];
|
|
331
|
+
decisions.clear();
|
|
332
|
+
phase = "None";
|
|
333
|
+
api = null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function onOptionChange(key, value) {
|
|
337
|
+
if (key === "backgroundMode") backgroundMode = value === true;
|
|
338
|
+
if (key === "performanceMode") liteMode = value === true;
|
|
339
|
+
applyModes();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function init() {
|
|
343
|
+
const registerWith = (utils) => {
|
|
344
|
+
utils.registerPlugin({
|
|
345
|
+
id: PLUGIN_ID,
|
|
346
|
+
name: "BetterClient",
|
|
347
|
+
description:
|
|
348
|
+
"Makes the client lighter without changing how it looks: pauses videos and animations nobody can see, blocks telemetry, and pauses the client while you are in a game.",
|
|
349
|
+
version,
|
|
350
|
+
options: OPTIONS,
|
|
351
|
+
onEnable,
|
|
352
|
+
onDisable,
|
|
353
|
+
onOptionChange,
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
if (window.PerfectionistUtils) {
|
|
358
|
+
registerWith(window.PerfectionistUtils);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
addEventListener("perfectionist:utils-ready", (e) => registerWith(e.detail ?? window.PerfectionistUtils), { once: true });
|
|
362
|
+
}
|