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);
})();
@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