perfectionist-plugins 2.4.5 → 2.4.7

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 CHANGED
@@ -22,8 +22,8 @@ Built for [Pengu Loader](https://pengu.lol)
22
22
  | 🛡️ **Ban Blocker** | 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 on your own pick turn, so their ban can never land on it. |
23
23
  | 🔓 **Unlock Profile Skins** | In the **Set profile background** window, every skin shows as unlocked, even event skins (Signature). Browse the full list and pick any skin you like. |
24
24
  | 🎨 **Hide Unowned Skins** | In champ select, the skin carousel only shows the skins you own for the champion you picked. The arrows and dots only move through owned skins, and the ones that are left keep their normal spacing. |
25
- | ✅ **Auto Accept** | 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. |
26
- | 🚪 **Dodge Helper** | 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. |
25
+ | ✅ **Auto Accept** | Accepts the match for you when the ready check pops up. |
26
+ | 🚪 **Dodge Helper** | Adds a **DODGE** button in ranked Solo/Duo and Flex and a **QUIT** button in other modes, with the LP and queue-penalty cost of the next dodge. |
27
27
  | 🎁 **Auto Claimer** | Claims rewards for you the moment they become claimable: event tracks, missions and the TFT Battle Pass. Three optional extras: auto-buy shop items Riot lists for 1 BE, auto-open mystery loot (icons, ward skins), and auto-open chests with keys. |
28
28
  | 📬 **Auto Read** | Marks quests, loot, Clash, TFT and client notices as read, so no badge or dot stays lit. |
29
29
 
package/auto-accept.js CHANGED
@@ -1,19 +1,16 @@
1
1
  const PLUGIN_ID = "auto-accept";
2
- export const version = "1.1.2";
2
+ export const version = "1.1.3";
3
3
 
4
4
  const PHASE_URI = "/lol-gameflow/v1/gameflow-phase";
5
5
  const READY_CHECK_URI = "/lol-matchmaking/v1/ready-check";
6
6
  const ACCEPT_URI = "/lol-matchmaking/v1/ready-check/accept";
7
- const OPTION_KEY = "autoAccept";
8
- const DELAY_KEY = "perfectionist:auto-accept:delay";
9
- const MAX_DELAY = 15000;
7
+ const ACCEPT_KEY = "autoAccept";
10
8
  const RETRY_MS = 700;
11
9
  const MAX_ATTEMPTS = 4;
12
10
  const GAME_SELECT_GUARD_MS = 1500;
13
11
  const STYLE_ID = "perf-auto-accept-style";
14
12
  const ROOT_ID = "perfAutoAccept";
15
13
  const TOGGLE_ID = "perfAutoAcceptToggle";
16
- const DELAY_INPUT_ID = "perfAutoAcceptDelay";
17
14
 
18
15
  const CSS = `#perfAutoAccept {
19
16
  display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important;
@@ -25,24 +22,7 @@ const CSS = `#perfAutoAccept {
25
22
  font-family: var(--font-body, "LoL Body", Arial, sans-serif) !important;
26
23
  font-size: 12px !important; letter-spacing: 0.3px !important; color: #a09b8c !important;
27
24
  }
28
- #perfAutoAccept .auto-accept-button[selected] .auto-accept-button-text { color: #f0e6d2 !important; }
29
- #perfAutoAccept .auto-accept-delay {
30
- display: flex !important; align-items: center !important; box-sizing: border-box !important;
31
- width: 82px !important; height: 24px !important; margin-left: 0 !important;
32
- background: #1e2328 !important; border: 1px solid #785a28 !important; border-radius: 0 !important;
33
- transition: border-color 0.15s ease, background-color 0.15s ease !important;
34
- }
35
- #perfAutoAccept .auto-accept-delay:hover { border-color: #c89b3c !important; }
36
- #perfAutoAccept .auto-accept-delay:focus-within { border-color: #c89b3c !important; background: #0a1428 !important; }
37
- #perfAutoAccept .auto-accept-delay input {
38
- width: 100% !important; height: 100% !important; box-sizing: border-box !important;
39
- background: transparent !important; border: none !important; outline: none !important; box-shadow: none !important;
40
- color: #f0e6d2 !important; font-family: var(--font-body, "LoL Body", Arial, sans-serif) !important;
41
- font-size: 12px !important; letter-spacing: 0.3px !important; text-align: center !important; padding: 0 6px !important;
42
- }
43
- #perfAutoAccept .auto-accept-delay input::placeholder { color: #5b5a56 !important; }
44
- #perfAutoAccept .auto-accept-delay input::-webkit-outer-spin-button,
45
- #perfAutoAccept .auto-accept-delay input::-webkit-inner-spin-button { -webkit-appearance: none !important; margin: 0 !important; }`;
25
+ #perfAutoAccept .auto-accept-button[selected] .auto-accept-button-text { color: #f0e6d2 !important; }`;
46
26
 
47
27
  let api = null;
48
28
  let active = false;
@@ -54,28 +34,10 @@ let unsubscribes = [];
54
34
  let removeStyle = null;
55
35
  let domObserver = null;
56
36
  let domTimer = null;
57
- let delayValue = readDelay();
58
-
59
- function readDelay() {
60
- try {
61
- const raw = Number(localStorage.getItem(DELAY_KEY));
62
- if (!Number.isFinite(raw) || raw < 0) return 0;
63
- return Math.min(Math.floor(raw), MAX_DELAY);
64
- } catch {
65
- return 0;
66
- }
67
- }
68
-
69
- function writeDelay(value) {
70
- delayValue = value;
71
- try {
72
- localStorage.setItem(DELAY_KEY, String(value));
73
- } catch {}
74
- }
75
37
 
76
38
  function enabled() {
77
39
  try {
78
- return api?.getOption?.(PLUGIN_ID, OPTION_KEY) === true;
40
+ return api?.getOption?.(PLUGIN_ID, ACCEPT_KEY, true) === true;
79
41
  } catch {
80
42
  return false;
81
43
  }
@@ -139,17 +101,11 @@ async function acceptNow() {
139
101
  }
140
102
  }
141
103
 
142
- function queueAccept() {
143
- if (!active || !enabled() || accepted || sending || timer) return;
144
- if (delayValue > 0) timer = setTimeout(acceptNow, delayValue);
145
- else acceptNow();
146
- }
147
-
148
104
  function onPhase(raw) {
149
105
  const phase = unwrap(raw);
150
106
  if (typeof phase !== "string") return;
151
107
  if (phase === "ReadyCheck") {
152
- if (!accepted) queueAccept();
108
+ if (!accepted) acceptNow();
153
109
  return;
154
110
  }
155
111
  resetReadyCheck();
@@ -167,13 +123,13 @@ function onReadyCheck(raw) {
167
123
  resetReadyCheck();
168
124
  return;
169
125
  }
170
- queueAccept();
126
+ acceptNow();
171
127
  }
172
128
 
173
129
  async function refresh() {
174
130
  const phase = unwrap(await api.lcu.get(PHASE_URI).catch(() => null));
175
131
  if (!active) return;
176
- if (phase === "ReadyCheck") queueAccept();
132
+ if (phase === "ReadyCheck") acceptNow();
177
133
  else resetReadyCheck();
178
134
  }
179
135
 
@@ -182,31 +138,18 @@ function syncButton() {
182
138
  if (!option) return;
183
139
  if (enabled()) option.setAttribute("selected", "true");
184
140
  else option.removeAttribute("selected");
185
- const field = document.getElementById(DELAY_INPUT_ID);
186
- if (field && document.activeElement !== field) field.value = String(delayValue);
187
141
  }
188
142
 
189
143
  function toggle() {
190
144
  const next = !enabled();
191
145
  try {
192
- api.setOption(PLUGIN_ID, OPTION_KEY, next);
146
+ api.setOption(PLUGIN_ID, ACCEPT_KEY, next, false);
193
147
  } catch {}
194
148
  syncButton();
195
- if (next) queueAccept();
149
+ if (next) acceptNow();
196
150
  else resetReadyCheck();
197
151
  }
198
152
 
199
- function onDelayInput(event) {
200
- const raw = Number(event.target.value);
201
- const next = Math.min(Math.max(Number.isFinite(raw) ? Math.floor(raw) : 0, 0), MAX_DELAY);
202
- writeDelay(next);
203
- if (event.type === "change") event.target.value = String(next);
204
- if (active && enabled() && timer) {
205
- clearTimer();
206
- queueAccept();
207
- }
208
- }
209
-
210
153
  function buildButton() {
211
154
  const root = document.createElement("div");
212
155
  root.id = ROOT_ID;
@@ -224,23 +167,7 @@ function buildButton() {
224
167
  toggle();
225
168
  });
226
169
 
227
- const input = document.createElement("lol-uikit-flat-input");
228
- input.className = "auto-accept-delay";
229
- const field = document.createElement("input");
230
- field.id = DELAY_INPUT_ID;
231
- field.type = "number";
232
- field.min = "0";
233
- field.max = String(MAX_DELAY);
234
- field.step = "100";
235
- field.placeholder = "ms";
236
- field.title = "Delay before accepting, in milliseconds";
237
- field.value = String(delayValue);
238
- field.addEventListener("input", onDelayInput);
239
- field.addEventListener("change", onDelayInput);
240
- field.addEventListener("click", (event) => event.stopPropagation());
241
- input.append(field);
242
-
243
- root.append(option, input);
170
+ root.append(option);
244
171
  return root;
245
172
  }
246
173
 
@@ -345,33 +272,16 @@ function onDisable() {
345
272
  api = null;
346
273
  }
347
274
 
348
- function onOptionChange(key) {
349
- if (key !== OPTION_KEY) return;
350
- syncButton();
351
- if (enabled()) queueAccept();
352
- else resetReadyCheck();
353
- }
354
-
355
275
  export function init() {
356
276
  const registerWith = (utils) => {
357
277
  utils.registerPlugin({
358
278
  id: PLUGIN_ID,
359
279
  name: "Auto Accept",
360
280
  description:
361
- "Accepts the ready check automatically, with an optional delay. The same switch is shown natively in the lobby footer.",
281
+ "Accepts the ready check automatically. Use the Auto Accept switch in the lobby footer to turn it on or off.",
362
282
  version,
363
- options: [
364
- {
365
- key: OPTION_KEY,
366
- label: "Auto accept matches",
367
- description:
368
- "Accepts the ready check for you. The Auto Accept switch in the lobby footer toggles this same setting.",
369
- default: true,
370
- },
371
- ],
372
283
  onEnable,
373
284
  onDisable,
374
- onOptionChange,
375
285
  });
376
286
  };
377
287
 
package/ban-blocker.js CHANGED
@@ -1,4 +1,4 @@
1
- export const version = "3.6.0";
1
+ export const version = "3.6.2";
2
2
 
3
3
  const PLUGIN_ID = "ban-blocker";
4
4
  const STYLE_ID = "perfectionist-ban-blocker-style";
@@ -70,6 +70,7 @@ const fetchedNames = new Set();
70
70
  const playerNames = new Map();
71
71
  const cellToPlayer = new Map();
72
72
  const buttons = new Map();
73
+ const swapSeen = new Set();
73
74
  const failedUntil = new Map();
74
75
  const failCount = new Map();
75
76
  let rows = [];
@@ -159,10 +160,19 @@ function linkPlayer(p, rowName, cellId) {
159
160
  return root;
160
161
  }
161
162
 
163
+ function pickStarted(s) {
164
+ const actions = s?.actions?.flat?.() ?? [];
165
+ return actions.some((a) => a.type === "pick" && (a.isInProgress || a.completed));
166
+ }
167
+
162
168
  function banPending(s) {
163
- const bans = (s?.actions?.flat?.() ?? []).filter((a) => a.type === "ban");
169
+ const actions = s?.actions?.flat?.() ?? [];
170
+ const bans = actions.filter((a) => a.type === "ban");
164
171
  if (!bans.length) return true;
165
- return bans.some((a) => !a.completed);
172
+ if (bans.some((a) => !a.completed)) return true;
173
+ const phase = s?.timer?.phase;
174
+ if (phase && phase !== "BAN_PICK") return false;
175
+ return !pickStarted(s);
166
176
  }
167
177
 
168
178
  function pump() {
@@ -254,6 +264,7 @@ function onSessionGone() {
254
264
  lastSession = null;
255
265
  goneAway = true;
256
266
  uiVisible = false;
267
+ swapSeen.clear();
257
268
  removeButtons();
258
269
  if (!goneTimer) {
259
270
  goneTimer = setTimeout(() => {
@@ -449,16 +460,31 @@ function isSelfRow(r) {
449
460
  return lastSession?.localPlayerCellId != null && (r.cell ?? r.slotId) === lastSession.localPlayerCellId;
450
461
  }
451
462
 
463
+ function rowKey(r) {
464
+ return String(r.slotId ?? r.cell ?? r.index);
465
+ }
466
+
452
467
  function nativeSwapIn(circle) {
453
- return !!circle.querySelector(".swap-button-component:not([data-bb])");
468
+ if (!circle) return false;
469
+ return [...circle.querySelectorAll(".swap-button-component")].some((el) => el.dataset.bb !== "1");
454
470
  }
455
471
 
456
472
  function ensureButtons() {
457
473
  const list = getRows();
458
474
  for (const r of list) {
475
+ if (!r.circle || !r.circle.isConnected) r.circle = r.el.querySelector(".champion-icon-container");
459
476
  if (!r.circle || !r.circle.isConnected) continue;
477
+ const key = rowKey(r);
460
478
  let btn = buttons.get(r.index);
461
- if (isSelfRow(r) || !nativeSwapIn(r.circle)) {
479
+ if (isSelfRow(r)) {
480
+ if (btn) {
481
+ btn.remove();
482
+ buttons.delete(r.index);
483
+ }
484
+ continue;
485
+ }
486
+ if (nativeSwapIn(r.circle)) swapSeen.add(key);
487
+ if (!swapSeen.has(key)) {
462
488
  if (btn) {
463
489
  btn.remove();
464
490
  buttons.delete(r.index);
@@ -531,6 +557,7 @@ function update() {
531
557
  renderButtons();
532
558
  } else if (uiVisible) {
533
559
  uiVisible = false;
560
+ swapSeen.clear();
534
561
  removeButtons();
535
562
  }
536
563
  }
@@ -605,6 +632,7 @@ function onDisable() {
605
632
  observer = null;
606
633
 
607
634
  uiVisible = false;
635
+ swapSeen.clear();
608
636
  goneAway = true;
609
637
  target = undefined;
610
638
  sent = 0;