Auto Clicker for Example Website

Automates clicking on certain buttons on example.com

// v1.4

const puppeteer = require('puppeteer');

(async () => {
    const DIR = {
        email: 'YOUR EMAIL',  // replace YOUR EMAIL with your email for auto login (keep everything else the same)
        password: 'YOUR PASSWORD',  // replace YOUR PASSWORD with your password for auto login

        login_url: 'https://app.educationperfect.com/app/login',

        // log-in page elements
        username_css: '#loginId',
        password_css: '#password',

        // home page elements
        home_css: 'div.view-segment-dashboard',

        // task-starter page elements
        baseList_css: 'div.baseLanguage',
        targetList_css: 'div.targetLanguage',
        start_button_css: 'button#start-button-main',

        // task page elements
        modal_question_css: 'td#question-field',
        modal_correct_answer_css: 'td#correct-answer-field',
        modal_user_answered_css: 'td#users-answer-field',
        modal_css: 'div[uib-modal-window=modal-window]',
        modal_backdrop_css: 'div[uib-modal-backdrop=modal-backdrop]',

        question_css: '#question-text',
        answer_box_css: 'input#answer-text',

        exit_button_css: 'button.exit-button',
        exit_continue_button_css: 'button.continue-button',

        continue_button_css: 'button#continue-button',
    }

    // launch browser
    puppeteer.launch({
        headless: false,
        defaultViewport: null,
        handleSIGINT: false
    })
        .then(async browser => {
            const page = (await browser.pages())[0];

            // Open EP page and log in
            console.log('Opening EP page...');
            await page.goto(DIR.login_url);
            console.log('Waiting for login page to load...');
            await page.waitForSelector(DIR.username_css);

            // THIS FILLS IN YOUR DETAILS TO LOG IN AUTOMATICALLY
            console.log('Filling in login details...');
            await page.type(DIR.username_css, DIR.email);
            await page.type(DIR.password_css, DIR.password);
            await page.keyboard.press('Enter');

            console.log('Waiting for home page to load...');
            await page.waitForSelector(DIR.home_css, { timeout: 0 });
            console.log('EP Home page loaded; Logged in.');


            // ===== Auto-answer code starts here ===== //
            let TOGGLE = false;
            let ENTER = true;
            let fullDict = {};
            let cutDict = {};

            // Basic answer-parsing
            function cleanString(string) {
                return String(string)
                    .replace(/\([^)]*\)/g, "").trim()
                    .split(";")[0].trim()
                    .split(",")[0].trim()
                    .split("|")[0].trim();
            }

            // Get words from the main task page
            async function wordList(selector) {
                return await page.$$eval(selector, els => {
                    let words = [];
                    els.forEach(i => words.push(i.textContent));
                    return words;
                });
            }

            // Refreshes the world lists on the main task page to enhance our vocabulary
            async function refreshWords() {
                const l1 = await wordList(DIR.baseList_css);
                const l2 = await wordList(DIR.targetList_css);
                for (let i = 0; i < l1.length; i++) {
                    fullDict[l2[i]] = cleanString(l1[i]);
                    fullDict[l1[i]] = cleanString(l2[i]);
                    cutDict[cleanString(l2[i])] = cleanString(l1[i]);
                    cutDict[cleanString(l1[i])] = cleanString(l2[i]);
                }
                console.log('Word Lists Refreshed.');
                await alert('Word Lists Refreshed.');
            }


            // extracts what (EP detected as) the user typed, from the fancy multicolored display
            // appended to logs for debugging/self-learning purposes
            async function getModalAnswered() {
                return await page.$$eval('td#users-answer-field > *', el => {
                    let answered = '';
                    el.forEach(i => {
                        if (i.textContent !== null && i.style.color !== 'rgba(0, 0, 0, 0.25)') answered = answered + i.textContent;
                    })
                    return answered;
                });
            }

            // Learn from the mistakes :)
            async function correctAnswer(question, answer) {
                // wait until modal content is fully loaded
                await page.waitForFunction((css) => {
                    return document.querySelector(css).textContent !== "blau";
                }, {}, DIR.modal_question_css);

                // extract modal contents (for debugging and correcting answers)
                let modalQuestion = await page.$eval(DIR.modal_question_css, el => el.textContent);
                let modalAnswer = await page.$eval(DIR.modal_correct_answer_css, el => el.textContent);
                let modalCutAnswer = cleanString(modalAnswer);
                let modalAnswered = await getModalAnswered();

                // dismisses the modal (bypasses the required cooldown)
                await page.$eval(DIR.continue_button_css, el => el.disabled = false);
                await page.click(DIR.continue_button_css);

                // update/correct answer dictionary
                fullDict[question] = modalCutAnswer;

                // logging for debugging if needed
                let log = "===== Details after Incorrect Answer: =====\n"
                log = log + `Detected Question: \n => ${question}\n`;
                log = log + `Inputted Answer: \n => ${answer}\n\n`;
                log = log + `Modal Question: \n => ${modalQuestion}\n`;
                log = log + `Modal Full Answer: \n => ${modalAnswer}\n`;
                log = log + `Modal Cut Answer: \n => ${modalCutAnswer}\n`;
                log = log + `Modal Detected Answered: \n => ${modalAnswered}\n\n\n`;

                console.log(log);
            }

            // deletes all existing modals and backdrops. Used to force-speed things up
            async function deleteModals() {
                await page.$$eval(DIR.modal_css, el => {
                    el.forEach(i => i.remove())
                });
                await page.$$eval(DIR.modal_backdrop_css, el => {
                    el.forEach(i => i.remove())
                });
            }

            // very advanced logic (ofc) used to find matching answer
            function findAnswer(question) {
                let answer = fullDict[question];
                if (answer) return answer;
                answer = fullDict[question.replace(",", ";")];
                if (answer) return answer;
                answer = cutDict[cleanString(question)];
                if (answer) return answer;
                console.log(`No answer found for ${question}`);
                return generateRandomString(8, 10);
            }

            // i love creating functions so here's one for the random string instead of just returning idk answer 
            // -joshatticus
            function generateRandomString(minLength, maxLength) {
                const length = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
                const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
                let result = '';
                for (let i = 0; i < length; i++) {
                    result += characters.charAt(Math.floor(Math.random() * characters.length));
                }
                return result;
            }

            // main function that continually answers questions until completion modal pops up or hotkey pressed again
            async function answerLoop() {
                if (TOGGLE) throw Error("Tried to initiate answerLoop while it is already running");

                TOGGLE = true;
                console.log("answerLoop entered.");

                while (TOGGLE) {
                    let question = await page.$eval(DIR.question_css, el => el.textContent);
                    let answer = findAnswer(question);

                    await page.click(DIR.answer_box_css, { clickCount: 3 });
                    await page.keyboard.sendCharacter(answer);
                    ENTER && page.keyboard.press('Enter');

                    // special case: modal pops up
                    if (await page.$(DIR.modal_css)) {
                        // incorrect answer and modal pops up; initiate answer-correction procedure
                        if (await page.$(DIR.modal_question_css) !== null) {
                            await correctAnswer(question, answer);
                            await deleteModals();
                            // list complete; clicks button to exit
                        } else if (await page.$(DIR.exit_button_css)) {
                            await page.click(DIR.exit_button_css);
                            break;
                        } else if (await page.$(DIR.exit_continue_button_css)) {
                            await page.click(DIR.exit_continue_button_css);
                            break;
                        } else {
                            // no idea what the modal is for so let's just pretend it doesn't exist
                            await deleteModals();
                        }
                    }
                }

                await deleteModals();
                TOGGLE = false;
                console.log('answerLoop Exited.');
            }

            // takes care of answerLoop toggling logic
            async function toggleLoop() {
                if (TOGGLE) {
                    TOGGLE = false;
                    console.log("Stopping answerLoop.");
                } else {
                    console.log("Starting answerLoop.");
                    answerLoop().catch(e => {
                        console.error(e);
                        TOGGLE = false
                    });
                }
            }

            async function toggleAuto() {
                if (ENTER) {
                    ENTER = false;
                    console.log("Switched to semi-auto mode.");
                    await alert("Switched to semi-auto mode.");
                } else {
                    ENTER = true;
                    console.log("Switched to auto mode.");
                    await alert("Switched to auto mode.");
                }
            }

            async function alert(msg) {
                await page.evaluate(m => window.alert(m), msg);
            }

            // Expose API functions to the page (for hotkey event listeners to call)
            await page.exposeFunction('refresh', refreshWords);
            await page.exposeFunction('startAnswer', toggleLoop);
            await page.exposeFunction('toggleMode', toggleAuto);

            // Add event listeners for hotkeys ON the page
            await page.evaluate(() => {
                document.addEventListener("keyup", async (event) => {
                    let key = event.key.toLowerCase();
                    if (key !== 'alt') {
                        if ((event.altKey && key === "r") || (key === "®")) {
                            await window.refresh();
                        } else if ((event.altKey && key === "s") || (key === "ß")) {
                            await window.startAnswer();
                        } else if ((event.altKey && key === "a") || (key === "å")) {
                            await window.toggleMode();
                        }
                    }
                });
            });
            console.log('Education Perfected V2 fully Loaded.');
        });
})();

// ==UserScript==
// @name         Auto Clicker for Example Website
// @namespace    http://example.com/
// @version      0.1
// @description  Automates clicking on certain buttons on example.com
// @author       Your Name
// @match        http://example.com/*
// @license      MIT
// @grant        none
// ==/UserScript==

// Your script code goes here

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址