How to only click a page element once in a for-loop that checks for a new HTML event?

Viewed 38

I'm writing a userscript for a website where occasionally a coin drop will appear on-screen and only a limited number of people on the site can claim it. My script detects when a new coin drop appears based on the length of the page element "coindrop-status", and when a new drop is detected it auto-clicks the prompt to open the initial drop splash screen, then auto-clicks the actual grab button within that splash screen.

The problem is that because the first auto-click is within a for-loop, it continuously spam-clicks to open the splash screen until the drop has been fully claimed and the loop breaks, preventing stage 2 of the auto-click function from clicking the actual button to grab the drop within the splash screen.


I've tried to solve this problem many times now but because coin drops are so infrequent, it's a massive pain to debug - how can I change my script so that when a drop is detected, the splash screen is only clicked once (so that it stays open) before clicking the grab button within it repeatedly?

var newDrop = false;
function dropCheck() {
    clearInterval(scanFreq);
    var coinLength = document.getElementsByClassName("coindrop-status").length - 1;
    for(var i = coinLength; i >= 0; i--) {
        if(document.getElementsByClassName("coindrop-status")[i].innerText == "Grab") {
            newDrop = true;
            document.getElementsByClassName("coindrop-status")[i].click();
            setTimeout(function() {document.elementFromPoint(1250, 840).click()},1000);
            setTimeout(function() {document.elementFromPoint(1250, 840).click()},1000);
        }
    }
    if(newDrop) {
        newDrop = false;
        setTimeout(dropCheck,800);
    } else {
        setTimeout(dropCheck,100);
    }
}
var scanFreq = setInterval(dropCheck,800);

Admittedly, clicking the grab button multiple times is probably overkill, but I figure at least it guarantees that the coin drop actually gets grabbed.


Forgive any bad coding practice I may have integrated into this script; I'm still learning to program. I'm sure there are much, much more elegant ways to accomplish the goal of this userscript, so if you have any other suggestions please feel free to give some constructive criticism as well.

1 Answers

First, you don't need to loop like this. Like at all. There is a class called MutationObserver which will call a callback only when elements in the DOM changed. My approach when waiting for a specific element to be added is basically this:

/**
 * Waits for a node to start to appear in DOM. Requires a CSS query to fetch it.
 * @param {string} query CSS query
 * @returns {Promise<HTMLElement>}
 */
function awaitElement(query) {
    return new Promise((resolve, reject)=>{
        const targetNode = document.body;

        const testNow = targetNode.querySelector(query);
        if(testNow != null) {
            resolve(testNow);
            return;
        }

        const observer = new MutationObserver((mutationList, observer) => {
            const result = targetNode.querySelector(query);
            if(result != null) {
                observer.disconnect();
                resolve(result)
            }
        });

        observer.observe(targetNode, { attributes: false, childList: true, subtree: true });
    });
}

Used as:

(async () => {
    while(true) {
        let myElm = await awaitElement(".coindrop-status");
        // handle the drop here. Note that you must remove the element somehow, otherwise you will get an endless loop
    }

})();

You will probably need to adjust my function a little, so that you can make it ignore coin drops you already handled. One way to handle this without any modification is add a custom class name to the handled coin divs, and then use the not selector when searching for more.

I also do not think it's really wise to use element from point. Doesn't the popup for claiming the coin have some selector as well?

Related