perfectionist-plugins 2.4.2 → 2.4.4

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.
@@ -0,0 +1,561 @@
1
+ const PLUGIN_ID = "dodge-helper";
2
+ export const version = "1.4.2";
3
+
4
+ const PHASE_URI = "/lol-gameflow/v1/gameflow-phase";
5
+ const SESSION_URI = "/lol-gameflow/v1/session";
6
+ const CHAMP_SELECT_URI = "/lol-champ-select/v1/session";
7
+ const SUMMONER_URI = "/lol-summoner/v1/current-summoner";
8
+ const RANKED_STATS_URI = "/lol-ranked/v1/current-ranked-stats";
9
+ const DODGE_URI = "/lol-gameflow/v1/session/dodge";
10
+ const QUIT_URI = "/lol-lobby-team-builder/champ-select/v1/session/quit";
11
+ const STORE_KEY = "perfectionist:dodge-helper:accounts:v1";
12
+ const DAY_MS = 24 * 60 * 60 * 1000;
13
+ const STYLE_ID = "perf-dodge-helper-style";
14
+ const ROOT_ID = "perfDodgeHelper";
15
+ const BUTTON_ID = "perfDodgeHelperButton";
16
+ const LP_ID = "perfDodgeHelperLp";
17
+
18
+ const RANKED_QUEUES = new Map([
19
+ [420, "RANKED_SOLO_5x5"],
20
+ [440, "RANKED_FLEX_SR"],
21
+ ]);
22
+ const APEX_TIERS = new Set(["MASTER", "GRANDMASTER", "CHALLENGER"]);
23
+ const PENALTY_TIERS = {
24
+ 1: { lp: 5, lockout: "6-minute" },
25
+ 2: { lp: 15, lockout: "30-minute" },
26
+ 3: { lp: 15, lockout: "12-hour" },
27
+ };
28
+
29
+ const CSS = `
30
+ .perf-dodge-anchor { position: relative !important; overflow: visible !important; }
31
+ #${ROOT_ID} {
32
+ position: relative !important; display: flex !important; align-items: flex-end !important;
33
+ justify-content: center !important; width: 125px !important; height: 32px !important; z-index: 20 !important;
34
+ }
35
+ #${ROOT_ID}.perf-dodge-quit { position: absolute !important; left: 0 !important; bottom: 0 !important; }
36
+ #${ROOT_ID}.perf-dodge-bar { margin-left: 12px !important; }
37
+ #${ROOT_ID}.perf-dodge-floating { position: fixed !important; right: 145px !important; bottom: 21px !important; }
38
+ #${ROOT_ID} lol-uikit-flat-button { width: 125px !important; height: 32px !important; }
39
+ #${ROOT_ID} .perf-dodge-lp {
40
+ position: absolute !important; left: 0 !important; right: 0 !important; bottom: calc(100% + 4px) !important;
41
+ text-align: center !important; white-space: nowrap !important;
42
+ font-family: var(--font-body, "LoL Body", Arial, sans-serif) !important;
43
+ font-size: 11px !important; font-weight: 700 !important; letter-spacing: 0.4px !important;
44
+ line-height: 1 !important; color: #cdbe91 !important;
45
+ }`;
46
+
47
+ let api = null;
48
+ let active = false;
49
+ let dodging = false;
50
+ let unsubscribes = [];
51
+ let removeStyle = null;
52
+ let domObserver = null;
53
+ let domTimer = null;
54
+ let networkWatch = null;
55
+ let penaltyToken = 0;
56
+ const state = {
57
+ phase: null,
58
+ queueId: 0,
59
+ isCustom: false,
60
+ showQuitButton: false,
61
+ gameMode: "",
62
+ account: null,
63
+ penalty: null,
64
+ lastSelfDodgeAt: 0,
65
+ lockedLocalPick: false,
66
+ anchor: null,
67
+ };
68
+
69
+ function unwrap(raw) {
70
+ if (typeof raw === "string") {
71
+ const text = raw.trim();
72
+ if (text.startsWith("{") || text.startsWith("[")) {
73
+ try {
74
+ return unwrap(JSON.parse(text));
75
+ } catch {
76
+ return raw;
77
+ }
78
+ }
79
+ return raw;
80
+ }
81
+ if (Array.isArray(raw) && raw.length >= 3 && typeof raw[1] === "string") {
82
+ return unwrap(raw[2]?.data ?? raw[2]);
83
+ }
84
+ if (raw && typeof raw === "object" && "data" in raw) {
85
+ const keys = Object.keys(raw);
86
+ if (keys.includes("uri") || keys.includes("eventType") || keys.length <= 2) return unwrap(raw.data);
87
+ }
88
+ return raw;
89
+ }
90
+
91
+ function readStore() {
92
+ try {
93
+ const parsed = JSON.parse(localStorage.getItem(STORE_KEY) || "{}");
94
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
95
+ } catch {
96
+ return {};
97
+ }
98
+ }
99
+
100
+ function accountKey() {
101
+ const account = state.account;
102
+ if (!account) return null;
103
+ if (account.puuid) return account.puuid;
104
+ if (account.summonerId) return `sid:${account.summonerId}`;
105
+ return null;
106
+ }
107
+
108
+ function currentTier() {
109
+ const key = accountKey();
110
+ if (!key) return 0;
111
+ const record = readStore()[key];
112
+ if (!record || typeof record.tier !== "number" || typeof record.at !== "number") return 0;
113
+ const drops = Math.floor((Date.now() - record.at) / DAY_MS);
114
+ return Math.max(0, Math.min(record.tier - drops, 3));
115
+ }
116
+
117
+ async function loadAccount() {
118
+ if (state.account) return state.account;
119
+ const summoner = await api.lcu.get(SUMMONER_URI).catch(() => null);
120
+ if (summoner) {
121
+ state.account = { puuid: summoner.puuid || "", summonerId: summoner.summonerId || 0 };
122
+ }
123
+ return state.account;
124
+ }
125
+
126
+ async function recordDodge() {
127
+ if (!active) return;
128
+ if (hasNativeQuit()) return;
129
+ const now = Date.now();
130
+ if (now - state.lastSelfDodgeAt < 1500) return;
131
+ state.lastSelfDodgeAt = now;
132
+ await loadAccount();
133
+ const key = accountKey();
134
+ if (!key) return;
135
+ const store = readStore();
136
+ const tier = Math.min(currentTier() + 1, 3);
137
+ store[key] = { tier, at: now };
138
+ try {
139
+ localStorage.setItem(STORE_KEY, JSON.stringify(store));
140
+ } catch {}
141
+ }
142
+
143
+ async function computePenalty(queueId) {
144
+ const rankedKey = RANKED_QUEUES.get(queueId);
145
+ if (!rankedKey) return { ranked: false, lp: 0, tier: 0, lockout: "" };
146
+ await loadAccount();
147
+ const stats = await api.lcu.get(RANKED_STATS_URI).catch(() => null);
148
+ const tierName = String(stats?.queueMap?.[rankedKey]?.tier ?? "").toUpperCase();
149
+ if (!tierName || tierName === "UNRANKED") return { ranked: true, lp: 0, tier: 0, lockout: "" };
150
+ const tier = Math.min(currentTier() + 1, 3);
151
+ const apex = APEX_TIERS.has(tierName);
152
+ return {
153
+ ranked: true,
154
+ lp: apex && tier === 1 ? 20 : PENALTY_TIERS[tier].lp,
155
+ lockout: PENALTY_TIERS[tier].lockout,
156
+ tier,
157
+ };
158
+ }
159
+
160
+ function isCustomQueue() {
161
+ return state.isCustom || state.gameMode === "PRACTICETOOL" || state.queueId === 0;
162
+ }
163
+
164
+ function hasNativeQuit() {
165
+ return state.showQuitButton || isCustomQueue();
166
+ }
167
+
168
+ function render() {
169
+ const button = document.getElementById(BUTTON_ID);
170
+ const lp = document.getElementById(LP_ID);
171
+ if (!button || !lp) return;
172
+ const ranked = RANKED_QUEUES.has(state.queueId);
173
+ const label = ranked ? "DODGE" : "QUIT";
174
+ if (button.textContent !== label) button.textContent = label;
175
+ const penalty = state.penalty;
176
+ const amount = ranked && penalty?.ranked ? penalty.lp : 0;
177
+ const text = amount > 0 ? `\u2212${amount} LP` : "";
178
+ if (lp.textContent !== text) lp.textContent = text;
179
+ lp.style.display = text ? "" : "none";
180
+ if (ranked) {
181
+ button.title =
182
+ amount > 0
183
+ ? `Dodge champion select. This costs about ${amount} LP and a ${penalty?.lockout ?? "queue"} lockout. Your dodge tier is shared across queues and drops one step every 24 hours.`
184
+ : "Dodge champion select. This queue is unranked, so no LP is lost, but the dodge still advances your shared dodge tier.";
185
+ } else {
186
+ button.title = "Leave champion select. This counts as a dodge and advances your shared dodge tier.";
187
+ }
188
+ lp.title =
189
+ ranked && amount > 0
190
+ ? "Estimated LP for this dodge, from this account's shared dodge tier. The tier drops one step every 24 hours."
191
+ : "";
192
+ }
193
+
194
+ async function refreshPenalty() {
195
+ if (!active || state.phase !== "ChampSelect" || !state.queueId || hasNativeQuit()) return;
196
+ const queueId = state.queueId;
197
+ const token = ++penaltyToken;
198
+ const penalty = await computePenalty(queueId);
199
+ if (!active || token !== penaltyToken || queueId !== state.queueId) return;
200
+ state.penalty = penalty;
201
+ render();
202
+ bindButton(document.getElementById(BUTTON_ID));
203
+ requestAnimationFrame(() => bindButton(document.getElementById(BUTTON_ID)));
204
+ }
205
+
206
+ function applySession(raw) {
207
+ const session = unwrap(raw);
208
+ if (!session) return;
209
+ const queue = session.gameData?.queue;
210
+ if (queue) {
211
+ const queueId = Number(queue.id ?? 0) || 0;
212
+ const isCustom = Boolean(session.gameData?.isCustomGame || queue.isCustom);
213
+ const gameMode = String(queue.gameMode ?? "");
214
+ if (queueId !== state.queueId || isCustom !== state.isCustom || gameMode !== state.gameMode) {
215
+ state.queueId = queueId;
216
+ state.isCustom = isCustom;
217
+ state.gameMode = gameMode;
218
+ state.penalty = null;
219
+ if (state.phase === "ChampSelect") refreshPenalty();
220
+ }
221
+ }
222
+ ensureMount();
223
+ }
224
+
225
+ function syncLocalPick(session) {
226
+ const cellId = Number(session?.localPlayerCellId);
227
+ if (!Number.isFinite(cellId)) return;
228
+ const actions = Array.isArray(session.actions) ? session.actions.flat() : [];
229
+ const locked = actions.some(
230
+ (action) => Number(action?.actorCellId) === cellId && action?.type === "pick" && action?.completed === true
231
+ );
232
+ if (locked) state.lockedLocalPick = true;
233
+ }
234
+
235
+ function applyChampSelect(raw) {
236
+ const session = unwrap(raw);
237
+ if (!session) return;
238
+ state.showQuitButton = Boolean(session.showQuitButton);
239
+ const queueId = Number(session.queueId ?? 0) || 0;
240
+ if (queueId > 0) state.queueId = queueId;
241
+ if (session.isCustomGame) state.isCustom = true;
242
+ syncLocalPick(session);
243
+ ensureMount();
244
+ syncWatching();
245
+ }
246
+
247
+ function unmount() {
248
+ for (const element of document.querySelectorAll(`#${ROOT_ID}`)) element.remove();
249
+ if (state.anchor) {
250
+ state.anchor.classList.remove("perf-dodge-anchor");
251
+ state.anchor = null;
252
+ }
253
+ }
254
+
255
+ function onButtonClick(event) {
256
+ event.preventDefault();
257
+ event.stopPropagation();
258
+ quitOrDodge();
259
+ }
260
+
261
+ function bindButton(button) {
262
+ if (!button) return;
263
+ if (!button.__perfClickBound) {
264
+ button.__perfClickBound = true;
265
+ button.addEventListener("click", onButtonClick, true);
266
+ }
267
+ const inner = button.shadowRoot?.querySelector(".lol-uikit-flat-button");
268
+ if (inner && !inner.__perfClickBound) {
269
+ inner.__perfClickBound = true;
270
+ inner.addEventListener("click", onButtonClick);
271
+ }
272
+ }
273
+
274
+ function build() {
275
+ const root = document.createElement("div");
276
+ root.id = ROOT_ID;
277
+ const lp = document.createElement("span");
278
+ lp.id = LP_ID;
279
+ lp.className = "perf-dodge-lp";
280
+ const button = document.createElement("lol-uikit-flat-button");
281
+ button.id = BUTTON_ID;
282
+ bindButton(button);
283
+ root.append(lp, button);
284
+ return root;
285
+ }
286
+
287
+ function host() {
288
+ const quitBar = document.querySelector(".bottom-right-buttons");
289
+ if (quitBar && quitBar.getBoundingClientRect().width > 1) return { element: quitBar, kind: "quit" };
290
+ const screen = document.querySelector(".champion-select");
291
+ if (screen) return { element: screen, kind: "floating" };
292
+ const bars = [...document.querySelectorAll(".champ-select-action-content")].filter(
293
+ (element) => element.getBoundingClientRect().width > 1
294
+ );
295
+ return bars[0] ? { element: bars[0], kind: "bar" } : null;
296
+ }
297
+
298
+ function ensureMount() {
299
+ if (!active || state.phase !== "ChampSelect" || hasNativeQuit()) {
300
+ unmount();
301
+ return;
302
+ }
303
+ const target = host();
304
+ if (!target) {
305
+ unmount();
306
+ return;
307
+ }
308
+ if (state.anchor && state.anchor !== target.element) {
309
+ state.anchor.classList.remove("perf-dodge-anchor");
310
+ state.anchor = null;
311
+ }
312
+ if (target.kind === "quit") {
313
+ target.element.classList.add("perf-dodge-anchor");
314
+ state.anchor = target.element;
315
+ }
316
+ const roots = [...document.querySelectorAll(`#${ROOT_ID}`)];
317
+ for (const extra of roots.slice(1)) extra.remove();
318
+ const root = roots[0] ?? build();
319
+ if (root.parentElement !== target.element) target.element.prepend(root);
320
+ else if (target.kind === "quit" && target.element.firstElementChild !== root) target.element.prepend(root);
321
+ root.classList.toggle("perf-dodge-quit", target.kind === "quit");
322
+ root.classList.toggle("perf-dodge-bar", target.kind === "bar");
323
+ root.classList.toggle("perf-dodge-floating", target.kind === "floating");
324
+ render();
325
+ bindButton(document.getElementById(BUTTON_ID));
326
+ requestAnimationFrame(() => bindButton(document.getElementById(BUTTON_ID)));
327
+ setTimeout(() => bindButton(document.getElementById(BUTTON_ID)), 500);
328
+ }
329
+
330
+ function scheduleMount() {
331
+ if (domTimer) return;
332
+ domTimer = setTimeout(() => {
333
+ domTimer = null;
334
+ ensureMount();
335
+ }, 200);
336
+ }
337
+
338
+ function startDomObserver() {
339
+ if (domObserver) return;
340
+ const target =
341
+ document.querySelector(".champion-select") ?? document.querySelector(".screen-root") ?? document.body;
342
+ domObserver = new MutationObserver(scheduleMount);
343
+ domObserver.observe(target, { childList: true, subtree: true });
344
+ }
345
+
346
+ function stopDomObserver() {
347
+ domObserver?.disconnect();
348
+ domObserver = null;
349
+ clearTimeout(domTimer);
350
+ domTimer = null;
351
+ }
352
+
353
+ function isDodgeUrl(url) {
354
+ return /\/(lol-gameflow\/v1\/session\/dodge|lol-lobby-team-builder\/champ-select\/v1\/session\/quit)(\?|$)/i.test(
355
+ String(url)
356
+ );
357
+ }
358
+
359
+ function installDodgeWatcher() {
360
+ if (networkWatch) return;
361
+ const originalFetch = window.fetch;
362
+ const originalOpen = XMLHttpRequest.prototype.open;
363
+ const originalSend = XMLHttpRequest.prototype.send;
364
+
365
+ const ourFetch = function (...args) {
366
+ const url = String(args[0]?.url ?? args[0] ?? "");
367
+ const method = String(args[1]?.method ?? "GET").toUpperCase();
368
+ const promise = originalFetch.apply(this, args);
369
+ if (method === "POST" && isDodgeUrl(url)) {
370
+ promise
371
+ .then((response) => {
372
+ if (response.ok) recordDodge();
373
+ })
374
+ .catch(() => {});
375
+ }
376
+ return promise;
377
+ };
378
+
379
+ const ourOpen = function (method, url, ...rest) {
380
+ this.__perfDodgeUrl = String(url);
381
+ this.__perfDodgeMethod = String(method ?? "GET").toUpperCase();
382
+ return originalOpen.call(this, method, url, ...rest);
383
+ };
384
+
385
+ const ourSend = function (body) {
386
+ if (this.__perfDodgeMethod === "POST" && isDodgeUrl(this.__perfDodgeUrl)) {
387
+ this.addEventListener("load", () => {
388
+ if (this.status >= 200 && this.status < 300) recordDodge();
389
+ });
390
+ }
391
+ return originalSend.call(this, body);
392
+ };
393
+
394
+ window.fetch = ourFetch;
395
+ XMLHttpRequest.prototype.open = ourOpen;
396
+ XMLHttpRequest.prototype.send = ourSend;
397
+
398
+ networkWatch = {
399
+ restore() {
400
+ if (window.fetch === ourFetch) window.fetch = originalFetch;
401
+ if (XMLHttpRequest.prototype.open === ourOpen) XMLHttpRequest.prototype.open = originalOpen;
402
+ if (XMLHttpRequest.prototype.send === ourSend) XMLHttpRequest.prototype.send = originalSend;
403
+ },
404
+ };
405
+ }
406
+
407
+ function removeDodgeWatcher() {
408
+ networkWatch?.restore();
409
+ networkWatch = null;
410
+ }
411
+
412
+ function syncWatching() {
413
+ if (state.phase === "ChampSelect" && !hasNativeQuit()) {
414
+ startDomObserver();
415
+ installDodgeWatcher();
416
+ } else {
417
+ stopDomObserver();
418
+ removeDodgeWatcher();
419
+ }
420
+ }
421
+
422
+ async function quitOrDodge() {
423
+ if (dodging || !state.queueId || hasNativeQuit()) return;
424
+ dodging = true;
425
+ const button = document.getElementById(BUTTON_ID);
426
+ if (button) button.disabled = true;
427
+ const ranked = RANKED_QUEUES.has(state.queueId);
428
+ const uri = ranked ? DODGE_URI : QUIT_URI;
429
+ try {
430
+ await api.lcu.send("POST", uri);
431
+ await recordDodge();
432
+ setTimeout(() => {
433
+ const current = document.getElementById(BUTTON_ID);
434
+ if (current) current.disabled = false;
435
+ }, 1500);
436
+ } catch (error) {
437
+ console.warn("[Dodge Helper] quit failed:", error);
438
+ if (button) button.disabled = false;
439
+ } finally {
440
+ dodging = false;
441
+ }
442
+ }
443
+
444
+ async function handlePhase(raw) {
445
+ const previous = state.phase;
446
+ const phase = typeof raw === "string" ? raw : unwrap(raw);
447
+ if (typeof phase === "string" && phase) state.phase = phase;
448
+ if (previous === "ChampSelect" && state.phase !== "ChampSelect" && state.phase !== "GameStart" && state.phase !== "InProgress") {
449
+ if (RANKED_QUEUES.has(state.queueId) && !state.lockedLocalPick && !hasNativeQuit()) {
450
+ recordDodge();
451
+ }
452
+ state.lockedLocalPick = false;
453
+ }
454
+ if (state.phase === "ChampSelect" && previous !== "ChampSelect") state.lockedLocalPick = false;
455
+ if (state.phase === "ChampSelect") {
456
+ await loadAccount();
457
+ const [session, champSelect] = await Promise.all([
458
+ api.lcu.get(SESSION_URI).catch(() => null),
459
+ api.lcu.get(CHAMP_SELECT_URI).catch(() => null),
460
+ ]);
461
+ if (session) applySession(session);
462
+ if (champSelect) applyChampSelect(champSelect);
463
+ ensureMount();
464
+ refreshPenalty();
465
+ } else {
466
+ stopDomObserver();
467
+ removeDodgeWatcher();
468
+ unmount();
469
+ state.penalty = null;
470
+ state.showQuitButton = false;
471
+ }
472
+ }
473
+
474
+ function handleSession(raw) {
475
+ applySession(raw);
476
+ }
477
+
478
+ function handleChampSelect(raw) {
479
+ applyChampSelect(raw);
480
+ if (state.phase === "ChampSelect") {
481
+ ensureMount();
482
+ refreshPenalty();
483
+ }
484
+ }
485
+
486
+ function onEnable(utils) {
487
+ api = utils;
488
+ active = true;
489
+ removeStyle = utils.addStyle(STYLE_ID, CSS);
490
+
491
+ const handlers = {
492
+ [PHASE_URI]: handlePhase,
493
+ [SESSION_URI]: handleSession,
494
+ [CHAMP_SELECT_URI]: handleChampSelect,
495
+ };
496
+ for (const [uri, handler] of Object.entries(handlers)) {
497
+ try {
498
+ const off = utils.subscribe(uri, handler);
499
+ if (off) unsubscribes.push(off);
500
+ } catch {}
501
+ }
502
+
503
+ loadAccount();
504
+ utils.lcu
505
+ .get(PHASE_URI)
506
+ .then((phase) => handlePhase(unwrap(phase)))
507
+ .catch(() => {});
508
+ utils.lcu
509
+ .get(SESSION_URI)
510
+ .then((session) => session && applySession(session))
511
+ .catch(() => {});
512
+ }
513
+
514
+ function onDisable() {
515
+ active = false;
516
+ for (const off of unsubscribes) {
517
+ try {
518
+ off();
519
+ } catch {}
520
+ }
521
+ unsubscribes = [];
522
+ stopDomObserver();
523
+ removeDodgeWatcher();
524
+ unmount();
525
+ removeStyle?.();
526
+ removeStyle = null;
527
+ state.phase = null;
528
+ state.queueId = 0;
529
+ state.isCustom = false;
530
+ state.showQuitButton = false;
531
+ state.gameMode = "";
532
+ state.account = null;
533
+ state.penalty = null;
534
+ state.lastSelfDodgeAt = 0;
535
+ state.lockedLocalPick = false;
536
+ penaltyToken = 0;
537
+ api = null;
538
+ }
539
+
540
+ export function init() {
541
+ const registerWith = (utils) => {
542
+ utils.registerPlugin({
543
+ id: PLUGIN_ID,
544
+ name: "Dodge Helper",
545
+ description:
546
+ "Adds a DODGE/QUIT button to champion select: DODGE with the estimated LP loss for ranked queues, QUIT for every other mode. Hidden when the client already shows its own QUIT button.",
547
+ version,
548
+ defaultEnabled: true,
549
+ onEnable,
550
+ onDisable,
551
+ });
552
+ };
553
+
554
+ if (window.PerfectionistUtils) {
555
+ registerWith(window.PerfectionistUtils);
556
+ return;
557
+ }
558
+ addEventListener("perfectionist:utils-ready", (event) => registerWith(event.detail ?? window.PerfectionistUtils), {
559
+ once: true,
560
+ });
561
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfectionist-plugins",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "description": "Small plugins that fix the annoying parts of League of Legends champ select. Built for Pengu Loader.",
5
5
  "type": "module",
6
6
  "main": "perfectionist-utils.js",
@@ -12,7 +12,9 @@
12
12
  "./unlock-profile-skins.js": "./unlock-profile-skins.js",
13
13
  "./auto-claimer.js": "./auto-claimer.js",
14
14
  "./auto-read.js": "./auto-read.js",
15
- "./better-client.js": "./better-client.js"
15
+ "./better-client.js": "./better-client.js",
16
+ "./dodge-helper.js": "./dodge-helper.js",
17
+ "./auto-accept.js": "./auto-accept.js"
16
18
  },
17
19
  "files": [
18
20
  "perfectionist-utils.js",
@@ -21,7 +23,9 @@
21
23
  "unlock-profile-skins.js",
22
24
  "auto-claimer.js",
23
25
  "auto-read.js",
24
- "better-client.js"
26
+ "better-client.js",
27
+ "dodge-helper.js",
28
+ "auto-accept.js"
25
29
  ],
26
30
  "repository": {
27
31
  "type": "git",
@@ -16,7 +16,7 @@ const BUNDLED_MODULES = [
16
16
  file: "perfectionist-utils.js",
17
17
  name: "Perfectionism Core",
18
18
  version,
19
- description: "Shared core for the Perfectionism plugins.",
19
+ description: "The base the other plugins run on. Adds this Perfectionism page to the client settings.",
20
20
  core: true,
21
21
  },
22
22
  {
@@ -24,56 +24,56 @@ const BUNDLED_MODULES = [
24
24
  file: "better-client.js",
25
25
  name: "BetterClient",
26
26
  version: "1.7.0",
27
- description: "Debloats the client: blocks telemetry, merges duplicate requests, caches immutable data, decodes images asynchronously and fixes several bugs.",
27
+ description: "Makes the client lighter and smoother without changing how it looks. Cuts tracking and repeated requests and fixes small bugs. Has an optional Performance mode for slower PCs.",
28
28
  },
29
29
  {
30
30
  id: "ban-blocker",
31
31
  file: "ban-blocker.js",
32
32
  name: "Ban Blocker",
33
33
  version: "3.6.0",
34
- description: "During bans, blocking a teammate instantly claims the champion they hover to ban.",
34
+ description: "Adds a BLOCK button to your teammates in champ select. While a teammate is blocked, the champion they are about to ban is prepicked for you, so their ban can never land on it.",
35
35
  },
36
36
  {
37
37
  id: "unlock-profile-skins",
38
38
  file: "unlock-profile-skins.js",
39
39
  name: "Unlock Profile Skins",
40
40
  version: "1.2.4",
41
- description: "Unlocks every skin in the Set profile background window.",
41
+ description: "Shows every skin as unlocked in the Set profile background window, event skins included, so you can pick any skin as your profile background.",
42
42
  },
43
43
  {
44
44
  id: "hide-unowned-skins",
45
45
  file: "hide-unowned-skins.js",
46
46
  name: "Hide Unowned Skins",
47
47
  version: "2.5.1",
48
- description: "In champion select, only shows the skins you own for the picked champion.",
48
+ description: "In champ select, the skin carousel only shows the skins you own for the champion you picked.",
49
+ },
50
+ {
51
+ id: "auto-accept",
52
+ file: "auto-accept.js",
53
+ name: "Auto Accept",
54
+ version: "1.1.1",
55
+ description: "Accepts the match for you when the ready check pops up. Add an optional delay, and switch it on or off from the lobby footer.",
56
+ },
57
+ {
58
+ id: "dodge-helper",
59
+ file: "dodge-helper.js",
60
+ name: "Dodge Helper",
61
+ version: "1.4.2",
62
+ description: "Adds a button to leave champ select: DODGE in ranked Solo/Duo and Flex, with the estimated LP you will lose, and QUIT in every other mode.",
49
63
  },
50
64
  {
51
65
  id: "auto-claimer",
52
66
  file: "auto-claimer.js",
53
67
  name: "Auto Claimer",
54
68
  version: "1.2.0",
55
- description: "Automatically claims battle pass, event and mission rewards the moment they become claimable.",
69
+ description: "Claims rewards for you as soon as they are ready: event tracks, missions and the TFT Battle Pass. Optional extras can also buy 1 BE shop items and open mystery loot and chests.",
56
70
  },
57
71
  {
58
72
  id: "auto-read",
59
73
  file: "auto-read.js",
60
74
  name: "Auto Read",
61
75
  version: "1.2.0",
62
- description: "Automatically marks quests, loot, Clash, TFT and client notices as read so no badge or dot stays lit.",
63
- },
64
- {
65
- id: "auto-accept",
66
- file: "auto-accept.js",
67
- name: "Auto Accept",
68
- version: "1.1.0",
69
- description: "Accepts the ready check automatically, with an optional delay and a native switch in the lobby footer.",
70
- },
71
- {
72
- id: "dodge-helper",
73
- file: "dodge-helper.js",
74
- name: "Dodge Helper",
75
- version: "1.4.0",
76
- description: "Adds a DODGE/QUIT button to champion select: DODGE with the estimated LP loss for ranked queues, QUIT for every other mode.",
76
+ description: "Marks quests, loot, Clash, TFT and client notices as read for you, so no badge or dot stays lit.",
77
77
  },
78
78
  ];
79
79
 
@@ -942,4 +942,4 @@ export async function init(context, coordsProvided) {
942
942
  } catch (error) {
943
943
  logError("startup failed.", error);
944
944
  }
945
- }
945
+ }