Skip to content

Instantly share code, notes, and snippets.

@lfhbento
Forked from spf13/script.js
Last active April 19, 2025 11:52
Show Gist options
  • Save lfhbento/3388607475edc23a571e8eaf568469e3 to your computer and use it in GitHub Desktop.
Save lfhbento/3388607475edc23a571e8eaf568469e3 to your computer and use it in GitHub Desktop.
Download all your Kindle books before Feb 26, 2025
// ==UserScript==
// @name Kindle Download
// @namespace http://tampermonkey.net/
// @version 2025-02-20
// @description Download all your kindle books
// @author You
// @match https://www.amazon.com/hz/mycd/digital-console/contentlist/booksPurchases/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=amazon.com
// @grant none
// ==/UserScript==
// 1. Log in to your Amazon account
// 2. Go to your Content Library > Books - https://www.amazon.com/hz/mycd/digital-console/contentlist/booksPurchases/dateDsc/
(async function () {
// Close the notification if it appears
function closeNotification() {
const notifClose = document.querySelector('span#notification-close');
if (notifClose) {
notifClose.click();
}
}
// Change to whatever device you want to select. 1 = first device, 2 = second device, etc
const DEVICE = 1;
// Pause for a given duration (in milliseconds)
function pause(duration = 1000) {
return new Promise((resolve) => setTimeout(resolve, duration));
}
await pause(5000);
const allPages = Array.from(document.querySelectorAll('a.page-item'));
const lastPage = allPages[allPages.length - 1];
const lastPageNumber = lastPage ? parseInt(lastPage.innerText, 10) : 1;
let currentPage = document.querySelector('a.page-item.active');
let currentPageNumber = parseInt(currentPage.innerText, 10);
do {
await pause(5000);
currentPage = document.querySelector('a.page-item.active');
currentPageNumber = parseInt(currentPage.innerText, 10);
console.log(`downloading page ${currentPageNumber} of ${lastPageNumber}`);
// They removed the id, so we have to use the class name now
// It's a bit more brittle but it should do.
const menus = Array.from(document.querySelectorAll('div[class*="Dropdown-module_dropdown_container"]'))
.map((container) =>
Array.from(container.children).find(
(child) => child.innerHTML.indexOf('DOWNLOAD_AND_TRANSFER_DIALOG') !== -1,
),
)
.filter((item) => !!item);
for (let menu of menus) {
// Extract the ASIN from the menu's id.
// E.g. "DOWNLOAD_AND_TRANSFER_ACTION_B07HYK662L" -> "B07HYK662L"
const dialog = menu.querySelector(`div[id^='DOWNLOAD_AND_TRANSFER_DIALOG_']`);
if (!dialog) {
console.warn(`No dialog found for menu`);
continue;
}
const parts = dialog.id.split('_');
const asin = parts[parts.length - 1];
console.log(`Processing book with ASIN: ${asin}`);
// Click the menu to open the dialog
menu.click();
const menuItem = Array.from(menu.childNodes).find((node) => node.querySelector(`div[id^='DOWNLOAD_AND_TRANSFER_DIALOG_']`));
menuItem.click();
await pause(500);
// Within the dialog, select the first radio button (device) to download.
// This selector targets the list for this ASIN.
const inputSelector = `ul#download_and_transfer_list_${asin} li[class^='ActionList-module_action_list_item__'] > div > label`;
const inputList = Array.from(menu.querySelectorAll(inputSelector));
console.log(inputList.length);
if (!inputList) {
console.warn(`No download option found for ASIN ${asin}`);
continue;
}
const deviceToCheck = inputList.length >= DEVICE ? DEVICE - 1 : 0;
const input = inputList[deviceToCheck];
if (!input) {
console.log(`No download option found for ASIN ${asin}`);
continue;
}
input.click();
await pause(500);
// Find the confirm button within the dialog for this ASIN.
const buttonSelector = `div[id^='DOWNLOAD_AND_TRANSFER_DIALOG_${asin}'] div[class^='DeviceDialogBox-module_button_container__'] > div[id$='_CONFIRM']`;
const button = document.querySelector(buttonSelector);
if (!button) {
console.warn(`No confirm button found for ASIN ${asin}`);
continue;
}
button.click();
await pause(1000);
closeNotification();
await pause(500);
}
if (currentPage) {
const nextPage = currentPage.nextElementSibling;
if (nextPage) {
nextPage.click();
}
}
} while (currentPageNumber < lastPageNumber);
})();
@JohanKlos
Copy link

Great script, thanks! I have more than 1000 books, so I needed some automation to go to the next page. Find my fork of your script here: https://gist.github.com/JohanKlos/3a92f6a55b8f1a8e712ce3c4b1510dd3

@tir38
Copy link

tir38 commented Feb 23, 2025

For some reason @403-html 's update https://gist.github.com/lfhbento/3388607475edc23a571e8eaf568469e3?permalink_comment_id=5449612#gistcomment-5449612 was not navigating to the next page and just re-downloading the same 25 books. Switching to OP's script fixed it.

@CJoshuaV
Copy link

Is there an easy way to tweak this to batch download Comixology content? I tried manually filtering for it, but the script no longer worked.

@crasher35
Copy link

This worked really well. I did have to register my husband's old kindle to my account though, otherwise, Amazon wasn't letting me download it at all. Not even manually. But once I did that, this script really works well.

For anyone following that YouTube video, this script doesn't have a green download button anymore. It just starts downloading on it's own. It also skips to the next page on its own now too. Really nice!

@Cassieh1111
Copy link

can you make a script so I can use on other sites such as ibookpile.in and https://oceanofpdf.com/ under the authors so I can get all the authors books in 1 go thank you hope you can help me

@hkeach
Copy link

hkeach commented Feb 24, 2025

I have 6 registered devices but when I try do download manually I get the "you don't have any compatible devices" message. The script seems to be running without error as it pages to the end of 300 books but they're not in my download directlory and it completed in only about 90 seconds.

Edit: I checked the console and this is what I see over and over:

Processing book with ASIN: B0042XA2Y0
userscript.html?name=Kindle-Download.user.js&id=1af2cd89-b9fd-4f18-91fc-b92fe022083a:83 0
userscript.html?name=Kindle-Download.user.js&id=1af2cd89-b9fd-4f18-91fc-b92fe022083a:94 No download option found for ASIN B0042XA2Y0

Edit#2: I don't have a real kindle registered to my account, only apps on ios devices. I'll try registering an old one.

@wilssearch
Copy link

Thank you for doing this! I have 12520 books to download. I didn't think I could get very many downloaded without your help!

@bookdragon9
Copy link

I'm sure someone must have said this before but you can actually get at the last 400 pages by telling it to sort in ascending date order. So the black hole is only there if you have more than 20,000. I only have 17.5k but my daughter has 28k and having to do the middle manually using the page at a time downloader. She hasn't got this working yet.
I currently have 2 machines going - one forwards and the other backwards.
It is working well although showing errors that it just seems to get around on the win10 machine while the win 11 machine stops dead and needs the urls changing to continue.
As long as the url following match is exactly the same as the url in the browser window it works well.

Thank you so much for providing this script :)

@wilssearch
Copy link

if there's no more than 1 page, then you get Uncaught (in promise) TypeError: lastPage is undefined. Easy to fix I think, with checking if pagination is even there, and if not then page number is just 1.

Edit: my take on this, works for me

(async function () {
  // Close the notification if it appears
  function closeNotification() {
    const notifClose = document.querySelector("span#notification-close");
    if (notifClose) {
      notifClose.click();
    }
  }

  // Pause for a given duration (in milliseconds)
  function pause(duration = 1000) {
    return new Promise((resolve) => setTimeout(resolve, duration));
  }

  await pause(5000);

  // #### MAIN CHANGE: different way of tracking pages #####
  let currentPageNumber = 1;
  let lastPageNumber = 1;

  // Get the last page number. If there's only one page, the pagination elements won't exist.
  const lastPageElement = document.querySelector("a.page-item:last-of-type");
  if (lastPageElement) {
    lastPageNumber = parseInt(lastPageElement.innerText, 10);
  }

  do {
    await pause(5000);

    console.log(`Downloading page ${currentPageNumber} of ${lastPageNumber}`);

    const menus = Array.from(
      document.querySelectorAll(
        'div[class*="Dropdown-module_dropdown_container"]'
      )
    )
      .map((container) => Array.from(container.children).find((child) => child.innerHTML && child.innerHTML.indexOf("DOWNLOAD_AND_TRANSFER_DIALOG") !== -1))
      .filter((item) => !!item);

    for (let menu of menus) {
      const dialog = menu.querySelector(`div[id^='DOWNLOAD_AND_TRANSFER_DIALOG_']`);

      if (!dialog) {
        console.warn(`No dialog found for menu element. Skipping.`);
        continue;
      }

      const parts = dialog.id.split("_");
      const asin = parts[parts.length - 1];
      console.log(`Processing book with ASIN: ${asin}`);

      menu.click();
      await pause(500);

      const inputSelector = `ul#download_and_transfer_list_${asin} li[class^='ActionList-module_action_list_item__'] > div > label`;
      const input = document.querySelector(inputSelector);
      if (!input) {
        console.warn(`No download option found for ASIN ${asin}`);
        closeNotification(); // Close the notification even if download option isn't found
        continue;
      }
      input.click();
      await pause(500);


      const buttonSelector = `div[id^='DOWNLOAD_AND_TRANSFER_DIALOG_${asin}'] div[class^='DeviceDialogBox-module_button_container__'] > div[id$='_CONFIRM']`;
      const button = document.querySelector(buttonSelector);
      if (!button) {
        console.warn(`No confirm button found for ASIN ${asin}`);
        closeNotification(); // Close the notification even if confirm button isn't found
        continue;
      }

      button.click();
      await pause(1000);

      closeNotification();
      await pause(500);
    }

    if (currentPageNumber < lastPageNumber) { // CHANGE: Check if there are pages, before going further
      const nextPage = document.querySelector("a.page-item.next:not(.disabled)");
      if (nextPage) {
        nextPage.click();
        currentPageNumber++;
      }
    }
  } while (currentPageNumber < lastPageNumber); // CHANGE: Use < instead of <=, so it doesn't re-download stuff
})();

@wilssearch
Copy link

I have no clue how to use this. Do I just add this to the original script and if so, where - at the bottom before the ending lines})();? Or do I need to add it in different locations? And if so what line numbers? Thank you for your help as I have over 12,000 books to download and doing this 1 page at a time is driving me nuts!
Thank you in advance.

@asakins
Copy link

asakins commented Feb 24, 2025

Real excited when I came across this script but am having problems running it. For every book in my library (5984 items - a lot of Comixology items) I get this message:

[Log] No download option found for ASIN B0C2B2JQ9P
[Log] Processing book with ASIN: B08QW77G1C
[Log] 0
[Log] No download option found for ASIN B08QW77G1C
[Log] Processing book with ASIN: B00OF9SEYG

Has anyone else seen the "No download option found for" message for the books in their library?

@hkeach
Copy link

hkeach commented Feb 24, 2025 via email

@403-html
Copy link

403-html commented Feb 24, 2025

I have no clue how to use this. Do I just add this to the original script and if so, where - at the bottom before the ending lines})();? Or do I need to add it in different locations? And if so what line numbers? Thank you for your help as I have over 12,000 books to download and doing this 1 page at a time is driving me nuts! Thank you in advance.

it was to OP so he could for example adjust for people with just one page. For multi-page (more than 1) Amazon's library his script works well. No need to update it.

@cindy1874
Copy link

I keep getting an "invalid userscript" message when trying to paste any of these codes on Tampermonkey

@Ldcgreen
Copy link

The OP script worked like a charm! I had 5500 books on 221 pages.

Thanks for sharing this script! It is a life-saver!

@cindy1874
Copy link

HELP!! I finally got the batch download code applied to Tampermonkey. Problem is, nothing happens!! It says "no scripts running" and I've refreshed the Amazon page and redownloaded Tampermonkey, all to no avail. I'm not particularly computer literate, so I'm kind of dependent on y'all. Not to mention time is running out for me to download my kindle books. HELP!!! This is the code I am using:

// ==UserScript==
// @name Amazon Kindle Book Downloader
// @namespace http://tampermonkey.net/
// @Version 0.2.1
// @description Adds a button to trigger downloads of all Kindle books on the page
// @author Chris Hollindale
// @match https://www.amazon.com/hz/mycd/digital-console/contentlist/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @run-at document-idle
// @license MIT
// ==/UserScript==

(function() {
'use strict';
(async function () {
// Close the notification if it appears
function closeNotification() {
const notifClose = document.querySelector("span#notification-close");
if (notifClose) {
notifClose.click();
}
}

// Pause for a given duration (in milliseconds)
function pause(duration = 1000) {
return new Promise((resolve) => setTimeout(resolve, duration));
}

await pause(5000);

// #### MAIN CHANGE: different way of tracking pages #####
let currentPageNumber = 1;
let lastPageNumber = 1;

// Get the last page number. If there's only one page, the pagination elements won't exist.
const lastPageElement = document.querySelector("a.page-item:last-of-type");
if (lastPageElement) {
lastPageNumber = parseInt(lastPageElement.innerText, 10);
}

do {
await pause(500);

console.log(`Downloading page ${currentPageNumber} of ${lastPageNumber}`);

const menus = Array.from(
  document.querySelectorAll(
    'div[class*="Dropdown-module_dropdown_container"]'
  )
)
  .map((container) => Array.from(container.children).find((child) => child.innerHTML && child.innerHTML.indexOf("DOWNLOAD_AND_TRANSFER_DIALOG") !== -1))
  .filter((item) => !!item);

for (let menu of menus) {
  const dialog = menu.querySelector(`div[id^='DOWNLOAD_AND_TRANSFER_DIALOG_']`);

  if (!dialog) {
    console.warn(`No dialog found for menu element. Skipping.`);
    continue;
  }

  const parts = dialog.id.split("_");
  const asin = parts[parts.length - 1];
  console.log(`Processing book with ASIN: ${asin}`);

  menu.click();
  await pause(500);

  const inputSelector = `ul#download_and_transfer_list_${asin} li[class^='ActionList-module_action_list_item__'] > div > label`;
  const input = document.querySelector(inputSelector);
  if (!input) {
    console.warn(`No download option found for ASIN ${asin}`);
    closeNotification(); // Close the notification even if download option isn't found
    continue;
  }
  input.click();
  await pause(500);


  const buttonSelector = `div[id^='DOWNLOAD_AND_TRANSFER_DIALOG_${asin}'] div[class^='DeviceDialogBox-module_button_container__'] > div[id$='_CONFIRM']`;
  const button = document.querySelector(buttonSelector);
  if (!button) {
    console.warn(`No confirm button found for ASIN ${asin}`);
    closeNotification(); // Close the notification even if confirm button isn't found
    continue;
  }

  button.click();
  await pause(500);

  closeNotification();
  await pause(500);
}

if (currentPageNumber < lastPageNumber) { // CHANGE: Check if there are pages, before going further
  const nextPage = document.querySelector("a.page-item.next:not(.disabled)");
  if (nextPage) {
    nextPage.click();
    currentPageNumber++;
  }
}

} while (currentPageNumber < lastPageNumber); // CHANGE: Use < instead of <=, so it doesn't re-download stuff
})();

@asakins
Copy link

asakins commented Feb 24, 2025

This happens when you don't have a physical kindle device registered to your account. I registered an old one and it started working

Thanks for the insight. Argh... I was hoping that wasn't the answer. I had a kindle years ago, rarely used it, and got rid of it.

@Ldcgreen
Copy link

@lfhbento Thanks for outstanding code! It worked and saved me so much time!

@strawberryfrost
Copy link

I have it going, but my collection is so large it stops after page 399. My library is 43,492 so 1740 pages, but Amazon only shows 400 pages. I've downloaded the first 400 pages of A-Z, and last of A-Z. A-Z authors first 400 and last 400. I'll do most recent and oldest purchases. I'm afraid I'll be missing some books. Any suggestions? I plan on doing individual categories for collections, but have 1,461 not in a collections so I'm afraid I'll be missing some books. Is there a way for it to continue?

@Joebugg
Copy link

Joebugg commented Feb 25, 2025

Amazon: You're making it really hard to justify being honest. Also, it's not sailing the 7 seas, to use my preferred reader software.
Ugh, only found this out tonight. :(

@Mirah444
Copy link

Great script, thanks! I have more than 1000 books, so I needed some automation to go to the next page. Find my fork of your script here: https://gist.github.com/JohanKlos/3a92f6a55b8f1a8e712ce3c4b1510dd3

This script worked perfectly to download my collections. I was able to open each collection and click the Start Downloads Button and it worked!

FYI...It does a continuous loop on the last page. On the aged system used it takes about 1 min 30 secs per page. I calculated the time to return to click the Stop Downloads button. Then moved to the next category to start again.

Thank you very much for sharing this to help others!

@bookdragon9
Copy link

I have it going, but my collection is so large it stops after page 399. My library is 43,492 so 1740 pages, but Amazon only shows 400 pages. I've downloaded the first 400 pages of A-Z, and last of A-Z. A-Z authors first 400 and last 400. I'll do most recent and oldest purchases. I'm afraid I'll be missing some books. Any suggestions? I plan on doing individual categories for collections, but have 1,461 not in a collections so I'm afraid I'll be missing some books. Is there a way for it to continue?

Reverse the sort sequence and it starts back with page 1 now being the first one you bought. Copy the link from the browser page then paste it into the @Move line and save. Go to the browser page and refresh. I had 2 machines going from both directions.

@Joebugg
Copy link

Joebugg commented Feb 25, 2025

On an older machine with slow (5Mb/s) hotspot, it sometimes fails to download all, but I went back on the few it missed.
Too bad this script doesn't have a hard limit of say, 2 downloads at a time as an option.

@DebInOkla
Copy link

DebInOkla commented Feb 25, 2025

What is the code so I dont have to restart each page? I am not good at this and I am using TamperMonkey. I have 400+ pages/ Probably around 487. I have changed the code..... or think I have but still only doing 25 at a time and I am on Page 87

@knittingmommy
Copy link

Thanks for everything you did @lfhbento. I managed to download all 15339 purchased books in my kindle library! I'm done.

@knittingmommy
Copy link

I have it going, but my collection is so large it stops after page 399. My library is 43,492 so 1740 pages, but Amazon only shows 400 pages. I've downloaded the first 400 pages of A-Z, and last of A-Z. A-Z authors first 400 and last 400. I'll do most recent and oldest purchases. I'm afraid I'll be missing some books. Any suggestions? I plan on doing individual categories for collections, but have 1,461 not in a collections so I'm afraid I'll be missing some books. Is there a way for it to continue?

Reverse the sort sequence and it starts back with page 1 now being the first one you bought. Copy the link from the browser page then paste it into the @Move line and save. Go to the browser page and refresh. I had 2 machines going from both directions.

And I thought I had a large library! You definitely have me beat. I was able to get all mine through the method you used above. I never could get the script to run past page 400.

@tagoofy
Copy link

tagoofy commented Feb 25, 2025

Just wanted to say "Thank you" @lfhbento.
The script worked great (when Amazon didn't screw up). The change for device in the list was also much appreciated.

@9999years
Copy link

Worked perfectly, thanks!

@nickahoy
Copy link

Thank you so much @lfhbento. Ran it from the browser console and it worked brilliantly.

@mbucherl
Copy link

@lfhbento missed your brilliant assist. Since I started learning how to be a python prompt engineer, I wonder if I can get my books by using the browser accessible https://read.amazon.com/kindle-library? What do you think?

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