perfectionist-plugins 2.4.8 → 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 -732
- package/better-client.js +362 -793
- package/dodge-helper.js +377 -713
- package/hide-unowned-skins.js +63 -687
- package/package.json +40 -40
- package/perfectionist-utils.js +539 -301
- package/unlock-profile-skins.js +173 -652
package/unlock-profile-skins.js
CHANGED
|
@@ -1,152 +1,87 @@
|
|
|
1
|
-
export const version = "
|
|
1
|
+
export const version = "2.0.0";
|
|
2
2
|
|
|
3
3
|
const PLUGIN_ID = "unlock-profile-skins";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
4
|
+
|
|
5
|
+
// The "Set profile background" window is the rcp-fe-lol-skins-picker client plugin, and nothing else in the
|
|
6
|
+
// client uses it. Its data binding is private to it, so unlocking the data there changes this window only:
|
|
7
|
+
// Collection, champ select, the store and loot keep seeing the real inventory.
|
|
8
|
+
const PICKER_PLUGIN = "rcp-fe-lol-skins-picker";
|
|
9
|
+
const CHAMPIONS_PATH = /^\/lol-champions\/v1\/inventories\/\d+\/champions$/;
|
|
10
|
+
const MINIMAL_PATH = /^\/lol-champions\/v1\/inventories\/\d+\/champions-minimal$/;
|
|
11
|
+
const AUGMENTS_PATH = /^\/lol-inventory\/v2\/inventory\/SKIN_AUGMENT$/i;
|
|
12
|
+
const GROUPING_SELECTOR = ".flyout-component lol-uikit-framed-dropdown.control-panel-grouping-options";
|
|
13
|
+
const TRANSLATIONS_URI = "/fe/lol-skins-picker/trans.json";
|
|
14
|
+
const TIER_KEY = "control_panel_grouping_by_tier";
|
|
15
|
+
|
|
11
16
|
const FAKE_PURCHASE_DATE = 1577836800000;
|
|
12
17
|
const FAKE_RENTAL_END = 4102444800000;
|
|
13
|
-
const
|
|
14
|
-
const GROUPING_OPTIONS = "lol-uikit-dropdown-option";
|
|
15
|
-
const HARVEST_WAIT_MS = 2000;
|
|
16
|
-
const INVENTORY_TEXT_CACHE_LIMIT = 2;
|
|
18
|
+
const SHADOW_CHAMPION_ID = 60000;
|
|
17
19
|
|
|
18
|
-
let filterActive = true;
|
|
19
20
|
let api = null;
|
|
20
|
-
let
|
|
21
|
-
let
|
|
22
|
-
let
|
|
23
|
-
let
|
|
21
|
+
let active = false;
|
|
22
|
+
let handles = [];
|
|
23
|
+
let unwatchGrouping = null;
|
|
24
|
+
let pickerApi = null;
|
|
25
|
+
let pickerSelectSkin = null;
|
|
26
|
+
let tierLabel = null;
|
|
24
27
|
const augmentIds = new Set();
|
|
25
|
-
const pendingTargetLoads = new Set();
|
|
26
28
|
const variantIdRemap = new Map();
|
|
27
|
-
const inventoryTextCache = new Map();
|
|
28
|
-
|
|
29
|
-
function readDefaultEnabled() {
|
|
30
|
-
try {
|
|
31
|
-
const saved = JSON.parse(localStorage.getItem(STATE_KEY) || "{}");
|
|
32
|
-
return saved.enabled?.[PLUGIN_ID] !== false;
|
|
33
|
-
} catch {
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
29
|
|
|
38
30
|
function logError(scope, error) {
|
|
39
|
-
console.error(`[
|
|
31
|
+
console.error(`[Perfectionism] Unlock Profile Skins: ${scope}`, error);
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const dialog of document.querySelectorAll("lol-uikit-dialog-frame")) {
|
|
48
|
-
const rect = dialog.getBoundingClientRect();
|
|
49
|
-
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
50
|
-
if (/set profile background/i.test(dialog.textContent || "")) return true;
|
|
34
|
+
function cloneData(value) {
|
|
35
|
+
try {
|
|
36
|
+
return structuredClone(value);
|
|
37
|
+
} catch {
|
|
38
|
+
return JSON.parse(JSON.stringify(value));
|
|
51
39
|
}
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function shouldFilter() {
|
|
56
|
-
return filterActive && (isPickerOpen() || Date.now() < pickerArmUntil);
|
|
57
40
|
}
|
|
58
41
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
startTierGrouping();
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (Date.now() >= pickerArmUntil) {
|
|
69
|
-
pickerArmTimer = null;
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
pickerArmTimer = setTimeout(waitForPicker, 100);
|
|
42
|
+
function ownedRecord() {
|
|
43
|
+
return {
|
|
44
|
+
owned: true,
|
|
45
|
+
loyaltyReward: false,
|
|
46
|
+
xboxGPReward: false,
|
|
47
|
+
rental: { rented: false, purchaseDate: FAKE_PURCHASE_DATE, endDate: 0, winCountRemaining: 0 },
|
|
73
48
|
};
|
|
74
|
-
pickerArmTimer = setTimeout(waitForPicker, 50);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function cleanPath(url) {
|
|
78
|
-
let path = String(url ?? "").split("?")[0];
|
|
79
|
-
const origin = /^[a-z][a-z0-9+.-]*:\/\/[^/]+/i.exec(path);
|
|
80
|
-
if (origin) path = path.slice(origin[0].length) || "/";
|
|
81
|
-
return path;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function pathIdOf(url) {
|
|
85
|
-
const match = INVENTORY_PATH.exec(cleanPath(url));
|
|
86
|
-
return match ? Number(match[1]) : null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function isTargetUrl(url) {
|
|
90
|
-
const id = pathIdOf(url);
|
|
91
|
-
return id !== null && summonerId !== null && id === summonerId;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isAugmentUrl(url) {
|
|
95
|
-
return AUGMENT_INVENTORY_PATH.test(cleanPath(url));
|
|
96
49
|
}
|
|
97
50
|
|
|
98
51
|
function unlockOwnership(ownership) {
|
|
99
|
-
if (!ownership || typeof ownership !== "object") return
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
52
|
+
if (!ownership || typeof ownership !== "object") return;
|
|
53
|
+
const wasOwned = ownership.owned === true && ownership.rental?.rented !== true;
|
|
54
|
+
ownership.owned = true;
|
|
55
|
+
if (wasOwned) return;
|
|
56
|
+
const rental = ownership.rental;
|
|
57
|
+
if (rental && typeof rental === "object") {
|
|
58
|
+
rental.rented = false;
|
|
59
|
+
rental.purchaseDate = FAKE_PURCHASE_DATE;
|
|
60
|
+
if (rental.endDate !== undefined) rental.endDate = FAKE_RENTAL_END;
|
|
105
61
|
}
|
|
106
|
-
if (
|
|
107
|
-
const rental = ownership.rental;
|
|
108
|
-
if (rental && typeof rental === "object") {
|
|
109
|
-
if (rental.purchaseDate !== FAKE_PURCHASE_DATE) {
|
|
110
|
-
rental.purchaseDate = FAKE_PURCHASE_DATE;
|
|
111
|
-
changed = true;
|
|
112
|
-
}
|
|
113
|
-
if (rental.endDate !== undefined && rental.endDate !== FAKE_RENTAL_END) {
|
|
114
|
-
rental.endDate = FAKE_RENTAL_END;
|
|
115
|
-
changed = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (ownership.purchaseDate !== undefined && ownership.purchaseDate !== FAKE_PURCHASE_DATE) {
|
|
119
|
-
ownership.purchaseDate = FAKE_PURCHASE_DATE;
|
|
120
|
-
changed = true;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return changed;
|
|
62
|
+
if (ownership.purchaseDate !== undefined) ownership.purchaseDate = FAKE_PURCHASE_DATE;
|
|
124
63
|
}
|
|
125
64
|
|
|
126
|
-
function
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
65
|
+
function unlockItem(item) {
|
|
66
|
+
if (!item || typeof item !== "object") return;
|
|
67
|
+
if (item.ownership && typeof item.ownership === "object") unlockOwnership(item.ownership);
|
|
68
|
+
else if (typeof item.id === "number" || typeof item.contentId === "string") item.ownership = ownedRecord();
|
|
69
|
+
if (item.owned === false) item.owned = true;
|
|
70
|
+
if (item.unlocked === false) item.unlocked = true;
|
|
133
71
|
}
|
|
134
72
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const values = Array.isArray(node) ? node : Object.values(node);
|
|
144
|
-
for (const value of values) {
|
|
145
|
-
if (value && typeof value === "object") harvestFromPayload(value);
|
|
73
|
+
function unlockAugments(holder) {
|
|
74
|
+
const list = holder?.skinAugments?.augments;
|
|
75
|
+
if (!Array.isArray(list)) return;
|
|
76
|
+
for (const aug of list) {
|
|
77
|
+
if (!aug || typeof aug !== "object") continue;
|
|
78
|
+
if (typeof aug.contentId === "string") augmentIds.add(aug.contentId);
|
|
79
|
+
if (aug.ownership && typeof aug.ownership === "object") unlockOwnership(aug.ownership);
|
|
80
|
+
else aug.ownership = ownedRecord();
|
|
146
81
|
}
|
|
147
82
|
}
|
|
148
83
|
|
|
149
|
-
function
|
|
84
|
+
function variantLabel(overlays) {
|
|
150
85
|
for (const overlay of overlays || []) {
|
|
151
86
|
const match = /splash_tile_([a-z0-9]+)\.png$/i.exec(String(overlay?.tileLCOverlayPath || ""));
|
|
152
87
|
if (match) return match[1].charAt(0).toUpperCase() + match[1].slice(1);
|
|
@@ -154,600 +89,186 @@ function variantLabelFromOverlays(overlays) {
|
|
|
154
89
|
return "Variant";
|
|
155
90
|
}
|
|
156
91
|
|
|
92
|
+
// Signature/event skins keep their colour variants as augments with tile overlays on one tier. Split each
|
|
93
|
+
// overlay variant into its own tile so every variant can be picked; the fake ids are mapped back to the real
|
|
94
|
+
// tier id when the profile saves the choice (see wrapSelectSkin).
|
|
157
95
|
function splitVariantTiers(skin) {
|
|
158
|
-
const tiers = skin
|
|
96
|
+
const tiers = skin?.questSkinInfo?.tiers;
|
|
97
|
+
if (!Array.isArray(tiers)) return;
|
|
159
98
|
const parent = typeof skin.id === "number" ? skin.id : null;
|
|
160
99
|
const taken = new Set(tiers.map((tier) => tier?.id));
|
|
161
100
|
const out = [];
|
|
162
|
-
let changed = false;
|
|
163
101
|
let sibling = 0;
|
|
164
102
|
for (const tier of tiers) {
|
|
165
103
|
out.push(tier);
|
|
166
104
|
const augments = tier?.skinAugments?.augments;
|
|
167
105
|
if (!Array.isArray(augments) || augments.length < 2) continue;
|
|
168
|
-
const withOverlays = augments.filter((aug) => Array.isArray(aug?.overlays) && aug.overlays.some((o) => o
|
|
169
|
-
const plain = augments.filter((aug) => withOverlays.
|
|
106
|
+
const withOverlays = augments.filter((aug) => Array.isArray(aug?.overlays) && aug.overlays.some((o) => o?.tileLCOverlayPath));
|
|
107
|
+
const plain = augments.filter((aug) => !withOverlays.includes(aug));
|
|
170
108
|
if (withOverlays.length === 0 || plain.length === 0) continue;
|
|
171
109
|
tier.skinAugments = { ...tier.skinAugments, augments: plain };
|
|
172
110
|
for (const aug of withOverlays) {
|
|
173
|
-
let id = parent !== null ? parent + 1000000 * sibling : -(tier.id ?? 0) * 10 - sibling;
|
|
111
|
+
let id = parent !== null ? parent + 1000000 * (sibling + 1) : -((tier.id ?? 0) * 10) - sibling - 1;
|
|
174
112
|
while (taken.has(id)) id += parent !== null ? 1000000 : -1;
|
|
175
113
|
taken.add(id);
|
|
176
|
-
sibling
|
|
114
|
+
sibling += 1;
|
|
177
115
|
variantIdRemap.set(id, tier.id);
|
|
178
116
|
out.push({
|
|
179
117
|
...tier,
|
|
180
118
|
id,
|
|
181
|
-
name: `${tier.name ?? ""} ${
|
|
119
|
+
name: `${tier.name ?? ""} ${variantLabel(aug.overlays)}`.trim(),
|
|
182
120
|
skinAugments: { ...tier.skinAugments, augments: [aug] },
|
|
183
121
|
});
|
|
184
|
-
changed = true;
|
|
185
122
|
}
|
|
186
123
|
}
|
|
187
|
-
|
|
188
|
-
return changed;
|
|
124
|
+
skin.questSkinInfo.tiers = out;
|
|
189
125
|
}
|
|
190
126
|
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
for (const
|
|
195
|
-
|
|
127
|
+
function unlockSkin(skin) {
|
|
128
|
+
unlockItem(skin);
|
|
129
|
+
unlockAugments(skin);
|
|
130
|
+
for (const chroma of Array.isArray(skin?.chromas) ? skin.chromas : []) {
|
|
131
|
+
unlockItem(chroma);
|
|
132
|
+
unlockAugments(chroma);
|
|
133
|
+
}
|
|
134
|
+
const tiers = skin?.questSkinInfo?.tiers;
|
|
135
|
+
if (Array.isArray(tiers)) {
|
|
136
|
+
for (const tier of tiers) {
|
|
137
|
+
unlockItem(tier);
|
|
138
|
+
unlockAugments(tier);
|
|
139
|
+
}
|
|
140
|
+
splitVariantTiers(skin);
|
|
196
141
|
}
|
|
197
|
-
return out;
|
|
198
142
|
}
|
|
199
143
|
|
|
200
|
-
function
|
|
201
|
-
if (!
|
|
202
|
-
|
|
203
|
-
|
|
144
|
+
function unlockChampions(data) {
|
|
145
|
+
if (!Array.isArray(data)) return data;
|
|
146
|
+
const before = augmentIds.size;
|
|
147
|
+
const out = cloneData(data).filter((champion) => !(typeof champion?.id === "number" && champion.id >= SHADOW_CHAMPION_ID));
|
|
148
|
+
for (const champion of out) {
|
|
149
|
+
if (!champion || typeof champion !== "object") continue;
|
|
150
|
+
// The picker copies the champion's ownership onto its base skin and every tier, so an unowned
|
|
151
|
+
// champion would hide all of its skins. Unlock the champion too (only inside this window).
|
|
152
|
+
unlockItem(champion);
|
|
153
|
+
for (const skin of Array.isArray(champion.skins) ? champion.skins : []) unlockSkin(skin);
|
|
154
|
+
}
|
|
155
|
+
if (augmentIds.size !== before) queueMicrotask(() => augmentHandle()?.reapply());
|
|
156
|
+
return out;
|
|
204
157
|
}
|
|
205
158
|
|
|
206
|
-
function
|
|
207
|
-
if (!
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (!Array.isArray(node) && node.questSkinInfo && Array.isArray(node.questSkinInfo.tiers)) {
|
|
218
|
-
changed = splitVariantTiers(node) || changed;
|
|
219
|
-
}
|
|
220
|
-
const augList = node.skinAugments?.augments;
|
|
221
|
-
if (Array.isArray(augList)) {
|
|
222
|
-
for (const aug of augList) {
|
|
223
|
-
if (!aug || typeof aug !== "object") continue;
|
|
224
|
-
if (typeof aug.contentId === "string") augmentIds.add(aug.contentId);
|
|
225
|
-
if (aug.ownership && typeof aug.ownership === "object") {
|
|
226
|
-
changed = unlockOwnership(aug.ownership) || changed;
|
|
227
|
-
} else {
|
|
228
|
-
aug.ownership = synthesizedOwnership();
|
|
229
|
-
changed = true;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const championNode = Array.isArray(node.skins);
|
|
234
|
-
if (!championNode) {
|
|
235
|
-
if (node.ownership && typeof node.ownership === "object") {
|
|
236
|
-
changed = unlockOwnership(node.ownership) || changed;
|
|
237
|
-
} else if (typeof node.contentId === "string" && Array.isArray(node.overlays)) {
|
|
238
|
-
node.ownership = synthesizedOwnership();
|
|
239
|
-
changed = true;
|
|
240
|
-
}
|
|
241
|
-
if (node.owned === false) {
|
|
242
|
-
node.owned = true;
|
|
243
|
-
changed = true;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
const values = Array.isArray(node)
|
|
247
|
-
? node
|
|
248
|
-
: Object.keys(node)
|
|
249
|
-
.filter((key) => key !== "ownership")
|
|
250
|
-
.map((key) => node[key]);
|
|
251
|
-
for (const value of values) {
|
|
252
|
-
if (value && typeof value === "object") changed = unlockDeep(value) || changed;
|
|
253
|
-
}
|
|
254
|
-
return changed;
|
|
159
|
+
function unlockMinimal(data) {
|
|
160
|
+
if (!Array.isArray(data)) return data;
|
|
161
|
+
return cloneData(data)
|
|
162
|
+
.filter((champion) => !(typeof champion?.id === "number" && champion.id >= SHADOW_CHAMPION_ID))
|
|
163
|
+
.map((champion) => {
|
|
164
|
+
unlockItem(champion);
|
|
165
|
+
return champion;
|
|
166
|
+
});
|
|
255
167
|
}
|
|
256
168
|
|
|
257
|
-
function unlockAugmentInventory(
|
|
258
|
-
if (!Array.isArray(
|
|
259
|
-
|
|
169
|
+
function unlockAugmentInventory(data) {
|
|
170
|
+
if (!Array.isArray(data)) return data;
|
|
171
|
+
const out = cloneData(data);
|
|
260
172
|
const present = new Set();
|
|
261
|
-
for (const item of
|
|
173
|
+
for (const item of out) {
|
|
262
174
|
if (!item || typeof item !== "object") continue;
|
|
263
175
|
if (typeof item.uuid === "string") present.add(item.uuid);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
changed = true;
|
|
267
|
-
}
|
|
268
|
-
if (item.ownershipType !== undefined && item.ownershipType !== "OWNED") {
|
|
269
|
-
item.ownershipType = "OWNED";
|
|
270
|
-
changed = true;
|
|
271
|
-
}
|
|
176
|
+
item.owned = true;
|
|
177
|
+
if (item.ownershipType !== undefined) item.ownershipType = "OWNED";
|
|
272
178
|
}
|
|
273
179
|
for (const id of augmentIds) {
|
|
274
180
|
if (present.has(id)) continue;
|
|
275
|
-
|
|
276
|
-
uuid: id,
|
|
277
|
-
inventoryType: "SKIN_AUGMENT",
|
|
278
|
-
ownershipType: "OWNED",
|
|
279
|
-
owned: true,
|
|
280
|
-
quantity: 1,
|
|
281
|
-
purchaseDate: FAKE_PURCHASE_DATE,
|
|
282
|
-
});
|
|
283
|
-
changed = true;
|
|
284
|
-
}
|
|
285
|
-
return changed;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function ensureAugmentIds() {
|
|
289
|
-
if (augmentIds.size > 0) return Promise.resolve();
|
|
290
|
-
if (harvestPromise) return harvestPromise;
|
|
291
|
-
harvestPromise = (async () => {
|
|
292
|
-
const waits = [...pendingTargetLoads];
|
|
293
|
-
if (waits.length > 0) {
|
|
294
|
-
await Promise.race([
|
|
295
|
-
Promise.allSettled(waits),
|
|
296
|
-
new Promise((resolve) => setTimeout(resolve, HARVEST_WAIT_MS)),
|
|
297
|
-
]);
|
|
298
|
-
}
|
|
299
|
-
if (augmentIds.size > 0) return;
|
|
300
|
-
try {
|
|
301
|
-
const get = rawFetch || ((...args) => window.fetch(...args));
|
|
302
|
-
let id = summonerId;
|
|
303
|
-
if (id === null) {
|
|
304
|
-
const response = await get(CURRENT_SUMMONER_URI);
|
|
305
|
-
const summoner = await response.json().catch(() => null);
|
|
306
|
-
id = typeof summoner?.summonerId === "number" ? summoner.summonerId : null;
|
|
307
|
-
if (id !== null) summonerId = id;
|
|
308
|
-
}
|
|
309
|
-
if (id === null) return;
|
|
310
|
-
const response = await get(`/lol-champions/v1/inventories/${id}/champions`);
|
|
311
|
-
const json = await response.json().catch(() => null);
|
|
312
|
-
if (json) harvestFromPayload(json);
|
|
313
|
-
} catch {}
|
|
314
|
-
})().finally(() => {
|
|
315
|
-
harvestPromise = null;
|
|
316
|
-
});
|
|
317
|
-
return harvestPromise;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
let tierObserver = null;
|
|
321
|
-
let tierAppliedForOpen = false;
|
|
322
|
-
let tierDropdown = null;
|
|
323
|
-
let tierArmListener = null;
|
|
324
|
-
|
|
325
|
-
function findGroupingDropdown() {
|
|
326
|
-
for (const dd of document.querySelectorAll("lol-uikit-framed-dropdown")) {
|
|
327
|
-
const labels = [...dd.querySelectorAll(GROUPING_OPTIONS)].map((o) => o.textContent.trim().toLowerCase());
|
|
328
|
-
if (labels.includes("my collection") && labels.includes(TIER_LABEL)) return dd;
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function applyTierGrouping() {
|
|
334
|
-
if (tierDropdown && !tierDropdown.isConnected) {
|
|
335
|
-
tierDropdown = null;
|
|
336
|
-
tierAppliedForOpen = false;
|
|
181
|
+
out.push({ uuid: id, inventoryType: "SKIN_AUGMENT", ownershipType: "OWNED", owned: true, quantity: 1, purchaseDate: FAKE_PURCHASE_DATE });
|
|
337
182
|
}
|
|
338
|
-
|
|
339
|
-
if (!isPickerOpen()) return;
|
|
340
|
-
const dd = tierDropdown ?? findGroupingDropdown();
|
|
341
|
-
if (!dd) {
|
|
342
|
-
tierAppliedForOpen = false;
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
tierDropdown = dd;
|
|
346
|
-
const options = [...dd.querySelectorAll(GROUPING_OPTIONS)];
|
|
347
|
-
const tier = options.find((o) => o.textContent.trim().toLowerCase() === TIER_LABEL);
|
|
348
|
-
if (!tier) return;
|
|
349
|
-
const current = options.find((o) => o.hasAttribute("selected"));
|
|
350
|
-
if (current !== tier) tier.click();
|
|
351
|
-
tierAppliedForOpen = true;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function startTierGrouping() {
|
|
355
|
-
if (tierObserver) return;
|
|
356
|
-
tierObserver = new MutationObserver(() => {
|
|
357
|
-
if (!isPickerOpen()) {
|
|
358
|
-
stopTierGrouping();
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
try {
|
|
362
|
-
applyTierGrouping();
|
|
363
|
-
} catch (error) {
|
|
364
|
-
logError("tier grouping failed.", error);
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
tierObserver.observe(document.documentElement ?? document, { childList: true, subtree: true });
|
|
368
|
-
applyTierGrouping();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function stopTierGrouping() {
|
|
372
|
-
tierObserver?.disconnect();
|
|
373
|
-
tierObserver = null;
|
|
374
|
-
tierAppliedForOpen = false;
|
|
375
|
-
tierDropdown = null;
|
|
183
|
+
return out;
|
|
376
184
|
}
|
|
377
185
|
|
|
378
|
-
function
|
|
379
|
-
|
|
380
|
-
ensureSummonerId();
|
|
186
|
+
function augmentHandle() {
|
|
187
|
+
return handles[2] ?? null;
|
|
381
188
|
}
|
|
382
189
|
|
|
383
|
-
function
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const deadline = Date.now() + timeout;
|
|
387
|
-
return new Promise((resolve) => {
|
|
388
|
-
const step = () => {
|
|
389
|
-
if (summonerId !== null) return resolve(true);
|
|
390
|
-
if (Date.now() >= deadline) return resolve(false);
|
|
391
|
-
setTimeout(step, 40);
|
|
392
|
-
};
|
|
393
|
-
setTimeout(step, 0);
|
|
394
|
-
});
|
|
190
|
+
function remapId(id) {
|
|
191
|
+
const numeric = Number(id);
|
|
192
|
+
return variantIdRemap.has(numeric) ? variantIdRemap.get(numeric) : id;
|
|
395
193
|
}
|
|
396
194
|
|
|
397
|
-
function
|
|
398
|
-
if (summonerId !== null || summonerPending) return;
|
|
399
|
-
summonerPending = true;
|
|
400
|
-
let request;
|
|
195
|
+
async function wrapSelectSkin() {
|
|
401
196
|
try {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
function rewriteInventoryText(raw) {
|
|
420
|
-
if (!shouldFilter() || typeof raw !== "string") return raw;
|
|
421
|
-
if (inventoryTextCache.has(raw)) {
|
|
422
|
-
const cached = inventoryTextCache.get(raw);
|
|
423
|
-
inventoryTextCache.delete(raw);
|
|
424
|
-
inventoryTextCache.set(raw, cached);
|
|
425
|
-
return cached;
|
|
426
|
-
}
|
|
427
|
-
let rewritten = raw;
|
|
428
|
-
try {
|
|
429
|
-
const json = JSON.parse(raw);
|
|
430
|
-
if (unlockDeep(json)) rewritten = JSON.stringify(json);
|
|
431
|
-
} catch {}
|
|
432
|
-
inventoryTextCache.set(raw, rewritten);
|
|
433
|
-
while (inventoryTextCache.size > INVENTORY_TEXT_CACHE_LIMIT) {
|
|
434
|
-
inventoryTextCache.delete(inventoryTextCache.keys().next().value);
|
|
197
|
+
const picker = await api.whenRcpReady(PICKER_PLUGIN, 60000);
|
|
198
|
+
if (!active || !picker || typeof picker.selectSkin !== "function" || pickerApi) return;
|
|
199
|
+
const original = picker.selectSkin;
|
|
200
|
+
pickerApi = picker;
|
|
201
|
+
pickerSelectSkin = original;
|
|
202
|
+
picker.selectSkin = function (onSelected, onDefault, onReady) {
|
|
203
|
+
const select =
|
|
204
|
+
typeof onSelected === "function"
|
|
205
|
+
? function (id, ...rest) {
|
|
206
|
+
return onSelected.call(this, active ? remapId(id) : id, ...rest);
|
|
207
|
+
}
|
|
208
|
+
: onSelected;
|
|
209
|
+
return original.call(this, select, onDefault, onReady);
|
|
210
|
+
};
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logError("could not hook the skin picker.", error);
|
|
435
213
|
}
|
|
436
|
-
return rewritten;
|
|
437
214
|
}
|
|
438
215
|
|
|
439
|
-
function
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (!targetFrame && !augmentFrame) return null;
|
|
444
|
-
let frame;
|
|
445
|
-
try {
|
|
446
|
-
frame = JSON.parse(text);
|
|
447
|
-
} catch {
|
|
448
|
-
return null;
|
|
449
|
-
}
|
|
450
|
-
if (!Array.isArray(frame) || frame.length < 3) return null;
|
|
451
|
-
const payload = frame[2];
|
|
452
|
-
if (!payload || typeof payload !== "object" || typeof payload.uri !== "string") return null;
|
|
453
|
-
let changed;
|
|
454
|
-
if (isTargetUrl(payload.uri)) {
|
|
455
|
-
changed = unlockDeep(payload.data);
|
|
456
|
-
} else if (isAugmentUrl(payload.uri)) {
|
|
457
|
-
changed = unlockAugmentInventory(payload.data);
|
|
458
|
-
} else {
|
|
459
|
-
return null;
|
|
216
|
+
function unwrapSelectSkin() {
|
|
217
|
+
if (pickerApi && pickerSelectSkin) {
|
|
218
|
+
if (Object.prototype.hasOwnProperty.call(pickerApi, "selectSkin")) delete pickerApi.selectSkin;
|
|
219
|
+
if (pickerApi.selectSkin !== pickerSelectSkin && typeof pickerSelectSkin === "function") pickerApi.selectSkin = pickerSelectSkin;
|
|
460
220
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function wrapMessageListener(listener) {
|
|
466
|
-
if (typeof listener !== "function" || listener.__perfProfileWrap) return listener;
|
|
467
|
-
const wrapped = function (event) {
|
|
468
|
-
if (event && typeof event.data === "string") {
|
|
469
|
-
const rewritten = rewriteWebSocketFrame(event.data);
|
|
470
|
-
if (rewritten !== null) {
|
|
471
|
-
const fresh = new MessageEvent("message", {
|
|
472
|
-
data: rewritten,
|
|
473
|
-
origin: event.origin,
|
|
474
|
-
lastEventId: event.lastEventId,
|
|
475
|
-
ports: event.ports ? [...event.ports] : [],
|
|
476
|
-
source: event.source ?? null,
|
|
477
|
-
});
|
|
478
|
-
return listener.call(this, fresh);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return listener.call(this, event);
|
|
482
|
-
};
|
|
483
|
-
wrapped.__perfProfileWrap = true;
|
|
484
|
-
wrapped.__perfProfileOriginal = listener;
|
|
485
|
-
return wrapped;
|
|
221
|
+
pickerApi = null;
|
|
222
|
+
pickerSelectSkin = null;
|
|
486
223
|
}
|
|
487
224
|
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
if (!proto || proto.addEventListener.__perfProfileFilter) return;
|
|
491
|
-
|
|
492
|
-
const origAdd = proto.addEventListener;
|
|
493
|
-
const origRemove = proto.removeEventListener;
|
|
494
|
-
const origDispatch = proto.dispatchEvent;
|
|
495
|
-
|
|
496
|
-
proto.addEventListener = function (type, listener, options) {
|
|
497
|
-
return origAdd.call(this, type, type === "message" ? wrapMessageListener(listener) : listener, options);
|
|
498
|
-
};
|
|
499
|
-
proto.removeEventListener = function (type, listener, options) {
|
|
500
|
-
return origRemove.call(this, type, (listener && listener.__perfProfileOriginal) || listener, options);
|
|
501
|
-
};
|
|
502
|
-
proto.dispatchEvent = function (event) {
|
|
503
|
-
if (event && event.type === "message" && typeof event.data === "string") {
|
|
504
|
-
const rewritten = rewriteWebSocketFrame(event.data);
|
|
505
|
-
if (rewritten !== null) {
|
|
506
|
-
return origDispatch.call(
|
|
507
|
-
this,
|
|
508
|
-
new MessageEvent("message", {
|
|
509
|
-
data: rewritten,
|
|
510
|
-
origin: event.origin,
|
|
511
|
-
lastEventId: event.lastEventId,
|
|
512
|
-
ports: event.ports ? [...event.ports] : [],
|
|
513
|
-
source: event.source ?? null,
|
|
514
|
-
}),
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return origDispatch.call(this, event);
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
const descriptor = Object.getOwnPropertyDescriptor(proto, "onmessage");
|
|
522
|
-
if (descriptor && descriptor.set && descriptor.get) {
|
|
523
|
-
Object.defineProperty(proto, "onmessage", {
|
|
524
|
-
configurable: true,
|
|
525
|
-
enumerable: descriptor.enumerable,
|
|
526
|
-
get: descriptor.get,
|
|
527
|
-
set(value) {
|
|
528
|
-
descriptor.set.call(this, typeof value === "function" ? wrapMessageListener(value) : value);
|
|
529
|
-
},
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
proto.addEventListener.__perfProfileFilter = true;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function installFetchFilter() {
|
|
537
|
-
const original = window.fetch;
|
|
538
|
-
if (typeof original !== "function" || original.__perfProfileFilter) return;
|
|
539
|
-
rawFetch = (...args) => original.call(window, ...args);
|
|
540
|
-
|
|
541
|
-
const wrapped = function (input, init) {
|
|
542
|
-
const url = typeof input === "string" ? input : (input && input.url) || "";
|
|
543
|
-
const inventory = pathIdOf(url) !== null;
|
|
544
|
-
const augmentRequest = isAugmentUrl(url);
|
|
545
|
-
let options = init;
|
|
546
|
-
if (shouldFilter() && options && typeof options.body === "string") {
|
|
547
|
-
const mapped = remapVariantIds(options.body);
|
|
548
|
-
if (mapped !== options.body) options = { ...options, body: mapped };
|
|
549
|
-
}
|
|
550
|
-
const promise = original.call(this, input, options);
|
|
551
|
-
if (!shouldFilter() || (!inventory && !augmentRequest)) return promise;
|
|
552
|
-
if (inventory) maybeRefreshSummoner();
|
|
553
|
-
|
|
554
|
-
return whenSummonerKnown(SUMMONER_WAIT_MS).then(() => {
|
|
555
|
-
const target = isTargetUrl(url);
|
|
556
|
-
if (!augmentRequest && !target) return promise;
|
|
557
|
-
if (target && augmentIds.size === 0) {
|
|
558
|
-
const latch = (async () => {
|
|
559
|
-
try {
|
|
560
|
-
const response = await promise;
|
|
561
|
-
if (response && response.ok && augmentIds.size === 0) {
|
|
562
|
-
const json = await response.clone().json().catch(() => null);
|
|
563
|
-
if (json) harvestFromPayload(json);
|
|
564
|
-
}
|
|
565
|
-
} catch {}
|
|
566
|
-
pendingTargetLoads.delete(latch);
|
|
567
|
-
})();
|
|
568
|
-
pendingTargetLoads.add(latch);
|
|
569
|
-
}
|
|
570
|
-
const ready = augmentRequest && augmentIds.size === 0 ? ensureAugmentIds() : Promise.resolve();
|
|
571
|
-
return ready
|
|
572
|
-
.then(() => promise)
|
|
573
|
-
.then((response) => {
|
|
574
|
-
if (!response || !response.ok) return response;
|
|
575
|
-
if (augmentRequest) {
|
|
576
|
-
return response
|
|
577
|
-
.clone()
|
|
578
|
-
.json()
|
|
579
|
-
.catch(() => null)
|
|
580
|
-
.then((json) => {
|
|
581
|
-
if (!json || !unlockAugmentInventory(json)) return response;
|
|
582
|
-
return new Response(JSON.stringify(json), {
|
|
583
|
-
status: response.status,
|
|
584
|
-
statusText: response.statusText,
|
|
585
|
-
headers: response.headers,
|
|
586
|
-
});
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
return response
|
|
590
|
-
.clone()
|
|
591
|
-
.text()
|
|
592
|
-
.catch(() => null)
|
|
593
|
-
.then((text) => {
|
|
594
|
-
if (text === null) return response;
|
|
595
|
-
const rewritten = rewriteInventoryText(text);
|
|
596
|
-
if (rewritten === text) return response;
|
|
597
|
-
return new Response(rewritten, {
|
|
598
|
-
status: response.status,
|
|
599
|
-
statusText: response.statusText,
|
|
600
|
-
headers: response.headers,
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
});
|
|
605
|
-
};
|
|
606
|
-
wrapped.__perfProfileFilter = true;
|
|
607
|
-
window.fetch = wrapped;
|
|
608
|
-
}
|
|
609
|
-
function installXhrFilter() {
|
|
610
|
-
const proto = window.XMLHttpRequest && window.XMLHttpRequest.prototype;
|
|
611
|
-
if (!proto || proto.open.__perfProfileFilter) return;
|
|
612
|
-
|
|
613
|
-
const origOpen = proto.open;
|
|
614
|
-
const origSend = proto.send;
|
|
615
|
-
const textDescriptor = Object.getOwnPropertyDescriptor(proto, "responseText");
|
|
616
|
-
const responseDescriptor = Object.getOwnPropertyDescriptor(proto, "response");
|
|
617
|
-
|
|
618
|
-
proto.open = function (method, url, ...rest) {
|
|
619
|
-
this.__perfProfileUrl = typeof url === "string" ? url : "";
|
|
620
|
-
return origOpen.call(this, method, url, ...rest);
|
|
621
|
-
};
|
|
622
|
-
proto.open.__perfProfileFilter = true;
|
|
623
|
-
|
|
624
|
-
proto.send = function (...args) {
|
|
625
|
-
const url = this.__perfProfileUrl || "";
|
|
626
|
-
const target = isTargetUrl(url);
|
|
627
|
-
const augment = !target && isAugmentUrl(url);
|
|
628
|
-
if (shouldFilter() && pathIdOf(url) !== null) maybeRefreshSummoner();
|
|
629
|
-
if (shouldFilter() && typeof args[0] === "string") {
|
|
630
|
-
const mapped = remapVariantIds(args[0]);
|
|
631
|
-
if (mapped !== args[0]) args = [mapped, ...args.slice(1)];
|
|
632
|
-
}
|
|
633
|
-
const xhr = this;
|
|
634
|
-
if (target && shouldFilter() && augmentIds.size === 0 && textDescriptor && textDescriptor.get) {
|
|
635
|
-
const latch = new Promise((resolve) => {
|
|
636
|
-
const finish = () => {
|
|
637
|
-
if (augmentIds.size === 0) {
|
|
638
|
-
try {
|
|
639
|
-
harvestFromPayload(JSON.parse(textDescriptor.get.call(xhr)));
|
|
640
|
-
} catch {}
|
|
641
|
-
}
|
|
642
|
-
pendingTargetLoads.delete(latch);
|
|
643
|
-
resolve();
|
|
644
|
-
};
|
|
645
|
-
xhr.addEventListener("load", finish, { once: true });
|
|
646
|
-
xhr.addEventListener("error", finish, { once: true });
|
|
647
|
-
xhr.addEventListener("abort", finish, { once: true });
|
|
648
|
-
});
|
|
649
|
-
pendingTargetLoads.add(latch);
|
|
650
|
-
}
|
|
651
|
-
if (textDescriptor && textDescriptor.get && (pathIdOf(url) !== null || augment)) {
|
|
652
|
-
const cache = { raw: null, gate: null, filtered: null };
|
|
653
|
-
const gateOpen = () => shouldFilter() && (augment || isTargetUrl(url));
|
|
654
|
-
const rewriteBody = (json) => (augment ? unlockAugmentInventory(json) : unlockDeep(json));
|
|
655
|
-
const filteredText = () => {
|
|
656
|
-
const gate = gateOpen();
|
|
657
|
-
if (cache.filtered !== null && cache.gate === gate) return cache.filtered;
|
|
658
|
-
const raw = textDescriptor.get.call(xhr);
|
|
659
|
-
cache.gate = gate;
|
|
660
|
-
cache.filtered = raw;
|
|
661
|
-
if (gate) {
|
|
662
|
-
if (augment) {
|
|
663
|
-
try {
|
|
664
|
-
const json = JSON.parse(raw);
|
|
665
|
-
if (rewriteBody(json)) cache.filtered = JSON.stringify(json);
|
|
666
|
-
} catch {}
|
|
667
|
-
} else {
|
|
668
|
-
cache.filtered = rewriteInventoryText(raw);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
return cache.filtered;
|
|
672
|
-
};
|
|
673
|
-
Object.defineProperty(xhr, "responseText", { configurable: true, get: filteredText });
|
|
674
|
-
if (responseDescriptor && responseDescriptor.get) {
|
|
675
|
-
Object.defineProperty(xhr, "response", {
|
|
676
|
-
configurable: true,
|
|
677
|
-
get() {
|
|
678
|
-
const type = xhr.responseType;
|
|
679
|
-
if (type === "" || type === "text") return filteredText();
|
|
680
|
-
const value = responseDescriptor.get.call(xhr);
|
|
681
|
-
if (type === "json" && gateOpen() && value && typeof value === "object") rewriteBody(value);
|
|
682
|
-
return value;
|
|
683
|
-
},
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
if (augment && shouldFilter() && augmentIds.size === 0) {
|
|
688
|
-
const sendArgs = args;
|
|
689
|
-
ensureAugmentIds().finally(() => {
|
|
690
|
-
try {
|
|
691
|
-
origSend.apply(xhr, sendArgs);
|
|
692
|
-
} catch {}
|
|
693
|
-
});
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
return origSend.apply(this, args);
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function installSourceFilter() {
|
|
225
|
+
async function loadTierLabel() {
|
|
226
|
+
if (tierLabel !== null) return tierLabel;
|
|
701
227
|
try {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
} catch (error) {
|
|
708
|
-
logError("could not install the source filter.", error);
|
|
228
|
+
const response = await fetch(TRANSLATIONS_URI);
|
|
229
|
+
const strings = response.ok ? await response.json() : null;
|
|
230
|
+
tierLabel = String(strings?.[TIER_KEY] ?? "").trim().toLowerCase();
|
|
231
|
+
} catch {
|
|
232
|
+
tierLabel = "";
|
|
709
233
|
}
|
|
234
|
+
return tierLabel;
|
|
710
235
|
}
|
|
711
236
|
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
}
|
|
237
|
+
async function groupByTier(dropdown) {
|
|
238
|
+
const label = await loadTierLabel();
|
|
239
|
+
if (!active || !dropdown.isConnected) return;
|
|
240
|
+
const options = [...dropdown.querySelectorAll("lol-uikit-dropdown-option")];
|
|
241
|
+
if (options.length === 0) return;
|
|
242
|
+
const tier = (label && options.find((option) => option.textContent.trim().toLowerCase() === label)) ?? options[options.length - 1];
|
|
243
|
+
if (tier && !tier.hasAttribute("selected")) tier.click();
|
|
720
244
|
}
|
|
721
245
|
|
|
722
246
|
function onEnable(utils) {
|
|
723
247
|
api = utils;
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
248
|
+
active = true;
|
|
249
|
+
const transform = (match, fn) => utils.transformBinding({ identifier: PICKER_PLUGIN, match: (path) => match.test(path), transform: fn });
|
|
250
|
+
handles = [
|
|
251
|
+
transform(CHAMPIONS_PATH, unlockChampions),
|
|
252
|
+
transform(MINIMAL_PATH, unlockMinimal),
|
|
253
|
+
transform(AUGMENTS_PATH, unlockAugmentInventory),
|
|
254
|
+
];
|
|
255
|
+
unwatchGrouping = utils.watch(GROUPING_SELECTOR, (dropdown) => {
|
|
256
|
+
groupByTier(dropdown).catch((error) => logError("tier grouping failed.", error));
|
|
257
|
+
});
|
|
258
|
+
wrapSelectSkin();
|
|
734
259
|
}
|
|
735
260
|
|
|
736
261
|
function onDisable() {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
pickerArmUntil = 0;
|
|
744
|
-
stopTierGrouping();
|
|
262
|
+
active = false;
|
|
263
|
+
for (const handle of handles) handle.dispose();
|
|
264
|
+
handles = [];
|
|
265
|
+
unwatchGrouping?.();
|
|
266
|
+
unwatchGrouping = null;
|
|
267
|
+
unwrapSelectSkin();
|
|
745
268
|
api = null;
|
|
746
269
|
}
|
|
747
270
|
|
|
748
271
|
export function init() {
|
|
749
|
-
installSourceFilter();
|
|
750
|
-
|
|
751
272
|
const registerWith = (utils) => {
|
|
752
273
|
utils.registerPlugin({
|
|
753
274
|
id: PLUGIN_ID,
|
|
@@ -762,6 +283,6 @@ export function init() {
|
|
|
762
283
|
if (window.PerfectionistUtils) {
|
|
763
284
|
registerWith(window.PerfectionistUtils);
|
|
764
285
|
} else {
|
|
765
|
-
addEventListener("perfectionist:utils-ready", (event) => registerWith(event.detail), { once: true });
|
|
286
|
+
addEventListener("perfectionist:utils-ready", (event) => registerWith(event.detail ?? window.PerfectionistUtils), { once: true });
|
|
766
287
|
}
|
|
767
|
-
}
|
|
288
|
+
}
|