// ==UserScript== // @name Auto EB // @namespace http://tampermonkey.net/ // @version 1.61 // @description Complete your EB tasks in seconds. // @author ReTrn. // @grant GM_xmlhttpRequest // @require https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js // @require https://alb-cdn.web.app/popupjs/pu.min.js // @require https://raw.githubusercontent.com/ChuTM/auto-eb/refs/heads/main/services/updater.user.js // @run-at document-end // @match https://lms1.wiseman.com.hk/lms/user/secure/course/eb/select_lesson/ // @match https://lms1.wiseman.com.hk/lms/user/secure/course/eb/select_theme/select_module/select_skill/ // @updateURL https://raw.githubusercontent.com/ChuTM/auto-eb/refs/heads/main/dist/autoeb.user.js // @downloadURL https://raw.githubusercontent.com/ChuTM/auto-eb/refs/heads/main/dist/autoeb.user.js // ==/UserScript== (() => { var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e2) { reject(e2); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e2) { reject(e2); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/config.js var MAP = "c3D1RFP9eM[UjINfOZi0Qg+mhkxSJ5p* uX8B}`-rs,LqAH@lnbVT.C{z4YWtGv72^/aw|do_6\\yE~]K"; var TIMEOUT = (type = "default", length = 0) => { if (parseInt(localStorage == null ? void 0 : localStorage.AUTOEB_TIMEOUT)) return localStorage.AUTOEB_TIMEOUT; const BASE_DELAY = 200; let t = BASE_DELAY; if (type === "read") { t = length * Math.floor(Math.random() * 20 + 30); t = Math.max(t, 1200); } else if (type === "type") { t = length * Math.floor(Math.random() * 80 + 70); } else if (type === "click") { t = Math.floor(Math.random() * 400 + 300); } const jitter = Math.floor(Math.random() * 150) - 75; const totalDelay = Math.max(100, t + jitter); return totalDelay; }; var CORRECT_COUNT = (total) => { const target = localStorage.AUTOEB_CORRECT_TARGET || void 0; if (!target || isNaN(parseInt(target))) return total; return Math.round(parseFloat(target) * total); }; // src/utils.js var presistence = {}; function reportPresistence(e2, v) { presistence[v] = e2; } function getPresistence(e2) { return presistence[e2]; } function decrypt(encoded, seed) { if (!encoded) return "N/A"; let result = ""; for (let i = 0; i < encoded.length; i++) { let code = encoded.charCodeAt(i); let s = -1; if (code === 32) s = 0; else if (code >= 42 && code <= 57) s = code - 41; else if (code >= 64 && code <= 126) s = code - 47; if (s >= 0 && s <= 79) { s = (s + i + seed) % 80; result += MAP[s]; } else { result += encoded[i]; } } return result; } function decodeHtml(html) { if (!html) return ""; const doc = new DOMParser().parseFromString(html, "text/html"); const textContent = doc.documentElement.textContent || ""; return textContent.toLowerCase().replace(/\s+/g, " ").replace(/[^a-z0-9 ]/g, "").trim(); } function addToLog(m, type = "INFO") { const color = type === "DEV" ? "color: #00ff00" : "color: #ffffff"; console.log(`%c[Auto EB][${type}] ${m}`, color); } function getSimilarity(s1, s2) { if (!s1 || !s2) return 0; if (s1 === s2) return 1; let longer = s1.length > s2.length ? s1 : s2; let shorter = s1.length > s2.length ? s2 : s1; const costs = new Array(); for (let i = 0; i <= longer.length; i++) { let lastValue = i; for (let j = 0; j <= shorter.length; j++) { if (i === 0) costs[j] = j; else if (j > 0) { let newValue = costs[j - 1]; if (longer.charAt(i - 1) !== shorter.charAt(j - 1)) newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1; costs[j - 1] = lastValue; lastValue = newValue; } } if (i > 0) costs[shorter.length] = lastValue; } return (longer.length - costs[shorter.length]) / parseFloat(longer.length); } function getCorrectArray(question_count, correct_target) { const arr = []; for (let i = 0; i < correct_target; i++) arr.push(true); for (let i = correct_target; i < question_count; i++) arr.push(false); for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } function getQuestionCount() { return getIframeContext().documentElement.querySelector("group-pagination").childElementCount; } function simulateClick(el) { return __async(this, null, function* () { if (!el) return; const events = [ "pointerdown", "mousedown", "pointerup", "mouseup", "click" ]; for (const name of events) { const ev = new MouseEvent(name, { bubbles: true, cancelable: true, buttons: 1 }); el.dispatchEvent(ev); yield new Promise( (r) => setTimeout( r, (localStorage == null ? void 0 : localStorage.AUTOEB_TIMEOUT) || Math.floor(Math.random() * 30 + 20) ) ); } }); } function simulateTyping(el, text) { return __async(this, null, function* () { if (!el) return; el.focus(); el.value = ""; for (let i = 0; i < text.length; i++) { const char = text[i]; el.dispatchEvent( new KeyboardEvent("keydown", { key: char, bubbles: true }) ); el.dispatchEvent( new KeyboardEvent("keypress", { key: char, bubbles: true }) ); el.value += char; el.dispatchEvent(new Event("input", { bubbles: true })); el.dispatchEvent( new KeyboardEvent("keyup", { key: char, bubbles: true }) ); yield new Promise( (r) => setTimeout(r, Math.floor(Math.random() * 60 + 60)) ); } el.dispatchEvent(new Event("change", { bubbles: true })); el.blur(); }); } function sleep(ms) { return __async(this, null, function* () { return new Promise((resolve) => setTimeout(resolve, ms)); }); } function setAutoEBButtonCSS(target, type = "default", text, disabled = false) { e = target || getPresistence("autofill"); const map = { default: `position: fixed;bottom: 2rem;left: 2rem;background: white;padding: 0.7rem 1rem;border-radius: 11px;box-shadow: 0 0 10px 0px #00000035;cursor: pointer;display: block;font-family: sans-serif;font-weight: bold;transition: all 0.2s ease;outline: none;border: none;`, error: `position: fixed;bottom: 2rem;left: 2rem;background: #ff3e3e;color: white;padding: 0.7rem 1rem;border-radius: 11px;box-shadow: 0 0 10px 0px #00000035;cursor: pointer;display: block;font-family: sans-serif;font-weight: bold;transition: all 0.2s ease;outline: none;border: none;` }; if (typeof type === "string") e.style.cssText = map[type] || map.default; else if (typeof type === "object") { const style = type; Object.keys(style).forEach((w) => { const key = w, value = style[w]; e.style[key] = value; }); } if (text) e.innerText = text; e.disabled = disabled; } // src/logic.js var currentQuestion = 0; function getIframeContext() { var _a, _b; const overlay = (_a = document.querySelector(".overlay-player")) == null ? void 0 : _a.contentDocument; return (_b = overlay == null ? void 0 : overlay.querySelector("iframe")) == null ? void 0 : _b.contentDocument; } function isInTaskPage() { const iframe = getIframeContext(); return !!(iframe == null ? void 0 : iframe.querySelector( ".c_course-title.p_head-title.ng-star-inserted" )); } function getXMLData() { return __async(this, null, function* () { const iframe = getIframeContext(); if (!iframe) throw new Error("Course iframe not found"); const res = yield fetch( iframe.location.href.substring( 0, iframe.location.href.lastIndexOf("/") ) + "/course/course_pc.exml" ); return yield res.text(); }); } function getSeedFromXML(xml) { const seedMatch = xml.match(/seed="(\d+)"/); return seedMatch ? parseInt(seedMatch[1], 10) : 0; } function crackCourse() { return __async(this, null, function* () { try { const xmlString = yield getXMLData(); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlString, "text/xml"); const seed = getSeedFromXML(xmlString); const questions = xmlDoc.querySelectorAll("question"); return Array.from(questions).map((q, idx) => { let answers = []; const qType = q.getAttribute("type"); if (qType === "smc") { const qCorrectAttr = q.getAttribute("correct"); if (qCorrectAttr) { const isNumeric = /^\d+$/.test(qCorrectAttr); let val = isNumeric ? qCorrectAttr : decrypt(qCorrectAttr, seed); if (val) answers.push(val); } } else { q.querySelectorAll("[correct]").forEach((node) => { const nodeCorrect = node.getAttribute("correct"); if (nodeCorrect) { let val = decrypt(nodeCorrect, seed); if (val) { answers.push( val.includes("/") ? val.split("/")[0] : val ); } } }); } let bodyParts = Array.from( q.querySelectorAll("set text:not([correct])") ).map((t) => t.getAttribute("text") || t.textContent); let reconstructedBody = bodyParts.join(" ").trim(); if (reconstructedBody.length < 3) { reconstructedBody = q.getAttribute("text") || ""; } return { id: idx, type: qType, headerText: q.getAttribute("text") || "", bodyText: decodeHtml(reconstructedBody), answers }; }); } catch (err) { addToLog(`CRITICAL: XML Error: ${err.message}`, "DEV"); return []; } }); } function inputAnswerForCurrentQuestion(correct = true) { return __async(this, null, function* () { var _a, _b; const allQuestions = yield crackCourse(); const iframe = getIframeContext(); if (!iframe || allQuestions.length === 0) return false; const uiHead = decodeHtml( ((_a = iframe.querySelector(".c_question-head")) == null ? void 0 : _a.innerText) || "" ); const uiBody = decodeHtml( ((_b = iframe.querySelector(".c_question-body")) == null ? void 0 : _b.innerText) || "" ); const isRadio = iframe.querySelectorAll('input[type="radio"]').length > 0; addToLog(`Current UI BODY ${uiBody}`); if (uiHead.includes("Students\u2019 Voices") || /student.*voice/.test(uiHead.toLowerCase())) { const target = iframe.querySelectorAll('input[type="checkbox"]')[0]; console.log(target); if (target) { yield new Promise((r) => setTimeout(r, TIMEOUT("click"))); yield simulateClick(target); return true; } addToLog( "Special case matched but target not found, falling back to normal matching.", "WARN" ); addToLog(`UI Head: ${uiHead}`, "DEV"); addToLog(`UI Body: ${uiBody}`, "DEV"); } let found = null; if (isRadio) { found = allQuestions.find((q) => { const xmlHead = decodeHtml(q.headerText); return xmlHead.length > 5 && (uiHead.includes(xmlHead) || xmlHead.includes(uiHead)); }); } else { let bestScore = 0; allQuestions.forEach((q) => { const score = getSimilarity(uiBody, q.bodyText); if (score > bestScore && score > 0.6) { bestScore = score; found = q; } }); } if (found && found.answers.length > 0) { const totalTextLength = uiHead.length + uiBody.length; const readingTime = TIMEOUT("read", totalTextLength); yield new Promise((r) => setTimeout(r, readingTime)); const inputs = iframe.querySelectorAll( 'input:not([type="hidden"]), select, .c_input-box' ); for (let i = 0; i < found.answers.length; i++) { const ans = found.answers[i]; if (isRadio) { const radioIdx = parseInt(ans) - 1; const radios = iframe.querySelectorAll('input[type="radio"]'); let targetRadio = radios[radioIdx]; if (!correct) { targetRadio = radioIdx === 0 ? radios[1] : radios[0]; } if (targetRadio) { yield new Promise((r) => setTimeout(r, TIMEOUT("click"))); yield simulateClick(targetRadio); } } else { const el = inputs[i]; if (el) { const finalValue = correct ? ans : ans + "x"; yield new Promise((r) => setTimeout(r, TIMEOUT("click"))); if (el.tagName === "SELECT") { el.value = finalValue; el.dispatchEvent( new Event("change", { bubbles: true }) ); } else { yield simulateTyping(el, finalValue); } } } } return true; } return false; }); } function startAutomation() { addToLog( `Starting automation for Question ${currentQuestion + 1}...`, "INFO" ); const totalQuestions = getQuestionCount(); const correctArray = getCorrectArray( totalQuestions, CORRECT_COUNT(totalQuestions) ); inputAnswerForCurrentQuestion(correctArray[currentQuestion]).then( (success) => __async(null, null, function* () { if (success) { const iframe = getIframeContext(); yield new Promise((r) => setTimeout(r, TIMEOUT("click"))); const submitBtn = iframe == null ? void 0 : iframe.querySelector( "button[btn-for='submit']" ); if (!submitBtn) return; yield simulateClick(submitBtn); yield new Promise( (r) => setTimeout( r, (localStorage == null ? void 0 : localStorage.AUTOEB_TIMEOUT) || Math.floor(Math.random() * 400 + 600) ) ); const nextBtn = iframe == null ? void 0 : iframe.querySelector("button[btn-for='next']"); if (!nextBtn || nextBtn.offsetParent === null) { addToLog("Finished: No 'Next' button found.", "INFO"); setAutoEBButtonCSS(void 0, "default", "Lesson Completed"); if (sessionStorage.AUTOEB_FULL_AUTOMATION === "TASK_START") { addToLog("All questions attempted. Submitting", "INFO"); sessionStorage.AUTOEB_FULL_AUTOMATION = "TASK_END_CONTINUE"; yield simulateClick( iframe.querySelector("button[btn-for='end']") ); location.reload(); } return; } yield simulateClick(nextBtn); currentQuestion++; if ((localStorage == null ? void 0 : localStorage.AUTOEB_AVOID_CONTINUOUS_ANSWERING) !== "AVOID") { setTimeout( startAutomation, (localStorage == null ? void 0 : localStorage.AUTOEB_TIMEOUT) || Math.floor(Math.random() * 500 + 500) ); } } else { addToLog( "Automation stopped: No match found for this screen.", "WARN" ); setAutoEBButtonCSS( void 0, "error", `Error: NO MATCH FOUND`, false ); pujs.popup( "Error Encountered", // Title `AutoEB couldn't automatically match this page with the correct answer. Please open the source data and select the option manually.`, // Message [ { // Buttons text: "Open XML", color: "lightgreen", callback: () => { getPresistence("XMLData").click(); } }, { // Cancel Button text: "Cancel", callback: () => { } } ], "horiz" ); } }) ); } function getAvailableTasks() { const table_tasksRows = document.querySelectorAll( ".table-responsive>table.table>tbody>tr" ); return Array.from(table_tasksRows).reduce((tasks, row) => { try { const aElement = row.querySelector("a.popup.link-blue"); const mark = row.querySelector( "td.text-center>span>span" ).textContent; if (mark === "-") { tasks.push(aElement); } } catch (err) { } return tasks; }, []); } function automaticallyCheckAndChooseDifficulty() { setTimeout(() => __async(null, null, function* () { var _a; console.log( sessionStorage.AUTOEB_FULL_AUTOMATION === "TASK_START", isInTaskPage() ); if (sessionStorage.AUTOEB_FULL_AUTOMATION === "TASK_START" && isInTaskPage()) { iframeContent = getIframeContext(); let headingText = (_a = iframeContent == null ? void 0 : iframeContent.querySelector( "span[tag='h1']>span[tag='b']" )) == null ? void 0 : _a.innerText; if (headingText == "LEVEL OF DIFFICULTY") { const AUTOEB_DIFFICULTY_LEVEL = localStorage.AUTOEB_DIFFICULTY_LEVEL || "Challenging"; const index = AUTOEB_DIFFICULTY_LEVEL === "Challenging" ? 1 : 0; yield simulateClick( iframeContent.querySelectorAll( "label.c_start_group-field_label" )[index] ); yield sleep(200); yield simulateClick( iframeContent.querySelector("button[btn-for='start']") ); yield sleep(1e3); startAutomation(); addToLog( `Difficulty level "${AUTOEB_DIFFICULTY_LEVEL}" selected` ); } else { startAutomation(); } } }), 5e3); } function startFullAutomation() { var _a; sessionStorage.AUTOEB_FULL_AUTOMATION = "TASK_START"; (_a = getAvailableTasks()[0]) == null ? void 0 : _a.click(); } // res/ui.html var ui_default = '
\n\n'; // res/style.css var style_default = '.pujs-poAlert {\n z-index: 9999999999 !important;\n}\n.pujs-poAlert::-webkit-scrollbar,\n.pujs-poAlert *::-webkit-scrollbar {\n display: none !important;\n}\n.homepage .auto-eb-hidden {\n display: none;\n}\n#xml-container {\n padding: 15px !important;\n background: #2d2d2d !important;\n border-radius: 8px;\n margin: 0 !important;\n /* CRITICAL: pre-wrap preserves the tabs while allowing horizontal scrolling if needed */\n white-space: pre !important;\n overflow: auto !important;\n}\n#xml-block {\n display: block !important;\n white-space: pre !important;\n line-height: 1.4 !important;\n font-size: 13px !important;\n /* FORCE TAB SIZE */\n tab-size: 4 !important;\n -moz-tab-size: 4 !important;\n -o-tab-size: 4 !important;\n font-family: "Consolas", "Monaco", "Courier New", monospace !important;\n color: #ccc !important;\n}\n/* Stop Prism from messing with the layout */\npre[class*="language-"] {\n margin: 0 !important;\n padding: 0 !important;\n}\n\n.autoeb-overlay {\n font-size: 16px;\n\n position: fixed;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n\n background: rgba(255, 255, 255, 0.75);\n backdrop-filter: blur(17px);\n -webkit-backdrop-filter: blur(17px);\n z-index: 10000000;\n\n pointer-events: none;\n opacity: 0;\n\n transition: 1s;\n\n transform: translateY(50%);\n}\n\n.autoeb-overlay .component {\n width: 95%;\n background-color: white;\n margin: 2em auto;\n padding: 2em;\n border-radius: 17px;\n border: 1.5px solid rgba(0, 0, 0, 0.05);\n box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.1);\n}\n\n.autoeb-overlay .component > .title {\n font-size: 2em;\n font-weight: 600;\n}\n\n.autoeb-overlay .component .layer {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n\n .information {\n padding: 1em 2em;\n width: 80%;\n display: flex;\n flex-direction: column;\n\n .title {\n font-size: 1.2em;\n font-weight: 400;\n color: rgba(0, 0, 0, 0.6);\n }\n\n .description {\n font-size: 0.8em;\n font-weight: 400;\n color: rgba(0, 0, 0, 0.4);\n display: flex;\n flex-direction: column;\n }\n\n .description > .variable {\n font-size: 0.6em;\n width: fit-content;\n padding: 2px 6px;\n background: rgba(0, 0, 0, 0.05);\n border-radius: 5px;\n }\n }\n\n .action {\n padding: 1em 2em;\n color: rgba(0, 0, 0, 0.6);\n }\n\n input {\n color: rgba(0, 0, 0, 0.6);\n border: 1px solid black;\n border-radius: 3px;\n padding: 2px 7px;\n }\n\n .action {\n width: 20%;\n min-width: 200px;\n }\n .AUTOEB_AVOID_CONTINUOUS_ANSWERING-descriptive-text {\n content: "Continuously";\n }\n #AUTOEB_AVOID_CONTINUOUS_ANSWERING:checked\n + .AUTOEB_AVOID_CONTINUOUS_ANSWERING-descriptive-text {\n content: "AVOID";\n }\n}\n\n.autoeb-settings-button {\n position: fixed;\n bottom: 2rem;\n right: 2rem;\n z-index: 9999999;\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 1em;\n}\n\n.autoeb-settings-button .settings {\n display: block;\n height: 40px;\n cursor: pointer;\n display: flex;\n justify-content: center;\n gap: 6px;\n align-items: center;\n background: #2a2a2a;\n color: white;\n border: 1px solid #444;\n padding: 0.5rem;\n border-radius: 11px;\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);\n cursor: pointer;\n font-family: sans-serif;\n font-weight: 500;\n}\n\n.close-button {\n font-size: 3em;\n cursor: pointer;\n color: black;\n font-weight: 300;\n position: absolute;\n top: 2rem;\n right: 2rem;\n height: 24px;\n width: 24px;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n'; // res/xml_data.html var xml_data_default = `AutoEB has decrypted the source XML File.
Look for the \`correct\` attributes for the answers.
NO XML DATA FOUND
© Copyright 2026 ChuTM. All rights reserved.