├── ARAM_ui.txt
├── README.md
├── auto-accept.js
├── index.css
├── index.js
├── pre-pick.js
└── utils.js
/README.md:
--------------------------------------------------------------------------------
1 | # aram-pengu-loader-plugin
2 | Plugins build with PenguLoader to Customize League Client 🛠
3 |
4 | **Plugin build with PenguLoader**
5 | Features:
6 | - Auto Accept Match
7 | - Pre pick champions in ARAM mode - no miss your champion anymore :)
8 |
9 | ## Screenshots
10 |
11 | 
12 |
13 | currently in development 🚧
14 |
--------------------------------------------------------------------------------
/auto-accept.js:
--------------------------------------------------------------------------------
1 | import { htmlToElement } from "./utils";
2 |
3 | // AUTO ACCEPT FEATURE
4 | export const initSettingAutoAccept = () => {
5 | const checkbox = htmlToElement(`
6 |
7 |
8 |
9 |
10 |
11 |
`);
12 | const listMenu = document.querySelector(
13 | "div.lol-social-lower-pane-container > lol-social-roster > lol-uikit-scrollable > div.list-content"
14 | );
15 | listMenu.insertBefore(checkbox, listMenu.firstChild);
16 |
17 | const checkboxInput = document.querySelector("#autoAccept");
18 | const checked = DataStore.get("autoAcceptMode");
19 | checkboxInput.checked = checked;
20 | checkboxInput.addEventListener("change", () => {
21 | // Check if the checkbox is checked
22 | if (checkboxInput.checked) {
23 | console.log("checked");
24 | DataStore.set("autoAcceptMode", true);
25 | } else {
26 | console.log("un checked");
27 | DataStore.set("autoAcceptMode", false);
28 | }
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | /* *{
2 | font-family: 'Dancing Script', cursive;
3 | --font-display: 'Dancing Script', cursive;
4 | --font-body :'Lora', serif;
5 |
6 | } */
7 | .vng-age-rating {
8 | display: none !important;
9 | }
10 | .regalia-banner-asset-static-image {
11 | display: none !important;
12 | }
13 | .ken-modal-champions {
14 | margin-top: 15px;
15 | color: #a09b8c;
16 | width: 100%;
17 | display: block;
18 | overflow: hidden;
19 | text-align: center;
20 | }
21 | .ken-champion-list {
22 | padding: 10px;
23 | display: grid;
24 | grid-template-columns: repeat(3, minmax(0, 1fr));
25 | gap: 20px;
26 | margin-top: 10px;
27 | }
28 | .active-item {
29 | border: 1.5px solid transparent !important;
30 | border-image: linear-gradient(to top, #785a28 0, #463714 50%, #463714 100%) 1
31 | stretch !important;
32 | }
33 | .ken-champion-item {
34 | padding: 1px;
35 | border: 1.5px solid transparent;
36 | cursor: pointer;
37 | display: flex;
38 | flex-direction: column-reverse;
39 | align-items: center;
40 | justify-content: center;
41 | gap: 10px;
42 |
43 | font-size: 12px;
44 | font-weight: 700;
45 | /* text-transform: uppercase; */
46 | margin-bottom: auto;
47 | }
48 |
49 | .ken-champion-item > img {
50 | width: 48px;
51 | height: 48px;
52 | }
53 | .auto-accept-checkbox {
54 | margin-bottom: 10px;
55 | margin-left: 10px;
56 | }
57 | .auto-pick-champ {
58 | display: flex;
59 | gap: 10px;
60 | margin-left: 10px;
61 | }
62 | .aram-icon-auto-pick {
63 | background-size: 15px;
64 | display: inline-block;
65 | height: 18px;
66 | margin: 0 10px;
67 | width: 18px;
68 | }
69 | /* #rcp-fe-viewport-root>.rcp-fe-viewport-main{
70 | width : 100% !important
71 | }
72 | #rcp-fe-viewport-root>.rcp-fe-viewport-sidebar{
73 | position: absolute;
74 | right : 0;
75 | top : 0
76 | } */
77 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import { initSettingAutoAccept } from "./auto-accept";
2 | import "./index.css";
3 | import {
4 | initUIPrePickInChampSelect,
5 | prePickChampionEvent,
6 | prePickChampionsUI,
7 | } from "./pre-pick";
8 | import { waitForElm } from "./utils";
9 | console.log("Hello, League Client!, Ken Plugin is running");
10 |
11 | /* load theme from URL */
12 | function injectCSS(url) {
13 | const link = document.createElement("link");
14 | link.setAttribute("rel", "stylesheet");
15 | link.setAttribute("type", "text/css");
16 | link.setAttribute("href", url);
17 | document.body.appendChild(link);
18 | }
19 |
20 | function subscribe() {
21 | const uri = document.querySelector('link[rel="riot:plugins:websocket"]').href;
22 | console.log("url socket", uri, DataStore);
23 |
24 | if (uri) {
25 | console.log({ uri });
26 | handleGetSummonerData();
27 | }
28 |
29 | // ======== socket
30 | const socket = new WebSocket(uri, "wamp");
31 |
32 | socket.onopen = () => socket.send(JSON.stringify([5, "OnJsonApiEvent"]));
33 | socket.onmessage = async (message) => {
34 | const [_id, _event, info] = JSON.parse(message.data);
35 | console.log("URL : ", info.uri);
36 | // console.log(DataStore.get("summoner"));
37 | // @todo process data
38 | listenAutoAccept(info);
39 |
40 | listenAfterMatch(info);
41 | };
42 | }
43 |
44 | const handleGetSummonerData = async () => {
45 | const res = await fetch("/lol-summoner/v1/current-summoner");
46 | const summoner = await res.json();
47 | console.log(summoner);
48 | DataStore.set("summoner", summoner);
49 | const championList = DataStore.get("champions");
50 | const pool = DataStore.get("champPool");
51 | // DataStore.set("champPool", []);
52 | console.log("init pool", pool);
53 | if (!championList) {
54 | const resC = await fetch(
55 | `/lol-champions/v1/inventories/${summoner.summonerId}/champions`
56 | );
57 | const champs = await resC.json();
58 | // console.log({ champs });
59 | const d = [...champs]
60 | .map((v) => ({ ...v, checked: false }))
61 | .sort((a, b) => b.id - a.id);
62 | d.shift();
63 | DataStore.set("champions", d);
64 | }
65 |
66 | // undefined
67 | if (!pool) {
68 | console.log("NO POOL");
69 | if (!championList) {
70 | DataStore.set("champPool", []);
71 | } else {
72 | // init
73 | const p = championList.map((v) => v).filter((z) => z.checked);
74 | DataStore.set("champPool", p);
75 | }
76 | } else {
77 | // init
78 | const p = championList.map((v) => v).filter((z) => z.checked);
79 | DataStore.set("champPool", p);
80 | }
81 | };
82 |
83 | const listenAfterMatch = ({ uri, data }) => {
84 | const isPrePickMode = DataStore.get("prePickMode");
85 | if (uri === "/lol-champ-select/v1/session") {
86 | // console.log("SELECT 😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂");
87 | // console.log(JSON.stringify(data));
88 |
89 | // handle when lobby has changed
90 | if (isPrePickMode) {
91 | prePickChampionEvent(data);
92 | }
93 | }
94 | };
95 | const listenAutoAccept = ({ uri, data }) => {
96 | const isAutoAcceptMode = DataStore.get("autoAcceptMode");
97 | if (uri === "/lol-gameflow/v1/gameflow-phase") {
98 | console.log("listenAutoAccept", { data, isAutoAcceptMode });
99 | // logic goes here
100 | if (data === "ReadyCheck" && isAutoAcceptMode) {
101 | fetch("/lol-matchmaking/v1/ready-check/accept", {
102 | method: "POST",
103 | });
104 | }
105 | }
106 | };
107 | // +=========== window
108 |
109 | window.addEventListener("load", () => {
110 | const url =
111 | "https://fonts.googleapis.com/css2?family=Dancing+Script&family=Lora:ital,wght@1,500&family=Montserrat:wght@100&display=swap";
112 | /* ^----- put your link here */
113 | /* the server must support HTTPS, otherwise the theme will be denied */
114 | injectCSS(url);
115 | subscribe();
116 |
117 | // menu rendered
118 | waitForElm(
119 | "div.lol-social-lower-pane-container > lol-social-roster > lol-uikit-scrollable > div.list-content"
120 | ).then(() => {
121 | initSettingAutoAccept();
122 | prePickChampionsUI();
123 | // initAramBoostUI();
124 |
125 | initUIPrePickInChampSelect();
126 | // /lol-champ-select/v1/session/my-selection/reroll
127 | });
128 | });
129 |
--------------------------------------------------------------------------------
/pre-pick.js:
--------------------------------------------------------------------------------
1 | import { delay, htmlToElement } from "./utils";
2 | const initStateCheckboxPrePick = () => {
3 | // Add event and init value
4 | const prePickCheckbox = document.getElementById("autoPickChamp");
5 | const prePickModeCheck = DataStore.get("prePickMode");
6 | prePickCheckbox.checked = prePickModeCheck;
7 | prePickCheckbox.addEventListener("change", () => {
8 | // Check if the checkbox is checked
9 | if (prePickCheckbox.checked) {
10 | DataStore.set("prePickMode", true);
11 | } else {
12 | DataStore.set("prePickMode", false);
13 | }
14 | });
15 | };
16 | const handleSelect = (id) => {
17 | const champs = DataStore.get("champions");
18 | const pool = DataStore.get("champPool");
19 | const selected = champs.findIndex((c) => c.id === id);
20 | if (selected !== -1) {
21 | champs[selected].checked = !champs[selected].checked;
22 | DataStore.set("championss", champs);
23 |
24 | const champion = document.getElementById(id + "-prepick-champ");
25 | if (champs[selected].checked) {
26 | champion.classList.add("active-item");
27 |
28 | const newPool = [...pool];
29 |
30 | newPool.push(champs[selected]);
31 | DataStore.set("champPool", newPool);
32 | renderListOrder();
33 | } else {
34 | champion.classList.remove("active-item");
35 |
36 | const newPool = pool.filter((z) => z.id !== champs[selected].id);
37 | DataStore.set("champPool", newPool);
38 | renderListOrder();
39 | }
40 |
41 | DataStore.set("champions", champs);
42 | }
43 | };
44 |
45 | const renderListOrder = () => {
46 | const orderList = document.getElementById("ken-order-list");
47 | // const champs = DataStore.get("champions");
48 | const pool = DataStore.get("champPool");
49 | console.log({ pool });
50 | const text = pool.map((v) => v.name).join(" , ");
51 | orderList.textContent = "[" + text + "]";
52 | };
53 |
54 | const renderListChampions = () => {
55 | const listChamp = document.getElementById("ken-champion-list");
56 |
57 | const listChampData = DataStore.get("champions");
58 | const wrapper = document.createElement("div");
59 | wrapper.className = "ken-champion-list";
60 |
61 | listChampData
62 | .sort((a, b) => {
63 | if (a.name < b.name) {
64 | return -1;
65 | }
66 | if (a.name > b.name) {
67 | return 1;
68 | }
69 | return 0;
70 | })
71 | .map((c) => {
72 | const champion = document.createElement("div");
73 | const avatar = document.createElement("img");
74 | avatar.src = c.squarePortraitPath;
75 |
76 | let className = "ken-champion-item";
77 | if (c.checked) {
78 | className = className + " active-item";
79 | }
80 | champion.className = className;
81 | champion.id = `${c.id}-prepick-champ`;
82 | champion.textContent = c.name;
83 | champion.appendChild(avatar);
84 |
85 | champion.addEventListener("click", () => {
86 | console.log("Click ", c);
87 | // champion.textContent = c.name;
88 | handleSelect(c.id);
89 | });
90 |
91 | wrapper.appendChild(champion);
92 | });
93 |
94 | listChamp.replaceChildren(wrapper);
95 | };
96 |
97 | const initClearButton = () => {
98 | const btn = document.getElementById("clear-pool");
99 | btn.addEventListener("click", () => {
100 | DataStore.set("champPool", []);
101 |
102 | const champs = DataStore.get("champions");
103 | DataStore.set(
104 | "champions",
105 | champs.map((v) => ({ ...v, checked: false }))
106 | );
107 | renderListOrder();
108 | renderListChampions();
109 | });
110 | };
111 |
112 | export function prePickChampionsUI() {
113 | const listMenu = document.querySelector(
114 | "div.lol-social-lower-pane-container > lol-social-roster > lol-uikit-scrollable > div.list-content"
115 | );
116 |
117 | const selectChampUI = document.createElement("div");
118 | const listChamp = document.createElement("div");
119 | // listChamp.className = "ken-champion-list";
120 | listChamp.id = "ken-champion-list";
121 | selectChampUI.className = "ken-modal-champions";
122 | selectChampUI.id = "ken-modal-champions";
123 |
124 | // Item
125 | selectChampUI.appendChild(listChamp);
126 |
127 | const checkbox = htmlToElement(`
128 |
129 |
130 |
131 |
132 |
133 |
`);
134 |
135 | selectChampUI.appendChild(checkbox);
136 |
137 | // Order list
138 |
139 | const orderList = document.createElement("div");
140 | orderList.id = "ken-order-list";
141 | orderList.style.marginLeft = "10px";
142 | listMenu.appendChild(orderList);
143 | renderListOrder();
144 |
145 | // Clear button
146 | const clearBtn =
147 | htmlToElement(`
148 |
149 | Clear
150 |
151 |
`);
152 |
153 | listMenu.appendChild(clearBtn);
154 |
155 | selectChampUI.appendChild(listChamp);
156 |
157 | listMenu.appendChild(selectChampUI);
158 |
159 | initClearButton();
160 | renderListChampions();
161 |
162 | // 12
163 | initStateCheckboxPrePick();
164 | }
165 | // id 2 index 1
166 | export async function prePickChampionEvent(match) {
167 | try {
168 | // BUG : Force pick a champ not in pool
169 | // save global
170 | // const myPickInfo = DataStore.get("myPickInfo");
171 | // let finalChampId = myPickInfo.id;
172 | // let finalChampIndex = myPickInfo.index;
173 |
174 | let finalChampId = null;
175 | let finalChampIndex = null;
176 | console.log("pre pick sTART ====== ", {});
177 | // TODOS : Debug
178 | const res = await fetch("/lol-gameflow/v1/gameflow-phase");
179 | const status = await res.json();
180 | // const status = "ChampSelect";
181 | console.log("==------==== GET PHASE", status);
182 | if (status !== "ChampSelect") return;
183 | const pool = DataStore.get("champPool");
184 |
185 | // Read action
186 | // only in ARAM mode
187 | if (!match.benchEnabled) return;
188 | console.log("BENCH", match.benchChampions);
189 | const currentChampId = match.myTeam.find(
190 | (z) => z.cellId === match.localPlayerCellId
191 | ).championId;
192 | const currentChampIndex = pool.findIndex((z) => z.id === currentChampId);
193 |
194 | if (currentChampIndex === -1) {
195 | console.log("===== YOUR CURRENT CHAMP NOT IN POOL", currentChampId);
196 | } else {
197 | console.log(
198 | "===== ✅✅✅ YOUR CURRENT CHAMP IN POOL",
199 | pool[currentChampIndex]?.name
200 | );
201 | finalChampId = currentChampId;
202 | finalChampIndex = currentChampIndex;
203 | console.log("INFO ==========", {
204 | myChampPick: pool[currentChampIndex]?.name,
205 | champWanted: pool[finalChampIndex]?.name,
206 | });
207 | }
208 |
209 | // CHECK BENCH
210 |
211 | match.benchChampions.map((champ) => {
212 | const poolIndex = pool.findIndex((v) => v.id === champ.championId);
213 |
214 | //
215 | // if index of new champ smaller current index, mean new champ got higher priority
216 | if (poolIndex !== -1) {
217 | // This champ in pool
218 | console.log(
219 | "=== compare ",
220 | `${pool[poolIndex]?.name} (${pool[poolIndex]?.id})`,
221 | `${pool[finalChampIndex]?.name} (${pool[finalChampIndex]?.id})`
222 | );
223 | if (finalChampIndex === null || poolIndex < finalChampIndex) {
224 | finalChampIndex = poolIndex;
225 | finalChampId = pool[finalChampIndex]?.id;
226 | }
227 | }
228 | });
229 |
230 | // my best pick
231 | // if current pick in pool and high priority , skip
232 | // console.log({ finalChampIndex, currentChampIndex });
233 | // if (currentChampIndex !== -1 && currentChampIndex <= finalChampIndex)
234 | // return;
235 |
236 | if (finalChampId === null || finalChampIndex === null) return;
237 |
238 | console.log(
239 | "STAR FETCH AND SELECT ",
240 | pool[finalChampIndex]?.name,
241 | "with ID",
242 | finalChampId
243 | );
244 | const update = await fetch(
245 | `/lol-champ-select/v1/session/bench/swap/${finalChampId}`,
246 | {
247 | method: "POST",
248 | }
249 | );
250 | console.log("DONE ======== 🎉🎉🎉", update.json());
251 |
252 | // debugger;
253 | // DataStore.cl
254 | } catch (error) {
255 | console.log("ERROR ======================");
256 | console.log(error);
257 | console.log(JSON.stringify(match));
258 | }
259 | }
260 |
261 | export function updateListOrderPrePick() {}
262 |
263 | export async function initUIPrePickInChampSelect() {
264 | const timeContainer = () =>
265 | document.getElementsByClassName("timer-status")?.[0];
266 | while (!timeContainer()) {
267 | // console.log("NO BOOST AREA", timeContainer());
268 | await delay(500);
269 | }
270 |
271 | const checkbox = htmlToElement(`
272 |
273 |
274 |
275 |
276 |
277 |
`);
278 |
279 | const container = document.getElementsByClassName(
280 | "loadouts-edit-wrapper"
281 | )?.[0];
282 |
283 | container.prepend(checkbox);
284 | initStateCheckboxPrePick();
285 | }
286 |
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | export function htmlToElement(html) {
2 | var template = document.createElement("template");
3 | html = html.trim(); // Never return a text node of whitespace as the result
4 | template.innerHTML = html;
5 | return template.content.firstChild;
6 | }
7 | export function waitForElm(selector) {
8 | return new Promise((resolve) => {
9 | if (document.querySelector(selector)) {
10 | return resolve(document.querySelector(selector));
11 | }
12 |
13 | const observer = new MutationObserver((mutations) => {
14 | if (document.querySelector(selector)) {
15 | observer.disconnect();
16 | resolve(document.querySelector(selector));
17 | }
18 | });
19 |
20 | observer.observe(document.body, {
21 | childList: true,
22 | subtree: true,
23 | });
24 | });
25 | }
26 |
27 | export const delay = (t) => new Promise((r) => setTimeout(r, t));
28 |
--------------------------------------------------------------------------------