0

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 Answers1

0

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?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • I was using element from point for testing purposes only because I knew it worked (at least on my monitor when the browser was in full screen) based on some testing I did in the browser's console - just gave me one less part of the code that could be causing problems for the time being. I've since changed that chunk to a query selector using its ID, which is definitely much more elegant. Like I said, I have limited programming experience so I have a lot of room for improvement in terms of optimizing my code (or getting it to work in the first place... lol.) – AuspiciousSilver34 Sep 25 '22 at 05:32
  • @AuspiciousSilver34 And do you have any questions getting my snippet to work? It would be more efficient than using a timeout IMO. – Tomáš Zato Sep 26 '22 at 10:02