perfectionist-plugins 1.0.2 → 1.0.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.
package/ban-blocker.js CHANGED
@@ -1,4 +1,4 @@
1
- export const version = "3.4.0";
1
+ export const version = "3.4.2";
2
2
 
3
3
  const PLUGIN_ID = "ban-blocker";
4
4
  const STYLE_ID = "perfectionist-ban-blocker-style";
@@ -13,6 +13,7 @@ const FAIL_COOLDOWN_MAX_MS = 30000;
13
13
  const POLL_MS = 40;
14
14
  const ENSURE_MS = 300;
15
15
  const DETECT_RETRY_MS = 2000;
16
+ const GONE_RESET_MS = 1500;
16
17
 
17
18
  const BAN_GLYPH_SVG =
18
19
  "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>" +
@@ -58,11 +59,14 @@ let ensureTimer = null;
58
59
  let detectTimer = null;
59
60
  let refreshTimer = null;
60
61
  let pumpTimer = null;
62
+ let goneTimer = null;
61
63
  let observer = null;
62
64
  let pollInFlight = false;
63
65
  let uiScheduled = false;
64
66
 
65
67
  const enabled = new Map();
68
+ const alias = new Map();
69
+ const fetchedNames = new Set();
66
70
  const playerNames = new Map();
67
71
  const cellToPlayer = new Map();
68
72
  const buttons = new Map();
@@ -96,13 +100,62 @@ function isPicked(members, champ) {
96
100
  return (members ?? []).some((p) => p.championId === champ && p.completed);
97
101
  }
98
102
 
103
+ function canonOf(id) {
104
+ return (id != null && alias.get(id)) || id;
105
+ }
106
+
99
107
  function isBlocked(id) {
100
- return enabled.get(id) === true;
108
+ return enabled.get(canonOf(id)) === true;
109
+ }
110
+
111
+ function isWeakKey(k) {
112
+ return typeof k === "string" && k.startsWith("cell:");
113
+ }
114
+
115
+ function payloadName(p) {
116
+ return norm(`${p?.gameName ?? ""}${p?.tagLine ?? ""}`) || norm(p?.summonerName) || "";
101
117
  }
102
118
 
103
- function playerId(p) {
119
+ function keyOf(p) {
104
120
  if (typeof p === "number") return p;
105
- return p?.summonerId ?? p?.puuid ?? null;
121
+ const strong = p?.summonerId || p?.puuid;
122
+ if (strong) return strong;
123
+ const n = payloadName(p);
124
+ if (n) return `name:${n}`;
125
+ return p?.cellId != null ? `cell:${p.cellId}` : null;
126
+ }
127
+
128
+ function linkPlayer(p, rowName, cellId) {
129
+ const strong = typeof p === "number" ? p : p?.summonerId || p?.puuid;
130
+ const pn = typeof p === "number" ? "" : payloadName(p);
131
+ const rn = norm(rowName);
132
+ const keys = [];
133
+ if (strong) keys.push(strong);
134
+ if (pn) keys.push(`name:${pn}`);
135
+ if (rn && rn !== pn) keys.push(`name:${rn}`);
136
+ const weak = cellId != null ? `cell:${cellId}` : null;
137
+
138
+ let root = null;
139
+ for (const k of keys) {
140
+ const r = alias.get(k);
141
+ if (r != null) {
142
+ root = r;
143
+ break;
144
+ }
145
+ }
146
+ if (root == null && keys.length) {
147
+ root = keys[0];
148
+ const prev = weak ? alias.get(weak) : null;
149
+ if (prev != null && isWeakKey(prev) && enabled.has(prev)) {
150
+ if (!enabled.has(root)) enabled.set(root, enabled.get(prev));
151
+ enabled.delete(prev);
152
+ }
153
+ }
154
+ if (root == null) root = (weak && alias.get(weak)) || weak;
155
+ if (root == null) return null;
156
+ for (const k of keys) alias.set(k, root);
157
+ if (weak) alias.set(weak, root);
158
+ return root;
106
159
  }
107
160
 
108
161
  function banPending(s) {
@@ -149,7 +202,10 @@ function decide(s) {
149
202
  const me = s?.localPlayerCellId;
150
203
 
151
204
  cellToPlayer.clear();
152
- for (const p of m) cellToPlayer.set(p.cellId, playerId(p));
205
+ for (const p of m) {
206
+ const row = rows.find((r) => r.slotId === p.cellId);
207
+ cellToPlayer.set(p.cellId, linkPlayer(p, row?.name, p.cellId));
208
+ }
153
209
 
154
210
  let myPick = null;
155
211
  for (const a of actions) {
@@ -160,7 +216,7 @@ function decide(s) {
160
216
  }
161
217
  myActionId = myPick ? myPick.id : null;
162
218
 
163
- const blockedCells = new Set(m.filter((p) => isBlocked(playerId(p))).map((p) => p.cellId));
219
+ const blockedCells = new Set(m.filter((p) => isBlocked(cellToPlayer.get(p.cellId))).map((p) => p.cellId));
164
220
 
165
221
  let threat = 0;
166
222
  for (const a of actions) {
@@ -193,6 +249,15 @@ function onSessionGone() {
193
249
  goneAway = true;
194
250
  uiVisible = false;
195
251
  removeButtons();
252
+ if (!goneTimer) {
253
+ goneTimer = setTimeout(() => {
254
+ goneTimer = null;
255
+ enabled.clear();
256
+ alias.clear();
257
+ playerNames.clear();
258
+ fetchedNames.clear();
259
+ }, GONE_RESET_MS);
260
+ }
196
261
  }
197
262
 
198
263
  function onSession(raw) {
@@ -208,6 +273,8 @@ function onSession(raw) {
208
273
  return;
209
274
  }
210
275
  if (!s?.myTeam) return;
276
+ clearTimeout(goneTimer);
277
+ goneTimer = null;
211
278
  goneAway = false;
212
279
  lastSession = s;
213
280
 
@@ -256,29 +323,27 @@ function getRows() {
256
323
  }
257
324
 
258
325
  function ensureName(cell) {
259
- const id = playerId(cell);
260
- if (id == null || playerNames.has(id)) return;
261
- playerNames.set(id, []);
326
+ const strong = cell?.summonerId || cell?.puuid;
327
+ if (!strong || fetchedNames.has(strong)) return;
262
328
  const get = api?.lcu?.get;
263
- if (!get) {
264
- playerNames.delete(id);
265
- return;
266
- }
267
- const byPuuid = typeof id === "string" && id.length > 20;
268
- const path = byPuuid ? `/lol-summoner/v2/summoners/puuid/${id}` : `/lol-summoner/v1/summoners/${id}`;
329
+ if (!get) return;
330
+ fetchedNames.add(strong);
331
+ const byPuuid = typeof strong === "string" && strong.length > 20;
332
+ const path = byPuuid ? `/lol-summoner/v2/summoners/puuid/${strong}` : `/lol-summoner/v1/summoners/${strong}`;
269
333
  get(path)
270
334
  .then((data) => {
271
335
  const names = [norm(data?.displayName), norm(data?.gameName)].filter(Boolean);
272
- if (names.length) playerNames.set(id, names);
273
- else playerNames.delete(id);
336
+ if (!names.length) return;
337
+ const root = canonOf(strong);
338
+ playerNames.set(root, [...new Set([...(playerNames.get(root) ?? []), ...names])]);
274
339
  })
275
- .catch(() => playerNames.delete(id));
340
+ .catch(() => fetchedNames.delete(strong));
276
341
  }
277
342
 
278
343
  function nameMatches(rowName, id) {
279
344
  const rn = norm(rowName);
280
345
  if (!rn) return false;
281
- return (playerNames.get(id) ?? []).some(
346
+ return (playerNames.get(canonOf(id)) ?? []).some(
282
347
  (n) => n === rn || (n.length >= 3 && rn.length >= 3 && (n.startsWith(rn) || rn.startsWith(n))),
283
348
  );
284
349
  }
@@ -292,8 +357,13 @@ function bindRowsToTeam(session) {
292
357
  for (const r of list) {
293
358
  const cell =
294
359
  (r.slotId != null ? cells.find((c) => c.cellId === r.slotId) : null) ??
295
- cells.find((c) => nameMatches(r.name, playerId(c)));
296
- r.player = cell ? playerId(cell) : null;
360
+ cells.find((c) => nameMatches(r.name, keyOf(c)));
361
+ if (cell) {
362
+ r.player = linkPlayer(cell, r.name, cell.cellId);
363
+ cellToPlayer.set(cell.cellId, r.player);
364
+ } else {
365
+ r.player = null;
366
+ }
297
367
  }
298
368
  }
299
369
 
@@ -303,14 +373,18 @@ function stopEvent(e) {
303
373
  e.preventDefault();
304
374
  }
305
375
 
376
+ function playerForBadge(btn) {
377
+ const rowEl = btn.closest(".summoner-object");
378
+ const r = rowEl ? rows.find((x) => x.el === rowEl) : rows[Number(btn.dataset.slot)];
379
+ const byCell = btn.dataset.cell ? cellToPlayer.get(Number(btn.dataset.cell)) ?? null : null;
380
+ return canonOf(byCell ?? r?.player ?? null);
381
+ }
382
+
306
383
  function toggle(btn) {
307
- let id = btn.dataset.cell ? (cellToPlayer.get(Number(btn.dataset.cell)) ?? null) : null;
308
- if (id == null) {
309
- if (lastSession) bindRowsToTeam(lastSession);
310
- const list = getRows();
311
- const rowEl = btn.closest(".summoner-object");
312
- const r = list.find((x) => x.el === rowEl) ?? list[Number(btn.dataset.slot)];
313
- id = r?.player ?? null;
384
+ let id = playerForBadge(btn);
385
+ if (id == null && lastSession) {
386
+ bindRowsToTeam(lastSession);
387
+ id = playerForBadge(btn);
314
388
  }
315
389
  if (id == null) return;
316
390
  const myCell = lastSession?.localPlayerCellId;
@@ -386,7 +460,7 @@ function ensureButtons() {
386
460
  function renderButtons() {
387
461
  const hasSession = !!lastSession?.myTeam;
388
462
  for (const [, btn] of buttons) {
389
- const id = btn.dataset.cell ? (cellToPlayer.get(Number(btn.dataset.cell)) ?? null) : null;
463
+ const id = playerForBadge(btn);
390
464
  const on = id != null && isBlocked(id);
391
465
  btn.classList.toggle("on", on);
392
466
  btn.classList.toggle("unresolved", id == null && hasSession);
@@ -505,7 +579,8 @@ function onDisable() {
505
579
  clearTimeout(refreshTimer);
506
580
  clearTimeout(detectTimer);
507
581
  clearTimeout(pumpTimer);
508
- pollTimer = ensureTimer = refreshTimer = detectTimer = pumpTimer = null;
582
+ clearTimeout(goneTimer);
583
+ pollTimer = ensureTimer = refreshTimer = detectTimer = pumpTimer = goneTimer = null;
509
584
  observer?.disconnect();
510
585
  observer = null;
511
586
 
@@ -518,6 +593,8 @@ function onDisable() {
518
593
  cellToPlayer.clear();
519
594
  enabled.clear();
520
595
  playerNames.clear();
596
+ alias.clear();
597
+ fetchedNames.clear();
521
598
  lastSession = null;
522
599
  rows = [];
523
600
  removeButtons();
@@ -1,4 +1,4 @@
1
- export const version = "2.1.1";
1
+ export const version = "2.2.0";
2
2
 
3
3
  const PLUGIN_ID = "hide-unowned-skins";
4
4
  const STYLE_ID = "perfectionist-hide-unowned-skins-style";
@@ -11,7 +11,7 @@ const CAROUSEL_URI = "/lol-champ-select/v1/skin-carousel-skins";
11
11
  const CURRENT_SUMMONER_URI = "/lol-summoner/v1/current-summoner";
12
12
  const SUMMONER_TTL = 10 * 60 * 1000;
13
13
  const INVENTORY_TTL = 60 * 1000;
14
- const SESSION_POLL_MS = 1000;
14
+ const REFRESH_DEBOUNCE_MS = 350;
15
15
  const RETRY_MS = 5000;
16
16
  const STATE_KEY = "perfectionist-plugins:state";
17
17
 
@@ -57,6 +57,7 @@ function filterSkinList(list) {
57
57
  }
58
58
 
59
59
  function recordClientOrder(list) {
60
+ scheduleRefresh();
60
61
  if (Array.isArray(list)) {
61
62
  clientSkinOrder = list.map((skin) => (skin && typeof skin.id === "number" ? skin.id : null)).filter((id) => id !== null);
62
63
  }
@@ -262,7 +263,8 @@ let carouselOrder = [];
262
263
  let selectedSkinId = null;
263
264
  let failure = null;
264
265
  let requestToken = 0;
265
- let pollTimer = null;
266
+ let refreshTimer = null;
267
+ let unsubscribe = null;
266
268
  let pollInFlight = false;
267
269
  let selectInFlight = false;
268
270
  let sessionMisses = 0;
@@ -452,7 +454,7 @@ function resetState() {
452
454
  failure = null;
453
455
  sessionMisses = 0;
454
456
  championMisses = 0;
455
- applyAll();
457
+ applyAllGuarded();
456
458
  }
457
459
 
458
460
  function loadChampion(championId) {
@@ -467,7 +469,7 @@ function loadChampion(championId) {
467
469
  if (token !== requestToken) return;
468
470
  ownedSkinIds = owned;
469
471
  carouselOrder = carouselIds.length > 0 ? carouselIds : inventoryOrder;
470
- applyAll();
472
+ applyAllGuarded();
471
473
  },
472
474
  (error) => {
473
475
  if (token !== requestToken) return;
@@ -507,7 +509,23 @@ function updateForSession(session, selection) {
507
509
  }
508
510
  }
509
511
 
510
- applyAll();
512
+ applyAllGuarded();
513
+ }
514
+
515
+ function scheduleRefresh() {
516
+ clearTimeout(refreshTimer);
517
+ refreshTimer = setTimeout(pollSession, REFRESH_DEBOUNCE_MS);
518
+ }
519
+
520
+ function onSessionPush(raw) {
521
+ const msg = raw ?? {};
522
+ if (msg.eventType === "Delete") {
523
+ resetState();
524
+ return;
525
+ }
526
+ const s = msg.data ?? msg;
527
+ if (!s?.myTeam) return;
528
+ updateForSession(s, null);
511
529
  }
512
530
 
513
531
  async function pollSession() {
@@ -515,11 +533,9 @@ async function pollSession() {
515
533
  ensureSourceFilter();
516
534
  pollInFlight = true;
517
535
  try {
518
- const [session, selection] = await Promise.all([
519
- api.lcu.get(SESSION_URI),
520
- api.lcu.get(SELECTION_URI).catch(() => null),
521
- ]);
536
+ const session = await api.lcu.get(SESSION_URI);
522
537
  sessionMisses = 0;
538
+ const selection = await api.lcu.get(SELECTION_URI).catch(() => null);
523
539
  updateForSession(session, selection);
524
540
  } catch {
525
541
  sessionMisses++;
@@ -534,16 +550,21 @@ function onEnable(utils) {
534
550
  filterActive = true;
535
551
  removeStyle = api.addStyle(STYLE_ID, CSS);
536
552
 
537
- renderObserver = new MutationObserver(() => applyAllGuarded());
553
+ renderObserver = new MutationObserver(() => {
554
+ applyAllGuarded();
555
+ if (document.querySelector(SKIN_ITEM_SELECTOR)) scheduleRefresh();
556
+ });
538
557
  renderObserver.observe(document.documentElement ?? document, OBSERVER_OPTIONS);
539
558
 
540
- pollTimer = setInterval(pollSession, SESSION_POLL_MS);
559
+ unsubscribe = api.subscribe(SESSION_URI, onSessionPush);
541
560
  pollSession();
542
561
  }
543
562
 
544
563
  function onDisable() {
545
- clearInterval(pollTimer);
546
- pollTimer = null;
564
+ unsubscribe?.();
565
+ unsubscribe = null;
566
+ clearTimeout(refreshTimer);
567
+ refreshTimer = null;
547
568
  renderObserver?.disconnect();
548
569
  removeStyle?.();
549
570
  renderObserver = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfectionist-plugins",
3
- "version": "1.0.2",
3
+ "version": "1.0.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",
@@ -6,7 +6,7 @@ const PANEL_ID = "perfectionist-plugins-panel";
6
6
  const STYLE_ID = "perfectionist-plugins-style";
7
7
  const TAB_TITLE = "Perfectionism";
8
8
 
9
- export const version = "1.0.1";
9
+ export const version = "1.0.4";
10
10
 
11
11
  const PKG = "perfectionist-plugins";
12
12
  const CDN = "https://cdn.jsdelivr.net/npm";
@@ -26,14 +26,14 @@ const BUNDLED_MODULES = [
26
26
  id: "ban-blocker",
27
27
  file: "ban-blocker.js",
28
28
  name: "Ban Blocker",
29
- version: "3.4.0",
29
+ version: "3.4.2",
30
30
  description: "During bans, blocking a teammate instantly claims the champion they hover to ban.",
31
31
  },
32
32
  {
33
33
  id: "hide-unowned-skins",
34
34
  file: "hide-unowned-skins.js",
35
35
  name: "Hide Unowned Skins",
36
- version: "2.1.1",
36
+ version: "2.2.0",
37
37
  description: "In champion select, only shows the skins you own for the picked champion.",
38
38
  },
39
39
  ];
@@ -113,11 +113,6 @@ async function loadState() {
113
113
  state = { installed: saved.installed, enabled: saved.enabled ?? {} };
114
114
  return;
115
115
  }
116
- for (const mod of BUNDLED_MODULES) {
117
- if (mod.core) continue;
118
- state.installed[mod.id] = true;
119
- state.enabled[mod.id] = mod.defaultEnabled ?? true;
120
- }
121
116
  saveState();
122
117
  }
123
118
 
@@ -128,11 +123,15 @@ function saveState() {
128
123
  enabled: state.enabled,
129
124
  };
130
125
  try {
131
- if (typeof DataStore !== "undefined") DataStore.set(STATE_KEY, record);
132
126
  localStorage.setItem(STATE_KEY, JSON.stringify(record));
133
127
  } catch (error) {
134
128
  console.error("[Perfectionism] Could not save plugin state.", error);
135
129
  }
130
+ try {
131
+ if (typeof DataStore !== "undefined") DataStore.set(STATE_KEY, record);
132
+ } catch (error) {
133
+ console.error("[Perfectionism] Could not save plugin state.", error);
134
+ }
136
135
  }
137
136
 
138
137
  function logError(scope, error) {