perfectionist-plugins 1.9.0 → 2.4.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 +143 -99
- package/better-client.js +1093 -0
- package/hide-unowned-skins.js +175 -58
- package/package.json +5 -3
- package/perfectionist-utils.js +69 -12
- package/unlock-profile-skins.js +153 -67
package/unlock-profile-skins.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const version = "1.2.
|
|
1
|
+
export const version = "1.2.4";
|
|
2
2
|
|
|
3
3
|
const PLUGIN_ID = "unlock-profile-skins";
|
|
4
4
|
const PICKER_BUTTON_SELECTOR = ".style-profile-skin-picker-button";
|
|
@@ -6,12 +6,14 @@ const CURRENT_SUMMONER_URI = "/lol-summoner/v1/current-summoner";
|
|
|
6
6
|
const INVENTORY_PATH = /^\/lol-champions\/v1\/inventories\/(\d+)\/champions$/;
|
|
7
7
|
const AUGMENT_INVENTORY_PATH = /^\/lol-inventory\/v2\/inventory\/SKIN_AUGMENT$/i;
|
|
8
8
|
const SUMMONER_TTL = 10 * 60 * 1000;
|
|
9
|
+
const SUMMONER_WAIT_MS = 1200;
|
|
9
10
|
const STATE_KEY = "perfectionist-plugins:state";
|
|
10
11
|
const FAKE_PURCHASE_DATE = 1577836800000;
|
|
11
12
|
const FAKE_RENTAL_END = 4102444800000;
|
|
12
13
|
const TIER_LABEL = "tier";
|
|
13
14
|
const GROUPING_OPTIONS = "lol-uikit-dropdown-option";
|
|
14
15
|
const HARVEST_WAIT_MS = 2000;
|
|
16
|
+
const INVENTORY_TEXT_CACHE_LIMIT = 2;
|
|
15
17
|
|
|
16
18
|
let filterActive = true;
|
|
17
19
|
let api = null;
|
|
@@ -22,6 +24,7 @@ let harvestPromise = null;
|
|
|
22
24
|
const augmentIds = new Set();
|
|
23
25
|
const pendingTargetLoads = new Set();
|
|
24
26
|
const variantIdRemap = new Map();
|
|
27
|
+
const inventoryTextCache = new Map();
|
|
25
28
|
|
|
26
29
|
function readDefaultEnabled() {
|
|
27
30
|
try {
|
|
@@ -196,17 +199,24 @@ function unlockDeep(node) {
|
|
|
196
199
|
}
|
|
197
200
|
}
|
|
198
201
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
202
|
+
const championNode = Array.isArray(node.skins);
|
|
203
|
+
if (!championNode) {
|
|
204
|
+
if (node.ownership && typeof node.ownership === "object") {
|
|
205
|
+
changed = unlockOwnership(node.ownership) || changed;
|
|
206
|
+
} else if (typeof node.contentId === "string" && Array.isArray(node.overlays)) {
|
|
207
|
+
node.ownership = synthesizedOwnership();
|
|
208
|
+
changed = true;
|
|
209
|
+
}
|
|
210
|
+
if (node.owned === false) {
|
|
211
|
+
node.owned = true;
|
|
212
|
+
changed = true;
|
|
213
|
+
}
|
|
208
214
|
}
|
|
209
|
-
const values = Array.isArray(node)
|
|
215
|
+
const values = Array.isArray(node)
|
|
216
|
+
? node
|
|
217
|
+
: Object.keys(node)
|
|
218
|
+
.filter((key) => key !== "ownership")
|
|
219
|
+
.map((key) => node[key]);
|
|
210
220
|
for (const value of values) {
|
|
211
221
|
if (value && typeof value === "object") changed = unlockDeep(value) || changed;
|
|
212
222
|
}
|
|
@@ -279,6 +289,7 @@ function ensureAugmentIds() {
|
|
|
279
289
|
let tierObserver = null;
|
|
280
290
|
let tierAppliedForOpen = false;
|
|
281
291
|
let tierDropdown = null;
|
|
292
|
+
let tierArmListener = null;
|
|
282
293
|
|
|
283
294
|
function findGroupingDropdown() {
|
|
284
295
|
for (const dd of document.querySelectorAll("lol-uikit-framed-dropdown")) {
|
|
@@ -312,6 +323,10 @@ function applyTierGrouping() {
|
|
|
312
323
|
function startTierGrouping() {
|
|
313
324
|
if (tierObserver) return;
|
|
314
325
|
tierObserver = new MutationObserver(() => {
|
|
326
|
+
if (!isPickerOpen()) {
|
|
327
|
+
stopTierGrouping();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
315
330
|
try {
|
|
316
331
|
applyTierGrouping();
|
|
317
332
|
} catch (error) {
|
|
@@ -334,12 +349,34 @@ function maybeRefreshSummoner() {
|
|
|
334
349
|
ensureSummonerId();
|
|
335
350
|
}
|
|
336
351
|
|
|
352
|
+
function whenSummonerKnown(timeout) {
|
|
353
|
+
if (summonerId !== null) return Promise.resolve(true);
|
|
354
|
+
ensureSummonerId();
|
|
355
|
+
const deadline = Date.now() + timeout;
|
|
356
|
+
return new Promise((resolve) => {
|
|
357
|
+
const step = () => {
|
|
358
|
+
if (summonerId !== null) return resolve(true);
|
|
359
|
+
if (Date.now() >= deadline) return resolve(false);
|
|
360
|
+
setTimeout(step, 40);
|
|
361
|
+
};
|
|
362
|
+
setTimeout(step, 0);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
337
366
|
function ensureSummonerId() {
|
|
338
367
|
if (summonerId !== null || summonerPending) return;
|
|
339
|
-
const get = api?.lcu?.get;
|
|
340
|
-
if (!get) return;
|
|
341
368
|
summonerPending = true;
|
|
342
|
-
|
|
369
|
+
let request;
|
|
370
|
+
try {
|
|
371
|
+
const get = api?.lcu?.get;
|
|
372
|
+
request = get
|
|
373
|
+
? get(CURRENT_SUMMONER_URI, { ttl: SUMMONER_TTL })
|
|
374
|
+
: (rawFetch || window.fetch)(CURRENT_SUMMONER_URI).then((response) => response.json());
|
|
375
|
+
} catch {
|
|
376
|
+
summonerPending = false;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
Promise.resolve(request)
|
|
343
380
|
.then((summoner) => {
|
|
344
381
|
summonerId = typeof summoner?.summonerId === "number" ? summoner.summonerId : null;
|
|
345
382
|
})
|
|
@@ -348,13 +385,31 @@ function ensureSummonerId() {
|
|
|
348
385
|
summonerPending = false;
|
|
349
386
|
});
|
|
350
387
|
}
|
|
388
|
+
function rewriteInventoryText(raw) {
|
|
389
|
+
if (typeof raw !== "string") return raw;
|
|
390
|
+
if (inventoryTextCache.has(raw)) {
|
|
391
|
+
const cached = inventoryTextCache.get(raw);
|
|
392
|
+
inventoryTextCache.delete(raw);
|
|
393
|
+
inventoryTextCache.set(raw, cached);
|
|
394
|
+
return cached;
|
|
395
|
+
}
|
|
396
|
+
let rewritten = raw;
|
|
397
|
+
try {
|
|
398
|
+
const json = JSON.parse(raw);
|
|
399
|
+
if (unlockDeep(json)) rewritten = JSON.stringify(json);
|
|
400
|
+
} catch {}
|
|
401
|
+
inventoryTextCache.set(raw, rewritten);
|
|
402
|
+
while (inventoryTextCache.size > INVENTORY_TEXT_CACHE_LIMIT) {
|
|
403
|
+
inventoryTextCache.delete(inventoryTextCache.keys().next().value);
|
|
404
|
+
}
|
|
405
|
+
return rewritten;
|
|
406
|
+
}
|
|
351
407
|
|
|
352
408
|
function rewriteWebSocketFrame(text) {
|
|
353
409
|
if (!filterActive || typeof text !== "string") return null;
|
|
354
410
|
const targetFrame = text.indexOf("lol-champions/v1/inventories") !== -1;
|
|
355
411
|
const augmentFrame = text.indexOf("SKIN_AUGMENT") !== -1;
|
|
356
412
|
if (!targetFrame && !augmentFrame) return null;
|
|
357
|
-
if (!isPickerOpen()) return null;
|
|
358
413
|
let frame;
|
|
359
414
|
try {
|
|
360
415
|
frame = JSON.parse(text);
|
|
@@ -454,53 +509,72 @@ function installFetchFilter() {
|
|
|
454
509
|
|
|
455
510
|
const wrapped = function (input, init) {
|
|
456
511
|
const url = typeof input === "string" ? input : (input && input.url) || "";
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
if (filterActive &&
|
|
461
|
-
const mapped = remapVariantIds(
|
|
462
|
-
if (mapped !==
|
|
512
|
+
const inventory = pathIdOf(url) !== null;
|
|
513
|
+
const augmentRequest = isAugmentUrl(url);
|
|
514
|
+
let options = init;
|
|
515
|
+
if (filterActive && options && typeof options.body === "string") {
|
|
516
|
+
const mapped = remapVariantIds(options.body);
|
|
517
|
+
if (mapped !== options.body) options = { ...options, body: mapped };
|
|
463
518
|
}
|
|
464
|
-
const promise = original.call(this, input,
|
|
465
|
-
if (filterActive
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
519
|
+
const promise = original.call(this, input, options);
|
|
520
|
+
if (!filterActive || (!inventory && !augmentRequest)) return promise;
|
|
521
|
+
if (inventory) maybeRefreshSummoner();
|
|
522
|
+
|
|
523
|
+
return whenSummonerKnown(SUMMONER_WAIT_MS).then(() => {
|
|
524
|
+
const target = isTargetUrl(url);
|
|
525
|
+
if (!augmentRequest && !target) return promise;
|
|
526
|
+
if (target && augmentIds.size === 0) {
|
|
527
|
+
const latch = (async () => {
|
|
528
|
+
try {
|
|
529
|
+
const response = await promise;
|
|
530
|
+
if (response && response.ok && augmentIds.size === 0) {
|
|
531
|
+
const json = await response.clone().json().catch(() => null);
|
|
532
|
+
if (json) harvestFromPayload(json);
|
|
533
|
+
}
|
|
534
|
+
} catch {}
|
|
535
|
+
pendingTargetLoads.delete(latch);
|
|
536
|
+
})();
|
|
537
|
+
pendingTargetLoads.add(latch);
|
|
538
|
+
}
|
|
539
|
+
const ready = augmentRequest && augmentIds.size === 0 ? ensureAugmentIds() : Promise.resolve();
|
|
540
|
+
return ready
|
|
541
|
+
.then(() => promise)
|
|
542
|
+
.then((response) => {
|
|
543
|
+
if (!response || !response.ok) return response;
|
|
544
|
+
if (augmentRequest) {
|
|
545
|
+
return response
|
|
546
|
+
.clone()
|
|
547
|
+
.json()
|
|
548
|
+
.catch(() => null)
|
|
549
|
+
.then((json) => {
|
|
550
|
+
if (!json || !unlockAugmentInventory(json)) return response;
|
|
551
|
+
return new Response(JSON.stringify(json), {
|
|
552
|
+
status: response.status,
|
|
553
|
+
statusText: response.statusText,
|
|
554
|
+
headers: response.headers,
|
|
555
|
+
});
|
|
556
|
+
});
|
|
472
557
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
.json()
|
|
487
|
-
.catch(() => null)
|
|
488
|
-
.then((json) => {
|
|
489
|
-
if (!json) return response;
|
|
490
|
-
const changed = augment ? unlockAugmentInventory(json) : unlockDeep(json);
|
|
491
|
-
if (!changed) return response;
|
|
492
|
-
return new Response(JSON.stringify(json), {
|
|
493
|
-
status: response.status,
|
|
494
|
-
statusText: response.statusText,
|
|
495
|
-
headers: response.headers,
|
|
558
|
+
return response
|
|
559
|
+
.clone()
|
|
560
|
+
.text()
|
|
561
|
+
.catch(() => null)
|
|
562
|
+
.then((text) => {
|
|
563
|
+
if (text === null) return response;
|
|
564
|
+
const rewritten = rewriteInventoryText(text);
|
|
565
|
+
if (rewritten === text) return response;
|
|
566
|
+
return new Response(rewritten, {
|
|
567
|
+
status: response.status,
|
|
568
|
+
statusText: response.statusText,
|
|
569
|
+
headers: response.headers,
|
|
570
|
+
});
|
|
496
571
|
});
|
|
497
|
-
|
|
498
|
-
|
|
572
|
+
});
|
|
573
|
+
});
|
|
499
574
|
};
|
|
500
575
|
wrapped.__perfProfileFilter = true;
|
|
501
576
|
window.fetch = wrapped;
|
|
502
577
|
}
|
|
503
|
-
|
|
504
578
|
function installXhrFilter() {
|
|
505
579
|
const proto = window.XMLHttpRequest && window.XMLHttpRequest.prototype;
|
|
506
580
|
if (!proto || proto.open.__perfProfileFilter) return;
|
|
@@ -543,23 +617,24 @@ function installXhrFilter() {
|
|
|
543
617
|
});
|
|
544
618
|
pendingTargetLoads.add(latch);
|
|
545
619
|
}
|
|
546
|
-
if (textDescriptor && textDescriptor.get && (
|
|
620
|
+
if (textDescriptor && textDescriptor.get && (pathIdOf(url) !== null || augment)) {
|
|
547
621
|
const cache = { raw: null, gate: null, filtered: null };
|
|
548
|
-
const gateOpen = () => filterActive &&
|
|
622
|
+
const gateOpen = () => filterActive && (augment || isTargetUrl(url));
|
|
549
623
|
const rewriteBody = (json) => (augment ? unlockAugmentInventory(json) : unlockDeep(json));
|
|
550
624
|
const filteredText = () => {
|
|
551
|
-
const raw = textDescriptor.get.call(xhr);
|
|
552
625
|
const gate = gateOpen();
|
|
553
|
-
if (cache.filtered
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
626
|
+
if (cache.filtered !== null && cache.gate === gate) return cache.filtered;
|
|
627
|
+
const raw = textDescriptor.get.call(xhr);
|
|
628
|
+
cache.gate = gate;
|
|
629
|
+
cache.filtered = raw;
|
|
630
|
+
if (gate) {
|
|
631
|
+
if (augment) {
|
|
558
632
|
try {
|
|
559
633
|
const json = JSON.parse(raw);
|
|
560
|
-
|
|
561
|
-
if (rewritten !== null) cache.filtered = rewritten;
|
|
634
|
+
if (rewriteBody(json)) cache.filtered = JSON.stringify(json);
|
|
562
635
|
} catch {}
|
|
636
|
+
} else {
|
|
637
|
+
cache.filtered = rewriteInventoryText(raw);
|
|
563
638
|
}
|
|
564
639
|
}
|
|
565
640
|
return cache.filtered;
|
|
@@ -578,7 +653,7 @@ function installXhrFilter() {
|
|
|
578
653
|
});
|
|
579
654
|
}
|
|
580
655
|
}
|
|
581
|
-
if (augment && filterActive &&
|
|
656
|
+
if (augment && filterActive && augmentIds.size === 0) {
|
|
582
657
|
const sendArgs = args;
|
|
583
658
|
ensureAugmentIds().finally(() => {
|
|
584
659
|
try {
|
|
@@ -597,6 +672,7 @@ function installSourceFilter() {
|
|
|
597
672
|
installWebSocketFilter();
|
|
598
673
|
installFetchFilter();
|
|
599
674
|
installXhrFilter();
|
|
675
|
+
ensureSummonerId();
|
|
600
676
|
} catch (error) {
|
|
601
677
|
logError("could not install the source filter.", error);
|
|
602
678
|
}
|
|
@@ -617,11 +693,21 @@ function onEnable(utils) {
|
|
|
617
693
|
filterActive = true;
|
|
618
694
|
ensureSourceFilter();
|
|
619
695
|
ensureSummonerId();
|
|
620
|
-
|
|
696
|
+
tierArmListener = () => {
|
|
697
|
+
if (tierObserver) return;
|
|
698
|
+
setTimeout(() => {
|
|
699
|
+
if (tierArmListener && isPickerOpen()) startTierGrouping();
|
|
700
|
+
}, 0);
|
|
701
|
+
};
|
|
702
|
+
document.addEventListener("click", tierArmListener, true);
|
|
703
|
+
if (isPickerOpen()) startTierGrouping();
|
|
621
704
|
}
|
|
622
705
|
|
|
623
706
|
function onDisable() {
|
|
624
707
|
filterActive = false;
|
|
708
|
+
inventoryTextCache.clear();
|
|
709
|
+
if (tierArmListener) document.removeEventListener("click", tierArmListener, true);
|
|
710
|
+
tierArmListener = null;
|
|
625
711
|
stopTierGrouping();
|
|
626
712
|
api = null;
|
|
627
713
|
}
|