perfectionist-plugins 1.6.0 → 1.8.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/auto-read.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const PLUGIN_ID = "auto-read";
2
- const version = "1.0.0";
2
+ const version = "1.1.0";
3
3
 
4
4
  const SWEEP_DEBOUNCE_MS = 500;
5
5
  const ITEM_COOLDOWN_MS = 10 * 60 * 1000;
@@ -9,6 +9,16 @@ const MISSIONS_URI = "/lol-missions/v1/missions";
9
9
  const MISSIONS_PLAYER_URI = "/lol-missions/v1/player";
10
10
  const LOOT_URI = "/lol-loot/v1/player-loot";
11
11
  const CLASH_FLAGS_URI = "/lol-clash/v1/simple-state-flags";
12
+ const REWARDS_GRANTS_URI = "/lol-rewards/v1/grants";
13
+ const REWARDS_VIEW_URI = "/lol-rewards/v1/grants/view";
14
+ const LOOT_NOTIF_URI = "/lol-loot/v1/player-loot-notifications";
15
+ const MASTERY_NOTIF_URI = "/lol-champion-mastery/v1/notifications";
16
+ const MASTERY_ACK_URI = "/lol-champion-mastery/v1/notifications/ack";
17
+ const HONOR_NOTICES = [
18
+ { uri: "/lol-honor-v2/v1/reward-granted", ack: "/lol-honor-v2/v1/reward-granted/ack", present: (d) => Number(d?.quantity ?? -1) >= 0 || Boolean(d?.rewardType) },
19
+ { uri: "/lol-honor-v2/v1/mutual-honor", ack: "/lol-honor-v2/v1/mutual-honor/ack", present: (d) => (d?.summoners?.length ?? 0) > 0 },
20
+ { uri: "/lol-honor-v2/v1/level-change", ack: "/lol-honor-v2/v1/level-change/ack", present: (d) => Number(d?.currentState?.level ?? -1) >= 0 },
21
+ ];
12
22
  const RANKED_NOTIF_URI = "/lol-ranked/v1/notifications";
13
23
  const EOS_NOTIF_URI = "/lol-ranked/v1/eos-notifications";
14
24
  const NOTICE_LIST_URIS = [
@@ -39,6 +49,7 @@ let sweepRunning = false;
39
49
  let sweepQueued = false;
40
50
  let unsubscribes = [];
41
51
  const cooldowns = new Map();
52
+ const markedGrants = new Set();
42
53
 
43
54
  function on(key) {
44
55
  try {
@@ -116,6 +127,49 @@ async function sweepClash() {
116
127
  }
117
128
  }
118
129
 
130
+ async function sweepRewards() {
131
+ const grants = await getJson(REWARDS_GRANTS_URI).catch(() => null);
132
+ if (Array.isArray(grants) && grants.length && ready("rewards-grants", BULK_COOLDOWN_MS)) {
133
+ const ids = [];
134
+ for (const grant of grants) {
135
+ const id = grant?.grantId ?? grant?.id;
136
+ if (typeof id !== "string" || !id || markedGrants.has(id)) continue;
137
+ ids.push(id);
138
+ }
139
+ if (ids.length) {
140
+ stamp("rewards-grants");
141
+ for (const id of ids) markedGrants.add(id);
142
+ await sendJson("PATCH", REWARDS_VIEW_URI, ids).catch(() => {
143
+ for (const id of ids) markedGrants.delete(id);
144
+ });
145
+ }
146
+ }
147
+
148
+ const notes = await getJson(LOOT_NOTIF_URI).catch(() => null);
149
+ if (Array.isArray(notes)) {
150
+ for (const note of notes) {
151
+ const id = note?.id ?? note?.lootId;
152
+ if (!id || !ready(`lootnote:${id}`, ITEM_COOLDOWN_MS)) continue;
153
+ stamp(`lootnote:${id}`);
154
+ await sendJson("POST", `${LOOT_NOTIF_URI}/${encodeURIComponent(id)}/acknowledge`).catch(() => {});
155
+ }
156
+ }
157
+
158
+ const mastery = await getJson(MASTERY_NOTIF_URI).catch(() => null);
159
+ if (Number(mastery?.championId ?? 0) > 0 && ready("mastery", ITEM_COOLDOWN_MS)) {
160
+ stamp("mastery");
161
+ await sendJson("POST", MASTERY_ACK_URI).catch(() => {});
162
+ }
163
+
164
+ for (const notice of HONOR_NOTICES) {
165
+ const data = await getJson(notice.uri).catch(() => null);
166
+ const key = `honor:${notice.uri}`;
167
+ if (!data || !notice.present(data) || !ready(key, ITEM_COOLDOWN_MS)) continue;
168
+ stamp(key);
169
+ await sendJson("POST", notice.ack).catch(() => {});
170
+ }
171
+ }
172
+
119
173
  async function sweepRanked() {
120
174
  for (const uri of [RANKED_NOTIF_URI, EOS_NOTIF_URI]) {
121
175
  const list = await getJson(uri).catch(() => null);
@@ -176,6 +230,7 @@ async function runSweep() {
176
230
  try {
177
231
  if (on("quests")) await sweepQuests().catch(() => {});
178
232
  if (on("loot")) await sweepLootMarks().catch(() => {});
233
+ if (on("rewards")) await sweepRewards().catch(() => {});
179
234
  if (on("clash")) await sweepClash().catch(() => {});
180
235
  if (on("ranked")) await sweepRanked().catch(() => {});
181
236
  if (on("notices")) await sweepNotices().catch(() => {});
@@ -192,7 +247,7 @@ function onEnable(utils) {
192
247
  api = utils;
193
248
  active = true;
194
249
  cooldowns.clear();
195
- for (const uri of [MISSIONS_URI, LOOT_URI, CLASH_FLAGS_URI, RANKED_NOTIF_URI, EOS_NOTIF_URI, ...NOTICE_LIST_URIS]) {
250
+ for (const uri of [MISSIONS_URI, LOOT_URI, CLASH_FLAGS_URI, REWARDS_GRANTS_URI, LOOT_NOTIF_URI, MASTERY_NOTIF_URI, ...HONOR_NOTICES.map((n) => n.uri), RANKED_NOTIF_URI, EOS_NOTIF_URI, ...NOTICE_LIST_URIS]) {
196
251
  try {
197
252
  const off = api.subscribe(uri, scheduleSweep);
198
253
  if (off) unsubscribes.push(off);
@@ -215,6 +270,7 @@ function onDisable() {
215
270
  }
216
271
  unsubscribes = [];
217
272
  cooldowns.clear();
273
+ markedGrants.clear();
218
274
  api = null;
219
275
  syncPips();
220
276
  }
@@ -242,6 +298,7 @@ export function init() {
242
298
  items: [
243
299
  { id: "quests", label: "Quests", description: "Quest and mission badges, including the mission button counter.", default: true },
244
300
  { id: "loot", label: "Loot marks", description: "The new mark on freshly earned loot.", default: true },
301
+ { id: "rewards", label: "Rewards", description: "The dot on your avatar for new tokens, titles and banners, plus mastery, honor and loot notifications.", default: true },
245
302
  { id: "clash", label: "Clash", description: "Clash tab flags and Clash notifications.", default: true },
246
303
  { id: "ranked", label: "Ranked", description: "Ranked, end-of-season and champion mastery notices.", default: true },
247
304
  { id: "notices", label: "Notices", description: "Patch, patcher, leaver buster, lobby, behavior and gameflow notices.", default: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfectionist-plugins",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
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.6.0";
9
+ export const version = "1.7.0";
10
10
 
11
11
  const PKG = "perfectionist-plugins";
12
12
  const CDN = "https://cdn.jsdelivr.net/npm";
@@ -54,7 +54,7 @@ const BUNDLED_MODULES = [
54
54
  id: "auto-read",
55
55
  file: "auto-read.js",
56
56
  name: "Auto Read",
57
- version: "1.0.0",
57
+ version: "1.1.0",
58
58
  description: "Automatically marks quests, loot, Clash and client notices as read so no badge or dot stays lit.",
59
59
  },
60
60
  ];
@@ -267,7 +267,7 @@ function getOption(id, key) {
267
267
  return stored === undefined ? Boolean(fallback) : Boolean(stored);
268
268
  }
269
269
 
270
- function setOption(id, key, value) {
270
+ function setOption(id, key, value, refresh = true) {
271
271
  if (!state.options[id]) state.options[id] = {};
272
272
  state.options[id][key] = Boolean(value);
273
273
  saveState();
@@ -277,7 +277,7 @@ function setOption(id, key, value) {
277
277
  } catch (error) {
278
278
  logError(`${entry?.name ?? id} could not apply the option change.`, error);
279
279
  }
280
- refreshPanel();
280
+ if (refresh) refreshPanel();
281
281
  }
282
282
 
283
283
  function getPlugins() {
@@ -599,7 +599,7 @@ function buildOptionRow(mod, option, disabled, onChange) {
599
599
  input.setAttribute("aria-label", `${mod.name}: ${option.label}`);
600
600
  input.addEventListener("change", () => {
601
601
  if (onChange) onChange(input.checked);
602
- else setOption(mod.id, option.key, input.checked);
602
+ else setOption(mod.id, option.key, input.checked, false);
603
603
  });
604
604
  row.append(input, el("span", "perf-box"), el("span", "perf-opt-label", option.label));
605
605
  return row;
@@ -616,7 +616,10 @@ function buildChips(mod, option) {
616
616
  chip.type = "button";
617
617
  chip.title = item.description ?? "";
618
618
  if (getOption(mod.id, key)) chip.classList.add("perf-chip--on");
619
- chip.addEventListener("click", () => setOption(mod.id, key, !getOption(mod.id, key)));
619
+ chip.addEventListener("click", () => {
620
+ setOption(mod.id, key, !getOption(mod.id, key), false);
621
+ chip.classList.toggle("perf-chip--on", getOption(mod.id, key));
622
+ });
620
623
  row.append(chip);
621
624
  }
622
625
  block.append(row);
@@ -650,7 +653,7 @@ function buildOptions(mod) {
650
653
  }
651
654
  const master = buildOptionRow(mod, option, false, (checked) => {
652
655
  for (const input of childInputs) input.disabled = !checked;
653
- setOption(mod.id, option.key, checked);
656
+ setOption(mod.id, option.key, checked, false);
654
657
  });
655
658
  const groupKey = `${mod.id}:${option.key}`;
656
659
  const setExpanded = (open) => {
@@ -743,9 +746,25 @@ function buildPanel() {
743
746
  return panel;
744
747
  }
745
748
 
749
+ function panelScrollTop(panel) {
750
+ return panel?.querySelector("lol-uikit-scrollable")?.scrollTop ?? 0;
751
+ }
752
+
753
+ function applyScrollTop(panel, top) {
754
+ if (!top || !panel) return;
755
+ const host = panel.querySelector("lol-uikit-scrollable");
756
+ if (host) host.scrollTop = top;
757
+ }
758
+
746
759
  function refreshPanel() {
747
760
  const current = document.getElementById(PANEL_ID);
748
- if (current) current.replaceWith(buildPanel());
761
+ if (!current) return;
762
+ const top = panelScrollTop(current);
763
+ const next = buildPanel();
764
+ current.replaceWith(next);
765
+ if (!top) return;
766
+ applyScrollTop(next, top);
767
+ requestAnimationFrame(() => applyScrollTop(next, top));
749
768
  }
750
769
 
751
770
  function setNavActive(activeNav) {