├── 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 | ![](https://i.imgur.com/1BQqZas.png) 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 | --------------------------------------------------------------------------------