Skip to content

Instantly share code, notes, and snippets.

@virgiliu
Created November 1, 2020 14:13
Show Gist options
  • Save virgiliu/eefbadef4de9d2ecb2e01020ae471892 to your computer and use it in GitHub Desktop.
Save virgiliu/eefbadef4de9d2ecb2e01020ae471892 to your computer and use it in GitHub Desktop.
Gumroad bulk download
// Run this in the content download page and it will trigger download for everything
var sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
var waitTime = 1500; //ms
var x = $( "button:contains('Download')" );
for(var i = 0; i < x.length ; i++)
{
(function(idx) {
// Wait needed because browser blocks network calls if you make too many too fast
sleep(i * waitTime).then(() => {
x[idx].click();
});
})(i)
}
@Kawaru86
Copy link

Kawaru86 commented Mar 22, 2024 via email

@rursache
Copy link

rursache commented Apr 5, 2024

@InfiniteCanvas thanks for your work, the script works flawlessly!

@obsessedcake
Copy link

Hi all! I've update https://github.com/obsessedcake/gumroad-utils. If anyone interested, take a look 😄

@AzureArtism
Copy link

@InfiniteCanvas @Kawaru86 Thanks, it worked!

@virgiliu
Copy link
Author

virgiliu commented May 2, 2024

Hi all! I've update https://github.com/obsessedcake/gumroad-utils. If anyone interested, take a look 😄

@obsessedcake Thanks for building and sharing that. For me the original script was a one-time thing which I made public by mistake, but since people started talking I decided to leave it up even though I don't have the time to maintain it 😅

@Hs211221
Copy link

Can anyone please help me to download from the beginning and how to code it please.. that'd be great help thankyou

@H1mawari
Copy link

H1mawari commented Sep 24, 2024

Hi all! I've update https://github.com/obsessedcake/gumroad-utils. If anyone interested, take a look 😄

I've tried using the library but can't download any files via the URL I'm getting an error
image
What is this error and is there any way to fix it

@Beagon
Copy link

Beagon commented Nov 20, 2024

@itswzyss Good idea. Went through little tests with my file. Once a number of links attained, Gumroad decide to cut the flow and stop the connexion. So, I decided to reduce the file in multiple files, once per artist (even if you have multiple purchases). 🤣 One thing more. Content creator can use external links to host their files instead of gumroad. I alert of that in the files & console.

let promises = await Promise.all(Array.from(document.querySelectorAll("article a.stretched-link")) // Get promises with purchases download links.
    .map((link) => { return link.href })
    .map((link) => {
        return fetch(link) // Get link from purchase link.
            .then(res => res.text())
            .then(text => {

                let parser = new DOMParser(); // Create DOMContent from fetch content with download links.
                let doc = parser.parseFromString(text, "text/html");

                var script = doc.querySelector("script[data-component-name]");// Get script in which the download content JS is.
                
                if(JSON.parse(script.innerText).content.content_items.length === 0) 
                    console.log(JSON.parse(script.innerText).creator.name + " use an external hosting service. Please watch their files to get the purchased download links"); // Alert in console for external hosting services.

                return {
                    artist: JSON.parse(script.innerText).creator.name,
                    links: (JSON.parse(script.innerText).content.content_items.length > 0 ?
                        JSON.parse(script.innerText).content.content_items.map((item) => { return "https://app.gumroad.com" + item.download_url }) :
                        ["external link in following page : " + link])
                };// Return both the artist and the associated download URLs (if content is in external website from gumroad, the page will be alerted).
            });
    }));

let timer = 0; // Timer to delay the download (to avoid download throttle).

promises // Need the promises to be resolved from here.
    .reduce((acc, d) => {
        const found = acc.find(a => a.artist === d.artist);
        const value = d.links.flat(Infinity);
        if (!found) acc.push({ artist: d.artist, links: [value] })
        else found.links.push(value);
        return acc;
    }, [])// Regroup links per artist.
    .sort(function (a, b) {
        return a.artist.localeCompare(b.artist);
    })// Sort artist per name.
    .forEach((data) => {

        setTimeout(function () {
            var blob = new Blob([data.links.flat(Infinity).join("\n")], { type: "text/plain;charset=utf-8" });
            var url = window.URL || window.webkitURL;
            var link = url.createObjectURL(blob);// Creation of download link.

            var a = document.createElement("a");
            a.download = "downloads_" + data.artist + "_gumroad.txt";
            document.body.appendChild(a);
            a.href = link;// Creation of the download button.


            a.click(); // Click to begin download.
            a.remove(); // Remove the download button.
        }, timer += 1500);// Delay to avoid download throttle.
    });// From this, download
    ```

Small problem found, the element selector for the JSON wasn't correct anymore. Here is an updated version:

let promises = await Promise.all(Array.from(document.querySelectorAll("article a.stretched-link")) // Get promises with purchases download links.
    .map((link) => { return link.href })
    .map((link) => {
        return fetch(link) // Get link from purchase link.
            .then(res => res.text())
            .then(text => {

                let parser = new DOMParser(); // Create DOMContent from fetch content with download links.
                let doc = parser.parseFromString(text, "text/html");

                var script = doc.querySelector("script[data-component-name=\"DownloadPageWithContent\"]");// Get script in which the download content JS is.
                
                if(JSON.parse(script.innerText).content.content_items.length === 0) 
                    console.log(JSON.parse(script.innerText).creator.name + " use an external hosting service. Please watch their files to get the purchased download links"); // Alert in console for external hosting services.

                return {
                    artist: JSON.parse(script.innerText).creator.name,
                    links: (JSON.parse(script.innerText).content.content_items.length > 0 ?
                        JSON.parse(script.innerText).content.content_items.map((item) => { return "https://app.gumroad.com" + item.download_url }) :
                        ["external link in following page : " + link])
                };// Return both the artist and the associated download URLs (if content is in external website from gumroad, the page will be alerted).
            });
    }));

let timer = 0; // Timer to delay the download (to avoid download throttle).

promises // Need the promises to be resolved from here.
    .reduce((acc, d) => {
        const found = acc.find(a => a.artist === d.artist);
        const value = d.links.flat(Infinity);
        if (!found) acc.push({ artist: d.artist, links: [value] })
        else found.links.push(value);
        return acc;
    }, [])// Regroup links per artist.
    .sort(function (a, b) {
        return a.artist.localeCompare(b.artist);
    })// Sort artist per name.
    .forEach((data) => {

        setTimeout(function () {
            var blob = new Blob([data.links.flat(Infinity).join("\n")], { type: "text/plain;charset=utf-8" });
            var url = window.URL || window.webkitURL;
            var link = url.createObjectURL(blob);// Creation of download link.

            var a = document.createElement("a");
            a.download = "downloads_" + data.artist + "_gumroad.txt";
            document.body.appendChild(a);
            a.href = link;// Creation of the download button.


            a.click(); // Click to begin download.
            a.remove(); // Remove the download button.
        }, timer += 1500);// Delay to avoid download throttle.
    });// From this, download

@n0c0ntext
Copy link

n0c0ntext commented Nov 30, 2024

hi sorry am I missing something? I've put the code in developer tools same with the download page, It just shows an error or does nothing apart from says undefined. Do I need to put the code somewhere else?

@ayancey
Copy link

ayancey commented Dec 5, 2024

hi sorry am I missing something? I've put the code in developer tools same with the download page, It just shows an error or does nothing apart from says undefined. Do I need to put the code somewhere else?

Try replacing button:contains('Download') with a:contains('Download')

@dreamspy
Copy link

dreamspy commented Jan 31, 2025

I had to modify the script for it to work. I'm using the "href" attribute, and simply adding http://gumroad.com in front of that variable. The previous script was using the "data-resource-id" which is empty / undefined.

Also I had some problems using console download managers to download the urls, since gumroad asks for confirmation before starting the file download. But I was able to use the Chrono Chrome extension. Just simply press the "new" button and paste in the list of URLs with each URL in a new line.

var x = $("a.button:contains('Download')");

var result = [];

for (var i = 0; i < x.length; i++) {
  result.push(x[i].getAttribute("href"));
}

var currentUrl = window.location.href;
var newBaseUrl = currentUrl.replace("/d/", "/r/");
var newUrls = [];

result.forEach(function (resourceId) {
  newUrls.push("https://gumroad.com" + resourceId);
});

var blob = new Blob([newUrls.join("\n")], { type: "text/plain" });

var a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "urls.txt";
a.style.display = "none";
document.body.appendChild(a);
a.click();

document.body.removeChild(a);
URL.revokeObjectURL(a.href);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment