[Script] GiftCardBot (Auto Gift Card Redeemer)

I just wanted to share a little script I use to batch redeem Reward Points for Gift Cards — because doing it manually takes simply too much time and clicking.
The script requires the Tampermonkey browser extension. Currently, it only works on the English version of Makerworld, but it should be very easy to adapt it for other localizations.

Privacy:
The script does not access any personal data and runs entirely on your own machine. It simply automates a series of clicks to simulate user interaction.

Installaltion:

  • Open Tampermonkey, create a New Userscript, and paste in the source code.
  • Save the Script
  • Done

How to Use:
(Assumes Tampermonkey and the script are already installed.)

  1. Navigate to https://makerworld.com/en/points — or reload the page if you arrived from elsewhere on Makerworld.
    2.(The script only activates on that specific page.)*
  2. You’ll see a big green button in the top right corner labeled “Start Gift Card Redemption.”
  3. Click the button to automatically redeem your points until they’re all converted.

See ths script in action (100% speed):
Aufzeichnung2025-04-27144312-ezgif.com-optimize (1)

Limitations:

  • English only: Currently, the script only works with the English localization of Makerworld.
  • Fixed timing between actions: The click sequence relies on fixed wait times between actions, which has worked well for me.
    You can adjust the timing by editing the await wait(*XYZ*); lines in the code (values are in milliseconds).
  • Potential loading errors: Occasionally, the script might throw an error if a page element doesn’t load in time.
    Simply restart the process to redeem your remaining points.
  • No manual stop: The script will continue running until all points are redeemed (as intended).
    If you want to stop manually, reload the page.
  • Gift card codes are not saved automatically: Since most of my gift cards require manual review anyway, the script does not automatically save the codes. You’ll need to access your redeemed cards manually later.
  • The script is designed to throw an error when no gift cards are available, but since this situation rarely occurs, I haven’t had the opportunity to test it yet.
  • The script comes as is. Feel free to modify it or use it in your own project.

Source Code:

// ==UserScript==
// @name         MakerWorld Gift Card Redeemer Bot
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Adds a visible button to manually start the Gift Card redemption process on MakerWorld Points page with extensive debugging output in console logs if enough points are available to redeem a Gift Card automatically
// @author       AU3D
// @match        https://makerworld.com/en/points
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Helper function to wait a certain number of milliseconds
    async function wait(milliseconds) {
        return new Promise(resolve => setTimeout(resolve, milliseconds));
    }

    // Main function to find and click the Gift Card
    async function clickGiftCard() {
        console.log('[GiftCardBot] Searching for Gift Card elements...');
        const cards = document.querySelectorAll('.MuiBox-root.mw-css-1np7691');
        console.log(`[GiftCardBot] Found ${cards.length} cards on the page.`);

        let giftCardElement = null;

        // Search for the first card that contains "Gift Card" in its title
        for (const card of cards) {
            const titleElement = card.querySelector('.mw-css-3pz2ix');
            if (titleElement) {
                console.log(`[GiftCardBot] Checking card title: "${titleElement.textContent.trim()}"`);
                if (titleElement.textContent.includes('Gift Card')) {
                    giftCardElement = card;
                    console.log('[GiftCardBot] Found a Gift Card!');
                    break;
                }
            } else {
                console.warn('[GiftCardBot] No title element found in a card.');
            }
        }

        if (!giftCardElement) {
            console.error('[GiftCardBot] No Gift Card found on the page.');
            return;
        }

        // Find the button inside the parent element
        const buttonContainer = giftCardElement.querySelector('.MuiBox-root.mw-css-0');
        const button = buttonContainer?.querySelector('button');

        if (!button) {
            console.error('[GiftCardBot] No redeem button found inside Gift Card element.');
            return;
        }

        console.log('[GiftCardBot] Clicking the Gift Card redeem button...');
        button.click();
        await wait(300);

        // Handle the opened dialog
        await handleDialog();
    }

    // Function to handle the dialog after clicking
async function handleDialog() {
    console.log('[GiftCardBot] Waiting for dialog to open...');
    const dialog = document.querySelector('.MuiDialog-container.MuiDialog-scrollPaper.mw-css-fh1hs4');

    if (!dialog) {
        console.error('[GiftCardBot] Dialog not found.');
        return;
    }
    console.log('[GiftCardBot] Dialog found.');

    // Find the element showing the balance
    const balanceElement = dialog.querySelector('.mw-css-12ezz6q');
    if (!balanceElement) {
        console.error('[GiftCardBot] Balance element not found inside the dialog.');
        return;
    }
    const originalBalanceText = balanceElement.textContent.trim();
    console.log(`[GiftCardBot] Raw balance text: "${originalBalanceText}"`);

    // Clean the balance string: keep only digits, comma, and dot
    const cleanedBalanceString = originalBalanceText.replace(/[^0-9,\.]/g, '');
    console.log(`[GiftCardBot] Cleaned balance string: "${cleanedBalanceString}"`);

    const balance = parseFloat(cleanedBalanceString.replace(/,/g, ''));
    console.log(`[GiftCardBot] Parsed numeric balance: ${balance}`);

    if (isNaN(balance)) {
        console.error('[GiftCardBot] Balance parsing failed (NaN detected)!');
        return;
    }

    // Find the required points element
    const requiredPointsElement = dialog.querySelector('.mw-css-z0vrvy');
    if (!requiredPointsElement) {
        console.error('[GiftCardBot] Required points element (price) not found inside the dialog.');
        return;
    }
    const originalRequiredText = requiredPointsElement.textContent.trim();
    console.log(`[GiftCardBot] Raw required points text: "${originalRequiredText}"`);

    // Clean the required points string
    const cleanedRequiredString = originalRequiredText.replace(/[^0-9,\.]/g, '');
    console.log(`[GiftCardBot] Cleaned required points string: "${cleanedRequiredString}"`);

    const requiredPoints = parseFloat(cleanedRequiredString.replace(/,/g, ''));
    console.log(`[GiftCardBot] Parsed required points: ${requiredPoints}`);

    if (isNaN(requiredPoints)) {
        console.error('[GiftCardBot] Required points parsing failed (NaN detected)!');
        return;
    }

    // Compare balance to required points
    if (balance < requiredPoints) {
        console.warn(`[GiftCardBot] Not enough points to redeem (Balance: ${balance}, Required: ${requiredPoints})`);
        alert('Not enough points available');
        return;
    }

    console.log(`[GiftCardBot] Sufficient points detected (Balance: ${balance}, Required: ${requiredPoints}), proceeding to redeem.`);

    // Proceed to find and click the redeem button
    const redeemButtonContainer = dialog.querySelector('.mw-css-10egq61');
    const redeemButton = redeemButtonContainer?.querySelector('button');

    if (!redeemButton) {
        console.error('[GiftCardBot] Redeem button not found inside the dialog.');
        return;
    }

    console.log('[GiftCardBot] Clicking the redeem button...');
    redeemButton.click();
    await wait(300);

    const confirmationDialog = document.querySelector('.mw-css-1maiz2c');
    if (!confirmationDialog) {
        console.error('[GiftCardBot] Confirmation dialog not found after redeem button click.');
        return;
    }
    console.log('[GiftCardBot] Confirmation dialog opened.');

    const confirmButton = confirmationDialog.querySelector('button.mw-css-7i8ckg');
    if (!confirmButton) {
        console.error('[GiftCardBot] Confirm purchase button not found inside confirmation dialog.');
        return;
    }

    console.log('[GiftCardBot] Clicking confirm button...');
    confirmButton.click();
    await wait(1000);

    const closeButton = document.querySelector('.mw-css-12i94o');
    if (!closeButton) {
        console.error('[GiftCardBot] Close button not found after confirmation.');
        return;
    }

    console.log('[GiftCardBot] Clicking close button to finalize the process...');
    closeButton.click();
    await wait(1000);

    console.log('[GiftCardBot] Completed one Gift Card redemption cycle, checking again...');
    await clickGiftCard();
}


    // Function to create and add the manual start button to the page
    function addStartButton() {
        const startButton = document.createElement('button');
        startButton.innerText = 'Start Gift Card Redemption';
        startButton.style.position = 'fixed';
        startButton.style.top = '100px';
        startButton.style.right = '20px';
        startButton.style.zIndex = '9999';
        startButton.style.padding = '15px 25px';
        startButton.style.backgroundColor = '#28a745';
        startButton.style.color = 'white';
        startButton.style.border = 'none';
        startButton.style.borderRadius = '8px';
        startButton.style.fontSize = '18px';
        startButton.style.cursor = 'pointer';
        startButton.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
        startButton.style.fontWeight = 'bold';

        startButton.addEventListener('click', () => {
            console.log('[GiftCardBot] Manual start button clicked, beginning redemption process...');
            clickGiftCard();
            startButton.remove(); // Optional: remove the button after first click
        });

        document.body.appendChild(startButton);
    }

    // Add the button once the page is fully loaded
    window.addEventListener('load', () => {
        setTimeout(addStartButton, 500);
    });

})();

1 Like

These class name are liable to change without notice.

I like the idea of your tool for those who wish to use it, my concern though is that any one of those changing the random suffix will break your script.

12i94o

These suffixes are created by the frame the MW site uses, annoyingly, many frameworks use random values rather than fixed IDs. Each time the page is updated these can and likely will change.

When websites are created from scratch and designed well, they use a mix of unique IDs and well-defined class names to suit their usage, these are then used each and every time the page is updated and when used on other pages as they describe the purpose. The random values serve to simplify the page creation rather than make it robustly defined for site-wide reuse.

I used to design websites as part of the software business I owned (illness forced me to sell it), we would never use the frameworks that used these random values because of the negatives. Unfortunately, others do not place as much emphasis as we did on robust reusable site-wide class definitions.

I also create my own scripts still and hate these things, my latest Patreon script to force full-width tables kept getting stuck with these things, their random class names were changed on average every 24 hours.

This brings me to the suggestion, one I used to fix the Patreon problem.

Find a fixed ID tag, one with a defined ID value that is part of the structure close to the tags you actually need and then use parent and child traversal to get to the actual class you seek. The structure is far less likely to change than the random class names.

This will give you far longer periods between changes in the HTML which will break your script.

I would, but they don’t use any. My other script for highlighting copied gift card codes has still been working two months later, even after a recent site update. So I’m optimistic it will continue working for at least a few more months before needing an update. I’ll update the script when the time comes.

I have taken a look, this is why I hate people using these useless non-standard-compliant frameworks.

You are clearly correct, there isn’t anything ID-related to latch onto.

You could filter for the content of the h3 tag; you would need one for each currency store, though, of course.

Be aware that when the gift cards become unavailable, an offer is added to an existing one (recently for the points getting 10 times more) or a new item is added, the page will be updated.

Fingers crossed.

2 Likes

The random ID is presumably from CSS modules. Not my personal favorite method of styling, but it’s an ok one. It essentially lets you have component-scoped styles in React while keeping nearly every other part of the experience the same (e.g. a separate stylesheet). I would figure the IDs change at build time, but maybe not.

The main site is built with NextJS, a framework built on React, which is declarative, so there’s no need to have comprehendable IDs on components.

You could in theory get an item by its text, but that’s also not ideal and could cause issues if they change the text. There’s really not a good way to do it.

1 Like

Ignoring these frameworks stop third-party access.

They go against the principle of reusability as they are all unique.

This means the browser can’t cache or reuse styles that should be and are identical, because each instance has a different name.

CSS was designed to allow one style to be used many times with the same class name.

These frameworks take that logic and all the associated benefits and throw them out the window.

If there is a common button design used ten times, these unique IDs mean it is seen as ten different buttons, wasting time and resources.

All because the designers of the framework were lazy and the user of the framework didn’t do any due diligence.

As a 35 year veteran of commercial software design with a good chunk of that in web design (plus desktop, mobile and massive installations) I have more than a little experience. I designed, wrote and sold a CMS that let users add content and let the CMS do the complicated bits.

It was written in a programming language I wrote specifically designed to create variant CMS solutions that merged the front and back end work with DB integration that could add any sort of data or content by essentially answering a few questions.

All CSS was compliant and reusable, all JavaScript was pure, no third-party libraries, you could change a setting and it would support one of many server-side scripting languages, another parameter determined the database it would use. If changes were needed you simply made them and compiled (technically transpiled). If a client needed to shift server that had different tech we processed the site again and uploaded elsewhere and all data remained as was cross-graded.

The reason I mention this is, if a small company such as mine can do things correctly and easily, why don’t the big companies?

4 Likes

Junior devs vibecoding on newest js framework like it’s fun…

2 Likes

I know it was rhetorical, but I can answer this question. Because EVERYTHING in a big company is a custom engineering solution. They have their own version of every bit of infrastructure, even when there are clear, robust and proven third-party solution. Because you have an entire hierarchy of engineers, across all levels, actively working to preserve their own job, and doing a lot of self-indulgent work because “it’s interesting” or “I can do it better” or… just because it’s another bit of tech they can patent, publish and profit from.

1 Like

They generally change when there’s a new release and they make layout changes, but otherwise they seem to be somewhat stable. I have to fix my extension as well from time to time. I just need someone to yell when it’s broken. :grin: