-
-
Save prehensilecode/fbd2d77ec8bd69c8381412bfbab0dfef to your computer and use it in GitHub Desktop.
Download all your Kindle books before Feb 26, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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/booksAll/dateDsc/* | |
// @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/booksAll/dateDsc/ | |
(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); | |
const allPages = Array.from(document.querySelectorAll("a.page-item")); | |
const lastPage = allPages[allPages.length - 1]; | |
const lastPageNumber = parseInt(lastPage.innerText, 10); | |
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}`); | |
// Select all buttons that open the "Download & transfer via USB" dialog. | |
// Note: This uses the DOWNLOAD_AND_TRANSFER prefix. | |
// const menus = Array.from( | |
// document.querySelectorAll('div[id*="DOWNLOAD_AND_TRANSFER_ACTION_"]') | |
// ).filter(el => !el.id.endsWith("CONFIRM") && !el.id.endsWith("CANCEL")); | |
// 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_']`) | |
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(); | |
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 input = document.querySelector(inputSelector); | |
if (!input) { | |
console.warn(`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); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment